[
  {
    "path": ".coderabbit.yaml",
    "content": "language: en-US\ntone_instructions: ''\nearly_access: false\nenable_free_tier: true\nreviews:\n  profile: chill\n  request_changes_workflow: false\n  high_level_summary: true\n  high_level_summary_placeholder: '@coderabbitai summary'\n  high_level_summary_in_walkthrough: false\n  auto_title_placeholder: '@coderabbitai'\n  auto_title_instructions: ''\n  review_status: false\n  commit_status: true\n  fail_commit_status: false\n  collapse_walkthrough: false\n  changed_files_summary: true\n  sequence_diagrams: true\n  estimate_code_review_effort: true\n  assess_linked_issues: true\n  related_issues: true\n  related_prs: true\n  suggested_labels: true\n  auto_apply_labels: false\n  suggested_reviewers: true\n  auto_assign_reviewers: false\n  poem: true\n  labeling_instructions: []\n  path_filters: []\n  path_instructions: []\n  abort_on_close: true\n  disable_cache: false\n  auto_review:\n    enabled: false\n    auto_incremental_review: true\n    ignore_title_keywords: []\n    labels: []\n    drafts: false\n    base_branches: []\n  finishing_touches:\n    docstrings:\n      enabled: true\n    unit_tests:\n      enabled: true\n  pre_merge_checks:\n    docstrings:\n      mode: warning\n      threshold: 80\n    title:\n      mode: warning\n      requirements: ''\n    description:\n      mode: warning\n    issue_assessment:\n      mode: warning\n  tools:\n    ast-grep:\n      rule_dirs: []\n      util_dirs: []\n      essential_rules: true\n      packages: []\n    shellcheck:\n      enabled: true\n    ruff:\n      enabled: true\n    markdownlint:\n      enabled: true\n    github-checks:\n      enabled: true\n      timeout_ms: 90000\n    languagetool:\n      enabled: true\n      enabled_rules: []\n      disabled_rules: []\n      enabled_categories: []\n      disabled_categories: []\n      enabled_only: false\n      level: default\n    biome:\n      enabled: true\n    hadolint:\n      enabled: true\n    swiftlint:\n      enabled: true\n    phpstan:\n      enabled: true\n      level: default\n    phpmd:\n      enabled: true\n    phpcs:\n      enabled: true\n    golangci-lint:\n      enabled: true\n    yamllint:\n      enabled: true\n    gitleaks:\n      enabled: true\n    checkov:\n      enabled: true\n    detekt:\n      enabled: true\n    eslint:\n      enabled: true\n    flake8:\n      enabled: true\n    rubocop:\n      enabled: true\n    buf:\n      enabled: true\n    regal:\n      enabled: true\n    actionlint:\n      enabled: true\n    pmd:\n      enabled: true\n    cppcheck:\n      enabled: true\n    semgrep:\n      enabled: true\n    circleci:\n      enabled: true\n    clippy:\n      enabled: true\n    sqlfluff:\n      enabled: true\n    prismaLint:\n      enabled: true\n    pylint:\n      enabled: true\n    oxc:\n      enabled: true\n    shopifyThemeCheck:\n      enabled: true\n    luacheck:\n      enabled: true\n    brakeman:\n      enabled: true\n    dotenvLint:\n      enabled: true\n    htmlhint:\n      enabled: true\n    checkmake:\n      enabled: true\nchat:\n  auto_reply: true\n  integrations:\n    jira:\n      usage: auto\n    linear:\n      usage: auto\nknowledge_base:\n  opt_out: false\n  web_search:\n    enabled: true\n  code_guidelines:\n    enabled: true\n    filePatterns: []\n  learnings:\n    scope: auto\n  issues:\n    scope: auto\n  jira:\n    usage: auto\n    project_keys: []\n  linear:\n    usage: auto\n    team_keys: []\n  pull_requests:\n    scope: auto\ncode_generation:\n  docstrings:\n    language: en-US\n    path_instructions: []\n  unit_tests:\n    path_instructions: []\nautopilot:\n  enabled: false\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"Postiz Dev Container\",\n\t\"image\": \"localhost/postiz-devcontainer\",\n\t\"features\": {},\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"settings\": {},\n\t\t\t\"extensions\": []\n\t\t}\n\t},\n\t\"forwardPorts\": [\"4200:4200\", \"3000:3000\"],\n\t\"mounts\": [\"source=/apps,destination=/apps/dist/,type=bind,consistency=cached\"]\n}\n\n"
  },
  {
    "path": ".dockerignore",
    "content": "# We want the docker builds to be clean, and as fast as possible. Don't send\n# any half-built stuff in the build context as a pre-caution (also saves copying\n# 180k files in node_modules that isn't used!).\n**/node_modules\nnode_modules/*\nnode_modules\ndocker-data/*\ndist\n.nx\n/apps/frontend/.next\n/apps/backend/dist\n/apps/workers/dist\n/apps/cron/dist\n/apps/commands/dist\n.devcontainer\n**/.git\n**/*.md\n**/LICENSE\n**/npm-debug.log\n**/*.vscode\n.git\n.github\nreports"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\n"
  },
  {
    "path": ".github/Dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/FUNDING.yaml",
    "content": "#patreon: Postiz\nopen_collective: postiz\n# github: gitroomhq\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/01_bug_report.yml",
    "content": "name: \"🐛 Bug Report\"\ndescription: \"Submit a bug report to help us improve,\\nif you have a problem installing the app please join our https://discord.postiz.com instead for help.\"\ntitle: \"Give your bug report a good title \"\nlabels: [\"type: bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: We value your time and effort to submit this bug report. 🙏\n  - type: textarea\n    id: description\n    validations:\n      required: true\n    attributes:\n      label: \"📜 Description\"\n      description: \"A clear and concise description of what the bug is.\"\n      placeholder: \"It bugs out when ...\"\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    validations:\n      required: true\n    attributes:\n      label: \"👟 Reproduction steps\"\n      description: \"How do you trigger this bug? Please walk us through it step by step.\"\n      placeholder: \"1. Go to '...'\n                    2. Click on '....'\n                    3. Scroll down to '....'\n                    4. See error\"\n  - type: textarea\n    id: expected-behavior\n    validations:\n      required: true\n    attributes:\n      label: \"👍 Expected behavior\"\n      description: \"What did you think should happen?\"\n      placeholder: \"It should ...\"\n  - type: textarea\n    id: actual-behavior\n    validations:\n      required: true\n    attributes:\n      label: \"👎 Actual Behavior with Screenshots\"\n      description: \"What did actually happen? Add screenshots, if applicable.\"\n      placeholder: \"It actually ...\"\n  - type: dropdown\n    id: operating-system\n    attributes:\n      label: \"💻 Operating system\"\n      description: \"What OS is your app running on?\"\n      options:\n        - Linux\n        - MacOS\n        - Windows\n        - Something else\n    validations:\n      required: true\n  - type: input\n    id: node-version\n    validations:\n      required: true\n    attributes:\n      label: \"🤖 Node Version\"\n      description: >\n        What Node version are you using?\n  - type: textarea\n    id: additional-context\n    validations:\n      required: false\n    attributes:\n      label: \"📃 Provide any additional context for the Bug.\"\n      description: \"Add any other context about the problem here.\"\n      placeholder: \"It actually ...\"\n  - type: checkboxes\n    id: no-duplicate-issues\n    attributes:\n      label: \"👀 Have you spent some time to check if this bug has been raised before?\"\n      options:\n        - label: \"I checked and didn't find similar issue\"\n          required: true\n  - type: dropdown\n    attributes:\n      label: Are you willing to submit PR?\n      description: This is absolutely not required, but we are happy to guide you in the contribution process. Find us in help-needed channel on [Discord](https://discord.gitroom.com)!\n      options:\n        - \"Yes I am willing to submit a PR!\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02_feature_request.yml",
    "content": "name: 🚀 Feature\ndescription: \"Submit a proposal for a new feature\"\ntitle: \"Give your feature request a title\"\nlabels: [\"type: feature-request\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        We value your time and efforts to submit this Feature request form. 🙏\n  - type: textarea\n    id: feature-description\n    validations:\n      required: true\n    attributes:\n      label: \"🔖 Feature description\"\n      description: \"A clear and concise description of what the feature is.\"\n      placeholder: \"You should add ...\"\n  - type: textarea\n    id: pitch\n    validations:\n      required: true\n    attributes:\n      label: \"🎤 Why is this feature needed ?\"\n      description: \"Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.\"\n      placeholder: \"In my use-case, ...\"\n  - type: textarea\n    id: solution\n    validations:\n      required: true\n    attributes:\n      label: \"✌️ How do you aim to achieve this?\"\n      description: \"A clear and concise description of what you want to happen.\"\n      placeholder: \"I want this feature to, ...\"\n  - type: textarea\n    id: alternative\n    validations:\n      required: false\n    attributes:\n      label: \"🔄️ Additional Information\"\n      description: \"A clear and concise description of any alternative solutions or additional solutions you've considered.\"\n      placeholder: \"I tried, ...\"\n  - type: checkboxes\n    id: no-duplicate-issues\n    attributes:\n      label: \"👀 Have you spent some time to check if this feature request has been raised before?\"\n      options:\n        - label: \"I checked and didn't find similar issue\"\n          required: true\n  - type: dropdown\n    id: willing-to-submit-pr\n    attributes:\n      label: Are you willing to submit PR?\n      description: This is absolutely not required, but we are happy to guide you in the contribution process. Find us in help-needed channel on [Discord](https://discord.gitroom.com)!\n      options:\n        - \"Yes I am willing to submit a PR!\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Disable the default option to open a blank issue\nblank_issues_enabled: true \n\n# Define your custom links\ncontact_links:\n  # The first link definition\n  - name: 🙏 Installation Issue\n    url: https://discord.postiz.com\n    about: If you have an installation / configuration issue.\n\n  # You can add more links if needed\n  - name: Security Issue\n    url: https://github.com/gitroomhq/postiz-app/security/advisories/new\n    about: Please submit security Issues our GitHub Security Advisories.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# What kind of change does this PR introduce?\n\neg: Bug fix, feature, docs update, ...\n\n# Why was this change needed?\n\nPlease link to related issues when possible, and explain WHY you changed things, not WHAT you changed.\n\n# Other information:\n\neg: Did you discuss this change with anybody before working on it (not required, but can be a good idea for bigger changes). Any plans for the future, etc?\n\n# Checklist:\n\nPut a \"X\" in the boxes below to indicate you have followed the checklist;\n\n- [ ] I have read the [CONTRIBUTING](https://github.com/gitroomhq/postiz-app/blob/main/CONTRIBUTING.md) guide.\n- [ ] I checked that there were not similar issues or PRs already open for this.\n- [ ] This PR fixes just ONE issue (do not include multiple issues or types of change in the same PR) For example, don't try and fix a UI issue and include new dependencies in the same PR.\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "\n# Copilot Coding Agent Instructions for Postiz\n\n## Project Architecture\n- Monorepo managed by NX, with apps in `apps/` and shared code in `libraries/`.\n- Main services: `frontend` (Next.js), `backend` (NestJS), `cron`, `commands`, `extension`, `sdk`, and `workers`.\n- Data layer uses Prisma ORM (`libraries/nestjs-libraries/src/database/prisma/schema.prisma`) with PostgreSQL as the default database.\n- Redis (BullMQ) is used for queues and caching.\n- Email notifications via Resend.\n- Social login integrations (Instagram, Facebook) and Make.com/N8N integrations.\n\n## Developer Workflows\n- Use Node.js 20.17.0 and pnpm 8+.\n- Install dependencies: `pnpm install`\n- Build all apps: `pnpm run build`\n- Run all apps in dev mode: `pnpm run dev`\n- Test: `pnpm test` (Jest, coverage enabled)\n- Individual app scripts are in each app's `package.json` (e.g., `pnpm --filter ./apps/backend run dev`).\n- Prisma DB commands: `pnpm run prisma-generate`, `pnpm run prisma-db-push`, `pnpm run prisma-reset`.\n- Docker: `docker compose -f ./docker-compose.dev.yaml up -d`\n\n## Conventions & Patterns\n- Use conventional commits (`feat:`, `fix:`, `chore:`).\n- PRs should include clear descriptions, related issue links, and UI screenshots/GIFs if relevant.\n- Comments are required for complex logic.\n- Shared code lives in `libraries/` (e.g., helpers, React shared libraries, NestJS modules).\n- Environment variables are managed via `.env` and referenced in Docker and scripts.\n- Make sure to keep the `.env.example` file updated with new environment variables.\n\n## Integration Points\n- External APIs: Social media (Instagram, Facebook), Make.com, N8N, Resend, Stripe, etc.\n- SDK (`apps/sdk`) provides programmatic access to Postiz features.\n- Extension (`apps/extension`) is built with Vite, React, TypeScript, and Tailwind CSS.\n\n## Key Files & Directories\n- `apps/` — Main services and applications\n- `libraries/` — Shared code and modules\n- `docker-compose.dev.yaml` — Local development Docker setup\n- `.env` — Environment configuration\n- `jest.config.ts` — Test configuration\n- `pnpm-workspace.yaml` — Workspace package management\n- `README.md` — General project overview\n- `libraries/nestjs-libraries/src/database/prisma/schema.prisma` — Database schema\n\n## Documentation\n- Main docs: https://docs.postiz.com/\n- Developer guide: https://docs.postiz.com/developer-guide\n- Public API: https://docs.postiz.com/public-api\n\n---\n\n# Logs\n\n- Where logs are used, ensure Sentry is imported using `import * as Sentry from \"@sentry/nextjs\"`\n- Enable logging in Sentry using `Sentry.init({ enableLogs: true })`\n- Reference the logger using `const { logger } = Sentry`\n- Sentry offers a `consoleLoggingIntegration` that can be used to log specific console error types automatically without instrumenting the individual logger calls\n\n## Configuration\n\nThe Sentry initialization needs to be updated to enable the logs feature.\n\n### Baseline\n\n```javascript\nimport * as Sentry from \"@sentry/nextjs\";\n\nSentry.init({\n  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n\n  enableLogs: true,\n});\n```\n\n### Logger Integration\n\n```javascript\nSentry.init({\n  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n  integrations: [\n    // send console.log, console.error, and console.warn calls as logs to Sentry\n    Sentry.consoleLoggingIntegration({ levels: [\"log\", \"error\", \"warn\"] }),\n  ],\n});\n```\n\n## Logger Examples\n\n`logger.fmt` is a template literal function that should be used to bring variables into the structured logs.\n\n```javascript\nimport * as Sentry from \"@sentry/nextjs\";\n\nconst { logger } = Sentry;\n\nlogger.trace(\"Starting database connection\", { database: \"users\" });\nlogger.debug(logger.fmt`Cache miss for user: ${userId}`);\nlogger.info(\"Updated profile\", { profileId: 345 });\nlogger.warn(\"Rate limit reached for endpoint\", {\n  endpoint: \"/api/results/\",\n  isEnterprise: false,\n});\nlogger.error(\"Failed to process payment\", {\n  orderId: \"order_123\",\n  amount: 99.99,\n});\nlogger.fatal(\"Database connection pool exhausted\", {\n  database: \"users\",\n  activeConnections: 100,\n});\n```\n\n---\n\nFor questions or unclear conventions, check the main README or ask for clarification in your PR description.\n\n"
  },
  {
    "path": ".github/workflows/build-containers.yml",
    "content": "---\nname: \"Build Containers\"\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - '*'\n\njobs:\n  build-containers-common:\n    runs-on: ubuntu-latest\n    outputs:\n      containerver: ${{ steps.getcontainerver.outputs.containerver }}\n    steps:\n      - name: Get Container Version\n        id: getcontainerver\n        run: |\n          echo \"containerver=${{ github.ref_name }}\" >> \"$GITHUB_OUTPUT\"\n\n  build-containers:\n    needs: build-containers-common\n    strategy:\n      matrix:\n        include:\n          - runnertags: ubuntu-latest\n            arch: amd64\n          - runnertags: ubuntu-24.04-arm\n            arch: arm64\n    runs-on: ${{ matrix.runnertags }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login to ghcr\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ github.token }}\n\n      - name: Build and Push Image\n        env:\n          CONTAINERVER: ${{ needs.build-containers-common.outputs.containerver }}\n          NEXT_PUBLIC_VERSION: ${{ github.ref_name }}\n        run: |\n          docker buildx build --platform linux/${{ matrix.arch }} \\\n            -f Dockerfile.dev \\\n            -t ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-${{ matrix.arch }} \\\n            --build-arg NEXT_PUBLIC_VERSION=${{ env.NEXT_PUBLIC_VERSION }} \\\n            --pull \\\n            --no-cache \\\n            --provenance=false --sbom=false \\\n            --output \"type=registry,name=ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-${{ matrix.arch }}\" .\n\n  build-container-manifest:\n    needs: [build-containers, build-containers-common]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Login to ghcr\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ github.token }}\n\n      - name: Create Docker Manifest\n        env:\n          CONTAINERVER: ${{ needs.build-containers-common.outputs.containerver }}\n        run: |\n          # Verify the architecture images\n          echo \"Verifying AMD64 image:\"\n          docker buildx imagetools inspect ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-amd64\n          \n          echo \"Verifying ARM64 image:\"\n          docker buildx imagetools inspect ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-arm64\n          \n          # Try to remove any existing manifests first\n          docker manifest rm ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }} || true\n          docker manifest rm ghcr.io/gitroomhq/postiz-app:latest || true\n          \n          # Create and push the version-specific manifest\n          docker manifest create ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }} \\\n            --amend ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-amd64 \\\n            --amend ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-arm64\n\n          docker manifest push ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}\n\n          # Create and push the latest manifest\n          docker manifest create ghcr.io/gitroomhq/postiz-app:latest \\\n            --amend ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-amd64 \\\n            --amend ghcr.io/gitroomhq/postiz-app:${{ env.CONTAINERVER }}-arm64\n\n          docker manifest push ghcr.io/gitroomhq/postiz-app:latest\n\n      - name: Verify Manifest\n        run: |\n          docker manifest inspect ghcr.io/gitroomhq/postiz-app:latest\n"
  },
  {
    "path": ".github/workflows/build-extension.yaml",
    "content": "name: Build Extension\n\non:\n  workflow_dispatch:\n\njobs:\n  submit:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: pnpm/action-setup@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Zip extensions\n        run: FRONTEND_URL=https://platform.postiz.com pnpm run build:extension\n\n      - name: Upload to Nextcloud\n        env:\n          NEXTCLOUD_URL: ${{ secrets.NEXTCLOUD_URL }}\n          NEXTCLOUD_USERNAME: ${{ secrets.NEXTCLOUD_USERNAME }}\n          NEXTCLOUD_PASSWORD: ${{ secrets.NEXTCLOUD_PASSWORD }}\n        run: |\n          curl -T apps/extension/extension.zip \\\n            -u \"$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD\" \\\n            \"$NEXTCLOUD_URL/extension.zip\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "---\nname: Build\n\non:\n  push:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    \n\n    strategy:\n      matrix:\n        node-version: ['22.12.0']\n        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 8\n          run_install: false\n\n      - name: Get pnpm store directory\n        shell: bash\n        run: |\n          echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n      - name: Setup pnpm cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            ${{ env.STORE_PATH }}\n            ${{ github.workspace }}/.next/cache\n          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}-\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Build\n        run: pnpm run build\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "---\nname: \"Code Quality  Analysis\"\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - apps/**\n      - '!apps/docs/**'\n      - libraries/**\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n\n    runs-on: 'ubuntu-latest'\n    permissions:\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - language: javascript-typescript\n            build-mode: none\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          build-mode: ${{ matrix.build-mode }}\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n        with:\n          category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/eslint",
    "content": "---\nname: ESLint\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - package.json\n      - apps/**\n      - '!apps/docs/**'\n      - libraries/**\n\n  pull_request:\n    paths:\n      - package.json\n      - apps/**\n      - '!apps/docs/**'\n      - libraries/**\n\njobs:\n  eslint:\n    name: Run eslint scanning\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      security-events: write\n      actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status\n    strategy:\n      matrix:\n        service: [\"backend\", \"frontend\"]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'npm'\n          cache-dependency-path: |\n            **/pnpm-lock.yaml\n\n      - name: Install ESLint\n        run: |\n          npm install eslint\n          npm install @microsoft/eslint-formatter-sarif@2.1.7\n\n      - name: Run ESLint\n        run: npx eslint apps/${{ matrix.service }}/\n          --config apps/${{ matrix.service }}/.eslintrc.json\n          --format @microsoft/eslint-formatter-sarif\n          --output-file apps/${{ matrix.service }}/eslint-results.sarif\n        continue-on-error: true\n\n      - name: Upload analysis results to GitHub\n        uses: github/codeql-action/upload-sarif@v3\n        with:\n          sarif_file: apps/${{ matrix.service }}/eslint-results.sarif\n          wait-for-processing: true\n"
  },
  {
    "path": ".github/workflows/issue-label-triggers.yml",
    "content": "---\nname: Issue Label Triggers\non:\n  issues:\n    types:\n      - labeled\n\njobs:\n  closed-public-website:\n    if: github.event.label.name == 'trigger-public-website'\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - name: Add comment\n        run: gh issue comment \"$NUMBER\" --body \"$BODY\" && gh issue close \"$NUMBER\"\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          GH_REPO: ${{ github.repository }}\n          NUMBER: ${{ github.event.issue.number }}\n          BODY: >\n            This issue concerns the public website, which is not open source and is not part of this repository.\n\n\n            If you have a question or concern about the content of the public website, please contact Nevo David.\n\n\n            If you are looking to contribute to the open source Postiz project code, this is the correct repository, and we welcome your contributions in a new GitHub issue.\n"
  },
  {
    "path": ".github/workflows/pr-docker-build.yml",
    "content": "name: Build and Publish PR Docker Image\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n\npermissions: write-all\n\njobs:\n  build-and-publish:\n    runs-on: ubuntu-latest\n\n    environment: \n      name: build-pr\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ github.token }}\n\n      - name: Set image tag\n        id: vars\n        run: echo \"IMAGE_TAG=ghcr.io/gitroomhq/postiz-app-pr:${{ github.event.pull_request.number }}\" >> $GITHUB_ENV\n\n      - name: Build Docker image from Dockerfile.dev\n        run: docker build -f Dockerfile.dev -t $IMAGE_TAG .\n\n      - name: Push Docker image to GHCR\n        run: docker push $IMAGE_TAG\n"
  },
  {
    "path": ".github/workflows/pr-quality.yml",
    "content": "name: PR Quality\n\npermissions:\n  contents: read\n  issues: read\n  pull-requests: write\n\non:\n  pull_request_target:\n    types: [opened, reopened]\n\njobs:\n  anti-slop:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: peakoss/anti-slop@v0\n        with:\n          max-failures: 4\n"
  },
  {
    "path": ".github/workflows/publish-extension.yml",
    "content": "name: Publish Extension\n\non:\n  workflow_dispatch:\n\njobs:\n  submit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: pnpm/action-setup@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install\n\n      - name: Zip extensions\n        run: FRONTEND_URL=https://platform.postiz.com pnpm run build:extension\n\n      - name: Publish to Chrome Web Store\n        uses: mnao305/chrome-extension-upload@v5.0.0\n        with:\n          extension-id: ${{ secrets.CHROME_EXTENSION_ID }}\n          client-id: ${{ secrets.CHROME_CLIENT_ID }}\n          client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}\n          refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}\n          file-path: apps/extension/extension.zip"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close inactive issues\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"*/30 * * * *\"\n\njobs:\n  close-issues:\n    if: github.repository == 'gitroomhq/postiz-app'\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v9\n        with:\n          days-before-issue-stale: 90\n          days-before-issue-close: 7\n          stale-issue-label: \"stale\"\n          stale-issue-message: \"This issue is stale because it has been open for 90 days with no activity.\"\n          close-issue-message: \"This issue was closed because it has been inactive for 7 days since being marked as stale.\" \n          exempt-issue-labels: \"no-stale-bot\"\n          \n          days-before-pr-stale: 90\n          days-before-pr-close: 7\n          stale-pr-label: \"stale\" \n          stale-pr-message: \"This PR is stale because it has been open for 90 days with no activity.\"\n          close-pr-message: \"This PR was closed because it has been inactive for 7 days since being marked as stale.\" \n          exempt-pr-label: \"no-stale-bot\" \n          \n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          operations-per-run: 180\n"
  },
  {
    "path": ".gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\ndist\ntmp\n/out-tsc\n.env\n# dependencies\nnode_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n.vscode/*\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\n.nx/cache\n.nx/workspace-data\n\n# Next.js\n.next\n\n# Vim files\n**/*.swp\n**/*.swo\n\n# Temporary files\n*.orig\n*.~*~\n*.tsbuildinfo\n\n# ignore Secrets folder\n.secrets/\nlibraries/plugins/src/plugins.ts\ni18n.cache\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"libraries/plugins/src/list/public-api\"]\n\tpath = libraries/plugins/src/list/public-api\n\turl = git@github.com:gitroomhq/public-api.git\n"
  },
  {
    "path": ".npmrc",
    "content": "ignore-workspace-root-check=true\nnode-linker=hoisted\nrestrict-manifest-changes=true\nsync-injected-deps-after-scripts[]=build\ninject-workspace-packages=true\n"
  },
  {
    "path": ".prettierignore",
    "content": "# Add files here to ignore them from prettier formatting\n/dist\n/coverage\n/.nx/cache"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "This project is Postiz, a tool to schedule social media and chat posts to 28+ channels.\nYou can add posts to the calendar, they will be added into a workflow and posted at the right time.\nYou can find things like:\n- Schedule posts\n- Calendar view\n- Analytics\n- Team management\n- Media library\n\nThis project is a monorepo with a root only package.json of dependencies.\nMade with PNPM.\nWe have 3 important folders\n\n- apps/backend - this is where the API code is (NESTJS)\n- apps/orchestrator - this is temporal, it's for background jobs (NESTJS) it contains all the workflows and activities\n- apps/frontend - this is the code of the frontend (Vite ReactJS)\n- /libraries contains a lot of services shared between backend and orchestrator and frontend components.\n\nWe are using only pnpm, don't use any other dependency manager.\nNever install frontend components from npmjs, focus on writing native components.\n\nThe project uses tailwind 3, before writing any component look at:\n- /apps/frontend/src/app/colors.scss\n- /apps/frontend/src/app/global.scss\n- /apps/frontend/tailwind.config.js\n\nAll the --color-custom* are deprecated, don't use them.\n\nAnd check other components in the system before to get the right design.\n\nWhen working on the backend we need to pass the 3 layers:\nController >> Service >> Repository (no shortcuts)\nIn some cases we will have\nController >> Mananger >> Service >> Repository.\n\nMost of the server logic should be inside of libs/server.\nThe backend repository is mostly used to write controller, and import files from libs.server.\n\nFor the frontend follow this:\n- Many of the UI components lives in /apps/frontend/src/components/ui\n- Routing is in /apps/frontend/src/app\n- Components are in /apps/frontend/src/components\n- always use SWR to fetch stuff, and use \"useFetch\" hook from /libraries/helpers/src/utils/custom.fetch.tsx\n\nWhen using SWR, each one have to be in a seperate hook and must comply with react-hooks/rules-of-hooks, never put eslint-disable-next-line on it.\n\nIt means that this is valid:\nconst useCommunity = () => {\n   return useSWR....\n}\n\nThis is not valid:\nconst useCommunity = () => {\n  return {\n    communities: () => useSWR<CommunitiesListResponse>(\"communities\", getCommunities),\n    providers: () => useSWR<ProvidersListResponse>(\"providers\", getProviders),\n  };\n}\n\n- Linting of the project can run only from the root.\n- Use only pnpm."
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nnevo@gitroom.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are welcome - code, docs, whatever it might be! If this is your first contribution to an Open Source project or you're a core maintainer of multiple projects, your time and interest in contributing to this project is most welcome.\n\n## Read the developers guide\n\nThe main documentation site has a [developer guide](https://docs.postiz.com/developer-guide) . That guide provides you a good understanding of the project structure, and how to setup your development environment. Read this document after you have read that guide. This document is intended to provide you a good understanding of how to submit your first contribution.\n\n## Write code with others\n\nThis is an open source project, with an open and welcoming community that is always keen to welcome new contributors. We recommend the two best ways to interact with the community are:\n\n- **GitHub issues**: To discuss more slowly, or longer-written messages.\n- **[Discord chat](https://discord.postiz.com)**: To chat with people [Discord chat](https://discord.postiz.com/) and a quicker feedback.\n\nAs a general rule;\n\n- **If a change is less than 3 lines**: You're probably safe just to submit the change without a discussion. This includes typos, dependency changes, and quick fixes, etc.\n- **If a change is more than 3 lines**: It's probably best to discuss the change in an issue or on discord first. This is simply because you might not be aware of the roadmap for the project, or understand the impact this change might have. We're just trying to save you time here, and importantly, avoid you being disappointed if your change isn't accepted.\n\n## Types of Contributions\n\nContributions can include:\n\n- **Code improvements:** Fixing bugs or adding new features.\n- **Documentation updates:** Enhancing clarity or adding missing information.\n- **Feature requests:** Suggesting new capabilities or integrations.\n- **Bug reports:** Identifying and reporting issues.\n\n## How to contribute\n\nThis project follows a Fork/Feature Branch/Pull Request model. If you're not familiar with this, here's how it works:\n\n1. **Fork the project:** Create a personal copy of the repository on your GitHub account.\n2. **Clone your fork:** Bring a copy of your fork to your local machine.\n   ```bash\n   git clone https://github.com/YOUR_USERNAME/postiz.git\n   ```\n3. **Create a new branch**: Start a new branch for your changes\n   ```bash\n   git checkout -b feature/your-feature-name\n   ```\n4. **Make your changes**: Implement the changes you wish to contribute.\n5. **Push your changes**: Upload your changes to your fork.\n   ```bash\n   git push -u origin feature/your-feature-name\n   ```\n6. **Create a pull request**: Propose your changes **to the main branch**.\n\n# Need Help?\n\nAgain, do check the [developer guide](https://docs.postiz.com/developer-guide). Much of what you probably need to know is in there.\n\nIf you encounter any issues, please visit our [support page](https://docs.postiz.com/support) or check the community forums. Your contributions help make Postiz better!\n"
  },
  {
    "path": "Dockerfile.dev",
    "content": "FROM node:22.20-bookworm-slim\nARG NEXT_PUBLIC_VERSION\nENV NEXT_PUBLIC_VERSION=$NEXT_PUBLIC_VERSION\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    g++ \\\n    make \\\n    python3-pip \\\n    bash \\\n    nginx \\\n&& rm -rf /var/lib/apt/lists/*\n\nRUN addgroup --system www \\\n && adduser --system --ingroup www --home /www --shell /usr/sbin/nologin www \\\n && mkdir -p /www \\\n && chown -R www:www /www /var/lib/nginx\n\n\nRUN npm --no-update-notifier --no-fund --global install pnpm@10.6.1 pm2\n\nWORKDIR /app\n\nCOPY . /app\nCOPY var/docker/nginx.conf /etc/nginx/nginx.conf\n\nRUN pnpm install\nRUN NODE_OPTIONS=\"--max-old-space-size=4096\" pnpm run build\n\nCMD [\"sh\", \"-c\", \"nginx && pnpm run pm2\"]\n"
  },
  {
    "path": "Jenkins/Build.Jenkinsfile",
    "content": "// Declarative Pipeline for building Node.js application and running SonarQube analysis triggered by a push event.\npipeline {\n    // Defines the execution environment. Using 'agent any' to ensure an agent is available.\n    agent any \n\n    // Global environment block removed to prevent Groovy scoping issues with manual path calculation.\n\n    stages {\n        // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins)\n        stage('Source Checkout') {\n            steps {\n                echo \"Workspace already populated by the initial SCM checkout. Proceeding.\"\n            }\n        }\n\n        // Stage 2: Setup Node.js v20 and install pnpm\n        stage('Setup Environment and Tools') {\n            steps {\n                sh '''\n                    echo \"Ensuring required utilities and Node.js are installed...\"\n                    sudo apt-get update\n                    sudo apt-get install -y curl unzip nodejs\n                    \n                    # 1. Install Node.js v20 \n                    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\n                    sudo apt-get install -y nodejs\n                    echo \"Node.js version: \\$(node -v)\"\n                    \n                    # 2. Install pnpm globally (version 8)\n                    npm install -g pnpm@8\n                    echo \"pnpm version: \\$(pnpm -v)\"\n                '''\n            }\n        }\n\n        // Stage 3: Install dependencies and build the application\n        stage('Install and Build') {\n            steps {\n                sh 'pnpm install'\n                sh 'pnpm run build'\n            }\n        }\n\n        // Stage 4: Run SonarQube analysis: Install scanner, get version, and execute.\n        stage('SonarQube Analysis') {\n            steps {\n                script {\n                    // 1. Get the short 8-character commit SHA for project versioning\n                    def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()\n                    echo \"Commit SHA (short) is: ${commitShaShort}\"\n                    \n                    // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY IN THIS STAGE ---\n                    sh \"\"\"\n                        echo \"Manually downloading and installing Sonar Scanner CLI...\"\n                        \n                        # Download the stable scanner CLI package\n                        curl -sS -o sonar-scanner.zip \\\n                        \"https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip\"\n                        \n                        # Added -o flag to force overwrite and prevent interactive prompt failure\n                        unzip -o -q sonar-scanner.zip -d .\n                    \"\"\"\n\n                    // 3. Find the extracted directory name and capture the full absolute bin path in Groovy\n                    // This is defined locally and used directly, avoiding environment variable issues.\n                    def scannerBinPath = sh(\n                        returnStdout: true,\n                        script: '''\n                            SCANNER_DIR=$(find . -maxdepth 1 -type d -name \"sonar-scanner*\" | head -n 1)\n                            # Get the full absolute path to the executable file\n                            echo \\$(pwd)/\\${SCANNER_DIR}/bin/sonar-scanner\n                        '''\n                    ).trim()\n                    \n                    echo \"Scanner executable path captured: ${scannerBinPath}\"\n                    \n                    // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN)\n                    withSonarQubeEnv(installationName: 'SonarQube-Server') {\n                        // 5. Execute the scanner using the Groovy variable directly.\n                        sh \"\"\"\n                            echo \"Starting SonarQube Analysis for project version: ${commitShaShort}\"\n                            \n                            # Execute the full, absolute path captured in the Groovy variable.\n                            '${scannerBinPath}' \\\\\n                                -Dsonar.projectVersion=${commitShaShort} \\\\\n                                -Dsonar.sources=.\n                                \n                            # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables\n                            # by the withSonarQubeEnv block.\n                        \"\"\"\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Jenkins/BuildPR.Jenkinsfile",
    "content": "// Declarative Pipeline for building Node.js application and running SonarQube analysis for a Pull Request.\npipeline {\n    // Defines the execution environment. Using 'agent any' to ensure an agent is available.\n    agent any\n\n    // Environment variables that hold PR details, provided by Jenkins Multibranch setup.\n    environment {\n        // FIX: Environment variables must be quoted or wrapped in a function call.\n        // We quote the 'env.CHANGE_ID' reference to fix the compilation error.\n        PR_KEY = \"${env.CHANGE_ID}\"\n        PR_BRANCH = \"${env.CHANGE_BRANCH}\"\n        PR_BASE = \"${env.CHANGE_TARGET}\"\n    }\n\n    stages {\n        // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins)\n        stage('Source Checkout') {\n            steps {\n                echo \"Workspace already populated by the initial SCM checkout. Proceeding.\"\n            }\n        }\n\n        // Stage 2: Setup Node.js v20, install pnpm, and install required tools (curl, unzip)\n        stage('Setup Environment and Tools') {\n            steps {\n                sh '''\n                    echo \"Ensuring required utilities and Node.js are installed...\"\n                    sudo apt-get update\n                    sudo apt-get install -y curl unzip nodejs\n                    \n                    # 1. Install Node.js v20 (closest matching the specified version '20.17.0')\n                    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\n                    sudo apt-get install -y nodejs\n                    echo \"Node.js version: \\$(node -v)\"\n                    \n                    # 2. Install pnpm globally (version 8)\n                    npm install -g pnpm@8\n                    echo \"pnpm version: \\$(pnpm -v)\"\n                '''\n            }\n        }\n\n        // Stage 3: Install dependencies and build the application\n        stage('Install and Build') {\n            steps {\n                sh 'pnpm install'\n                sh 'pnpm run build'\n            }\n        }\n\n        // Stage 4: Run SonarQube PR analysis: Install scanner locally, get version, and execute.\n        stage('SonarQube Pull Request Analysis') {\n            steps {\n                script {\n                    // 1. Get the short 8-character commit SHA for project versioning\n                    def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()\n                    echo \"Commit SHA (short) is: ${commitShaShort}\"\n                    \n                    // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY ---\n                    sh \"\"\"\n                        echo \"Manually downloading and installing Sonar Scanner CLI...\"\n                        curl -sS -o sonar-scanner.zip \\\n                        \"https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip\"\n                        unzip -o -q sonar-scanner.zip -d .\n                    \"\"\"\n\n                    // 3. Find the extracted directory name and capture the full absolute executable path.\n                    def scannerBinPath = sh(\n                        returnStdout: true,\n                        script: '''\n                            SCANNER_DIR=$(find . -maxdepth 1 -type d -name \"sonar-scanner*\" | head -n 1)\n                            # Get the full absolute path to the executable file\n                            echo \\$(pwd)/\\${SCANNER_DIR}/bin/sonar-scanner\n                        '''\n                    ).trim()\n                    \n                    echo \"Scanner executable path captured: ${scannerBinPath}\"\n                    \n                    // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN)\n                    withSonarQubeEnv(installationName: 'SonarQube-Server') {\n                        // 5. Execute the scanner using the Groovy variable directly with PR parameters.\n                        sh \"\"\"\n                            echo \"Starting SonarQube Pull Request Analysis for PR #${PR_KEY}\"\n                            \n                            '${scannerBinPath}' \\\\\n                                -Dsonar.projectVersion=${commitShaShort} \\\\\n                                -Dsonar.sources=. \\\\\n                                -Dsonar.pullrequest.key=${PR_KEY} \\\\\n                                -Dsonar.pullrequest.branch=${PR_BRANCH} \\\\\n                                -Dsonar.pullrequest.base=${PR_BASE}\n                                \n                            # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables\n                            # by the withSonarQubeEnv block.\n                        \"\"\"\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    Postiz - Social media schedule tool\n    Copyright (C) 2025 Nevo David\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://postiz.com/\" target=\"_blank\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/user-attachments/assets/765e9d72-3ee7-4a56-9d59-a2c9befe2311\">\n    <img alt=\"Postiz Logo\" src=\"https://github.com/user-attachments/assets/f0d30d70-dddb-4142-8876-e9aa6ed1cb99\" width=\"280\"/>\n  </picture>\n  </a>\n</p>\n\n<p align=\"center\">\n<a href=\"https://opensource.org/license/agpl-v3\">\n  <img src=\"https://img.shields.io/badge/License-AGPL%203.0-blue.svg\" alt=\"License\">\n</a>\n</p>\n\n<h3 align=\"center\"><strong><a href=\"https://github.com/gitroomhq/postiz-agent\">NEW: check out Postiz agent CLI! perfect for OpenClaw and other agents</a></strong></h3>\n<div align=\"center\">\n  <strong>\n  <h2>Your ultimate AI social media scheduling tool</h2><br />\n  <a href=\"https://postiz.com\">Postiz</a>: An alternative to: Buffer.com, Hypefury, Twitter Hunter, etc...<br /><br />\n  </strong>\n  Postiz offers everything you need to manage your social media posts,<br />build an audience, capture leads, and grow your business.\n</div>\n\n<div class=\"flex\" align=\"center\">\n  <br />\n  <img alt=\"Instagram\" src=\"https://postiz.com/svgs/socials/Instagram.svg\" width=\"32\">\n  <img alt=\"Youtube\" src=\"https://postiz.com/svgs/socials/Youtube.svg\" width=\"32\">\n  <img alt=\"Dribbble\" src=\"https://postiz.com/svgs/socials/Dribbble.svg\" width=\"32\">\n  <img alt=\"Linkedin\" src=\"https://postiz.com/svgs/socials/Linkedin.svg\" width=\"32\">\n  <img alt=\"Reddit\" src=\"https://postiz.com/svgs/socials/Reddit.svg\" width=\"32\">\n  <img alt=\"TikTok\" src=\"https://postiz.com/svgs/socials/TikTok.svg\" width=\"32\">\n  <img alt=\"Facebook\" src=\"https://postiz.com/svgs/socials/Facebook.svg\" width=\"32\">\n  <img alt=\"Pinterest\" src=\"https://postiz.com/svgs/socials/Pinterest.svg\" width=\"32\">\n  <img alt=\"Threads\" src=\"https://postiz.com/svgs/socials/Threads.svg\" width=\"32\">\n  <img alt=\"X\" src=\"https://postiz.com/svgs/socials/X.svg\" width=\"32\">\n  <img alt=\"Slack\" src=\"https://postiz.com/svgs/socials/Slack.svg\" width=\"32\">\n  <img alt=\"Discord\" src=\"https://postiz.com/svgs/socials/Discord.svg\" width=\"32\">\n  <img alt=\"Mastodon\" src=\"https://postiz.com/svgs/socials/Mastodon.svg\" width=\"32\">\n  <img alt=\"Bluesky\" src=\"https://postiz.com/svgs/socials/Bluesky.svg\" width=\"32\">\n</div>\n\n<p align=\"center\">\n  <br />\n  <a href=\"https://docs.postiz.com\" rel=\"dofollow\"><strong>Explore the docs »</strong></a>\n  <br />\n\n  <br />\n  <a href=\"https://youtube.com/@postizofficial\" rel=\"dofollow\"><strong>Watch the YouTube Tutorials»</strong></a>\n  <br />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://platform.postiz.com\">Register</a>\n  ·\n  <a href=\"https://discord.postiz.com\">Join Our Discord (devs only)</a>\n  ·\n  <a href=\"https://docs.postiz.com/public-api\">Public API</a><br />\n</p>\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@postiz/node\">NodeJS SDK</a>\n  ·\n  <a href=\"https://www.npmjs.com/package/n8n-nodes-postiz\">N8N custom node</a>\n  ·\n  <a href=\"https://apps.make.com/postiz\">Make.com integration</a>\n</p>\n\n\n<br />\n\n## New - Postiz-as-a-service - Enterprise (Cloud)\n\nIntegrate powerful social media scheduling capabilities into your SaaS. <br />Multi-tenant architecture designed for SaaS companies who want to offer social media management to their users.\n- **Skip App Approvals** - Use Postiz apps directly without going through lengthy social platform approval processes. Get the full power of Postiz instantly.\n- **Multi-Tenant Architecture** - each of your customers gets their own isolated environment with separate accounts, channels, and team management.\n- **Headless API** - Full REST API access to build your own frontend experience. Complete control over the user interface and branding.\n- **Full OAuth Support** - Connect all major social platforms including Facebook, Instagram, Twitter, LinkedIn, TikTok, and more.\n\n\n[Check it here](https://postiz.com/enterprise)\n\n<br /><br />\n\n## 🔌 See the leading Postiz features\n\n<p align=\"center\">\n  <a href=\"https://www.youtube.com/watch?v=BdsCVvEYgHU\" target=\"_blank\">\n    <img alt=\"Postiz\" src=\"https://github.com/user-attachments/assets/8b9b7939-da1a-4be5-95be-42c6fce772de\" />\n  </a>\n</p>\n\n## ✨ Features\n\n| ![Image 1](https://github.com/user-attachments/assets/a27ee220-beb7-4c7e-8c1b-2c44301f82ef) | ![Image 2](https://github.com/user-attachments/assets/eb5f5f15-ed90-47fc-811c-03ccba6fa8a2) |\n| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| ![Image 3](https://github.com/user-attachments/assets/d51786ee-ddd8-4ef8-8138-5192e9cfe7c3) | ![Image 4](https://github.com/user-attachments/assets/91f83c89-22f6-43d6-b7aa-d2d3378289fb) |\n\n### Our Sponsors\n\n| Sponsor |                                  Logo                                   | Description     |\n|---------|:-----------------------------------------------------------------------:|-----------------|\n| [Hostinger](https://www.hostinger.com/?ref=postiz) | <img src=\".github/sponsors/hostinger.png\" alt=\"Hostinger\" width=\"500\"/> | Hostinger is on a mission to make online success possible for anyone – from developers to aspiring bloggers and business owners |\n| [Virlo](https://dev.virlo.ai/?ref=postiz) | <img src=\"https://github.com/user-attachments/assets/25182598-5344-45fc-b9cd-e4cfa16aabfd\" alt=\"Virlo\" width=\"500\"/> | Virlo is the #1 social media trend spotting and all-in-one GTM tool for teams leveraging short-form video |\n\n\n\n# Intro\n\n- Schedule all your social media posts (many AI features)\n- Measure your work with analytics.\n- Collaborate with other team members to exchange or buy posts.\n- Invite your team members to collaborate, comment, and schedule posts.\n- At the moment there is no difference between the hosted version to the self-hosted version\n- Perfect for automation (API) with platforms like N8N, Make.com, Zapier, etc.\n\n## Tech Stack\n\n- Pnpm workspaces (Monorepo)\n- NextJS (React)\n- NestJS\n- Prisma (Default to PostgreSQL)\n- Temporal\n- Resend (email notifications)\n\n## Quick Start\n\nTo have the project up and running, please follow the [Quick Start Guide](https://docs.postiz.com/quickstart)\n\n## Sponsor Postiz\n\nWe now give a few options to Sponsor Postiz:\n- Just a donation: You like what we are building, and want to buy us some coffees so we can build faster.\n- Main Repository: Get your logo with a backlink from the main Postiz repository. Postiz has almost 3m downloads and 20k views per month.\n- Main Repository + Website: Get your logo on the central repository and the main website. Here are some metrics: - Website has 20k hits per month + 65 DR (strong backlink) - Repository has 20k hits per month + Almost 3m docker downloads.\n\nLink: https://opencollective.com/postiz\n\n## Postiz Compliance\n\n- Postiz is an open-source, self-hosted social media scheduling tool that supports platforms like X (formerly Twitter), Bluesky, Mastodon, Discord, and others.\n- Postiz hosted service uses official, platform-approved OAuth flows.\n- Postiz does not automate or scrape content from social media platforms.\n- Postiz does not collect, store, or proxy API keys or access tokens from users.\n- Postiz never ask users to paste API keys into our hosted product.\n- Postiz Users always authenticate directly with the social platform (e.g., X, Discord, etc.), ensuring platform compliance and data privacy.\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=gitroomhq/postiz-app&type=date&legend=top-left)](https://www.star-history.com/#gitroomhq/postiz-app&type=date&legend=top-left)\n\n## License\n\nThis repository's source code is available under the [AGPL-3.0 license](LICENSE).\n\n<br /><br /><br />\n\n<p align=\"center\">\n  <a href=\"https://www.g2.com/products/postiz/take_survey\" target=\"blank\"><img alt=\"g2\" src=\"https://github.com/user-attachments/assets/892cb74c-0b49-4589-b2f5-fbdbf7a98f66\" /></a>\n</p>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Introduction\n\nThe Postiz app is committed to ensuring the security and integrity of our users' data. This security policy outlines our procedures for handling security vulnerabilities and our disclosure policy.\n\n## Reporting Security Vulnerabilities\n\nIf you discover a security vulnerability in the Postiz app, please report it to us privately via email to one of the maintainers:\n\n- @nevo-david\n- @egelhaus ([E-Mail](mailto:egelhaus@ennogelhaus.de))\n\nWhen reporting a security vulnerability, please provide as much detail as possible, including:\n\n- A clear description of the vulnerability\n- Steps to reproduce the vulnerability\n- Any relevant code or configuration files\n\n## Supported Versions\n\nThis project currently only supports the latest release. We recommend that users always use the latest version of the Postiz app to ensure they have the latest security patches.\n\n## Disclosure Guidelines\n\nWe follow a private disclosure policy. If you discover a security vulnerability, please report it to us privately via email to one of the maintainers listed above. We will respond promptly to reports of vulnerabilities and work to resolve them as quickly as possible.\n\nWe will not publicly disclose security vulnerabilities until a patch or fix is available to prevent malicious actors from exploiting the vulnerability before a fix is released.\n\n## Security Vulnerability Response Process\n\nWe take security vulnerabilities seriously and will respond promptly to reports of vulnerabilities. Our response process includes:\n\n- Investigating the report and verifying the vulnerability.\n- Developing a patch or fix for the vulnerability.\n- Releasing the patch or fix as soon as possible.\n- Notifying users of the vulnerability and the patch or fix.\n\n## Template Attribution\n\nThis SECURITY.md file is based on the [GitHub Security Policy Template](https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository).\n\nThank you for helping to keep the `postiz-app` secure!\n"
  },
  {
    "path": "apps/backend/.gitignore",
    "content": "dist/\nnode_modules/\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n"
  },
  {
    "path": "apps/backend/nest-cli.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"monorepo\": false,\n  \"sourceRoot\": \"src\",\n  \"entryFile\": \"../../dist/backend/apps/backend/src/main\",\n  \"language\": \"ts\",\n  \"generateOptions\": {\n    \"spec\": false\n  },\n  \"compilerOptions\": {\n    \"manualRestart\": true,\n    \"tsConfigPath\": \"./tsconfig.build.json\",\n    \"webpack\": false,\n    \"deleteOutDir\": true,\n    \"assets\": [],\n    \"watchAssets\": false,\n    \"plugins\": []\n  }\n}\n"
  },
  {
    "path": "apps/backend/package.json",
    "content": "{\n  \"name\": \"postiz-backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"dotenv -e ../../.env -- nest start --watch --entryFile=./apps/backend/src/main\",\n    \"build\": \"cross-env NODE_ENV=production nest build\",\n    \"start\": \"dotenv -e ../../.env -- node --experimental-require-module ./dist/apps/backend/src/main.js\",\n    \"pm2\": \"pm2 start pnpm --name backend -- start\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "apps/backend/src/api/api.module.ts",
    "content": "import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';\nimport { AuthController } from '@gitroom/backend/api/routes/auth.controller';\nimport { AuthService } from '@gitroom/backend/services/auth/auth.service';\nimport { UsersController } from '@gitroom/backend/api/routes/users.controller';\nimport { AuthMiddleware } from '@gitroom/backend/services/auth/auth.middleware';\nimport { StripeController } from '@gitroom/backend/api/routes/stripe.controller';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { AnalyticsController } from '@gitroom/backend/api/routes/analytics.controller';\nimport { PoliciesGuard } from '@gitroom/backend/services/auth/permissions/permissions.guard';\nimport { PermissionsService } from '@gitroom/backend/services/auth/permissions/permissions.service';\nimport { IntegrationsController } from '@gitroom/backend/api/routes/integrations.controller';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { SettingsController } from '@gitroom/backend/api/routes/settings.controller';\nimport { PostsController } from '@gitroom/backend/api/routes/posts.controller';\nimport { MediaController } from '@gitroom/backend/api/routes/media.controller';\nimport { UploadModule } from '@gitroom/nestjs-libraries/upload/upload.module';\nimport { BillingController } from '@gitroom/backend/api/routes/billing.controller';\nimport { NotificationsController } from '@gitroom/backend/api/routes/notifications.controller';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';\nimport { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';\nimport { CopilotController } from '@gitroom/backend/api/routes/copilot.controller';\nimport { PublicController } from '@gitroom/backend/api/routes/public.controller';\nimport { RootController } from '@gitroom/backend/api/routes/root.controller';\nimport { TrackService } from '@gitroom/nestjs-libraries/track/track.service';\nimport { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';\nimport { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';\nimport { WebhookController } from '@gitroom/backend/api/routes/webhooks.controller';\nimport { SignatureController } from '@gitroom/backend/api/routes/signature.controller';\nimport { AutopostController } from '@gitroom/backend/api/routes/autopost.controller';\nimport { SetsController } from '@gitroom/backend/api/routes/sets.controller';\nimport { ThirdPartyController } from '@gitroom/backend/api/routes/third-party.controller';\nimport { MonitorController } from '@gitroom/backend/api/routes/monitor.controller';\nimport { NoAuthIntegrationsController } from '@gitroom/backend/api/routes/no.auth.integrations.controller';\nimport { EnterpriseController } from '@gitroom/backend/api/routes/enterprise.controller';\nimport { OAuthAppController } from '@gitroom/backend/api/routes/oauth-app.controller';\nimport { ApprovedAppsController } from '@gitroom/backend/api/routes/approved-apps.controller';\nimport { OAuthController, OAuthAuthorizedController } from '@gitroom/backend/api/routes/oauth.controller';\nimport { AuthProviderManager } from '@gitroom/backend/services/auth/providers/providers.manager';\nimport { GithubProvider } from '@gitroom/backend/services/auth/providers/github.provider';\nimport { GoogleProvider } from '@gitroom/backend/services/auth/providers/google.provider';\nimport { FarcasterProvider } from '@gitroom/backend/services/auth/providers/farcaster.provider';\nimport { WalletProvider } from '@gitroom/backend/services/auth/providers/wallet.provider';\nimport { OauthProvider } from '@gitroom/backend/services/auth/providers/oauth.provider';\n\nconst authenticatedController = [\n  UsersController,\n  AnalyticsController,\n  IntegrationsController,\n  SettingsController,\n  PostsController,\n  MediaController,\n  BillingController,\n  NotificationsController,\n  CopilotController,\n  WebhookController,\n  SignatureController,\n  AutopostController,\n  SetsController,\n  ThirdPartyController,\n  OAuthAppController,\n  ApprovedAppsController,\n  OAuthAuthorizedController,\n];\n@Module({\n  imports: [UploadModule],\n  controllers: [\n    RootController,\n    StripeController,\n    AuthController,\n    PublicController,\n    MonitorController,\n    EnterpriseController,\n    NoAuthIntegrationsController,\n    OAuthController,\n    ...authenticatedController,\n  ],\n  providers: [\n    AuthService,\n    StripeService,\n    OpenaiService,\n    ExtractContentService,\n    AuthMiddleware,\n    PoliciesGuard,\n    PermissionsService,\n    CodesService,\n    IntegrationManager,\n    TrackService,\n    ShortLinkService,\n    Nowpayments,\n    AuthProviderManager,\n    GithubProvider,\n    GoogleProvider,\n    FarcasterProvider,\n    WalletProvider,\n    OauthProvider,\n  ],\n  get exports() {\n    return [...this.imports, ...this.providers];\n  },\n})\nexport class ApiModule implements NestModule {\n  configure(consumer: MiddlewareConsumer) {\n    consumer.apply(AuthMiddleware).forRoutes(...authenticatedController);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/analytics.controller.ts",
    "content": "import { Controller, Get, Param, Query } from '@nestjs/common';\nimport { Organization } from '@prisma/client';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { ApiTags } from '@nestjs/swagger';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\n\n@ApiTags('Analytics')\n@Controller('/analytics')\nexport class AnalyticsController {\n  constructor(\n    private _integrationService: IntegrationService,\n    private _postsService: PostsService\n  ) {}\n\n  @Get('/:integration')\n  async getIntegration(\n    @GetOrgFromRequest() org: Organization,\n    @Param('integration') integration: string,\n    @Query('date') date: string\n  ) {\n    return this._integrationService.checkAnalytics(org, integration, date);\n  }\n\n  @Get('/post/:postId')\n  async getPostAnalytics(\n    @GetOrgFromRequest() org: Organization,\n    @Param('postId') postId: string,\n    @Query('date') date: string\n  ) {\n    return this._postsService.checkPostAnalytics(org.id, postId, +date);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/approved-apps.controller.ts",
    "content": "import { Controller, Delete, Get, Param } from '@nestjs/common';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { User } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\n\n@ApiTags('Approved Apps')\n@Controller('/user/approved-apps')\nexport class ApprovedAppsController {\n  constructor(private _oauthService: OAuthService) {}\n\n  @Get('/')\n  async list(@GetUserFromRequest() user: User) {\n    return this._oauthService.getApprovedApps(user.id);\n  }\n\n  @Delete('/:id')\n  async revoke(\n    @GetUserFromRequest() user: User,\n    @Param('id') id: string\n  ) {\n    return this._oauthService.revokeApp(user.id, id);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/auth.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  Param,\n  Post,\n  Query,\n  Req,\n  Res,\n} from '@nestjs/common';\nimport { Response, Request } from 'express';\n\nimport { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';\nimport { LoginUserDto } from '@gitroom/nestjs-libraries/dtos/auth/login.user.dto';\nimport { AuthService } from '@gitroom/backend/services/auth/auth.service';\nimport { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';\nimport { ForgotPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot.password.dto';\nimport { ResendActivationDto } from '@gitroom/nestjs-libraries/dtos/auth/resend-activation.dto';\nimport { ApiTags } from '@nestjs/swagger';\nimport { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';\nimport { EmailService } from '@gitroom/nestjs-libraries/services/email.service';\nimport { RealIP } from 'nestjs-real-ip';\nimport { UserAgent } from '@gitroom/nestjs-libraries/user/user.agent';\nimport { Provider } from '@prisma/client';\nimport * as Sentry from '@sentry/nestjs';\n\n@ApiTags('Auth')\n@Controller('/auth')\nexport class AuthController {\n  constructor(\n    private _authService: AuthService,\n    private _emailService: EmailService\n  ) {}\n\n  @Get('/can-register')\n  async canRegister() {\n    return {\n      register: await this._authService.canRegister(Provider.LOCAL as string),\n    };\n  }\n\n  @Post('/register')\n  async register(\n    @Req() req: Request,\n    @Body() body: CreateOrgUserDto,\n    @Res({ passthrough: false }) response: Response,\n    @RealIP() ip: string,\n    @UserAgent() userAgent: string\n  ) {\n    try {\n      const getOrgFromCookie = this._authService.getOrgFromCookie(\n        req?.cookies?.org\n      );\n\n      const { jwt, addedOrg } = await this._authService.routeAuth(\n        body.provider,\n        body,\n        ip,\n        userAgent,\n        getOrgFromCookie\n      );\n\n      const activationRequired =\n        body.provider === 'LOCAL' && this._emailService.hasProvider();\n\n      if (activationRequired) {\n        response.header('activate', 'true');\n        response.status(200).json({ activate: true });\n        return;\n      }\n\n      response.cookie('auth', jwt, {\n        domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n        ...(!process.env.NOT_SECURED\n          ? {\n              secure: true,\n              httpOnly: true,\n              sameSite: 'none',\n            }\n          : {}),\n        expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n      });\n\n      if (process.env.NOT_SECURED) {\n        response.header('auth', jwt);\n      }\n\n      if (typeof addedOrg !== 'boolean' && addedOrg?.organizationId) {\n        response.cookie('showorg', addedOrg.organizationId, {\n          domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n          ...(!process.env.NOT_SECURED\n            ? {\n                secure: true,\n                httpOnly: true,\n                sameSite: 'none',\n              }\n            : {}),\n          expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n        });\n\n        if (process.env.NOT_SECURED) {\n          response.header('showorg', addedOrg.organizationId);\n        }\n      }\n\n      Sentry.metrics.count('new_user', 1);\n      response.header('onboarding', 'true');\n      response.status(200).json({\n        register: true,\n      });\n    } catch (e: any) {\n      response.status(400).send(e.message);\n    }\n  }\n\n  @Post('/login')\n  async login(\n    @Req() req: Request,\n    @Body() body: LoginUserDto,\n    @Res({ passthrough: false }) response: Response,\n    @RealIP() ip: string,\n    @UserAgent() userAgent: string\n  ) {\n    try {\n      const getOrgFromCookie = this._authService.getOrgFromCookie(\n        req?.cookies?.org\n      );\n\n      const { jwt, addedOrg } = await this._authService.routeAuth(\n        body.provider,\n        body,\n        ip,\n        userAgent,\n        getOrgFromCookie\n      );\n\n      response.cookie('auth', jwt, {\n        domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n        ...(!process.env.NOT_SECURED\n          ? {\n              secure: true,\n              httpOnly: true,\n              sameSite: 'none',\n            }\n          : {}),\n        expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n      });\n\n      if (process.env.NOT_SECURED) {\n        response.header('auth', jwt);\n      }\n\n      if (typeof addedOrg !== 'boolean' && addedOrg?.organizationId) {\n        response.cookie('showorg', addedOrg.organizationId, {\n          domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n          ...(!process.env.NOT_SECURED\n            ? {\n                secure: true,\n                httpOnly: true,\n                sameSite: 'none',\n              }\n            : {}),\n          expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n        });\n\n        if (process.env.NOT_SECURED) {\n          response.header('showorg', addedOrg.organizationId);\n        }\n      }\n\n      response.header('reload', 'true');\n      response.status(200).json({\n        login: true,\n      });\n    } catch (e: any) {\n      response.status(400).send(e.message);\n    }\n  }\n\n  @Post('/forgot')\n  async forgot(@Body() body: ForgotPasswordDto) {\n    try {\n      await this._authService.forgot(body.email);\n      return {\n        forgot: true,\n      };\n    } catch (e) {\n      return {\n        forgot: false,\n      };\n    }\n  }\n\n  @Post('/forgot-return')\n  async forgotReturn(@Body() body: ForgotReturnPasswordDto) {\n    const reset = await this._authService.forgotReturn(body);\n    return {\n      reset: !!reset,\n    };\n  }\n\n  @Get('/oauth/:provider')\n  async oauthLink(@Param('provider') provider: string, @Query() query: any) {\n    return this._authService.oauthLink(provider, query);\n  }\n\n  @Post('/activate')\n  async activate(\n    @Body('code') code: string,\n    @Body('datafast_visitor_id') datafast_visitor_id: string,\n    @Res({ passthrough: false }) response: Response\n  ) {\n    const activate = await this._authService.activate(code, datafast_visitor_id);\n    if (!activate) {\n      return response.status(200).json({ can: false });\n    }\n\n    response.cookie('auth', activate, {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n    });\n\n    if (process.env.NOT_SECURED) {\n      response.header('auth', activate);\n    }\n\n    response.header('onboarding', 'true');\n\n    return response.status(200).json({ can: true });\n  }\n\n  @Post('/resend-activation')\n  async resendActivation(@Body() body: ResendActivationDto) {\n    try {\n      await this._authService.resendActivationEmail(body.email);\n      return {\n        success: true,\n      };\n    } catch (e: any) {\n      return {\n        success: false,\n        message: e.message,\n      };\n    }\n  }\n\n  @Post('/oauth/:provider/exists')\n  async oauthExists(\n    @Body('code') code: string,\n    @Param('provider') provider: string,\n    @Res({ passthrough: false }) response: Response\n  ) {\n    const { jwt, token } = await this._authService.checkExists(provider, code);\n\n    if (token) {\n      return response.json({ token });\n    }\n\n    response.cookie('auth', jwt, {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n    });\n\n    if (process.env.NOT_SECURED) {\n      response.header('auth', jwt);\n    }\n\n    response.header('reload', 'true');\n\n    response.status(200).json({\n      login: true,\n    });\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/autopost.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n  Query,\n} from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';\nimport { AutopostDto } from '@gitroom/nestjs-libraries/dtos/autopost/autopost.dto';\nimport { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@ApiTags('Autopost')\n@Controller('/autopost')\nexport class AutopostController {\n  constructor(private _autopostsService: AutopostService) {}\n\n  @Get('/')\n  async getAutoposts(@GetOrgFromRequest() org: Organization) {\n    return this._autopostsService.getAutoposts(org.id);\n  }\n\n  @Post('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.WEBHOOKS])\n  async createAutopost(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: AutopostDto\n  ) {\n    return this._autopostsService.createAutopost(org.id, body);\n  }\n\n  @Put('/:id')\n  async updateAutopost(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: AutopostDto,\n    @Param('id') id: string\n  ) {\n    return this._autopostsService.createAutopost(org.id, body, id);\n  }\n\n  @Delete('/:id')\n  async deleteAutopost(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._autopostsService.deleteAutopost(org.id, id);\n  }\n\n  @Post('/:id/active')\n  async changeActive(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body('active') active: boolean\n  ) {\n    return this._autopostsService.changeActive(org.id, id, active);\n  }\n\n  @Post('/send')\n  async sendWebhook(@Query('url') url: string) {\n    return this._autopostsService.loadXML(url);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/billing.controller.ts",
    "content": "import { Body, Controller, Get, HttpException, Param, Post, Req } from '@nestjs/common';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization, User } from '@prisma/client';\nimport { BillingSubscribeDto } from '@gitroom/nestjs-libraries/dtos/billing/billing.subscribe.dto';\nimport { ApiTags } from '@nestjs/swagger';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { Request } from 'express';\nimport { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\n\n@ApiTags('Billing')\n@Controller('/billing')\nexport class BillingController {\n  constructor(\n    private _subscriptionService: SubscriptionService,\n    private _stripeService: StripeService,\n    private _notificationService: NotificationService,\n    private _nowpayments: Nowpayments\n  ) {}\n\n  @Get('/check/:id')\n  async checkId(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') body: string\n  ) {\n    return {\n      status: await this._stripeService.checkSubscription(org.id, body),\n    };\n  }\n\n  @Get('/check-discount')\n  async checkDiscount(@GetOrgFromRequest() org: Organization) {\n    return {\n      offerCoupon: !(await this._stripeService.checkDiscount(org.paymentId))\n        ? false\n        : AuthService.signJWT({ discount: true }),\n    };\n  }\n\n  @Post('/apply-discount')\n  async applyDiscount(@GetOrgFromRequest() org: Organization) {\n    await this._stripeService.applyDiscount(org.paymentId);\n  }\n\n  @Post('/finish-trial')\n  async finishTrial(@GetOrgFromRequest() org: Organization) {\n    try {\n      await this._stripeService.finishTrial(org.paymentId);\n    } catch (err) {}\n    return {\n      finish: true,\n    };\n  }\n\n  @Get('/is-trial-finished')\n  async isTrialFinished(@GetOrgFromRequest() org: Organization) {\n    return {\n      finished: !org.isTrailing,\n    };\n  }\n\n  @Post('/embedded')\n  embedded(\n    @GetOrgFromRequest() org: Organization,\n    @GetUserFromRequest() user: User,\n    @Body() body: BillingSubscribeDto,\n    @Req() req: Request\n  ) {\n    const uniqueId = req?.cookies?.track;\n    return this._stripeService.embedded(\n      uniqueId,\n      org.id,\n      user.id,\n      body,\n      org.allowTrial\n    );\n  }\n\n  @Post('/subscribe')\n  subscribe(\n    @GetOrgFromRequest() org: Organization,\n    @GetUserFromRequest() user: User,\n    @Body() body: BillingSubscribeDto,\n    @Req() req: Request\n  ) {\n    const uniqueId = req?.cookies?.track;\n    return this._stripeService.subscribe(\n      uniqueId,\n      org.id,\n      user.id,\n      body,\n      org.allowTrial\n    );\n  }\n\n  @Get('/portal')\n  async modifyPayment(@GetOrgFromRequest() org: Organization) {\n    const customer = await this._stripeService.getCustomerByOrganizationId(\n      org.id\n    );\n    const { url } = await this._stripeService.createBillingPortalLink(customer);\n    return {\n      portal: url,\n    };\n  }\n\n  @Get('/')\n  getCurrentBilling(@GetOrgFromRequest() org: Organization) {\n    return this._subscriptionService.getSubscriptionByOrganizationId(org.id);\n  }\n\n  @Post('/cancel')\n  async cancel(\n    @GetOrgFromRequest() org: Organization,\n    @GetUserFromRequest() user: User,\n    @Body() body: { feedback: string }\n  ) {\n    await this._notificationService.sendEmail(\n      process.env.EMAIL_FROM_ADDRESS,\n      'Subscription Cancelled',\n      `Organization ${org.name} has cancelled their subscription because: ${body.feedback}`,\n      user.email\n    );\n\n    return this._stripeService.setToCancel(org.id);\n  }\n\n  @Post('/prorate')\n  prorate(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: BillingSubscribeDto\n  ) {\n    return this._stripeService.prorate(org.id, body);\n  }\n\n  @Post('/lifetime')\n  async lifetime(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: { code: string }\n  ) {\n    return this._stripeService.lifetimeDeal(org.id, body.code);\n  }\n\n  @Get('/charges')\n  async getCharges(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new HttpException('Unauthorized', 400);\n    }\n\n    return this._stripeService.getCharges(org.id);\n  }\n\n  @Post('/refund-charges')\n  async refundCharges(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: { chargeIds: string[] }\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new HttpException('Unauthorized', 400);\n    }\n\n    return this._stripeService.refundCharges(org.id, body.chargeIds);\n  }\n\n  @Post('/cancel-subscription')\n  async cancelSubscription(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new HttpException('Unauthorized', 400);\n    }\n\n    return this._stripeService.cancelSubscription(org.id);\n  }\n\n  @Post('/add-subscription')\n  async addSubscription(\n    @Body() body: { subscription: string },\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new Error('Unauthorized');\n    }\n\n    await this._subscriptionService.addSubscription(\n      org.id,\n      user.id,\n      body.subscription\n    );\n  }\n\n  @Get('/crypto')\n  async crypto(@GetOrgFromRequest() org: Organization) {\n    return this._nowpayments.createPaymentPage(org.id);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/copilot.controller.ts",
    "content": "import {\n  Logger,\n  Controller,\n  Get,\n  Post,\n  Req,\n  Res,\n  Query,\n  Param,\n} from '@nestjs/common';\nimport {\n  CopilotRuntime,\n  OpenAIAdapter,\n  copilotRuntimeNodeHttpEndpoint,\n  copilotRuntimeNextJSAppRouterEndpoint,\n} from '@copilotkit/runtime';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { MastraAgent } from '@ag-ui/mastra';\nimport { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';\nimport { Request, Response } from 'express';\nimport { RuntimeContext } from '@mastra/core/di';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\nexport type ChannelsContext = {\n  integrations: string;\n  organization: string;\n  ui: string;\n};\n\n@Controller('/copilot')\nexport class CopilotController {\n  constructor(\n    private _subscriptionService: SubscriptionService,\n    private _mastraService: MastraService\n  ) {}\n  @Post('/chat')\n  chatAgent(@Req() req: Request, @Res() res: Response) {\n    if (\n      process.env.OPENAI_API_KEY === undefined ||\n      process.env.OPENAI_API_KEY === ''\n    ) {\n      Logger.warn('OpenAI API key not set, chat functionality will not work');\n      return;\n    }\n\n    const copilotRuntimeHandler = copilotRuntimeNodeHttpEndpoint({\n      endpoint: '/copilot/chat',\n      runtime: new CopilotRuntime(),\n      serviceAdapter: new OpenAIAdapter({\n        model: 'gpt-4.1',\n      }),\n    });\n\n    return copilotRuntimeHandler(req, res);\n  }\n\n  @Post('/agent')\n  @CheckPolicies([AuthorizationActions.Create, Sections.AI])\n  async agent(\n    @Req() req: Request,\n    @Res() res: Response,\n    @GetOrgFromRequest() organization: Organization\n  ) {\n    if (\n      process.env.OPENAI_API_KEY === undefined ||\n      process.env.OPENAI_API_KEY === ''\n    ) {\n      Logger.warn('OpenAI API key not set, chat functionality will not work');\n      return;\n    }\n    const mastra = await this._mastraService.mastra();\n    const runtimeContext = new RuntimeContext<ChannelsContext>();\n    runtimeContext.set(\n      'integrations',\n      req?.body?.variables?.properties?.integrations || []\n    );\n\n    runtimeContext.set('organization', JSON.stringify(organization));\n    runtimeContext.set('ui', 'true');\n\n    const agents = MastraAgent.getLocalAgents({\n      resourceId: organization.id,\n      mastra,\n      // @ts-ignore\n      runtimeContext,\n    });\n\n    const runtime = new CopilotRuntime({\n      agents,\n    });\n\n    const copilotRuntimeHandler = copilotRuntimeNextJSAppRouterEndpoint({\n      endpoint: '/copilot/agent',\n      runtime,\n      // properties: req.body.variables.properties,\n      serviceAdapter: new OpenAIAdapter({\n        model: 'gpt-4.1',\n      }),\n    });\n\n    return copilotRuntimeHandler.handleRequest(req, res);\n  }\n\n  @Get('/credits')\n  calculateCredits(\n    @GetOrgFromRequest() organization: Organization,\n    @Query('type') type: 'ai_images' | 'ai_videos'\n  ) {\n    return this._subscriptionService.checkCredits(\n      organization,\n      type || 'ai_images'\n    );\n  }\n\n  @Get('/:thread/list')\n  @CheckPolicies([AuthorizationActions.Create, Sections.AI])\n  async getMessagesList(\n    @GetOrgFromRequest() organization: Organization,\n    @Param('thread') threadId: string\n  ): Promise<any> {\n    const mastra = await this._mastraService.mastra();\n    const memory = await mastra.getAgent('postiz').getMemory();\n    try {\n      return await memory.query({\n        resourceId: organization.id,\n        threadId,\n      });\n    } catch (err) {\n      return { messages: [] };\n    }\n  }\n\n  @Get('/list')\n  @CheckPolicies([AuthorizationActions.Create, Sections.AI])\n  async getList(@GetOrgFromRequest() organization: Organization) {\n    const mastra = await this._mastraService.mastra();\n    // @ts-ignore\n    const memory = await mastra.getAgent('postiz').getMemory();\n    const list = await memory.getThreadsByResourceIdPaginated({\n      resourceId: organization.id,\n      perPage: 100000,\n      page: 0,\n      orderBy: 'createdAt',\n      sortDirection: 'DESC',\n    });\n\n    return {\n      threads: list.threads.map((p) => ({\n        id: p.id,\n        title: p.title,\n      })),\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/enterprise.controller.ts",
    "content": "import { Body, Controller, Param, Post, Res } from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\n\n@ApiTags('Enterprise')\n@Controller('/enterprise')\nexport class EnterpriseController {\n  constructor(\n    private _integrationManager: IntegrationManager,\n    private _organizationService: OrganizationService,\n    private _integrationService: IntegrationService,\n    private _postsService: PostsService\n  ) {}\n\n  @Post('/create-user')\n  async createUser(@Body('params') params: string) {\n    try {\n      const { id, name, saasName, email } = AuthService.verifyJWT(params) as {\n        id: string;\n        name: string;\n        email: string;\n        saasName: string;\n      };\n\n      try {\n        return await this._organizationService.createMaxUser(\n          id,\n          name,\n          saasName,\n          email\n        );\n      } catch (err) {\n        return { create: false };\n      }\n    } catch (err) {\n      return { success: false };\n    }\n  }\n\n  @Post('/url')\n  async redirectParams(@Body('params') params: string) {\n    try {\n      const load = AuthService.verifyJWT(params) as {\n        redirectUrl: string;\n        apiKey: string;\n        refreshId?: string;\n        provider: string;\n        webhookUrl: string;\n      };\n\n      if (!load || !load.redirectUrl || !load.apiKey || !load.provider) {\n        return;\n      }\n\n      const org = await this._organizationService.getOrgByApiKey(load.apiKey);\n\n      if (!org) {\n        throw new Error('Organization not found');\n      }\n\n      if (\n        !this._integrationManager\n          .getAllowedSocialsIntegrations()\n          .includes(load.provider)\n      ) {\n        throw new Error('Integration not allowed');\n      }\n\n      const integrationProvider = this._integrationManager.getSocialIntegration(\n        load.provider\n      );\n\n      const { codeVerifier, state, url } =\n        await integrationProvider.generateAuthUrl();\n\n      if (load.refreshId) {\n        await ioRedis.set(`refresh:${state}`, load.refreshId, 'EX', 3600);\n      }\n\n      await ioRedis.set(`webhookUrl:${state}`, load.webhookUrl, 'EX', 3600);\n      await ioRedis.set(`redirect:${state}`, load.redirectUrl, 'EX', 3600);\n      await ioRedis.set(`organization:${state}`, org.id, 'EX', 3600);\n      await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 3600);\n\n      return url;\n    } catch (err) {}\n  }\n\n  @Post('/delete-channel')\n  async deleteChannel(@Body('params') params: string) {\n    try {\n      const load = AuthService.verifyJWT(params) as {\n        apiKey: string;\n        id: string;\n      };\n\n      if (!load || !load.apiKey || !load.id) {\n        return { success: false };\n      }\n\n      const org = await this._organizationService.getOrgByApiKey(load.apiKey);\n\n      if (!org) {\n        return { success: false };\n      }\n\n      const isTherePosts = await this._integrationService.getPostsForChannel(\n        org.id,\n        load.id\n      );\n      if (isTherePosts.length) {\n        for (const post of isTherePosts) {\n          this._postsService.deletePost(org.id, post.group).catch(() => {});\n        }\n      }\n\n      await this._integrationService.deleteChannel(org.id, load.id);\n      return { success: true };\n    } catch (err) {\n      return { success: false };\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/integrations.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n  Query,\n} from '@nestjs/common';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization, User } from '@prisma/client';\nimport { IntegrationFunctionDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.function.dto';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { ApiTags } from '@nestjs/swagger';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';\nimport { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\n\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';\nimport { MoltbookProvider } from '@gitroom/nestjs-libraries/integrations/social/moltbook.provider';\nimport {\n  AuthorizationActions,\n  Sections,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\nimport { uniqBy } from 'lodash';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\n\n@ApiTags('Integrations')\n@Controller('/integrations')\nexport class IntegrationsController {\n  constructor(\n    private _integrationManager: IntegrationManager,\n    private _integrationService: IntegrationService,\n    private _postService: PostsService,\n    private _refreshIntegrationService: RefreshIntegrationService\n  ) {}\n\n  @Post('/provider/:id/connect')\n  @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])\n  async saveProviderPage(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: any\n  ) {\n    return this._integrationService.saveProviderPage(org.id, id, body);\n  }\n\n  @Get('/:identifier/internal-plugs')\n  getInternalPlugs(@Param('identifier') identifier: string) {\n    return this._integrationManager.getInternalPlugs(identifier);\n  }\n\n  @Get('/customers')\n  getCustomers(@GetOrgFromRequest() org: Organization) {\n    return this._integrationService.customers(org.id);\n  }\n\n  @Put('/:id/group')\n  async updateIntegrationGroup(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: { group: string }\n  ) {\n    return this._integrationService.updateIntegrationGroup(\n      org.id,\n      id,\n      body.group\n    );\n  }\n\n  @Put('/:id/customer-name')\n  async updateOnCustomerName(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: { name: string }\n  ) {\n    return this._integrationService.updateOnCustomerName(org.id, id, body.name);\n  }\n\n  @Get('/list')\n  async getIntegrationList(@GetOrgFromRequest() org: Organization) {\n    return {\n      integrations: await Promise.all(\n        (\n          await this._integrationService.getIntegrationsList(org.id)\n        ).map(async (p) => {\n          const findIntegration = this._integrationManager.getSocialIntegration(\n            p.providerIdentifier\n          );\n          return {\n            name: p.name,\n            id: p.id,\n            internalId: p.internalId,\n            disabled: p.disabled,\n            editor: findIntegration.editor,\n            picture: p.picture || '/no-picture.jpg',\n            identifier: p.providerIdentifier,\n            inBetweenSteps: p.inBetweenSteps,\n            refreshNeeded: p.refreshNeeded,\n            isCustomFields: !!findIntegration.customFields,\n            ...(findIntegration.customFields\n              ? { customFields: await findIntegration.customFields() }\n              : {}),\n            display: p.profile,\n            type: p.type,\n            time: JSON.parse(p.postingTimes),\n            changeProfilePicture: !!findIntegration?.changeProfilePicture,\n            changeNickName: !!findIntegration?.changeNickname,\n            customer: p.customer,\n            additionalSettings: p.additionalSettings || '[]',\n          };\n        })\n      ),\n    };\n  }\n\n  @Post('/:id/settings')\n  async updateProviderSettings(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body('additionalSettings') body: string\n  ) {\n    if (typeof body !== 'string') {\n      throw new Error('Invalid body');\n    }\n\n    await this._integrationService.updateProviderSettings(org.id, id, body);\n  }\n  @Post('/:id/nickname')\n  async setNickname(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: { name: string; picture: string }\n  ) {\n    const integration = await this._integrationService.getIntegrationById(\n      org.id,\n      id\n    );\n    if (!integration) {\n      throw new Error('Invalid integration');\n    }\n\n    const manager = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n    if (!manager.changeProfilePicture && !manager.changeNickname) {\n      throw new Error('Invalid integration');\n    }\n\n    const { url } = manager.changeProfilePicture\n      ? await manager.changeProfilePicture(\n          integration.internalId,\n          integration.token,\n          body.picture\n        )\n      : { url: '' };\n\n    const { name } = manager.changeNickname\n      ? await manager.changeNickname(\n          integration.internalId,\n          integration.token,\n          body.name\n        )\n      : { name: '' };\n\n    return this._integrationService.updateNameAndUrl(id, name, url);\n  }\n\n  @Get('/:id')\n  getSingleIntegration(\n    @Param('id') id: string,\n    @Query('order') order: string,\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    return this._integrationService.getIntegrationForOrder(\n      id,\n      order,\n      user.id,\n      org.id\n    );\n  }\n\n  @Get('/social/:integration')\n  @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])\n  async getIntegrationUrl(\n    @Param('integration') integration: string,\n    @Query('refresh') refresh: string,\n    @Query('externalUrl') externalUrl: string,\n    @Query('onboarding') onboarding: string,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    if (\n      !this._integrationManager\n        .getAllowedSocialsIntegrations()\n        .includes(integration)\n    ) {\n      throw new Error('Integration not allowed');\n    }\n\n    const integrationProvider =\n      this._integrationManager.getSocialIntegration(integration);\n\n    if (integrationProvider.externalUrl && !externalUrl) {\n      throw new Error('Missing external url');\n    }\n\n    try {\n      const getExternalUrl = integrationProvider.externalUrl\n        ? {\n            ...(await integrationProvider.externalUrl(externalUrl)),\n            instanceUrl: externalUrl,\n          }\n        : undefined;\n\n      const { codeVerifier, state, url } =\n        await integrationProvider.generateAuthUrl(getExternalUrl);\n\n      if (refresh) {\n        await ioRedis.set(`refresh:${state}`, refresh, 'EX', 3600);\n      }\n\n      if (onboarding === 'true') {\n        await ioRedis.set(`onboarding:${state}`, 'true', 'EX', 3600);\n      }\n\n      await ioRedis.set(`organization:${state}`, org.id, 'EX', 3600);\n      await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 3600);\n      await ioRedis.set(\n        `external:${state}`,\n        JSON.stringify(getExternalUrl),\n        'EX',\n        3600\n      );\n\n      return { url };\n    } catch (err) {\n      return { err: true };\n    }\n  }\n\n  @Post('/:id/time')\n  async setTime(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: IntegrationTimeDto\n  ) {\n    return this._integrationService.setTimes(org.id, id, body);\n  }\n\n  @Post('/mentions')\n  async mentions(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: IntegrationFunctionDto\n  ) {\n    const getIntegration = await this._integrationService.getIntegrationById(\n      org.id,\n      body.id\n    );\n    if (!getIntegration) {\n      throw new Error('Invalid integration');\n    }\n\n    let newList: any[] | { none: true } = [];\n    try {\n      newList = (await this.functionIntegration(org, body)) || [];\n    } catch (err) {\n      console.log(err);\n    }\n\n    if (!Array.isArray(newList) && newList?.none) {\n      return newList;\n    }\n\n    const list = await this._integrationService.getMentions(\n      getIntegration.providerIdentifier,\n      body?.data?.query\n    );\n\n    if (Array.isArray(newList) && newList.length) {\n      await this._integrationService.insertMentions(\n        getIntegration.providerIdentifier,\n        newList\n          .map((p: any) => ({\n            name: p.label || '',\n            username: p.id || '',\n            image: p.image || '',\n            doNotCache: p.doNotCache || false,\n          }))\n          .filter((f: any) => f.name && !f.doNotCache)\n      );\n    }\n\n    return uniqBy(\n      [\n        ...list.map((p) => ({\n          id: p.username,\n          image: p.image,\n          label: p.name,\n        })),\n        ...(newList as any[]),\n      ],\n      (p) => p.id\n    ).filter((f) => f.label && f.id);\n  }\n\n  @Post('/function')\n  async functionIntegration(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: IntegrationFunctionDto\n  ): Promise<any> {\n    const getIntegration = await this._integrationService.getIntegrationById(\n      org.id,\n      body.id\n    );\n    if (!getIntegration) {\n      throw new Error('Invalid integration');\n    }\n\n    const integrationProvider = this._integrationManager.getSocialIntegration(\n      getIntegration.providerIdentifier\n    );\n    if (!integrationProvider) {\n      throw new Error('Invalid provider');\n    }\n\n    // @ts-ignore\n    if (integrationProvider[body.name]) {\n      try {\n        // @ts-ignore\n        const load = await integrationProvider[body.name](\n          getIntegration.token,\n          body.data,\n          getIntegration.internalId,\n          getIntegration\n        );\n\n        return load;\n      } catch (err) {\n        if (err instanceof RefreshToken) {\n          const data = await this._refreshIntegrationService.refresh(\n            getIntegration\n          );\n\n          if (!data) {\n            return;\n          }\n\n          const { accessToken } = data;\n\n          if (accessToken) {\n            if (integrationProvider.refreshWait) {\n              await timer(10000);\n            }\n            return this.functionIntegration(org, body);\n          }\n\n          return false;\n        }\n\n        return false;\n      }\n    }\n    throw new Error('Function not found');\n  }\n\n  @Post('/disable')\n  disableChannel(\n    @GetOrgFromRequest() org: Organization,\n    @Body('id') id: string\n  ) {\n    return this._integrationService.disableChannel(org.id, id);\n  }\n\n  @Post('/enable')\n  enableChannel(\n    @GetOrgFromRequest() org: Organization,\n    @Body('id') id: string\n  ) {\n    return this._integrationService.enableChannel(\n      org.id,\n      // @ts-ignore\n      org?.subscription?.totalChannels || pricing.FREE.channel,\n      id\n    );\n  }\n\n  @Delete('/')\n  async deleteChannel(\n    @GetOrgFromRequest() org: Organization,\n    @Body('id') id: string\n  ) {\n    const isTherePosts = await this._integrationService.getPostsForChannel(\n      org.id,\n      id\n    );\n    if (isTherePosts.length) {\n      for (const post of isTherePosts) {\n        this._postService.deletePost(org.id, post.group).catch((err) => {});\n      }\n    }\n\n    return this._integrationService.deleteChannel(org.id, id);\n  }\n\n  @Get('/plug/list')\n  async getPlugList() {\n    return { plugs: this._integrationManager.getAllPlugs() };\n  }\n\n  @Get('/:id/plugs')\n  async getPlugsByIntegrationId(\n    @Param('id') id: string,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    return this._integrationService.getPlugsByIntegrationId(org.id, id);\n  }\n\n  @Post('/:id/plugs')\n  async postPlugsByIntegrationId(\n    @Param('id') id: string,\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: PlugDto\n  ) {\n    return this._integrationService.createOrUpdatePlug(org.id, id, body);\n  }\n\n  @Put('/plugs/:id/activate')\n  async changePlugActivation(\n    @Param('id') id: string,\n    @GetOrgFromRequest() org: Organization,\n    @Body('status') status: boolean\n  ) {\n    return this._integrationService.changePlugActivation(org.id, id, status);\n  }\n\n  @Get('/telegram/updates')\n  async getUpdates(@Query() query: { word: string; id?: number }) {\n    return new TelegramProvider().getBotId(query);\n  }\n\n  @Post('/moltbook/register')\n  async moltbookRegister(\n    @Body() body: { name: string; description: string }\n  ) {\n    try {\n      const provider = new MoltbookProvider();\n      const result = await provider.registerAgent(body.name, body.description);\n      return {\n        apiKey: result.api_key,\n        claimUrl: result.claim_url,\n        verificationCode: result.verification_code,\n      };\n    } catch (err: any) {\n      return { error: err.message || 'Registration failed' };\n    }\n  }\n\n  @Get('/moltbook/status')\n  async moltbookStatus(@Query('apiKey') apiKey: string) {\n    try {\n      const provider = new MoltbookProvider();\n      const result = await provider.checkAgentStatus(apiKey);\n      return { claimed: result?.status === 'claimed' };\n    } catch (err) {\n      return { claimed: false };\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/media.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Query,\n  Req,\n  Res,\n  UploadedFile,\n  UseInterceptors,\n  UsePipes,\n} from '@nestjs/common';\nimport { Request, Response } from 'express';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { ApiTags } from '@nestjs/swagger';\nimport handleR2Upload from '@gitroom/nestjs-libraries/upload/r2.uploader';\nimport { FileInterceptor } from '@nestjs/platform-express';\nimport { CustomFileValidationPipe } from '@gitroom/nestjs-libraries/upload/custom.upload.validation';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';\nimport { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';\nimport { VideoFunctionDto } from '@gitroom/nestjs-libraries/dtos/videos/video.function.dto';\n\n@ApiTags('Media')\n@Controller('/media')\nexport class MediaController {\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _mediaService: MediaService,\n    private _subscriptionService: SubscriptionService\n  ) {}\n\n  @Delete('/:id')\n  deleteMedia(@GetOrgFromRequest() org: Organization, @Param('id') id: string) {\n    return this._mediaService.deleteMedia(org.id, id);\n  }\n\n  @Post('/generate-video')\n  generateVideo(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: VideoDto\n  ) {\n    console.log('hello');\n    return this._mediaService.generateVideo(org, body);\n  }\n\n  @Post('/generate-image')\n  async generateImage(\n    @GetOrgFromRequest() org: Organization,\n    @Req() req: Request,\n    @Body('prompt') prompt: string,\n    isPicturePrompt = false\n  ) {\n    const total = await this._subscriptionService.checkCredits(org);\n    if (process.env.STRIPE_PUBLISHABLE_KEY && total.credits <= 0) {\n      return false;\n    }\n\n    return {\n      output:\n        (isPicturePrompt ? '' : 'data:image/png;base64,') +\n        (await this._mediaService.generateImage(prompt, org, isPicturePrompt)),\n    };\n  }\n\n  @Post('/generate-image-with-prompt')\n  async generateImageFromText(\n    @GetOrgFromRequest() org: Organization,\n    @Req() req: Request,\n    @Body('prompt') prompt: string\n  ) {\n    const image = await this.generateImage(org, req, prompt, true);\n    if (!image) {\n      return false;\n    }\n\n    const file = await this.storage.uploadSimple(image.output);\n\n    return this._mediaService.saveFile(org.id, file.split('/').pop(), file);\n  }\n\n  @Post('/upload-server')\n  @UseInterceptors(FileInterceptor('file'))\n  @UsePipes(new CustomFileValidationPipe())\n  async uploadServer(\n    @GetOrgFromRequest() org: Organization,\n    @UploadedFile() file: Express.Multer.File\n  ) {\n    const originalName = file?.originalname || '';\n    const uploadedFile = await this.storage.uploadFile(file);\n    return this._mediaService.saveFile(\n      org.id,\n      uploadedFile.originalname,\n      uploadedFile.path,\n      originalName\n    );\n  }\n\n  @Post('/save-media')\n  async saveMedia(\n    @GetOrgFromRequest() org: Organization,\n    @Req() req: Request,\n    @Body('name') name: string,\n    @Body('originalName') originalName: string\n  ) {\n    if (!name) {\n      return false;\n    }\n    return this._mediaService.saveFile(\n      org.id,\n      name,\n      process.env.CLOUDFLARE_BUCKET_URL + '/' + name,\n      originalName || undefined\n    );\n  }\n\n  @Post('/information')\n  saveMediaInformation(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: SaveMediaInformationDto\n  ) {\n    return this._mediaService.saveMediaInformation(org.id, body);\n  }\n\n  @Post('/upload-simple')\n  @UseInterceptors(FileInterceptor('file'))\n  async uploadSimple(\n    @GetOrgFromRequest() org: Organization,\n    @UploadedFile('file') file: Express.Multer.File,\n    @Body('preventSave') preventSave: string = 'false'\n  ) {\n    const originalName = file.originalname;\n    const getFile = await this.storage.uploadFile(file);\n\n    if (preventSave === 'true') {\n      const { path } = getFile;\n      return { path };\n    }\n\n    return this._mediaService.saveFile(\n      org.id,\n      getFile.originalname,\n      getFile.path,\n      originalName\n    );\n  }\n\n  @Post('/:endpoint')\n  async uploadFile(\n    @GetOrgFromRequest() org: Organization,\n    @Req() req: Request,\n    @Res() res: Response,\n    @Param('endpoint') endpoint: string\n  ) {\n    const upload = await handleR2Upload(endpoint, req, res);\n    if (endpoint !== 'complete-multipart-upload') {\n      return upload;\n    }\n\n    // @ts-ignore\n    const name = upload.Location.split('/').pop();\n    const originalName = req.body?.file?.name;\n\n    const saveFile = await this._mediaService.saveFile(\n      org.id,\n      name,\n      // @ts-ignore\n      upload.Location,\n      originalName || undefined\n    );\n\n    res.status(200).json({ ...upload, saved: saveFile });\n  }\n\n  @Get('/')\n  getMedia(\n    @GetOrgFromRequest() org: Organization,\n    @Query('page') page: number\n  ) {\n    return this._mediaService.getMedia(org.id, page);\n  }\n\n  @Get('/video-options')\n  getVideos() {\n    return this._mediaService.getVideoOptions();\n  }\n\n  @Post('/video/function')\n  videoFunction(\n    @Body() body: VideoFunctionDto\n  ) {\n    return this._mediaService.videoFunction(body.identifier, body.functionName, body.params);\n  }\n\n  @Get('/generate-video/:type/allowed')\n  generateVideoAllowed(\n    @GetOrgFromRequest() org: Organization,\n    @Param('type') type: string\n  ) {\n    return this._mediaService.generateVideoAllowed(org, type);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/monitor.controller.ts",
    "content": "import { Controller, Get, Param } from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\n\n@ApiTags('Monitor')\n@Controller('/monitor')\nexport class MonitorController {\n  @Get('/queue/:name')\n  async getMessagesGroup(@Param('name') name: string) {\n    return {\n      status: 'success',\n      message: `Queue ${name} is healthy.`,\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/no.auth.integrations.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  HttpException,\n  Param,\n  Post,\n  UseFilters,\n} from '@nestjs/common';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { ApiTags } from '@nestjs/swagger';\nimport { NotEnoughScopesFilter } from '@gitroom/nestjs-libraries/integrations/integration.missing.scopes';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { NotEnoughScopes } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport {\n  AuthorizationActions,\n  Sections,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\n\n@ApiTags('Integrations')\n@Controller('/integrations')\nexport class NoAuthIntegrationsController {\n  constructor(\n    private _integrationManager: IntegrationManager,\n    private _integrationService: IntegrationService,\n    private _refreshIntegrationService: RefreshIntegrationService,\n    private _organizationService: OrganizationService\n  ) {}\n\n  @Get('/')\n  getIntegrations() {\n    return this._integrationManager.getAllIntegrations();\n  }\n\n  @Post('/social-connect/:integration')\n  @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])\n  @UseFilters(new NotEnoughScopesFilter())\n  async connectSocialMedia(\n    @Param('integration') integration: string,\n    @Body() body: ConnectIntegrationDto\n  ) {\n    if (\n      !this._integrationManager\n        .getAllowedSocialsIntegrations()\n        .includes(integration)\n    ) {\n      throw new Error('Integration not allowed');\n    }\n\n    const integrationProvider =\n      this._integrationManager.getSocialIntegration(integration);\n\n    const getCodeVerifier = integrationProvider.customFields\n      ? 'none'\n      : await ioRedis.get(`login:${body.state}`);\n    if (!getCodeVerifier) {\n      throw new Error('Invalid state');\n    }\n\n    const organization = await ioRedis.get(`organization:${body.state}`);\n    if (!organization) {\n      throw new Error('Organization not found');\n    }\n\n    const org = await this._organizationService.getOrgById(organization);\n\n    if (!integrationProvider.customFields) {\n      await ioRedis.del(`login:${body.state}`);\n    }\n\n    const details = integrationProvider.externalUrl\n      ? await ioRedis.get(`external:${body.state}`)\n      : undefined;\n\n    if (details) {\n      await ioRedis.del(`external:${body.state}`);\n    }\n\n    const refresh = await ioRedis.get(`refresh:${body.state}`);\n    if (refresh) {\n      await ioRedis.del(`refresh:${body.state}`);\n    }\n\n    const onboarding = await ioRedis.get(`onboarding:${body.state}`);\n    if (onboarding) {\n      await ioRedis.del(`onboarding:${body.state}`);\n    }\n\n    const {\n      error,\n      accessToken,\n      expiresIn,\n      refreshToken,\n      id,\n      name,\n      picture,\n      username,\n      additionalSettings,\n      // eslint-disable-next-line no-async-promise-executor\n    } = await new Promise<AuthTokenDetails>(async (res) => {\n      try {\n        const auth = await integrationProvider.authenticate(\n          {\n            code: body.code,\n            codeVerifier: getCodeVerifier,\n            refresh: body.refresh,\n          },\n          details ? JSON.parse(details) : undefined\n        );\n\n        if (typeof auth === 'string') {\n          return res({\n            error: auth,\n            accessToken: '',\n            id: '',\n            name: '',\n            picture: '',\n            username: '',\n            additionalSettings: [],\n          });\n        }\n\n        if (refresh && integrationProvider.reConnect) {\n          console.log('reconnect');\n          try {\n            const newAuth = await integrationProvider.reConnect(\n              auth.id,\n              refresh,\n              auth.accessToken\n            );\n            return res({ ...newAuth, refreshToken: body.refresh });\n          } catch (err: any) {\n            return res({\n              error: err.message,\n              accessToken: '',\n              id: '',\n              name: '',\n              picture: '',\n              username: '',\n              additionalSettings: [],\n            });\n          }\n        }\n\n        return res(auth);\n      } catch (err) {\n        if (err instanceof NotEnoughScopes) {\n          return res({\n            error: err.message,\n            accessToken: '',\n            id: '',\n            name: '',\n            picture: '',\n            username: '',\n            additionalSettings: [],\n          });\n        }\n\n        return res({\n          error: 'Authentication failed',\n          accessToken: '',\n          id: '',\n          name: '',\n          picture: '',\n          username: '',\n          additionalSettings: [],\n        });\n      }\n    });\n\n    if (error) {\n      throw new NotEnoughScopes(error);\n    }\n\n    if (!id) {\n      throw new NotEnoughScopes('Invalid API key');\n    }\n\n    if (refresh && String(id) !== String(refresh)) {\n      throw new NotEnoughScopes(\n        'Please refresh the channel that needs to be refreshed'\n      );\n    }\n\n    let validName = name;\n    if (!validName) {\n      if (username) {\n        validName = username.split('.')[0] ?? username;\n      } else {\n        validName = `Channel_${String(id).slice(0, 8)}`;\n      }\n    }\n\n    if (\n      process.env.STRIPE_PUBLISHABLE_KEY &&\n      org.isTrailing &&\n      (await this._integrationService.checkPreviousConnections(\n        org.id,\n        String(id)\n      ))\n    ) {\n      throw new HttpException('', 412);\n    }\n\n    const createUpdate =\n      await this._integrationService.createOrUpdateIntegration(\n        additionalSettings,\n        !!integrationProvider.oneTimeToken,\n        org.id,\n        validName.trim(),\n        picture,\n        'social',\n        String(id),\n        integration,\n        accessToken,\n        refreshToken,\n        expiresIn,\n        username,\n        refresh ? false : integrationProvider.isBetweenSteps,\n        body.refresh,\n        +body.timezone,\n        details\n          ? AuthService.fixedEncryption(details)\n          : integrationProvider.customFields\n          ? AuthService.fixedEncryption(\n              Buffer.from(body.code, 'base64').toString()\n            )\n          : integrationProvider.isChromeExtension\n          ? AuthService.signJWT(\n              JSON.parse(Buffer.from(body.code, 'base64').toString())\n            )\n          : undefined\n      );\n\n    this._refreshIntegrationService\n      .startRefreshWorkflow(org.id, createUpdate.id, integrationProvider)\n      .catch((err) => {\n        console.log(err);\n      });\n\n    // Fetch pages if this is a two-step provider and not a refresh\n    let pages: any[] = [];\n    if (integrationProvider.isBetweenSteps && !refresh) {\n      try {\n        // Check which method the provider uses (pages or companies)\n        const fetchMethod =\n          'pages' in integrationProvider\n            ? 'pages'\n            : 'companies' in integrationProvider\n            ? 'companies'\n            : null;\n\n        if (fetchMethod) {\n          // @ts-ignore - dynamic method call\n          pages = await integrationProvider[fetchMethod](accessToken);\n        }\n      } catch (err) {\n        console.log('Failed to fetch pages:', err);\n      }\n    }\n\n    const webhookUrl = await ioRedis.get(`webhookUrl:${body.state}`);\n    if (webhookUrl) {\n      try {\n        await fetch(webhookUrl, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({\n            params: AuthService.signJWT({\n              apiKey: org.apiKey,\n            }),\n          }),\n        });\n      } catch (err) {}\n\n      await ioRedis.del(`webhookUrl:${body.state}`);\n    }\n\n    const returnURL = await ioRedis.get(`redirect:${body.state}`);\n    if (returnURL) {\n      await ioRedis.del(`redirect:${body.state}`);\n    }\n\n    const extensionToken = integrationProvider.isChromeExtension\n      ? AuthService.signJWT({\n          integrationId: createUpdate.id,\n          organizationId: org.id,\n          internalId: String(id),\n          provider: integration,\n        })\n      : undefined;\n\n    return {\n      ...createUpdate,\n      onboarding: onboarding === 'true',\n      pages,\n      ...(returnURL ? { returnURL } : {}),\n      ...(extensionToken ? { extensionToken } : {}),\n    };\n  }\n\n  @Post('/public/provider/:id/connect')\n  async saveProviderPage(@Param('id') id: string, @Body() body: any) {\n    if (!body.state) {\n      throw new Error('Invalid state');\n    }\n\n    const organization = await ioRedis.get(`organization:${body.state}`);\n    if (!organization) {\n      throw new Error('Organization not found');\n    }\n\n    const org = await this._organizationService.getOrgById(organization);\n\n    return this._integrationService.saveProviderPage(org.id, id, body);\n  }\n\n  @Post('/extension-refresh')\n  async extensionRefreshCookies(\n    @Body() body: { jwt: string; cookies: string }\n  ) {\n    let payload: any;\n    try {\n      payload = AuthService.verifyJWT(body.jwt);\n    } catch {\n      throw new HttpException('Invalid token', 401);\n    }\n\n    const { integrationId, organizationId, internalId, provider } = payload;\n    if (!integrationId || !organizationId || !internalId || !provider) {\n      throw new HttpException('Invalid token payload', 400);\n    }\n\n    const integration = await this._integrationService.getIntegrationById(\n      organizationId,\n      integrationId\n    );\n    if (!integration || integration.internalId !== internalId) {\n      throw new HttpException('Integration not found', 404);\n    }\n\n    const integrationProvider =\n      this._integrationManager.getSocialIntegration(provider);\n    if (!integrationProvider?.isChromeExtension) {\n      throw new HttpException('Not a Chrome extension integration', 400);\n    }\n\n    const authResult = await integrationProvider.authenticate({\n      code: body.cookies,\n      codeVerifier: '',\n    });\n\n    if (typeof authResult === 'string') {\n      throw new HttpException(authResult, 400);\n    }\n\n    if (String(authResult.id) !== String(integration.internalId)) {\n      await this._integrationService.refreshNeeded(\n        organizationId,\n        integrationId\n      );\n      return { success: false, reason: 'account_mismatch' };\n    }\n\n    await this._integrationService.createOrUpdateIntegration(\n      undefined,\n      false,\n      organizationId,\n      integration.name,\n      undefined,\n      'social',\n      integration.internalId,\n      integration.providerIdentifier,\n      authResult.accessToken,\n      '',\n      authResult.expiresIn,\n      undefined,\n      false,\n      undefined,\n      undefined,\n      AuthService.signJWT(\n        JSON.parse(Buffer.from(body.cookies, 'base64').toString())\n      )\n    );\n\n    return { success: true };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/notifications.controller.ts",
    "content": "import { Controller, Get } from '@nestjs/common';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { Organization, User } from '@prisma/client';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { ApiTags } from '@nestjs/swagger';\n\n@ApiTags('Notifications')\n@Controller('/notifications')\nexport class NotificationsController {\n  constructor(private _notificationsService: NotificationService) {}\n  @Get('/')\n  async mainPageList(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() organization: Organization\n  ) {\n    return this._notificationsService.getMainPageCount(\n      organization.id,\n      user.id\n    );\n  }\n\n  @Get('/list')\n  async notifications(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() organization: Organization\n  ) {\n    return this._notificationsService.getNotifications(\n      organization.id,\n      user.id\n    );\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/oauth-app.controller.ts",
    "content": "import { Body, Controller, Delete, Get, Post, Put } from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport {\n  AuthorizationActions,\n  Sections,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\nimport { CreateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/create-oauth-app.dto';\nimport { UpdateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/update-oauth-app.dto';\n\n@ApiTags('OAuth App')\n@Controller('/user/oauth-app')\nexport class OAuthAppController {\n  constructor(private _oauthService: OAuthService) {}\n\n  @Get('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async getApp(@GetOrgFromRequest() org: Organization) {\n    return this._oauthService.getApp(org.id);\n  }\n\n  @Post('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async createApp(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: CreateOAuthAppDto\n  ) {\n    return this._oauthService.createApp(org.id, body);\n  }\n\n  @Put('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async updateApp(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: UpdateOAuthAppDto\n  ) {\n    return this._oauthService.updateApp(org.id, body);\n  }\n\n  @Delete('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async deleteApp(@GetOrgFromRequest() org: Organization) {\n    return this._oauthService.deleteApp(org.id);\n  }\n\n  @Post('/rotate-secret')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async rotateSecret(@GetOrgFromRequest() org: Organization) {\n    return this._oauthService.rotateSecret(org.id);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/oauth.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  HttpException,\n  HttpStatus,\n  Post,\n  Query,\n} from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { User, Organization } from '@prisma/client';\nimport { AuthorizeOAuthQueryDto, ApproveOAuthDto } from '@gitroom/nestjs-libraries/dtos/oauth/authorize-oauth.dto';\nimport { TokenExchangeDto } from '@gitroom/nestjs-libraries/dtos/oauth/token-exchange.dto';\n\n@ApiTags('OAuth')\n@Controller('/oauth')\nexport class OAuthController {\n  constructor(private _oauthService: OAuthService) {}\n\n  @Get('/authorize')\n  async authorize(@Query() query: AuthorizeOAuthQueryDto) {\n    const app = await this._oauthService.validateAuthorizationRequest(\n      query.client_id\n    );\n\n    return {\n      app: {\n        name: app.name,\n        description: app.description,\n        picture: app.picture,\n        clientId: app.clientId,\n        redirectUrl: app.redirectUrl,\n      },\n      state: query.state,\n    };\n  }\n\n  @Post('/token')\n  async token(@Body() body: TokenExchangeDto) {\n    if (body.grant_type !== 'authorization_code') {\n      throw new HttpException(\n        { error: 'unsupported_grant_type' },\n        HttpStatus.BAD_REQUEST\n      );\n    }\n\n    return this._oauthService.exchangeCodeForToken(\n      body.code,\n      body.client_id,\n      body.client_secret\n    );\n  }\n}\n\n@ApiTags('OAuth')\n@Controller('/oauth')\nexport class OAuthAuthorizedController {\n  constructor(private _oauthService: OAuthService) {}\n\n  @Post('/authorize')\n  async approveOrDeny(\n    @Body() body: ApproveOAuthDto,\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    const app = await this._oauthService.validateAuthorizationRequest(\n      body.client_id\n    );\n\n    if (body.action === 'deny') {\n      const redirectUrl = new URL(app.redirectUrl);\n      redirectUrl.searchParams.set('error', 'access_denied');\n      if (body.state) {\n        redirectUrl.searchParams.set('state', body.state);\n      }\n      return { redirect: redirectUrl.toString() };\n    }\n\n    const code = await this._oauthService.createAuthorizationCode(\n      app.id,\n      user.id,\n      org.id\n    );\n\n    const redirectUrl = new URL(app.redirectUrl);\n    redirectUrl.searchParams.set('code', code);\n    if (body.state) {\n      redirectUrl.searchParams.set('state', body.state);\n    }\n    return { redirect: redirectUrl.toString() };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/posts.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n  Query,\n  Res,\n} from '@nestjs/common';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization, User } from '@prisma/client';\nimport { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';\nimport { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { ApiTags } from '@nestjs/swagger';\nimport { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';\nimport { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto';\nimport { AgentGraphService } from '@gitroom/nestjs-libraries/agent/agent.graph.service';\nimport { Response } from 'express';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';\nimport { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';\nimport {\n  AuthorizationActions,\n  Sections,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@ApiTags('Posts')\n@Controller('/posts')\nexport class PostsController {\n  constructor(\n    private _postsService: PostsService,\n    private _agentGraphService: AgentGraphService,\n    private _shortLinkService: ShortLinkService\n  ) {}\n\n  @Get('/:id/statistics')\n  async getStatistics(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._postsService.getStatistics(org.id, id);\n  }\n\n  @Get('/:id/missing')\n  async getMissingContent(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._postsService.getMissingContent(org.id, id);\n  }\n\n  @Put('/:id/release-id')\n  async updateReleaseId(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body('releaseId') releaseId: string\n  ) {\n    return this._postsService.updateReleaseId(org.id, id, releaseId);\n  }\n\n  @Post('/should-shortlink')\n  async shouldShortlink(@Body() body: { messages: string[] }) {\n    return { ask: this._shortLinkService.askShortLinkedin(body.messages) };\n  }\n\n  @Post('/:id/comments')\n  async createComment(\n    @GetOrgFromRequest() org: Organization,\n    @GetUserFromRequest() user: User,\n    @Param('id') id: string,\n    @Body() body: { comment: string }\n  ) {\n    return this._postsService.createComment(org.id, user.id, id, body.comment);\n  }\n\n  @Get('/tags')\n  async getTags(@GetOrgFromRequest() org: Organization) {\n    return { tags: await this._postsService.getTags(org.id) };\n  }\n\n  @Post('/tags')\n  async createTag(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: CreateTagDto\n  ) {\n    return this._postsService.createTag(org.id, body);\n  }\n\n  @Put('/tags/:id')\n  async editTag(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: CreateTagDto,\n    @Param('id') id: string\n  ) {\n    return this._postsService.editTag(id, org.id, body);\n  }\n\n  @Delete('/tags/:id')\n  async deleteTag(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._postsService.deleteTag(id, org.id);\n  }\n\n  @Get('/')\n  async getPosts(\n    @GetOrgFromRequest() org: Organization,\n    @Query() query: GetPostsDto\n  ) {\n    return this._postsService.getPostsMinified(org.id, query);\n  }\n\n  @Get('/find-slot')\n  async findSlot(@GetOrgFromRequest() org: Organization) {\n    return { date: await this._postsService.findFreeDateTime(org.id) };\n  }\n\n  @Get('/find-slot/:id')\n  async findSlotIntegration(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id?: string\n  ) {\n    return { date: await this._postsService.findFreeDateTime(org.id, id) };\n  }\n\n  @Get('/list')\n  async getPostsList(\n    @GetOrgFromRequest() org: Organization,\n    @Query() query: GetPostsListDto\n  ) {\n    return this._postsService.getPostsList(org.id, query);\n  }\n\n  @Get('/old')\n  oldPosts(\n    @GetOrgFromRequest() org: Organization,\n    @Query('date') date: string\n  ) {\n    return this._postsService.getOldPosts(org.id, date);\n  }\n\n  @Get('/group/:group')\n  getPostsByGroup(@GetOrgFromRequest() org: Organization, @Param('group') group: string) {\n    return this._postsService.getPostsByGroup(org.id, group);\n  }\n\n  @Get('/:id')\n  getPost(@GetOrgFromRequest() org: Organization, @Param('id') id: string) {\n    return this._postsService.getPost(org.id, id);\n  }\n\n  @Post('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.POSTS_PER_MONTH])\n  async createPost(\n    @GetOrgFromRequest() org: Organization,\n    @Body() rawBody: any\n  ) {\n    console.log(JSON.stringify(rawBody, null, 2));\n    const body = await this._postsService.mapTypeToPost(rawBody, org.id);\n    return this._postsService.createPost(org.id, body);\n  }\n\n  @Post('/generator/draft')\n  @CheckPolicies([AuthorizationActions.Create, Sections.POSTS_PER_MONTH])\n  generatePostsDraft(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: CreateGeneratedPostsDto\n  ) {\n    return this._postsService.generatePostsDraft(org.id, body);\n  }\n\n  @Post('/generator')\n  @CheckPolicies([AuthorizationActions.Create, Sections.POSTS_PER_MONTH])\n  async generatePosts(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: GeneratorDto,\n    @Res({ passthrough: false }) res: Response\n  ) {\n    res.setHeader('Content-Type', 'application/json; charset=utf-8');\n    for await (const event of this._agentGraphService.start(org.id, body)) {\n      res.write(JSON.stringify(event) + '\\n');\n    }\n\n    res.end();\n  }\n\n  @Delete('/:group')\n  deletePost(\n    @GetOrgFromRequest() org: Organization,\n    @Param('group') group: string\n  ) {\n    return this._postsService.deletePost(org.id, group);\n  }\n\n  @Put('/:id/date')\n  changeDate(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body('date') date: string,\n    @Body('action') action: 'schedule' | 'update' = 'schedule'\n  ) {\n    return this._postsService.changeDate(org.id, id, date, action);\n  }\n\n  @Post('/separate-posts')\n  async separatePosts(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: { content: string; len: number }\n  ) {\n    return this._postsService.separatePosts(body.content, body.len);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/public.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  Param,\n  Post,\n  Query,\n  Req,\n  Res,\n  StreamableFile,\n} from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { TrackService } from '@gitroom/nestjs-libraries/track/track.service';\nimport { RealIP } from 'nestjs-real-ip';\nimport { UserAgent } from '@gitroom/nestjs-libraries/user/user.agent';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { Request, Response } from 'express';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';\nimport { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service';\nimport { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { Readable, pipeline } from 'stream';\nimport { promisify } from 'util';\n\nconst pump = promisify(pipeline);\n\n@ApiTags('Public')\n@Controller('/public')\nexport class PublicController {\n  constructor(\n    private _agenciesService: AgenciesService,\n    private _trackService: TrackService,\n    private _agentGraphInsertService: AgentGraphInsertService,\n    private _postsService: PostsService,\n    private _nowpayments: Nowpayments,\n    private _subscriptionService: SubscriptionService\n  ) {}\n  @Post('/agent')\n  async createAgent(@Body() body: { text: string; apiKey: string }) {\n    if (\n      !body.apiKey ||\n      !process.env.AGENT_API_KEY ||\n      body.apiKey !== process.env.AGENT_API_KEY\n    ) {\n      return;\n    }\n    return this._agentGraphInsertService.newPost(body.text);\n  }\n\n  @Get('/agencies-list')\n  async getAgencyByUser() {\n    return this._agenciesService.getAllAgencies();\n  }\n\n  @Get('/agencies-list-slug')\n  async getAgencySlug() {\n    return this._agenciesService.getAllAgenciesSlug();\n  }\n\n  @Get('/agencies-information/:agency')\n  async getAgencyInformation(@Param('agency') agency: string) {\n    return this._agenciesService.getAgencyInformation(agency);\n  }\n\n  @Get('/agencies-list-count')\n  async getAgenciesCount() {\n    return this._agenciesService.getCount();\n  }\n\n  @Get(`/posts/:id`)\n  async getPreview(@Param('id') id: string) {\n    return (await this._postsService.getPostsRecursively(id, true)).map(\n      ({ childrenPost, ...p }) => ({\n        ...p,\n        ...(p.integration\n          ? {\n              integration: {\n                id: p.integration.id,\n                name: p.integration.name,\n                picture: p.integration.picture,\n                providerIdentifier: p.integration.providerIdentifier,\n                profile: p.integration.profile,\n              },\n            }\n          : {}),\n      })\n    );\n  }\n\n  @Get(`/posts/:id/comments`)\n  async getComments(@Param('id') postId: string) {\n    return { comments: await this._postsService.getComments(postId) };\n  }\n\n  @Post('/t')\n  async trackEvent(\n    @Res() res: Response,\n    @Req() req: Request,\n    @RealIP() ip: string,\n    @UserAgent() userAgent: string,\n    @Body()\n    body: { fbclid?: string; tt: TrackEnum; additional: Record<string, any> }\n  ) {\n    const uniqueId = req?.cookies?.track || makeId(10);\n    const fbclid = req?.cookies?.fbclid || body.fbclid;\n    await this._trackService.track(\n      uniqueId,\n      ip,\n      userAgent,\n      body.tt,\n      body.additional,\n      fbclid\n    );\n    if (!req.cookies.track) {\n      res.cookie('track', uniqueId, {\n        domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n        ...(!process.env.NOT_SECURED\n          ? {\n              secure: true,\n              httpOnly: true,\n            }\n          : {}),\n        sameSite: 'none',\n        expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n      });\n    }\n\n    if (body.fbclid && !req.cookies.fbclid) {\n      res.cookie('fbclid', body.fbclid, {\n        domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n        ...(!process.env.NOT_SECURED\n          ? {\n              secure: true,\n              httpOnly: true,\n            }\n          : {}),\n        sameSite: 'none',\n        expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n      });\n    }\n\n    res.status(200).json({\n      track: uniqueId,\n    });\n  }\n\n  @Post('/modify-subscription')\n  async modifySubscription(@Body('params') params: string) {\n    try {\n      const load = AuthService.verifyJWT(params) as {\n        orgId: string;\n        billing: 'FREE' | 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE';\n      };\n\n      if (!load || !load.orgId || !load.billing || !pricing[load.billing]) {\n        return { success: false };\n      }\n\n      const totalChannels = pricing[load.billing].channel || 0;\n\n      await this._subscriptionService.modifySubscriptionByOrg(\n        load.orgId,\n        totalChannels,\n        load.billing\n      );\n\n      return { success: true };\n    } catch (err) {\n      return { success: false };\n    }\n  }\n\n  @Post('/crypto/:path')\n  async cryptoPost(@Body() body: any, @Param('path') path: string) {\n    console.log('cryptoPost', body, path);\n    return this._nowpayments.processPayment(path, body);\n  }\n\n  @Get('/stream')\n  async streamFile(\n    @Query('url') url: string,\n    @Res() res: Response,\n    @Req() req: Request\n  ) {\n    if (!url.endsWith('mp4')) {\n      return res.status(400).send('Invalid video URL');\n    }\n\n    const ac = new AbortController();\n    const onClose = () => ac.abort();\n    req.on('aborted', onClose);\n    res.on('close', onClose);\n\n    const r = await fetch(url, { signal: ac.signal });\n\n    if (!r.ok && r.status !== 206) {\n      res.status(r.status);\n      throw new Error(`Upstream error: ${r.statusText}`);\n    }\n\n    const type = r.headers.get('content-type') ?? 'application/octet-stream';\n    res.setHeader('Content-Type', type);\n\n    const contentRange = r.headers.get('content-range');\n    if (contentRange) res.setHeader('Content-Range', contentRange);\n\n    const len = r.headers.get('content-length');\n    if (len) res.setHeader('Content-Length', len);\n\n    const acceptRanges = r.headers.get('accept-ranges') ?? 'bytes';\n    res.setHeader('Accept-Ranges', acceptRanges);\n\n    if (r.status === 206) res.status(206); // Partial Content for range responses\n\n    try {\n      await pump(Readable.fromWeb(r.body as any), res);\n    } catch (err) {\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/root.controller.ts",
    "content": "import { Controller, Get } from '@nestjs/common';\n@Controller('/')\nexport class RootController {\n  @Get('/')\n  getRoot(): string {\n    return 'App is running!';\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/sets.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n} from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { SetsService } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.service';\nimport {\n  UpdateSetsDto,\n  SetsDto,\n} from '@gitroom/nestjs-libraries/dtos/sets/sets.dto';\n\n@ApiTags('Sets')\n@Controller('/sets')\nexport class SetsController {\n  constructor(private _setsService: SetsService) {}\n\n  @Get('/')\n  async getSets(@GetOrgFromRequest() org: Organization) {\n    return this._setsService.getSets(org.id);\n  }\n\n  @Post('/')\n  async createASet(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: SetsDto\n  ) {\n    return this._setsService.createSet(org.id, body);\n  }\n\n  @Put('/')\n  async updateSet(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: UpdateSetsDto\n  ) {\n    return this._setsService.createSet(org.id, body);\n  }\n\n  @Delete('/:id')\n  async deleteSet(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._setsService.deleteSet(org.id, id);\n  }\n} "
  },
  {
    "path": "apps/backend/src/api/routes/settings.controller.ts",
    "content": "import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';\nimport { ShortlinkPreferenceDto } from '@gitroom/nestjs-libraries/dtos/settings/shortlink-preference.dto';\nimport { ApiTags } from '@nestjs/swagger';\nimport { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@ApiTags('Settings')\n@Controller('/settings')\nexport class SettingsController {\n  constructor(\n    private _organizationService: OrganizationService\n  ) {}\n\n  @Get('/team')\n  @CheckPolicies(\n    [AuthorizationActions.Create, Sections.TEAM_MEMBERS],\n    [AuthorizationActions.Create, Sections.ADMIN]\n  )\n  async getTeam(@GetOrgFromRequest() org: Organization) {\n    return this._organizationService.getTeam(org.id);\n  }\n\n  @Post('/team')\n  @CheckPolicies(\n    [AuthorizationActions.Create, Sections.TEAM_MEMBERS],\n    [AuthorizationActions.Create, Sections.ADMIN]\n  )\n  async inviteTeamMember(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: AddTeamMemberDto\n  ) {\n    return this._organizationService.inviteTeamMember(org.id, body);\n  }\n\n  @Delete('/team/:id')\n  @CheckPolicies(\n    [AuthorizationActions.Create, Sections.TEAM_MEMBERS],\n    [AuthorizationActions.Create, Sections.ADMIN]\n  )\n  deleteTeamMember(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._organizationService.deleteTeamMember(org, id);\n  }\n\n  @Get('/shortlink')\n  async getShortlinkPreference(@GetOrgFromRequest() org: Organization) {\n    return this._organizationService.getShortlinkPreference(org.id);\n  }\n\n  @Post('/shortlink')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async updateShortlinkPreference(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: ShortlinkPreferenceDto\n  ) {\n    return this._organizationService.updateShortlinkPreference(\n      org.id,\n      body.shortlink\n    );\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/signature.controller.ts",
    "content": "import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { SignatureService } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.service';\nimport { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';\n\n@ApiTags('Signatures')\n@Controller('/signatures')\nexport class SignatureController {\n  constructor(private _signatureService: SignatureService) {}\n\n  @Get('/')\n  async getSignatures(@GetOrgFromRequest() org: Organization) {\n    return this._signatureService.getSignaturesByOrgId(org.id);\n  }\n\n  @Get('/default')\n  async getDefaultSignature(@GetOrgFromRequest() org: Organization) {\n    return (await this._signatureService.getDefaultSignature(org.id)) || {};\n  }\n\n  @Post('/')\n  async createSignature(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: SignatureDto\n  ) {\n    return this._signatureService.createOrUpdateSignature(org.id, body);\n  }\n\n  @Delete('/:id')\n  async deleteSignature(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._signatureService.deleteSignature(org.id, id);\n  }\n\n  @Put('/:id')\n  async updateSignature(\n    @Param('id') id: string,\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: SignatureDto\n  ) {\n    return this._signatureService.createOrUpdateSignature(org.id, body, id);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/stripe.controller.ts",
    "content": "import {\n  Controller,\n  HttpException,\n  Post,\n  RawBodyRequest,\n  Req,\n} from '@nestjs/common';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { ApiTags } from '@nestjs/swagger';\n\n@ApiTags('Stripe')\n@Controller('/stripe')\nexport class StripeController {\n  constructor(\n    private readonly _stripeService: StripeService,\n  ) {}\n\n  @Post('/')\n  stripe(@Req() req: RawBodyRequest<Request>) {\n    const event = this._stripeService.validateRequest(\n      req.rawBody,\n      // @ts-ignore\n      req.headers['stripe-signature'],\n      process.env.STRIPE_SIGNING_KEY\n    );\n\n    // Maybe it comes from another stripe webhook\n    if (\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      event?.data?.object?.metadata?.service !== 'gitroom' &&\n      event.type !== 'invoice.payment_succeeded'\n    ) {\n      return { ok: true };\n    }\n\n    try {\n      switch (event.type) {\n        case 'invoice.payment_succeeded':\n          return this._stripeService.paymentSucceeded(event);\n        case 'customer.subscription.created':\n          return this._stripeService.createSubscription(event);\n        case 'customer.subscription.updated':\n          return this._stripeService.updateSubscription(event);\n        case 'customer.subscription.deleted':\n          return this._stripeService.deleteSubscription(event);\n        default:\n          return { ok: true };\n      }\n    } catch (e) {\n      throw new HttpException(e, 500);\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/third-party.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  HttpException,\n  Param,\n  Post,\n  Delete,\n} from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { ThirdPartyManager } from '@gitroom/nestjs-libraries/3rdparties/thirdparty.manager';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\n\n@ApiTags('Third Party')\n@Controller('/third-party')\nexport class ThirdPartyController {\n  private storage = UploadFactory.createStorage();\n\n  constructor(\n    private _thirdPartyManager: ThirdPartyManager,\n    private _mediaService: MediaService,\n  ) {}\n\n  @Get('/list')\n  async getThirdPartyList() {\n    return this._thirdPartyManager.getAllThirdParties();\n  }\n\n  @Get('/')\n  async getSavedThirdParty(@GetOrgFromRequest() organization: Organization) {\n    return Promise.all(\n      (\n        await this._thirdPartyManager.getAllThirdPartiesByOrganization(\n          organization.id\n        )\n      ).map((thirdParty) => {\n        const { description, fields, position, title, identifier } =\n          this._thirdPartyManager.getThirdPartyByName(thirdParty.identifier);\n        return {\n          ...thirdParty,\n          title,\n          position,\n          fields,\n          description,\n        };\n      })\n    );\n  }\n\n  @Delete('/:id')\n  deleteById(\n    @GetOrgFromRequest() organization: Organization,\n    @Param('id') id: string\n  ) {\n    return this._thirdPartyManager.deleteIntegration(organization.id, id);\n  }\n\n  @Post('/:id/submit')\n  async generate(\n    @GetOrgFromRequest() organization: Organization,\n    @Param('id') id: string,\n    @Body() data: any\n  ) {\n    const thirdParty = await this._thirdPartyManager.getIntegrationById(\n      organization.id,\n      id\n    );\n\n    if (!thirdParty) {\n      throw new HttpException('Integration not found', 404);\n    }\n\n    const thirdPartyInstance = this._thirdPartyManager.getThirdPartyByName(\n      thirdParty.identifier\n    );\n\n    if (!thirdPartyInstance) {\n      throw new HttpException('Invalid identifier', 400);\n    }\n\n    const loadedData = await thirdPartyInstance?.instance?.sendData(\n      AuthService.fixedDecryption(thirdParty.apiKey),\n      data\n    );\n\n    const file = await this.storage.uploadSimple(loadedData);\n    return this._mediaService.saveFile(organization.id, file.split('/').pop(), file);\n  }\n\n  @Post('/function/:id/:functionName')\n  async callFunction(\n    @GetOrgFromRequest() organization: Organization,\n    @Param('id') id: string,\n    @Param('functionName') functionName: string,\n    @Body() data: any\n  ) {\n    const thirdParty = await this._thirdPartyManager.getIntegrationById(\n      organization.id,\n      id\n    );\n\n    if (!thirdParty) {\n      throw new HttpException('Integration not found', 404);\n    }\n\n    const thirdPartyInstance = this._thirdPartyManager.getThirdPartyByName(\n      thirdParty.identifier\n    );\n\n    if (!thirdPartyInstance) {\n      throw new HttpException('Invalid identifier', 400);\n    }\n\n    return thirdPartyInstance?.instance?.[functionName](\n      AuthService.fixedDecryption(thirdParty.apiKey),\n      data\n    );\n  }\n\n  @Post('/:identifier')\n  async addApiKey(\n    @GetOrgFromRequest() organization: Organization,\n    @Param('identifier') identifier: string,\n    @Body('api') api: string\n  ) {\n    const thirdParty = this._thirdPartyManager.getThirdPartyByName(identifier);\n    if (!thirdParty) {\n      throw new HttpException('Invalid identifier', 400);\n    }\n\n    const connect = await thirdParty.instance.checkConnection(api);\n    if (!connect) {\n      throw new HttpException('Invalid API key', 400);\n    }\n\n    try {\n      const save = await this._thirdPartyManager.saveIntegration(\n        organization.id,\n        identifier,\n        api,\n        {\n          name: connect.name,\n          username: connect.username,\n          id: connect.id,\n        }\n      );\n\n      return {\n        id: save.id,\n      };\n    } catch (e) {\n      console.log(e);\n      throw new HttpException('Integration Already Exists', 400);\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/users.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  HttpException,\n  Post,\n  Query,\n  Req,\n  Res,\n} from '@nestjs/common';\nimport { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';\nimport { sign } from 'jsonwebtoken';\nimport { Organization, User } from '@prisma/client';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { Response, Request } from 'express';\nimport { AuthService } from '@gitroom/backend/services/auth/auth.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { ApiTags } from '@nestjs/swagger';\nimport { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';\nimport { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';\nimport { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto';\nimport { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter';\nimport { RealIP } from 'nestjs-real-ip';\nimport { UserAgent } from '@gitroom/nestjs-libraries/user/user.agent';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { TrackService } from '@gitroom/nestjs-libraries/track/track.service';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@ApiTags('User')\n@Controller('/user')\nexport class UsersController {\n  constructor(\n    private _subscriptionService: SubscriptionService,\n    private _stripeService: StripeService,\n    private _authService: AuthService,\n    private _orgService: OrganizationService,\n    private _userService: UsersService,\n    private _trackService: TrackService\n  ) {}\n  @Get('/agent-media-sso')\n  async getAgentMediaSsoUrl(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() organization: Organization\n  ) {\n    if (!process.env.AGENT_MEDIA_SSO_KEY) {\n      throw new HttpException('Agent Media SSO is not configured', 400);\n    }\n\n    const token = sign(\n      { id: organization.id, displayName: organization.name },\n      process.env.AGENT_MEDIA_SSO_KEY\n    );\n\n    return { url: `https://agent-media.ai/sso/${token}` };\n  }\n\n  @Get('/self')\n  async getSelf(\n    @GetUserFromRequest() user: User,\n    @GetOrgFromRequest() organization: Organization,\n    @Req() req: Request\n  ) {\n    if (!organization) {\n      throw new HttpForbiddenException();\n    }\n\n    const impersonate = req.cookies.impersonate || req.headers.impersonate;\n    // @ts-ignore\n    return {\n      ...user,\n      orgId: organization.id,\n      // @ts-ignore\n      totalChannels: !process.env.STRIPE_PUBLISHABLE_KEY ? 10000 : organization?.subscription?.totalChannels || pricing.FREE.channel,\n      // @ts-ignore\n      tier: organization?.subscription?.subscriptionTier || (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'),\n      // @ts-ignore\n      role: organization?.users[0]?.role,\n      // @ts-ignore\n      isLifetime: !!organization?.subscription?.isLifetime,\n      admin: !!user.isSuperAdmin,\n      impersonate: !!impersonate,\n      isTrailing: !process.env.STRIPE_PUBLISHABLE_KEY ? false : organization?.isTrailing,\n      allowTrial: organization?.allowTrial,\n      streakSince: organization?.streakSince || null,\n      // @ts-ignore\n      publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN' ? organization?.apiKey : '',\n    };\n  }\n\n  @Get('/personal')\n  async getPersonalInformation(@GetUserFromRequest() user: User) {\n    return this._userService.getPersonal(user.id);\n  }\n\n  @Get('/impersonate')\n  async getImpersonate(\n    @GetUserFromRequest() user: User,\n    @Query('name') name: string\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new HttpException('Unauthorized', 400);\n    }\n\n    return this._userService.getImpersonateUser(name);\n  }\n\n  @Post('/impersonate')\n  async setImpersonate(\n    @GetUserFromRequest() user: User,\n    @Body('id') id: string,\n    @Res({ passthrough: true }) response: Response\n  ) {\n    if (!user.isSuperAdmin) {\n      throw new HttpException('Unauthorized', 400);\n    }\n\n    response.cookie('impersonate', id, {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n    });\n\n    if (process.env.NOT_SECURED) {\n      response.header('impersonate', id);\n    }\n  }\n\n  @Post('/personal')\n  async changePersonal(\n    @GetUserFromRequest() user: User,\n    @Body() body: UserDetailDto\n  ) {\n    return this._userService.changePersonal(user.id, body);\n  }\n\n  @Get('/email-notifications')\n  async getEmailNotifications(@GetUserFromRequest() user: User) {\n    return this._userService.getEmailNotifications(user.id);\n  }\n\n  @Post('/email-notifications')\n  async updateEmailNotifications(\n    @GetUserFromRequest() user: User,\n    @Body() body: EmailNotificationsDto\n  ) {\n    return this._userService.updateEmailNotifications(user.id, body);\n  }\n\n  @Post('/api-key/rotate')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async rotateApiKey(@GetOrgFromRequest() organization: Organization) {\n    return this._orgService.updateApiKey(organization.id);\n  }\n\n  @Get('/subscription')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async getSubscription(@GetOrgFromRequest() organization: Organization) {\n    const subscription =\n      await this._subscriptionService.getSubscriptionByOrganizationId(\n        organization.id\n      );\n\n    return subscription ? { subscription } : { subscription: undefined };\n  }\n\n  @Get('/subscription/tiers')\n  @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])\n  async tiers() {\n    return this._stripeService.getPackages();\n  }\n\n  @Post('/join-org')\n  async joinOrg(\n    @GetUserFromRequest() user: User,\n    @Body('org') org: string,\n    @Res({ passthrough: true }) response: Response\n  ) {\n    const getOrgFromCookie = this._authService.getOrgFromCookie(org);\n\n    if (!getOrgFromCookie) {\n      return response.status(200).json({ id: null });\n    }\n\n    const addedOrg = await this._orgService.addUserToOrg(\n      user.id,\n      getOrgFromCookie.id,\n      getOrgFromCookie.orgId,\n      getOrgFromCookie.role\n    );\n\n    response.status(200).json({\n      id: typeof addedOrg !== 'boolean' ? addedOrg.organizationId : null,\n    });\n  }\n\n  @Get('/organizations')\n  async getOrgs(@GetUserFromRequest() user: User) {\n    return (await this._orgService.getOrgsByUserId(user.id)).filter(\n      (f) => !f.users[0].disabled\n    );\n  }\n\n  @Post('/change-org')\n  changeOrg(\n    @Body('id') id: string,\n    @Res({ passthrough: true }) response: Response\n  ) {\n    response.cookie('showorg', id, {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n    });\n\n    if (process.env.NOT_SECURED) {\n      response.header('showorg', id);\n    }\n\n    response.status(200).send();\n  }\n\n  @Post('/logout')\n  logout(@Res({ passthrough: true }) response: Response) {\n    response.header('logout', 'true');\n    response.cookie('auth', '', {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      maxAge: -1,\n      expires: new Date(0),\n    });\n\n    response.cookie('showorg', '', {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      maxAge: -1,\n      expires: new Date(0),\n    });\n\n    response.cookie('impersonate', '', {\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: 'none',\n          }\n        : {}),\n      maxAge: -1,\n      expires: new Date(0),\n    });\n\n    response.status(200).send();\n  }\n\n  @Post('/t')\n  async trackEvent(\n    @Res({ passthrough: true }) res: Response,\n    @Req() req: Request,\n    @GetUserFromRequest() user: User,\n    @RealIP() ip: string,\n    @UserAgent() userAgent: string,\n    @Body()\n    body: { tt: TrackEnum; fbclid: string; additional: Record<string, any> }\n  ) {\n    const uniqueId = req?.cookies?.track || makeId(10);\n    const fbclid = req?.cookies?.fbclid || body.fbclid;\n    await this._trackService.track(\n      uniqueId,\n      ip,\n      userAgent,\n      body.tt,\n      body.additional,\n      fbclid,\n      user\n    );\n    if (!req.cookies.track) {\n      res.cookie('track', uniqueId, {\n        domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n        ...(!process.env.NOT_SECURED\n          ? {\n              secure: true,\n              httpOnly: true,\n              sameSite: 'none',\n            }\n          : {}),\n        expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n      });\n    }\n\n    res.status(200).json({\n      track: uniqueId,\n    });\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/api/routes/webhooks.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  Param,\n  Post,\n  Put,\n  Query,\n} from '@nestjs/common';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { ApiTags } from '@nestjs/swagger';\nimport { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport {\n  UpdateDto,\n  WebhooksDto,\n} from '@gitroom/nestjs-libraries/dtos/webhooks/webhooks.dto';\nimport { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@ApiTags('Webhooks')\n@Controller('/webhooks')\nexport class WebhookController {\n  constructor(private _webhooksService: WebhooksService) {}\n\n  @Get('/')\n  async getStatistics(@GetOrgFromRequest() org: Organization) {\n    return this._webhooksService.getWebhooks(org.id);\n  }\n\n  @Post('/')\n  @CheckPolicies([AuthorizationActions.Create, Sections.WEBHOOKS])\n  async createAWebhook(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: WebhooksDto\n  ) {\n    return this._webhooksService.createWebhook(org.id, body);\n  }\n\n  @Put('/')\n  async updateWebhook(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: UpdateDto\n  ) {\n    return this._webhooksService.createWebhook(org.id, body);\n  }\n\n  @Delete('/:id')\n  async deleteWebhook(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    return this._webhooksService.deleteWebhook(org.id, id);\n  }\n\n  @Post('/send')\n  async sendWebhook(@Body() body: any, @Query('url') url: string) {\n    try {\n      await fetch(url, {\n        method: 'POST',\n        body: JSON.stringify(body),\n        headers: { 'Content-Type': 'application/json' },\n      });\n    } catch (err) {\n      /** sent **/\n    }\n\n    return { send: true };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/app.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module';\nimport { ApiModule } from '@gitroom/backend/api/api.module';\nimport { APP_GUARD } from '@nestjs/core';\nimport { PoliciesGuard } from '@gitroom/backend/services/auth/permissions/permissions.guard';\nimport { PublicApiModule } from '@gitroom/backend/public-api/public.api.module';\nimport { ThrottlerBehindProxyGuard } from '@gitroom/nestjs-libraries/throttler/throttler.provider';\nimport { ThrottlerModule } from '@nestjs/throttler';\nimport { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module';\nimport { ThirdPartyModule } from '@gitroom/nestjs-libraries/3rdparties/thirdparty.module';\nimport { VideoModule } from '@gitroom/nestjs-libraries/videos/video.module';\nimport { SentryModule } from '@sentry/nestjs/setup';\nimport { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception';\nimport { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module';\nimport { getTemporalModule } from '@gitroom/nestjs-libraries/temporal/temporal.module';\nimport { TemporalRegisterMissingSearchAttributesModule } from '@gitroom/nestjs-libraries/temporal/temporal.register';\nimport { InfiniteWorkflowRegisterModule } from '@gitroom/nestjs-libraries/temporal/infinite.workflow.register';\nimport { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\n\n@Global()\n@Module({\n  imports: [\n    SentryModule.forRoot(),\n    DatabaseModule,\n    ApiModule,\n    PublicApiModule,\n    AgentModule,\n    ThirdPartyModule,\n    VideoModule,\n    ChatModule,\n    getTemporalModule(false),\n    TemporalRegisterMissingSearchAttributesModule,\n    InfiniteWorkflowRegisterModule,\n    ThrottlerModule.forRoot({\n      throttlers: [\n        {\n          ttl: 3600000,\n          limit: process.env.API_LIMIT ? Number(process.env.API_LIMIT) : 30,\n        },\n      ],\n      storage: new ThrottlerStorageRedisService(ioRedis),\n    }),\n  ],\n  controllers: [],\n  providers: [\n    FILTER,\n    {\n      provide: APP_GUARD,\n      useClass: ThrottlerBehindProxyGuard,\n    },\n    {\n      provide: APP_GUARD,\n      useClass: PoliciesGuard,\n    },\n  ],\n  exports: [\n    DatabaseModule,\n    ApiModule,\n    PublicApiModule,\n    AgentModule,\n    ThrottlerModule,\n    ChatModule,\n  ],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "apps/backend/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "apps/backend/src/main.ts",
    "content": "import { initializeSentry } from '@gitroom/nestjs-libraries/sentry/initialize.sentry';\ninitializeSentry('backend', true);\nimport compression from 'compression';\n\nimport { loadSwagger } from '@gitroom/helpers/swagger/load.swagger';\nimport { json } from 'express';\nimport { Runtime } from '@temporalio/worker';\nRuntime.install({ shutdownSignals: [] });\n\nprocess.env.TZ = 'UTC';\n\nimport cookieParser from 'cookie-parser';\nimport { Logger, ValidationPipe } from '@nestjs/common';\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\n\nimport { SubscriptionExceptionFilter } from '@gitroom/backend/services/auth/permissions/subscription.exception';\nimport { HttpExceptionFilter } from '@gitroom/nestjs-libraries/services/exception.filter';\nimport { ConfigurationChecker } from '@gitroom/helpers/configuration/configuration.checker';\nimport { startMcp } from '@gitroom/nestjs-libraries/chat/start.mcp';\n\nasync function start() {\n  const app = await NestFactory.create(AppModule, {\n    rawBody: true,\n    cors: {\n      ...(!process.env.NOT_SECURED ? { credentials: true } : {}),\n      allowedHeaders: [\n        'Content-Type',\n        'Authorization',\n        'x-copilotkit-runtime-client-gql-version',\n      ],\n      exposedHeaders: [\n        'reload',\n        'onboarding',\n        'activate',\n        'x-copilotkit-runtime-client-gql-version',\n        ...(process.env.NOT_SECURED ? ['auth', 'showorg', 'impersonate'] : []),\n      ],\n      origin: [\n        process.env.FRONTEND_URL,\n        'http://localhost:6274',\n        ...(process.env.MAIN_URL ? [process.env.MAIN_URL] : []),\n      ],\n    },\n  });\n\n  await startMcp(app);\n\n  app.useGlobalPipes(\n    new ValidationPipe({\n      transform: true,\n    })\n  );\n\n  app.use(['/copilot/*', '/posts'], (req: any, res: any, next: any) => {\n    json({ limit: '50mb' })(req, res, next);\n  });\n\n  app.use(cookieParser());\n  app.use(compression());\n  app.useGlobalFilters(new SubscriptionExceptionFilter());\n  app.useGlobalFilters(new HttpExceptionFilter());\n\n  loadSwagger(app);\n\n  const port = process.env.PORT || 3000;\n\n  try {\n    await app.listen(port);\n\n    checkConfiguration(); // Do this last, so that users will see obvious issues at the end of the startup log without having to scroll up.\n\n    Logger.log(`🚀 Backend is running on: http://localhost:${port}`);\n  } catch (e) {\n    Logger.error(`Backend failed to start on port ${port}`, e);\n  }\n}\n\nfunction checkConfiguration() {\n  const checker = new ConfigurationChecker();\n  checker.readEnvFromProcess();\n  checker.check();\n\n  if (checker.hasIssues()) {\n    for (const issue of checker.getIssues()) {\n      Logger.warn(issue, 'Configuration issue');\n    }\n\n    Logger.warn('Configuration issues found: ' + checker.getIssuesCount());\n  } else {\n    Logger.log('Configuration check completed without any issues');\n  }\n}\n\nstart();\n"
  },
  {
    "path": "apps/backend/src/public-api/public.api.module.ts",
    "content": "import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';\nimport { AuthService } from '@gitroom/backend/services/auth/auth.service';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { PoliciesGuard } from '@gitroom/backend/services/auth/permissions/permissions.guard';\nimport { PermissionsService } from '@gitroom/backend/services/auth/permissions/permissions.service';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { UploadModule } from '@gitroom/nestjs-libraries/upload/upload.module';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';\nimport { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';\nimport { PublicIntegrationsController } from '@gitroom/backend/public-api/routes/v1/public.integrations.controller';\nimport { PublicAuthMiddleware } from '@gitroom/backend/services/auth/public.auth.middleware';\n\nconst authenticatedController = [PublicIntegrationsController];\n@Module({\n  imports: [UploadModule],\n  controllers: [...authenticatedController],\n  providers: [\n    AuthService,\n    StripeService,\n    OpenaiService,\n    ExtractContentService,\n    PoliciesGuard,\n    PermissionsService,\n    CodesService,\n    IntegrationManager,\n  ],\n  get exports() {\n    return [...this.imports, ...this.providers];\n  },\n})\nexport class PublicApiModule implements NestModule {\n  configure(consumer: MiddlewareConsumer) {\n    consumer.apply(PublicAuthMiddleware).forRoutes(...authenticatedController);\n  }\n}\n\n"
  },
  {
    "path": "apps/backend/src/public-api/routes/v1/public.integrations.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Delete,\n  Get,\n  HttpException,\n  Param,\n  Post,\n  Put,\n  Query,\n  UploadedFile,\n  UseInterceptors,\n} from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';\nimport { Organization } from '@prisma/client';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { FileInterceptor } from '@nestjs/platform-express';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';\nimport {\n  AuthorizationActions,\n  Sections,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\nimport { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';\nimport { VideoFunctionDto } from '@gitroom/nestjs-libraries/dtos/videos/video.function.dto';\nimport { UploadDto } from '@gitroom/nestjs-libraries/dtos/media/upload.dto';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { GetNotificationsDto } from '@gitroom/nestjs-libraries/dtos/notifications/get.notifications.dto';\nimport axios from 'axios';\nimport { Readable } from 'stream';\nimport { lookup, extension } from 'mime-types';\nimport * as Sentry from '@sentry/nestjs';\nimport { socialIntegrationList, IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { getValidationSchemas } from '@gitroom/nestjs-libraries/chat/validation.schemas.helper';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\n\n@ApiTags('Public API')\n@Controller('/public/v1')\nexport class PublicIntegrationsController {\n  private storage = UploadFactory.createStorage();\n\n  constructor(\n    private _integrationService: IntegrationService,\n    private _postsService: PostsService,\n    private _mediaService: MediaService,\n    private _notificationService: NotificationService,\n    private _integrationManager: IntegrationManager,\n    private _refreshIntegrationService: RefreshIntegrationService\n  ) {}\n\n  @Post('/upload')\n  @UseInterceptors(FileInterceptor('file'))\n  async uploadSimple(\n    @GetOrgFromRequest() org: Organization,\n    @UploadedFile('file') file: Express.Multer.File\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    if (!file) {\n      throw new HttpException({ msg: 'No file provided' }, 400);\n    }\n\n    const getFile = await this.storage.uploadFile(file);\n    return this._mediaService.saveFile(\n      org.id,\n      getFile.originalname,\n      getFile.path\n    );\n  }\n\n  @Post('/upload-from-url')\n  async uploadsFromUrl(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: UploadDto\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const response = await axios.get(body.url, {\n      responseType: 'arraybuffer',\n    });\n\n    const buffer = Buffer.from(response.data);\n    const responseMime = response.headers?.['content-type']?.split(';')[0]?.trim();\n    const urlMime = lookup(body?.url?.split?.('?')?.[0]);\n    const mimetype = (urlMime || responseMime || 'image/jpeg') as string;\n    const ext = extension(mimetype) || 'jpg';\n\n    const getFile = await this.storage.uploadFile({\n      buffer,\n      mimetype,\n      size: buffer.length,\n      path: '',\n      fieldname: '',\n      destination: '',\n      stream: new Readable(),\n      filename: '',\n      originalname: `upload.${ext}`,\n      encoding: '',\n    });\n\n    return this._mediaService.saveFile(\n      org.id,\n      getFile.originalname,\n      getFile.path\n    );\n  }\n\n  @Get('/find-slot/:id')\n  async findSlotIntegration(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id?: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return { date: await this._postsService.findFreeDateTime(org.id, id) };\n  }\n\n  @Get('/posts')\n  async getPosts(\n    @GetOrgFromRequest() org: Organization,\n    @Query() query: GetPostsDto\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const posts = await this._postsService.getPosts(org.id, query);\n    return {\n      posts,\n      // comments,\n    };\n  }\n\n  @Post('/posts')\n  @CheckPolicies([AuthorizationActions.Create, Sections.POSTS_PER_MONTH])\n  async createPost(\n    @GetOrgFromRequest() org: Organization,\n    @Body() rawBody: any\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const body = await this._postsService.mapTypeToPost(\n      rawBody,\n      org.id,\n      rawBody.type === 'draft'\n    );\n    body.type = rawBody.type;\n\n    console.log(JSON.stringify(body, null, 2));\n    return this._postsService.createPost(org.id, body);\n  }\n\n  @Delete('/posts/:id')\n  async deletePost(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const getPostById = await this._postsService.getPost(org.id, id);\n    return this._postsService.deletePost(org.id, getPostById.group);\n  }\n\n  @Delete('/posts/group/:group')\n  deletePostByGroup(\n    @GetOrgFromRequest() org: Organization,\n    @Param('group') group: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._postsService.deletePost(org.id, group);\n  }\n\n  @Get('/is-connected')\n  async getActiveIntegrations(@GetOrgFromRequest() org: Organization) {\n    Sentry.metrics.count('public_api-request', 1);\n    return { connected: true };\n  }\n\n  @Get('/integrations')\n  async listIntegration(@GetOrgFromRequest() org: Organization) {\n    Sentry.metrics.count('public_api-request', 1);\n    return (await this._integrationService.getIntegrationsList(org.id)).map(\n      (org) => ({\n        id: org.id,\n        name: org.name,\n        identifier: org.providerIdentifier,\n        picture: org.picture,\n        disabled: org.disabled,\n        profile: org.profile,\n        customer: org.customer\n          ? {\n              id: org.customer.id,\n              name: org.customer.name,\n            }\n          : undefined,\n      })\n    );\n  }\n\n  @Get('/social/:integration')\n  @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])\n  async getIntegrationUrl(\n    @Param('integration') integration: string,\n    @Query('refresh') refresh: string,\n    @GetOrgFromRequest() org: Organization\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    if (\n      !this._integrationManager\n        .getAllowedSocialsIntegrations()\n        .includes(integration)\n    ) {\n      throw new HttpException({ msg: 'Integration not allowed' }, 400);\n    }\n\n    const integrationProvider =\n      this._integrationManager.getSocialIntegration(integration);\n\n    if (integrationProvider.externalUrl) {\n      throw new HttpException(\n        { msg: 'This integration requires an external URL and is not supported via the public API' },\n        400\n      );\n    }\n\n    try {\n      const { codeVerifier, state, url } =\n        await integrationProvider.generateAuthUrl();\n\n      if (refresh) {\n        await ioRedis.set(`refresh:${state}`, refresh, 'EX', 3600);\n      }\n\n      await ioRedis.set(`organization:${state}`, org.id, 'EX', 3600);\n      await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 3600);\n\n      return { url };\n    } catch (err) {\n      throw new HttpException({ msg: 'Failed to generate auth URL' }, 500);\n    }\n  }\n\n  @Get('/notifications')\n  async getNotifications(\n    @GetOrgFromRequest() org: Organization,\n    @Query() query: GetNotificationsDto\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._notificationService.getNotificationsPaginated(\n      org.id,\n      query.page ?? 0\n    );\n  }\n\n  @Post('/generate-video')\n  generateVideo(\n    @GetOrgFromRequest() org: Organization,\n    @Body() body: VideoDto\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._mediaService.generateVideo(org, body);\n  }\n\n  @Post('/video/function')\n  videoFunction(@Body() body: VideoFunctionDto) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._mediaService.videoFunction(\n      body.identifier,\n      body.functionName,\n      body.params\n    );\n  }\n\n  @Delete('/integrations/:id')\n  async deleteChannel(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const isTherePosts = await this._integrationService.getPostsForChannel(\n      org.id,\n      id\n    );\n    if (isTherePosts.length) {\n      for (const post of isTherePosts) {\n        this._postsService.deletePost(org.id, post.group).catch(() => {});\n      }\n    }\n\n    return this._integrationService.deleteChannel(org.id, id);\n  }\n\n  @Get('/integration-settings/:id')\n  async getIntegrationSettings(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const loadIntegration = await this._integrationService.getIntegrationById(\n      org.id,\n      id\n    );\n\n    const verified =\n      JSON.parse(loadIntegration.additionalSettings || '[]')?.find(\n        (p: any) => p?.title === 'Verified'\n      )?.value || false;\n\n    const integration = socialIntegrationList.find(\n      (p) => p.identifier === loadIntegration.providerIdentifier\n    )!;\n\n    if (!integration) {\n      return {\n        output: { rules: '', maxLength: 0, settings: {}, tools: [] as any[] },\n      };\n    }\n\n    const maxLength = integration.maxLength(verified);\n    const schemas = !integration.dto\n      ? false\n      : getValidationSchemas()[integration.dto.name];\n    const tools = this._integrationManager.getAllTools();\n    const rules = this._integrationManager.getAllRulesDescription();\n\n    return {\n      output: {\n        rules: rules[integration.identifier],\n        maxLength,\n        settings: !schemas ? 'No additional settings required' : schemas,\n        tools: tools[integration.identifier],\n      },\n    };\n  }\n\n  @Get('/posts/:id/missing')\n  async getMissingContent(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._postsService.getMissingContent(org.id, id);\n  }\n\n  @Put('/posts/:id/release-id')\n  async updateReleaseId(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body('releaseId') releaseId: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._postsService.updateReleaseId(org.id, id, releaseId);\n  }\n\n  @Get('/analytics/:integration')\n  async getAnalytics(\n    @GetOrgFromRequest() org: Organization,\n    @Param('integration') integration: string,\n    @Query('date') date: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._integrationService.checkAnalytics(org, integration, date);\n  }\n\n  @Get('/analytics/post/:postId')\n  async getPostAnalytics(\n    @GetOrgFromRequest() org: Organization,\n    @Param('postId') postId: string,\n    @Query('date') date: string\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    return this._postsService.checkPostAnalytics(org.id, postId, +date);\n  }\n\n  @Post('/integration-trigger/:id')\n  async triggerIntegrationTool(\n    @GetOrgFromRequest() org: Organization,\n    @Param('id') id: string,\n    @Body() body: { methodName: string; data: Record<string, string> }\n  ) {\n    Sentry.metrics.count('public_api-request', 1);\n    const getIntegration = await this._integrationService.getIntegrationById(\n      org.id,\n      id\n    );\n\n    if (!getIntegration) {\n      throw new HttpException({ msg: 'Integration not found' }, 404);\n    }\n\n    const integrationProvider = socialIntegrationList.find(\n      (p) => p.identifier === getIntegration.providerIdentifier\n    )!;\n\n    if (!integrationProvider) {\n      throw new HttpException({ msg: 'Integration provider not found' }, 404);\n    }\n\n    const tools = this._integrationManager.getAllTools();\n    if (\n      // @ts-ignore\n      !tools[integrationProvider.identifier]?.some(\n        (p: any) => p.methodName === body.methodName\n      ) ||\n      // @ts-ignore\n      !integrationProvider[body.methodName]\n    ) {\n      throw new HttpException({ msg: 'Tool not found' }, 404);\n    }\n\n    while (true) {\n      try {\n        // @ts-ignore\n        const result = await integrationProvider[body.methodName](\n          getIntegration.token,\n          body.data || {},\n          getIntegration.internalId,\n          getIntegration\n        );\n\n        return { output: result };\n      } catch (err) {\n        if (err instanceof RefreshToken) {\n          const data = await this._refreshIntegrationService.refresh(\n            getIntegration\n          );\n\n          if (!data) {\n            await this._integrationService.disconnectChannel(\n              org.id,\n              getIntegration\n            );\n            throw new HttpException(\n              { msg: 'Channel disconnected due to expired token' },\n              401\n            );\n          }\n\n          const { accessToken } = data;\n\n          if (accessToken) {\n            getIntegration.token = accessToken;\n\n            if (integrationProvider.refreshWait) {\n              await timer(10000);\n            }\n\n            continue;\n          }\n        }\n        throw new HttpException({ msg: 'Unexpected error' }, 500);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/auth.middleware.ts",
    "content": "import { Injectable, NestMiddleware } from '@nestjs/common';\nimport { Request, Response, NextFunction } from 'express';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { User } from '@prisma/client';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';\nimport { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';\nimport { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter';\nimport { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';\n\nexport const removeAuth = (res: Response) => {\n  res.cookie('auth', '', {\n    domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n    ...(!process.env.NOT_SECURED\n      ? {\n          secure: true,\n          httpOnly: true,\n          sameSite: 'none',\n        }\n      : {}),\n    expires: new Date(0),\n    maxAge: -1,\n  });\n  res.header('logout', 'true');\n};\n\n@Injectable()\nexport class AuthMiddleware implements NestMiddleware {\n  constructor(\n    private _organizationService: OrganizationService,\n    private _userService: UsersService\n  ) {}\n  async use(req: Request, res: Response, next: NextFunction) {\n    const auth = req.headers.auth || req.cookies.auth;\n    if (!auth) {\n      throw new HttpForbiddenException();\n    }\n    try {\n      let user = AuthService.verifyJWT(auth) as User | null;\n      const orgHeader = req.cookies.showorg || req.headers.showorg;\n\n      if (!user) {\n        throw new HttpForbiddenException();\n      }\n\n      if (!user.activated) {\n        throw new HttpForbiddenException();\n      }\n\n      const impersonate = req.cookies.impersonate || req.headers.impersonate;\n      if (user?.isSuperAdmin && impersonate) {\n        const loadImpersonate = await this._organizationService.getUserOrg(\n          impersonate\n        );\n\n        if (loadImpersonate) {\n          user = loadImpersonate.user;\n          user.isSuperAdmin = true;\n          delete user.password;\n\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-expect-error\n          req.user = user;\n\n          // @ts-ignore\n          loadImpersonate.organization.users =\n            loadImpersonate.organization.users.filter(\n              (f) => f.userId === user.id\n            );\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-expect-error\n          req.org = loadImpersonate.organization;\n          next();\n          return;\n        }\n      }\n\n      delete user.password;\n      const organization = (\n        await this._organizationService.getOrgsByUserId(user.id)\n      ).filter((f) => !f.users[0].disabled);\n      const setOrg =\n        organization.find((org) => org.id === orgHeader) || organization[0];\n\n      if (!organization) {\n        throw new HttpForbiddenException();\n      }\n\n      if (!setOrg.apiKey) {\n        await this._organizationService.updateApiKey(setOrg.id);\n      }\n\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-expect-error\n      req.user = user;\n\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-expect-error\n      req.org = setOrg;\n    } catch (err) {\n      throw new HttpForbiddenException();\n    }\n    next();\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/auth.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Provider, User } from '@prisma/client';\nimport { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';\nimport { LoginUserDto } from '@gitroom/nestjs-libraries/dtos/auth/login.user.dto';\nimport { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { AuthService as AuthChecker } from '@gitroom/helpers/auth/auth.service';\nimport { AuthProviderManager } from '@gitroom/backend/services/auth/providers/providers.manager';\nimport dayjs from 'dayjs';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';\nimport { EmailService } from '@gitroom/nestjs-libraries/services/email.service';\nimport { NewsletterService } from '@gitroom/nestjs-libraries/newsletter/newsletter.service';\n\n@Injectable()\nexport class AuthService {\n  constructor(\n    private _userService: UsersService,\n    private _organizationService: OrganizationService,\n    private _notificationService: NotificationService,\n    private _emailService: EmailService,\n    private _providerManager: AuthProviderManager\n  ) {}\n  async canRegister(provider: string) {\n    if (\n      process.env.DISABLE_REGISTRATION !== 'true' ||\n      provider === Provider.GENERIC\n    ) {\n      return true;\n    }\n\n    return (await this._organizationService.getCount()) === 0;\n  }\n\n  async routeAuth(\n    provider: Provider,\n    body: CreateOrgUserDto | LoginUserDto,\n    ip: string,\n    userAgent: string,\n    addToOrg?: boolean | { orgId: string; role: 'USER' | 'ADMIN'; id: string }\n  ) {\n    if (provider === Provider.LOCAL) {\n      if (process.env.DISALLOW_PLUS && body.email.includes('+')) {\n        throw new Error('Email with plus sign is not allowed');\n      }\n      const user = await this._userService.getUserByEmail(body.email);\n      if (body instanceof CreateOrgUserDto) {\n        if (user) {\n          throw new Error('Email already exists');\n        }\n\n        if (!(await this.canRegister(provider))) {\n          throw new Error('Registration is disabled');\n        }\n\n        const create = await this._organizationService.createOrgAndUser(\n          body,\n          ip,\n          userAgent\n        );\n\n        const addedOrg =\n          addToOrg && typeof addToOrg !== 'boolean'\n            ? await this._organizationService.addUserToOrg(\n                create.users[0].user.id,\n                addToOrg.id,\n                addToOrg.orgId,\n                addToOrg.role\n              )\n            : false;\n\n        const obj = { addedOrg, jwt: await this.jwt(create.users[0].user) };\n        await this._emailService.sendEmail(\n          body.email,\n          'Activate your account',\n          `Click <a href=\"${process.env.FRONTEND_URL}/auth/activate/${obj.jwt}\">here</a> to activate your account`,\n          'top'\n        );\n        return obj;\n      }\n\n      if (!user || !AuthChecker.comparePassword(body.password, user.password)) {\n        throw new Error('Invalid user name or password');\n      }\n\n      if (!user.activated) {\n        throw new Error('User is not activated');\n      }\n\n      return { addedOrg: false, jwt: await this.jwt(user) };\n    }\n\n    const user = await this.loginOrRegisterProvider(\n      provider,\n      body as CreateOrgUserDto,\n      ip,\n      userAgent\n    );\n\n    const addedOrg =\n      addToOrg && typeof addToOrg !== 'boolean'\n        ? await this._organizationService.addUserToOrg(\n            user.id,\n            addToOrg.id,\n            addToOrg.orgId,\n            addToOrg.role\n          )\n        : false;\n    return { addedOrg, jwt: await this.jwt(user) };\n  }\n\n  public getOrgFromCookie(cookie?: string) {\n    if (!cookie) {\n      return false;\n    }\n\n    try {\n      const getOrg: any = AuthChecker.verifyJWT(cookie);\n      if (dayjs(getOrg.timeLimit).isBefore(dayjs())) {\n        return false;\n      }\n\n      return getOrg as {\n        email: string;\n        role: 'USER' | 'ADMIN';\n        orgId: string;\n        id: string;\n      };\n    } catch (err) {\n      return false;\n    }\n  }\n\n  private async loginOrRegisterProvider(\n    provider: Provider,\n    body: CreateOrgUserDto,\n    ip: string,\n    userAgent: string\n  ) {\n    const providerInstance = this._providerManager.getProvider(provider);\n    const providerUser = await providerInstance.getUser(body.providerToken);\n\n    if (!providerUser) {\n      throw new Error('Invalid provider token');\n    }\n\n    const user = await this._userService.getUserByProvider(\n      providerUser.id,\n      provider\n    );\n    if (user) {\n      return user;\n    }\n\n    if (!(await this.canRegister(provider))) {\n      throw new Error('Registration is disabled');\n    }\n\n    const create = await this._organizationService.createOrgAndUser(\n      {\n        company: body.company,\n        email: providerUser.email,\n        password: '',\n        provider,\n        providerId: providerUser.id,\n        datafast_visitor_id: body.datafast_visitor_id,\n      },\n      ip,\n      userAgent\n    );\n\n    this._track('register', providerUser.email, body.datafast_visitor_id).catch(\n      (err) => {}\n    );\n\n    await NewsletterService.register(providerUser.email);\n\n    try {\n      if (providerInstance?.postRegistration) {\n        await providerInstance.postRegistration(body.providerToken, create.id);\n      }\n    } catch (err) {\n      // Don't fail registration if postRegistration fails\n    }\n\n    return create.users[0].user;\n  }\n\n  private async _track(\n    name: string,\n    email: string,\n    datafast_visitor_id: string\n  ) {\n    if (email && datafast_visitor_id && process.env.DATAFAST_API_KEY) {\n      try {\n        await fetch('https://datafa.st/api/v1/goals', {\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${process.env.DATAFAST_API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            datafast_visitor_id: datafast_visitor_id,\n            name: name,\n            metadata: {\n              email,\n            },\n          }),\n        });\n      } catch (err) {}\n    }\n  }\n\n  async forgot(email: string) {\n    const user = await this._userService.getUserByEmail(email);\n    if (!user || user.providerName !== Provider.LOCAL) {\n      return false;\n    }\n\n    const resetValues = AuthChecker.signJWT({\n      id: user.id,\n      expires: dayjs().add(20, 'minutes').format('YYYY-MM-DD HH:mm:ss'),\n    });\n\n    await this._notificationService.sendEmail(\n      user.email,\n      'Reset your password',\n      `You have requested to reset your passsord. <br />Click <a href=\"${process.env.FRONTEND_URL}/auth/forgot/${resetValues}\">here</a> to reset your password<br />The link will expire in 20 minutes`\n    );\n  }\n\n  forgotReturn(body: ForgotReturnPasswordDto) {\n    const user = AuthChecker.verifyJWT(body.token) as {\n      id: string;\n      expires: string;\n    };\n    if (dayjs(user.expires).isBefore(dayjs())) {\n      return false;\n    }\n\n    return this._userService.updatePassword(user.id, body.password);\n  }\n\n  async activate(code: string, tracking: string) {\n    const user = AuthChecker.verifyJWT(code) as {\n      id: string;\n      activated: boolean;\n      email: string;\n    };\n    if (user.id && !user.activated) {\n      const getUserAgain = await this._userService.getUserByEmail(user.email);\n      if (getUserAgain.activated) {\n        return false;\n      }\n      await this._userService.activateUser(user.id);\n      user.activated = true;\n      this._track('register', user.email, tracking).catch((err) => {});\n      await NewsletterService.register(user.email);\n      return this.jwt(user as any);\n    }\n\n    return false;\n  }\n\n  async resendActivationEmail(email: string) {\n    const user = await this._userService.getUserByEmail(email);\n\n    if (!user) {\n      throw new Error('User not found');\n    }\n\n    if (user.activated) {\n      throw new Error('Account is already activated');\n    }\n\n    const jwt = await this.jwt(user);\n\n    await this._emailService.sendEmail(\n      user.email,\n      'Activate your account',\n      `Click <a href=\"${process.env.FRONTEND_URL}/auth/activate/${jwt}\">here</a> to activate your account`,\n      'top'\n    );\n\n    return true;\n  }\n\n  oauthLink(provider: string, query?: any) {\n    const providerInstance = this._providerManager.getProvider(provider);\n    return providerInstance.generateLink(query);\n  }\n\n  async checkExists(provider: string, code: string) {\n    const providerInstance = this._providerManager.getProvider(provider);\n    const token = await providerInstance.getToken(code);\n    const user = await providerInstance.getUser(token);\n    if (!user) {\n      throw new Error('Invalid user');\n    }\n    const checkExists = await this._userService.getUserByProvider(\n      user.id,\n      provider as Provider\n    );\n    if (checkExists) {\n      return { jwt: await this.jwt(checkExists) };\n    }\n\n    return { token };\n  }\n\n  private async jwt(user: User) {\n    return AuthChecker.signJWT(user);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/permissions/permission.exception.class.ts",
    "content": "import { HttpException, HttpStatus } from '@nestjs/common';\n\nexport enum Sections {\n  CHANNEL = 'channel',\n  POSTS_PER_MONTH = 'posts_per_month',\n  VIDEOS_PER_MONTH = 'videos_per_month',\n  TEAM_MEMBERS = 'team_members',\n  COMMUNITY_FEATURES = 'community_features',\n  FEATURED_BY_GITROOM = 'featured_by_gitroom',\n  AI = 'ai',\n  IMPORT_FROM_CHANNELS = 'import_from_channels',\n  ADMIN = 'admin',\n  WEBHOOKS = 'webhooks',\n}\n\nexport enum AuthorizationActions {\n  Create = 'create',\n  Read = 'read',\n  Update = 'update',\n  Delete = 'delete',\n}\n\nexport class SubscriptionException extends HttpException {\n  constructor(message: { section: Sections; action: AuthorizationActions }) {\n    super(message, HttpStatus.PAYMENT_REQUIRED);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/permissions/permissions.ability.ts",
    "content": "import { SetMetadata } from '@nestjs/common';\nimport { AuthorizationActions, Sections } from './permission.exception.class';\n\nexport const CHECK_POLICIES_KEY = 'check_policy';\nexport type AbilityPolicy = [AuthorizationActions, Sections];\nexport const CheckPolicies = (...handlers: AbilityPolicy[]) =>\n  SetMetadata(CHECK_POLICIES_KEY, handlers);\n"
  },
  {
    "path": "apps/backend/src/services/auth/permissions/permissions.guard.ts",
    "content": "import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport {\n  AppAbility,\n  PermissionsService,\n} from '@gitroom/backend/services/auth/permissions/permissions.service';\nimport {\n  AbilityPolicy,\n  CHECK_POLICIES_KEY,\n} from '@gitroom/backend/services/auth/permissions/permissions.ability';\nimport { Organization } from '@prisma/client';\nimport { Request } from 'express';\nimport { SubscriptionException } from './permission.exception.class';\n\n@Injectable()\nexport class PoliciesGuard implements CanActivate {\n  constructor(\n    private _reflector: Reflector,\n    private _authorizationService: PermissionsService\n  ) {}\n\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const request: Request = context.switchToHttp().getRequest();\n    if (\n      request.path.indexOf('/auth') > -1 ||\n      request.path.indexOf('/auth') > -1 ||\n      request.path.indexOf('/integrations/social-connect') > -1 ||\n      request.path.indexOf('/integrations/provider') > -1\n    ) {\n      return true;\n    }\n\n    const policyHandlers =\n      this._reflector.get<AbilityPolicy[]>(\n        CHECK_POLICIES_KEY,\n        context.getHandler()\n      ) || [];\n\n    if (!policyHandlers || !policyHandlers.length) {\n      return true;\n    }\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-expect-error\n    const { org }: { org: Organization } = request;\n\n    // @ts-ignore\n    const ability = await this._authorizationService.check(org.id, org.createdAt, org.users[0].role, policyHandlers);\n\n    const item = policyHandlers.find(\n      (handler) => !this.execPolicyHandler(handler, ability)\n    );\n\n    if (item) {\n      throw new SubscriptionException({\n        section: item[1],\n        action: item[0],\n      });\n    }\n\n    return true;\n  }\n\n  private execPolicyHandler(handler: AbilityPolicy, ability: AppAbility) {\n    return ability.can(handler[0], handler[1]);\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/permissions/permissions.service.ts",
    "content": "import { Ability, AbilityBuilder, AbilityClass } from '@casl/ability';\nimport { Injectable } from '@nestjs/common';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport dayjs from 'dayjs';\nimport { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';\nimport { AuthorizationActions, Sections } from './permission.exception.class';\n\nexport type AppAbility = Ability<[AuthorizationActions, Sections]>;\n\n@Injectable()\nexport class PermissionsService {\n  constructor(\n    private _subscriptionService: SubscriptionService,\n    private _postsService: PostsService,\n    private _integrationService: IntegrationService,\n    private _webhooksService: WebhooksService\n  ) {}\n  async getPackageOptions(orgId: string) {\n    const subscription =\n      await this._subscriptionService.getSubscriptionByOrganizationId(orgId);\n\n    const tier =\n      subscription?.subscriptionTier ||\n      (!process.env.STRIPE_PUBLISHABLE_KEY ? 'PRO' : 'FREE');\n\n    const { channel, ...all } = pricing[tier];\n    return {\n      subscription,\n      options: {\n        ...all,\n        ...{ channel: tier === 'FREE' ? channel : -10 },\n      },\n    };\n  }\n\n  async check(\n    orgId: string,\n    created_at: Date,\n    permission: 'USER' | 'ADMIN' | 'SUPERADMIN',\n    requestedPermission: Array<[AuthorizationActions, Sections]>\n  ) {\n    const { can, build } = new AbilityBuilder<\n      Ability<[AuthorizationActions, Sections]>\n    >(Ability as AbilityClass<AppAbility>);\n\n    if (\n      requestedPermission.length === 0 ||\n      !process.env.STRIPE_PUBLISHABLE_KEY\n    ) {\n      for (const [action, section] of requestedPermission) {\n        can(action, section);\n      }\n      return build({\n        detectSubjectType: (item) =>\n          // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n          // @ts-ignore\n          item.constructor,\n      });\n    }\n\n    const { subscription, options } = await this.getPackageOptions(orgId);\n    for (const [action, section] of requestedPermission) {\n      // check for the amount of channels\n      if (section === Sections.CHANNEL) {\n        const totalChannels = (\n          await this._integrationService.getIntegrationsList(orgId)\n        ).filter((f) => !f.refreshNeeded).length;\n\n        if (\n          (options.channel && options.channel > totalChannels) ||\n          (subscription?.totalChannels || 0) > totalChannels\n        ) {\n          can(action, section);\n          continue;\n        }\n      }\n\n      if (section === Sections.WEBHOOKS) {\n        const totalWebhooks = await this._webhooksService.getTotal(orgId);\n        if (totalWebhooks < options.webhooks) {\n          can(AuthorizationActions.Create, section);\n          continue;\n        }\n      }\n\n      // check for posts per month\n      if (section === Sections.POSTS_PER_MONTH) {\n        const createdAt =\n          (await this._subscriptionService.getSubscription(orgId))?.createdAt ||\n          created_at;\n        const totalMonthPast = Math.abs(\n          dayjs(createdAt).diff(dayjs(), 'month')\n        );\n        const checkFrom = dayjs(createdAt).add(totalMonthPast, 'month');\n        const count = await this._postsService.countPostsFromDay(\n          orgId,\n          checkFrom.toDate()\n        );\n\n        if (count < options.posts_per_month) {\n          can(action, section);\n          continue;\n        }\n      }\n\n      if (section === Sections.TEAM_MEMBERS && options.team_members) {\n        can(action, section);\n        continue;\n      }\n\n      if (\n        section === Sections.ADMIN &&\n        ['ADMIN', 'SUPERADMIN'].includes(permission)\n      ) {\n        can(action, section);\n        continue;\n      }\n\n      if (\n        section === Sections.COMMUNITY_FEATURES &&\n        options.community_features\n      ) {\n        can(action, section);\n        continue;\n      }\n\n      if (\n        section === Sections.FEATURED_BY_GITROOM &&\n        options.featured_by_gitroom\n      ) {\n        can(action, section);\n        continue;\n      }\n\n      if (section === Sections.AI && options.ai) {\n        can(action, section);\n        continue;\n      }\n\n      if (\n        section === Sections.IMPORT_FROM_CHANNELS &&\n        options.import_from_channels\n      ) {\n        can(action, section);\n      }\n    }\n\n    return build({\n      detectSubjectType: (item) =>\n        // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n        // @ts-ignore\n        item.constructor,\n    });\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/permissions/subscription.exception.ts",
    "content": "import {\n  ArgumentsHost,\n  Catch,\n  ExceptionFilter,\n  HttpException,\n} from '@nestjs/common';\nimport { AuthorizationActions, Sections, SubscriptionException } from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@Catch(SubscriptionException)\nexport class SubscriptionExceptionFilter implements ExceptionFilter {\n  catch(exception: HttpException, host: ArgumentsHost) {\n    const ctx = host.switchToHttp();\n    const response = ctx.getResponse();\n    const status = exception.getStatus();\n    const error: { section: Sections; action: AuthorizationActions } =\n      exception.getResponse() as any;\n\n    const message = getErrorMessage(error);\n\n    response.status(status).json({\n      statusCode: status,\n      message,\n      url: process.env.FRONTEND_URL + '/billing',\n    });\n  }\n}\n\nconst getErrorMessage = (error: {\n  section: Sections;\n  action: AuthorizationActions;\n}) => {\n  switch (error.section) {\n    case Sections.POSTS_PER_MONTH:\n      switch (error.action) {\n        default:\n          return 'You have reached the maximum number of posts for your subscription. Please upgrade your subscription to add more posts.';\n      }\n    case Sections.CHANNEL:\n      switch (error.action) {\n        default:\n          return 'You have reached the maximum number of channels for your subscription. Please upgrade your subscription to add more channels.';\n      }\n    case Sections.WEBHOOKS:\n      switch (error.action) {\n        default:\n          return 'You have reached the maximum number of webhooks for your subscription. Please upgrade your subscription to add more webhooks.';\n      }\n    case Sections.VIDEOS_PER_MONTH:\n      switch (error.action) {\n        default:\n          return 'You have reached the maximum number of generated videos for your subscription. Please upgrade your subscription to generate more videos.';\n      }\n  }\n};\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/farcaster.provider.ts",
    "content": "import {\n  AuthProvider,\n  AuthProviderAbstract,\n} from '@gitroom/backend/services/auth/providers.interface';\nimport { NeynarAPIClient } from '@neynar/nodejs-sdk';\n\nconst client = new NeynarAPIClient({\n  apiKey: process.env.NEYNAR_SECRET_KEY || '00000000-000-0000-000-000000000000',\n});\n\n@AuthProvider({ provider: 'FARCASTER' })\nexport class FarcasterProvider extends AuthProviderAbstract {\n  generateLink() {\n    return '';\n  }\n\n  async getToken(code: string) {\n    const data = JSON.parse(Buffer.from(code, 'base64').toString());\n    const status = await client.lookupSigner({ signerUuid: data.signer_uuid });\n    if (status.status === 'approved') {\n      return data.signer_uuid;\n    }\n\n    return '';\n  }\n\n  async getUser(providerToken: string) {\n    const status = await client.lookupSigner({ signerUuid: providerToken });\n    if (status.status !== 'approved') {\n      return {\n        id: '',\n        email: '',\n      };\n    }\n\n    return {\n      id: String('farcaster_' + status.fid),\n      email: String('farcaster_' + status.fid),\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/github.provider.ts",
    "content": "import {\n  AuthProvider,\n  AuthProviderAbstract,\n} from '@gitroom/backend/services/auth/providers.interface';\n\n@AuthProvider({ provider: 'GITHUB' })\nexport class GithubProvider extends AuthProviderAbstract {\n  generateLink(): string {\n    return `https://github.com/login/oauth/authorize?client_id=${\n      process.env.GITHUB_CLIENT_ID\n    }&scope=user:email&redirect_uri=${encodeURIComponent(\n      `${process.env.FRONTEND_URL}/settings`\n    )}`;\n  }\n\n  async getToken(code: string): Promise<string> {\n    const { access_token } = await (\n      await fetch('https://github.com/login/oauth/access_token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Accept: 'application/json',\n        },\n        body: JSON.stringify({\n          client_id: process.env.GITHUB_CLIENT_ID,\n          client_secret: process.env.GITHUB_CLIENT_SECRET,\n          code,\n          redirect_uri: `${process.env.FRONTEND_URL}/settings`,\n        }),\n      })\n    ).json();\n\n    return access_token;\n  }\n\n  async getUser(access_token: string): Promise<{ email: string; id: string }> {\n    const data = await (\n      await fetch('https://api.github.com/user', {\n        headers: {\n          Authorization: `token ${access_token}`,\n        },\n      })\n    ).json();\n\n    const [{ email }] = await (\n      await fetch('https://api.github.com/user/emails', {\n        headers: {\n          Authorization: `token ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      email: email,\n      id: String(data.id),\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/google.provider.ts",
    "content": "import { google } from 'googleapis';\nimport { OAuth2Client } from 'google-auth-library/build/src/auth/oauth2client';\nimport {\n  AuthProvider,\n  AuthProviderAbstract,\n} from '@gitroom/backend/services/auth/providers.interface';\n\nconst clientAndYoutube = () => {\n  const client = new google.auth.OAuth2({\n    clientId: process.env.YOUTUBE_CLIENT_ID,\n    clientSecret: process.env.YOUTUBE_CLIENT_SECRET,\n    redirectUri: `${process.env.FRONTEND_URL}/integrations/social/youtube`,\n  });\n\n  const youtube = (newClient: OAuth2Client) =>\n    google.youtube({\n      version: 'v3',\n      auth: newClient,\n    });\n\n  const youtubeAnalytics = (newClient: OAuth2Client) =>\n    google.youtubeAnalytics({\n      version: 'v2',\n      auth: newClient,\n    });\n\n  const oauth2 = (newClient: OAuth2Client) =>\n    google.oauth2({\n      version: 'v2',\n      auth: newClient,\n    });\n\n  return { client, youtube, oauth2, youtubeAnalytics };\n};\n\n@AuthProvider({ provider: 'GOOGLE' })\nexport class GoogleProvider extends AuthProviderAbstract {\n  generateLink() {\n    const state = 'login';\n    const { client } = clientAndYoutube();\n    return client.generateAuthUrl({\n      access_type: 'online',\n      prompt: 'consent',\n      state,\n      redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/youtube`,\n      scope: [\n        'https://www.googleapis.com/auth/userinfo.profile',\n        'https://www.googleapis.com/auth/userinfo.email',\n      ],\n    });\n  }\n\n  async getToken(code: string) {\n    const { client, oauth2 } = clientAndYoutube();\n    const { tokens } = await client.getToken(code);\n    return tokens.access_token;\n  }\n\n  async getUser(providerToken: string) {\n    const { client, oauth2 } = clientAndYoutube();\n    client.setCredentials({ access_token: providerToken });\n    const user = oauth2(client);\n    const { data } = await user.userinfo.get();\n\n    return {\n      id: data.id!,\n      email: data.email,\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/oauth.provider.ts",
    "content": "import {\n  AuthProvider,\n  AuthProviderAbstract,\n} from '@gitroom/backend/services/auth/providers.interface';\n\n@AuthProvider({ provider: 'GENERIC' })\nexport class OauthProvider extends AuthProviderAbstract {\n  private getConfig() {\n    const {\n      POSTIZ_OAUTH_AUTH_URL,\n      POSTIZ_OAUTH_CLIENT_ID,\n      POSTIZ_OAUTH_CLIENT_SECRET,\n      POSTIZ_OAUTH_TOKEN_URL,\n      POSTIZ_OAUTH_USERINFO_URL,\n      FRONTEND_URL,\n    } = process.env;\n\n    if (\n      !POSTIZ_OAUTH_USERINFO_URL ||\n      !POSTIZ_OAUTH_TOKEN_URL ||\n      !POSTIZ_OAUTH_CLIENT_ID ||\n      !POSTIZ_OAUTH_CLIENT_SECRET ||\n      !POSTIZ_OAUTH_AUTH_URL ||\n      !FRONTEND_URL\n    ) {\n      throw new Error('POSTIZ_OAUTH environment variables are not set');\n    }\n\n    return {\n      authUrl: POSTIZ_OAUTH_AUTH_URL,\n      clientId: POSTIZ_OAUTH_CLIENT_ID,\n      clientSecret: POSTIZ_OAUTH_CLIENT_SECRET,\n      tokenUrl: POSTIZ_OAUTH_TOKEN_URL,\n      userInfoUrl: POSTIZ_OAUTH_USERINFO_URL,\n      frontendUrl: FRONTEND_URL,\n    };\n  }\n\n  generateLink(): string {\n    const { authUrl, clientId, frontendUrl } = this.getConfig();\n    const params = new URLSearchParams({\n      client_id: clientId,\n      scope: 'openid profile email',\n      response_type: 'code',\n      redirect_uri: `${frontendUrl}/settings`,\n    });\n\n    return `${authUrl}?${params.toString()}`;\n  }\n\n  async getToken(code: string): Promise<string> {\n    const { tokenUrl, clientId, clientSecret, frontendUrl } = this.getConfig();\n    const response = await fetch(`${tokenUrl}`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n        Accept: 'application/json',\n      },\n      body: new URLSearchParams({\n        grant_type: 'authorization_code',\n        client_id: clientId,\n        client_secret: clientSecret,\n        code,\n        redirect_uri: `${frontendUrl}/settings`,\n      }),\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Token request failed: ${error}`);\n    }\n\n    const { access_token } = await response.json();\n    return access_token;\n  }\n\n  async getUser(access_token: string): Promise<{ email: string; id: string }> {\n    const { userInfoUrl } = this.getConfig();\n    const response = await fetch(`${userInfoUrl}`, {\n      headers: {\n        Authorization: `Bearer ${access_token}`,\n        Accept: 'application/json',\n      },\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`User info request failed: ${error}`);\n    }\n\n    const { email, sub: id } = await response.json();\n    return { email, id };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/providers.manager.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { ModuleRef } from '@nestjs/core';\nimport { AuthProviderAbstract } from '@gitroom/backend/services/auth/providers.interface';\n\n@Injectable()\nexport class AuthProviderManager {\n  constructor(private _moduleRef: ModuleRef) {}\n\n  getProvider(provider: string): AuthProviderAbstract {\n    const metadata =\n      Reflect.getMetadata('auth-provider', AuthProviderAbstract) || [];\n\n    const found = metadata.find(\n      (m: any) => m.provider === provider\n    );\n\n    if (!found) {\n      throw new Error(`Auth provider ${provider} not found`);\n    }\n\n    return this._moduleRef.get(found.target, { strict: false });\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers/wallet.provider.ts",
    "content": "import {\n  AuthProvider,\n  AuthProviderAbstract,\n} from '@gitroom/backend/services/auth/providers.interface';\nimport { randomBytes } from 'crypto';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport bs58 from 'bs58';\nimport nacl from 'tweetnacl';\n\nfunction hexToUint8Array(hex) {\n  if (hex.startsWith('0x')) {\n    hex = hex.slice(2);\n  }\n\n  if (hex.length % 2 !== 0) {\n    throw new Error('Invalid hex string. It must have an even length.');\n  }\n\n  const byteLength = hex.length / 2;\n  const uint8Array = new Uint8Array(byteLength);\n\n  for (let i = 0; i < byteLength; i++) {\n    const byteHex = hex.substr(i * 2, 2);\n    uint8Array[i] = parseInt(byteHex, 16);\n  }\n\n  return uint8Array;\n}\n\n@AuthProvider({ provider: 'WALLET' })\nexport class WalletProvider extends AuthProviderAbstract {\n  async generateLink(params: { publicKey: string }) {\n    if (!params.publicKey) {\n      return;\n    }\n\n    const challenge = randomBytes(32).toString('hex');\n    await ioRedis.set(`wallet:${params.publicKey}`, challenge, 'EX', 60);\n\n    return challenge;\n  }\n\n  async getToken(code: string) {\n    const { publicKey, challenge, signature } = JSON.parse(\n      Buffer.from(code, 'base64').toString()\n    );\n\n    if (!publicKey || !challenge || !signature) {\n      return '';\n    }\n\n    const redisGet = await ioRedis.get(`wallet:${publicKey}`);\n    if (redisGet !== challenge) {\n      return '';\n    }\n\n    const publicKeyUint8 = bs58.decode(publicKey);\n    const messageUint8 = new TextEncoder().encode(challenge);\n    const signatureUint8 = hexToUint8Array(signature);\n    const isValid = nacl.sign.detached.verify(\n      messageUint8,\n      signatureUint8,\n      publicKeyUint8\n    );\n\n    if (!isValid) {\n      return '';\n    }\n\n    return code;\n  }\n\n  async getUser(providerToken: string) {\n    if ((await this.getToken(providerToken)) === '') {\n      return {\n        id: '',\n        email: '',\n      };\n    }\n\n    const { publicKey } = JSON.parse(\n      Buffer.from(providerToken, 'base64').toString()\n    );\n\n    return {\n      id: String(`wallet_${publicKey}`),\n      email: String(`wallet_${publicKey}`),\n    };\n  }\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/providers.interface.ts",
    "content": "import { Injectable } from '@nestjs/common';\n\nexport abstract class AuthProviderAbstract {\n  abstract generateLink(query?: any): Promise<string> | string;\n  abstract getToken(code: string): Promise<string>;\n  abstract getUser(\n    providerToken: string\n  ): Promise<{ email: string; id: string }> | false;\n  async postRegistration(\n    providerToken: string,\n    orgId: string\n  ): Promise<void> {}\n}\n\nexport interface AuthProviderParams {\n  provider: string;\n}\n\nexport function AuthProvider(params: AuthProviderParams) {\n  return function (target: any) {\n    Injectable()(target);\n\n    const existingMetadata =\n      Reflect.getMetadata('auth-provider', AuthProviderAbstract) || [];\n\n    existingMetadata.push({ target, provider: params.provider });\n\n    Reflect.defineMetadata(\n      'auth-provider',\n      existingMetadata,\n      AuthProviderAbstract\n    );\n  };\n}\n"
  },
  {
    "path": "apps/backend/src/services/auth/public.auth.middleware.ts",
    "content": "import { HttpStatus, Injectable, NestMiddleware } from '@nestjs/common';\nimport { Request, Response, NextFunction } from 'express';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\nimport { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter';\n\n@Injectable()\nexport class PublicAuthMiddleware implements NestMiddleware {\n  constructor(\n    private _organizationService: OrganizationService,\n    private _oauthService: OAuthService\n  ) {}\n  async use(req: Request, res: Response, next: NextFunction) {\n    const auth = (req.headers.authorization ||\n      req.headers.Authorization) as string;\n    if (!auth) {\n      res.status(HttpStatus.UNAUTHORIZED).json({ msg: 'No API Key found' });\n      return;\n    }\n    try {\n      if (auth.startsWith('pos_')) {\n        const authorization = await this._oauthService.getOrgByOAuthToken(auth);\n        if (!authorization) {\n          res\n            .status(HttpStatus.UNAUTHORIZED)\n            .json({ msg: 'Invalid OAuth token' });\n          return;\n        }\n\n        const org = authorization.organization;\n        if (!!process.env.STRIPE_SECRET_KEY && !org.subscription) {\n          res\n            .status(HttpStatus.UNAUTHORIZED)\n            .json({ msg: 'No subscription found' });\n          return;\n        }\n\n        // @ts-ignore\n        req.org = { ...org, users: [{ users: { role: 'SUPERADMIN' } }] };\n      } else {\n        const org = await this._organizationService.getOrgByApiKey(auth);\n        if (!org) {\n          res\n            .status(HttpStatus.UNAUTHORIZED)\n            .json({ msg: 'Invalid API key' });\n          return;\n        }\n\n        if (!!process.env.STRIPE_SECRET_KEY && !org.subscription) {\n          res\n            .status(HttpStatus.UNAUTHORIZED)\n            .json({ msg: 'No subscription found' });\n          return;\n        }\n\n        // @ts-ignore\n        req.org = { ...org, users: [{ users: { role: 'SUPERADMIN' } }] };\n      }\n    } catch (err) {\n      throw new HttpForbiddenException();\n    }\n    next();\n  }\n}\n"
  },
  {
    "path": "apps/backend/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"],\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"resolveJsonModule\": true,\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"ES2021\",\n    \"sourceMap\": true,\n    \"incremental\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"strictBindCallApply\": false,\n    \"forceConsistentCasingInFileNames\": false,\n    \"noFallthroughCasesInSwitch\": false,\n    \"outDir\": \"./dist\"\n  }\n}\n"
  },
  {
    "path": "apps/backend/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"esModuleInterop\": true\n  }\n}\n"
  },
  {
    "path": "apps/cli/.gitignore",
    "content": "node_modules\ndist\n*.log\n.DS_Store\n"
  },
  {
    "path": "apps/cli/.npmignore",
    "content": "src\nexamples\ntsconfig.json\ntsup.config.ts\n*.md\n!README.md\nnode_modules\n.git\n.gitignore\n"
  },
  {
    "path": "apps/cli/CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to the Postiz CLI will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [1.0.0] - 2026-02-13\n\n### Added\n- Initial release of Postiz CLI\n- `posts:create` - Create new social media posts\n- `posts:list` - List all posts with pagination and search\n- `posts:delete` - Delete posts by ID\n- `integrations:list` - List connected social media integrations\n- `upload` - Upload media files (images)\n- Environment variable configuration (POSTIZ_API_KEY, POSTIZ_API_URL)\n- Comprehensive help documentation\n- Example scripts for basic usage and AI agent integration\n- SKILL.md for AI agent usage patterns\n\n### Features\n- Command-line interface for Postiz API\n- Support for scheduled posts\n- Multi-platform posting via integrations\n- Media upload functionality\n- User-friendly error messages with emojis\n- JSON output for programmatic parsing\n- Comprehensive examples for AI agents\n"
  },
  {
    "path": "apps/cli/FEATURES.md",
    "content": "# Postiz CLI - Feature Summary\n\n## ✅ Complete Feature Set\n\n### Posts with Comments and Media - FULLY SUPPORTED\n\nThe Postiz CLI **fully supports** the complete API structure including:\n\n#### ✅ Posts with Comments\n- Main post content\n- Multiple comments/replies\n- Each comment can have different content\n- Configurable delays between comments\n\n#### ✅ Multiple Media per Post/Comment\n- Each post can have **multiple images** (array of MediaDto)\n- Each comment can have **its own images** (separate MediaDto arrays)\n- Support for various image formats (PNG, JPG, JPEG, GIF)\n- Media can be URLs or uploaded files\n\n#### ✅ Multi-Platform Posting\n- Post to multiple platforms in one request\n- Platform-specific content for each integration\n- Different media for different platforms\n\n#### ✅ Advanced Features\n- Scheduled posting with precise timestamps\n- URL shortening support\n- Tags and metadata\n- Delays between comments (in milliseconds)\n- Draft mode for review before posting\n\n## Usage Modes\n\n### 1. Simple Mode (Command Line)\n\nFor quick, simple posts:\n\n```bash\n# Single post\npostiz posts:create -c \"Hello!\" -i \"twitter-123\"\n\n# With multiple images\npostiz posts:create -c \"Post\" --image \"img1.jpg,img2.jpg,img3.jpg\" -i \"twitter-123\"\n\n# With comments (no custom media per comment)\npostiz posts:create -c \"Main\" --comments \"Comment 1;Comment 2\" -i \"twitter-123\"\n```\n\n**Limitations of Simple Mode:**\n- Comments share the same media as the main post\n- Cannot specify different images for each comment\n- Cannot set custom delays between comments\n\n### 2. Advanced Mode (JSON Files)\n\nFor complex posts with comments that have their own media:\n\n```bash\npostiz posts:create --json complex-post.json\n```\n\n**Capabilities:**\n- ✅ Each comment can have different media\n- ✅ Custom delays between comments\n- ✅ Multiple posts to different platforms\n- ✅ Platform-specific content and media\n- ✅ Full control over all API features\n\n## Real-World Examples\n\n### Example 1: Product Launch with Follow-up Comments\n\n**Main Post:** Product announcement with 3 product images\n**Comment 1:** Feature highlight with 1 feature screenshot (posted 1 hour later)\n**Comment 2:** Special offer with 1 promotional image (posted 2 hours later)\n\n```json\n{\n  \"type\": \"schedule\",\n  \"date\": \"2024-03-15T09:00:00Z\",\n  \"posts\": [{\n    \"integration\": { \"id\": \"twitter-123\" },\n    \"value\": [\n      {\n        \"content\": \"🚀 Launching our new product!\",\n        \"image\": [\n          { \"id\": \"p1\", \"path\": \"product-1.jpg\" },\n          { \"id\": \"p2\", \"path\": \"product-2.jpg\" },\n          { \"id\": \"p3\", \"path\": \"product-3.jpg\" }\n        ]\n      },\n      {\n        \"content\": \"⭐ Key features you'll love:\",\n        \"image\": [\n          { \"id\": \"f1\", \"path\": \"features-screenshot.jpg\" }\n        ],\n        \"delay\": 3600000\n      },\n      {\n        \"content\": \"🎁 Limited time: 50% off!\",\n        \"image\": [\n          { \"id\": \"o1\", \"path\": \"special-offer.jpg\" }\n        ],\n        \"delay\": 7200000\n      }\n    ]\n  }]\n}\n```\n\n### Example 2: Tutorial Thread\n\n**Main Post:** Introduction with overview image\n**Tweets 2-5:** Step-by-step with different screenshots for each step\n\n```json\n{\n  \"type\": \"now\",\n  \"posts\": [{\n    \"integration\": { \"id\": \"twitter-123\" },\n    \"value\": [\n      {\n        \"content\": \"🧵 How to use our CLI (1/5)\",\n        \"image\": [{ \"id\": \"1\", \"path\": \"overview.jpg\" }]\n      },\n      {\n        \"content\": \"Step 1: Installation (2/5)\",\n        \"image\": [{ \"id\": \"2\", \"path\": \"step1.jpg\" }],\n        \"delay\": 2000\n      },\n      {\n        \"content\": \"Step 2: Configuration (3/5)\",\n        \"image\": [{ \"id\": \"3\", \"path\": \"step2.jpg\" }],\n        \"delay\": 2000\n      },\n      {\n        \"content\": \"Step 3: First post (4/5)\",\n        \"image\": [{ \"id\": \"4\", \"path\": \"step3.jpg\" }],\n        \"delay\": 2000\n      },\n      {\n        \"content\": \"You're all set! 🎉 (5/5)\",\n        \"image\": [{ \"id\": \"5\", \"path\": \"done.jpg\" }],\n        \"delay\": 2000\n      }\n    ]\n  }]\n}\n```\n\n### Example 3: Multi-Platform Campaign\n\n**Same event, different content per platform:**\n\n```json\n{\n  \"type\": \"schedule\",\n  \"date\": \"2024-12-25T12:00:00Z\",\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"twitter-123\" },\n      \"value\": [\n        {\n          \"content\": \"Short, catchy Twitter post 🐦\",\n          \"image\": [{ \"id\": \"t1\", \"path\": \"twitter-square.jpg\" }]\n        },\n        {\n          \"content\": \"Thread continuation with details\",\n          \"image\": [{ \"id\": \"t2\", \"path\": \"twitter-details.jpg\" }],\n          \"delay\": 5000\n        }\n      ]\n    },\n    {\n      \"integration\": { \"id\": \"linkedin-456\" },\n      \"value\": [{\n        \"content\": \"Professional, detailed LinkedIn post with business context...\",\n        \"image\": [\n          { \"id\": \"l1\", \"path\": \"linkedin-wide.jpg\" },\n          { \"id\": \"l2\", \"path\": \"linkedin-graph.jpg\" }\n        ]\n      }]\n    },\n    {\n      \"integration\": { \"id\": \"facebook-789\" },\n      \"value\": [\n        {\n          \"content\": \"Engaging Facebook post for family/friends audience\",\n          \"image\": [\n            { \"id\": \"f1\", \"path\": \"facebook-photo1.jpg\" },\n            { \"id\": \"f2\", \"path\": \"facebook-photo2.jpg\" },\n            { \"id\": \"f3\", \"path\": \"facebook-photo3.jpg\" }\n          ]\n        },\n        {\n          \"content\": \"More info in the comments!\",\n          \"image\": [{ \"id\": \"f4\", \"path\": \"facebook-cta.jpg\" }],\n          \"delay\": 300000\n        }\n      ]\n    }\n  ]\n}\n```\n\n## API Structure Reference\n\n### Complete CreatePostDto\n\n```typescript\n{\n  type: 'now' | 'schedule' | 'draft' | 'update',\n  date: string,              // ISO 8601 date\n  shortLink: boolean,\n  tags: Array<{\n    value: string,\n    label: string\n  }>,\n  posts: Array<{\n    integration: {\n      id: string             // From integrations:list\n    },\n    value: Array<{           // Main post + comments\n      content: string,\n      image: Array<{         // Multiple images per post/comment\n        id: string,\n        path: string,\n        alt?: string,\n        thumbnail?: string\n      }>,\n      delay?: number,        // Milliseconds\n      id?: string\n    }>,\n    settings: {\n      __type: 'EmptySettings'\n    }\n  }>\n}\n```\n\n## For AI Agents\n\n### When to Use Simple Mode\n- Quick single posts\n- No need for comment-specific media\n- Posting to 1-2 platforms\n- Same content across platforms\n\n### When to Use Advanced Mode (JSON)\n- ✅ **Comments need their own media** ← YOUR USE CASE\n- ✅ Multi-platform with different content\n- ✅ Threads with step-by-step images\n- ✅ Timed follow-up comments\n- ✅ Complex campaigns\n\n### AI Agent Tips\n\n1. **Generate JSON programmatically** - Don't write JSON manually\n2. **Validate structure** - Use TypeScript types or JSON schema\n3. **Test with \"draft\" type** - Review before posting\n4. **Use unique image IDs** - Generate with UUID or random strings\n5. **Set appropriate delays** - Twitter: 2-5s, others: 30s-1min+\n\n## Files and Documentation\n\n- **examples/post-with-comments.json** - Post with comments, each having media\n- **examples/multi-platform-post.json** - Multi-platform campaign\n- **examples/thread-post.json** - Twitter thread example\n- **examples/EXAMPLES.md** - Comprehensive guide with all patterns\n- **SKILL.md** - Full AI agent usage guide\n- **README.md** - Installation and basic usage\n\n## Summary\n\n### Question: Does it support posts with comments, each with media?\n\n**Answer: YES! ✅**\n\n- ✅ Posts can have multiple comments\n- ✅ Each comment can have its own media (multiple images)\n- ✅ Each post can have multiple images\n- ✅ Use JSON files for full control\n- ✅ See examples/ directory for working templates\n- ✅ Fully compatible with the Postiz API structure\n\nThe CLI supports the **complete Postiz API** including all advanced features!\n"
  },
  {
    "path": "apps/cli/HOW_TO_RUN.md",
    "content": "# How to Run the Postiz CLI\n\nThere are several ways to run the CLI, depending on your needs.\n\n## Option 1: Direct Execution (Quick Test) ⚡\n\nThe built file at `apps/cli/dist/index.js` is already executable!\n\n```bash\n# From the monorepo root\nnode apps/cli/dist/index.js --help\n\n# Or run it directly (it has a shebang)\n./apps/cli/dist/index.js --help\n\n# Example command\nexport POSTIZ_API_KEY=your_key\nnode apps/cli/dist/index.js posts:list\n```\n\n## Option 2: Link Globally (Recommended for Development) 🔗\n\nThis creates a global `postiz` command you can use anywhere:\n\n```bash\n# From the monorepo root\ncd apps/cli\npnpm link --global\n\n# Now you can use it anywhere!\npostiz --help\npostiz posts:list\npostiz posts:create -c \"Hello!\" -i \"twitter-123\"\n\n# To unlink later\npnpm unlink --global\n```\n\n**After linking, you can use `postiz` from any directory!**\n\n## Option 3: Use pnpm Filter (From Root) 📦\n\n```bash\n# From the monorepo root\npnpm --filter postiz start -- --help\npnpm --filter postiz start -- posts:list\npnpm --filter postiz start -- posts:create -c \"Hello\" -i \"twitter-123\"\n```\n\n## Option 4: Use npm/npx (After Publishing) 🌐\n\nOnce published to npm:\n\n```bash\n# Install globally\nnpm install -g postiz\n\n# Or use with npx (no install)\nnpx postiz --help\nnpx postiz posts:list\n```\n\n## Quick Setup Guide\n\n### Step 1: Build the CLI\n\n```bash\n# From monorepo root\npnpm run build:cli\n```\n\n### Step 2: Set Your API Key\n\n```bash\nexport POSTIZ_API_KEY=your_api_key_here\n\n# To make it permanent, add to your shell profile:\necho 'export POSTIZ_API_KEY=your_api_key' >> ~/.bashrc\n# or ~/.zshrc if you use zsh\n```\n\n### Step 3: Choose Your Method\n\n**For quick testing:**\n```bash\nnode apps/cli/dist/index.js --help\n```\n\n**For regular use (recommended):**\n```bash\ncd apps/cli\npnpm link --global\npostiz --help\n```\n\n## Troubleshooting\n\n### \"Command not found: postiz\"\n\nIf you linked globally but still get this error:\n\n```bash\n# Check if it's linked\nwhich postiz\n\n# If not found, try linking again\ncd apps/cli\npnpm link --global\n\n# Or check your PATH\necho $PATH\n```\n\n### \"POSTIZ_API_KEY is not set\"\n\n```bash\nexport POSTIZ_API_KEY=your_key\n\n# Verify it's set\necho $POSTIZ_API_KEY\n```\n\n### Permission Denied\n\nIf you get permission errors:\n\n```bash\n# Make the file executable\nchmod +x apps/cli/dist/index.js\n\n# Then try again\n./apps/cli/dist/index.js --help\n```\n\n### Rebuild After Changes\n\nAfter making code changes, rebuild:\n\n```bash\npnpm run build:cli\n```\n\nIf you linked globally, the changes will be reflected immediately (no need to re-link).\n\n## Testing the CLI\n\n### Test Help Command\n\n```bash\npostiz --help\npostiz posts:create --help\n```\n\n### Test with Sample Command (requires API key)\n\n```bash\nexport POSTIZ_API_KEY=your_key\n\n# List integrations\npostiz integrations:list\n\n# Create a test post\npostiz posts:create \\\n  -c \"Test post from CLI\" \\\n  -i \"your-integration-id\"\n```\n\n## Development Workflow\n\n### 1. Make Changes\n\nEdit files in `apps/cli/src/`\n\n### 2. Rebuild\n\n```bash\npnpm run build:cli\n```\n\n### 3. Test\n\n```bash\n# If linked globally\npostiz --help\n\n# Or direct execution\nnode apps/cli/dist/index.js --help\n```\n\n### 4. Watch Mode (Auto-rebuild)\n\n```bash\n# From apps/cli directory\npnpm run dev\n\n# In another terminal, test your changes\npostiz --help\n```\n\n## Environment Variables\n\n### Required\n\n- `POSTIZ_API_KEY` - Your Postiz API key (required for all operations)\n\n### Optional\n\n- `POSTIZ_API_URL` - Custom API endpoint (default: `https://api.postiz.com`)\n\n### Setting Environment Variables\n\n**Temporary (current session):**\n```bash\nexport POSTIZ_API_KEY=your_key\nexport POSTIZ_API_URL=https://custom-api.com\n```\n\n**Permanent (add to shell profile):**\n```bash\n# For bash\necho 'export POSTIZ_API_KEY=your_key' >> ~/.bashrc\nsource ~/.bashrc\n\n# For zsh\necho 'export POSTIZ_API_KEY=your_key' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n## Using Aliases\n\nCreate a convenient alias:\n\n```bash\n# Add to ~/.bashrc or ~/.zshrc\nalias pz='postiz'\n\n# Now you can use\npz posts:list\npz posts:create -c \"Quick post\" -i \"twitter-123\"\n```\n\n## Production Deployment\n\n### Publish to npm\n\n```bash\n# From monorepo root\npnpm run publish-cli\n\n# Or from apps/cli\ncd apps/cli\npnpm run publish\n```\n\n### Install from npm\n\n```bash\n# Global install\nnpm install -g postiz\n\n# Project-specific\nnpm install postiz\nnpx postiz --help\n```\n\n## Summary of Methods\n\n| Method | Command | Use Case |\n|--------|---------|----------|\n| **Direct Node** | `node apps/cli/dist/index.js` | Quick testing, no installation |\n| **Direct Execution** | `./apps/cli/dist/index.js` | Same as above, slightly shorter |\n| **Global Link** | `postiz` (after `pnpm link --global`) | **Recommended** for development |\n| **pnpm Filter** | `pnpm --filter postiz start --` | From monorepo root |\n| **npm Global** | `postiz` (after `npm i -g postiz`) | After publishing to npm |\n| **npx** | `npx postiz` | One-off usage without installing |\n\n## Recommended Setup\n\nFor the best development experience:\n\n```bash\n# 1. Build\npnpm run build:cli\n\n# 2. Link globally\ncd apps/cli\npnpm link --global\n\n# 3. Set API key\nexport POSTIZ_API_KEY=your_key\n\n# 4. Test\npostiz --help\npostiz integrations:list\n\n# 5. Start using!\npostiz posts:create -c \"My first post\" -i \"twitter-123\"\n```\n\nNow you can use `postiz` from anywhere! 🚀\n"
  },
  {
    "path": "apps/cli/INTEGRATION_SETTINGS_DISCOVERY.md",
    "content": "# Integration Settings Discovery\n\nThe CLI now has a powerful feature to discover what settings are available for each integration!\n\n## New Command: `integrations:settings`\n\nGet the settings schema, validation rules, and maximum character limits for any integration.\n\n## Usage\n\n```bash\npostiz integrations:settings <integration-id>\n```\n\n## What It Returns\n\n```json\n{\n  \"output\": {\n    \"maxLength\": 280,\n    \"settings\": {\n      \"properties\": {\n        \"who_can_reply_post\": {\n          \"enum\": [\"everyone\", \"following\", \"mentionedUsers\", \"subscribers\", \"verified\"],\n          \"description\": \"Who can reply to this post\"\n        },\n        \"community\": {\n          \"pattern\": \"^(https://x.com/i/communities/\\\\d+)?$\",\n          \"description\": \"X community URL\"\n        }\n      },\n      \"required\": [\"who_can_reply_post\"]\n    }\n  }\n}\n```\n\n## Workflow\n\n### 1. List Your Integrations\n\n```bash\npostiz integrations:list\n```\n\nOutput:\n```json\n[\n  {\n    \"id\": \"reddit-abc123\",\n    \"name\": \"My Reddit Account\",\n    \"identifier\": \"reddit\",\n    \"provider\": \"reddit\"\n  },\n  {\n    \"id\": \"youtube-def456\",\n    \"name\": \"My YouTube Channel\",\n    \"identifier\": \"youtube\",\n    \"provider\": \"youtube\"\n  },\n  {\n    \"id\": \"twitter-ghi789\",\n    \"name\": \"@myhandle\",\n    \"identifier\": \"x\",\n    \"provider\": \"x\"\n  }\n]\n```\n\n### 2. Get Settings for Specific Integration\n\n```bash\npostiz integrations:settings reddit-abc123\n```\n\nOutput:\n```json\n{\n  \"output\": {\n    \"maxLength\": 40000,\n    \"settings\": {\n      \"properties\": {\n        \"subreddit\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"properties\": {\n              \"value\": {\n                \"properties\": {\n                  \"subreddit\": {\n                    \"type\": \"string\",\n                    \"minLength\": 2,\n                    \"description\": \"Subreddit name\"\n                  },\n                  \"title\": {\n                    \"type\": \"string\",\n                    \"minLength\": 2,\n                    \"description\": \"Post title\"\n                  },\n                  \"type\": {\n                    \"type\": \"string\",\n                    \"description\": \"Post type (text or link)\"\n                  },\n                  \"url\": {\n                    \"type\": \"string\",\n                    \"description\": \"URL for link posts\"\n                  },\n                  \"is_flair_required\": {\n                    \"type\": \"boolean\",\n                    \"description\": \"Whether flair is required\"\n                  },\n                  \"flair\": {\n                    \"properties\": {\n                      \"id\": \"string\",\n                      \"name\": \"string\"\n                    }\n                  }\n                },\n                \"required\": [\"subreddit\", \"title\", \"type\", \"is_flair_required\"]\n              }\n            }\n          }\n        }\n      },\n      \"required\": [\"subreddit\"]\n    }\n  }\n}\n```\n\n### 3. Use the Settings in Your Post\n\nNow you know what settings are available and required!\n\n```bash\npostiz posts:create \\\n  -c \"My post content\" \\\n  -p reddit \\\n  --settings '{\n    \"subreddit\": [{\n      \"value\": {\n        \"subreddit\": \"programming\",\n        \"title\": \"Check this out!\",\n        \"type\": \"text\",\n        \"url\": \"\",\n        \"is_flair_required\": false\n      }\n    }]\n  }' \\\n  -i \"reddit-abc123\"\n```\n\n## Examples by Platform\n\n### Reddit\n\n```bash\npostiz integrations:settings reddit-abc123\n```\n\nReturns:\n- Max length: 40,000 characters\n- Required settings: subreddit, title, type\n- Optional: flair\n\n### YouTube\n\n```bash\npostiz integrations:settings youtube-def456\n```\n\nReturns:\n- Max length: 5,000 characters (description)\n- Required settings: title, type (public/private/unlisted)\n- Optional: tags, thumbnail, selfDeclaredMadeForKids\n\n### X (Twitter)\n\n```bash\npostiz integrations:settings twitter-ghi789\n```\n\nReturns:\n- Max length: 280 characters (or 4,000 for verified)\n- Required settings: who_can_reply_post\n- Optional: community\n\n### LinkedIn\n\n```bash\npostiz integrations:settings linkedin-jkl012\n```\n\nReturns:\n- Max length: 3,000 characters\n- Optional settings: post_as_images_carousel, carousel_name\n\n### TikTok\n\n```bash\npostiz integrations:settings tiktok-mno345\n```\n\nReturns:\n- Max length: 150 characters (caption)\n- Required settings: privacy_level, duet, stitch, comment, autoAddMusic, brand_content_toggle, brand_organic_toggle, content_posting_method\n- Optional: title, video_made_with_ai\n\n### Instagram\n\n```bash\npostiz integrations:settings instagram-pqr678\n```\n\nReturns:\n- Max length: 2,200 characters\n- Required settings: post_type (post or story)\n- Optional: is_trial_reel, graduation_strategy, collaborators\n\n## No Additional Settings Required\n\nSome platforms don't require specific settings:\n\n```bash\npostiz integrations:settings threads-stu901\n```\n\nReturns:\n```json\n{\n  \"output\": {\n    \"maxLength\": 500,\n    \"settings\": \"No additional settings required\"\n  }\n}\n```\n\nPlatforms with no additional settings:\n- Threads\n- Mastodon\n- Bluesky\n- Telegram\n- Nostr\n- VK\n\n## Use Cases\n\n### 1. Discovery\n\nFind out what settings are available before posting:\n\n```bash\n# What settings does YouTube support?\npostiz integrations:settings youtube-123\n\n# What settings does Reddit support?\npostiz integrations:settings reddit-456\n```\n\n### 2. Validation\n\nCheck maximum character limits:\n\n```bash\npostiz integrations:settings twitter-789 | jq '.output.maxLength'\n# Output: 280\n```\n\n### 3. AI Agent Integration\n\nAI agents can call this endpoint to:\n- Discover available settings dynamically\n- Validate settings before posting\n- Adapt to platform-specific requirements\n\n```javascript\n// Get settings schema\nconst settings = await execSync(\n  `postiz integrations:settings ${integrationId}`,\n  { encoding: 'utf-8' }\n);\nconst schema = JSON.parse(settings);\n\n// Check max length\nif (content.length > schema.output.maxLength) {\n  content = content.substring(0, schema.output.maxLength);\n}\n\n// Use required settings\nconst requiredSettings = schema.output.settings.required || [];\n```\n\n### 4. Form Generation\n\nUse the schema to generate UI forms:\n\n```javascript\nconst settings = await getIntegrationSettings('reddit-123');\nconst schema = settings.output.settings;\n\n// Generate form fields from schema\nschema.properties.subreddit.items.properties.value.properties\n// → subreddit (text, minLength: 2)\n// → title (text, minLength: 2)\n// → type (select: text/link)\n// → etc.\n```\n\n## Combined Workflow\n\nComplete workflow for posting with correct settings:\n\n```bash\n#!/bin/bash\nexport POSTIZ_API_KEY=your_key\n\n# 1. List integrations\necho \"📋 Available integrations:\"\npostiz integrations:list\n\n# 2. Get settings for Reddit\necho \"\"\necho \"⚙️  Reddit settings:\"\nSETTINGS=$(postiz integrations:settings reddit-123)\necho $SETTINGS | jq '.output.maxLength'\necho $SETTINGS | jq '.output.settings'\n\n# 3. Create post with correct settings\necho \"\"\necho \"📝 Creating post...\"\npostiz posts:create \\\n  -c \"My post content\" \\\n  -p reddit \\\n  --settings '{\n    \"subreddit\": [{\n      \"value\": {\n        \"subreddit\": \"programming\",\n        \"title\": \"Interesting post\",\n        \"type\": \"text\",\n        \"url\": \"\",\n        \"is_flair_required\": false\n      }\n    }]\n  }' \\\n  -i \"reddit-123\"\n```\n\n## API Endpoint\n\nThe command calls:\n```\nGET /public/v1/integration-settings/:id\n```\n\nReturns:\n```typescript\n{\n  output: {\n    maxLength: number;\n    settings: ValidationSchema | \"No additional settings required\";\n  }\n}\n```\n\n## Error Handling\n\n### Integration Not Found\n\n```bash\npostiz integrations:settings invalid-id\n# ❌ Failed to get integration settings: Integration not found\n```\n\n### API Key Not Set\n\n```bash\npostiz integrations:settings reddit-123\n# ❌ Error: POSTIZ_API_KEY environment variable is required\n```\n\n## Tips\n\n1. **Always check settings first** before creating posts with custom settings\n2. **Use the schema** to validate your settings object\n3. **Check maxLength** to avoid exceeding character limits\n4. **For AI agents**: Cache the settings to avoid repeated API calls\n5. **Required fields** must be included in your settings object\n\n## Comparison: Before vs After\n\n### Before ❌\n\n```bash\n# Had to guess what settings are available\n# Had to read documentation or source code\n# Didn't know character limits\n```\n\n### After ✅\n\n```bash\n# Discover settings programmatically\npostiz integrations:settings reddit-123\n\n# See exactly what's required and optional\n# Know the exact character limits\n# Get validation schemas\n```\n\n## Summary\n\n✅ **Discover settings for any integration**\n✅ **Get character limits**\n✅ **See validation schemas**\n✅ **Know required vs optional fields**\n✅ **Perfect for AI agents**\n✅ **No more guesswork!**\n\n**Now you can discover what settings each platform supports!** 🎉\n"
  },
  {
    "path": "apps/cli/INTEGRATION_TOOLS_WORKFLOW.md",
    "content": "# Integration Tools Workflow\n\nSome integrations require additional data (like IDs, tags, playlists, etc.) before you can post. The CLI supports a complete workflow to discover and use these tools.\n\n## The Complete Workflow\n\n### Step 1: List Integrations\n\n```bash\npostiz integrations:list\n```\n\nGet your integration IDs.\n\n### Step 2: Get Integration Settings\n\n```bash\npostiz integrations:settings <integration-id>\n```\n\nThis returns:\n- `maxLength` - Character limit\n- `settings` - Required/optional fields\n- **`tools`** - Callable methods to fetch additional data\n\n### Step 3: Trigger Tools (If Needed)\n\nIf settings require IDs/data you don't have, use the tools:\n\n```bash\npostiz integrations:trigger <integration-id> <method-name> -d '{\"key\":\"value\"}'\n```\n\n### Step 4: Create Post with Complete Settings\n\nUse the data from Step 3 in your post settings.\n\n## Real-World Example: Reddit\n\n### 1. Get Reddit Integration Settings\n\n```bash\npostiz integrations:settings reddit-abc123\n```\n\n**Output:**\n```json\n{\n  \"output\": {\n    \"maxLength\": 40000,\n    \"settings\": {\n      \"properties\": {\n        \"subreddit\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"properties\": {\n              \"subreddit\": { \"type\": \"string\" },\n              \"title\": { \"type\": \"string\" },\n              \"flair\": {\n                \"properties\": {\n                  \"id\": { \"type\": \"string\" }  // ← Need flair ID!\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"tools\": [\n      {\n        \"methodName\": \"getFlairs\",\n        \"description\": \"Get available flairs for a subreddit\",\n        \"dataSchema\": [\n          {\n            \"key\": \"subreddit\",\n            \"description\": \"The subreddit name\",\n            \"type\": \"string\"\n          }\n        ]\n      },\n      {\n        \"methodName\": \"searchSubreddits\",\n        \"description\": \"Search for subreddits\",\n        \"dataSchema\": [\n          {\n            \"key\": \"query\",\n            \"description\": \"Search query\",\n            \"type\": \"string\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n### 2. Get Flairs for the Subreddit\n\n```bash\npostiz integrations:trigger reddit-abc123 getFlairs -d '{\"subreddit\":\"programming\"}'\n```\n\n**Output:**\n```json\n{\n  \"output\": [\n    {\n      \"id\": \"flair-12345\",\n      \"name\": \"Discussion\"\n    },\n    {\n      \"id\": \"flair-67890\",\n      \"name\": \"Tutorial\"\n    }\n  ]\n}\n```\n\n### 3. Create Post with Flair ID\n\n```bash\npostiz posts:create \\\n  -c \"Check out my project!\" \\\n  -p reddit \\\n  --settings '{\n    \"subreddit\": [{\n      \"value\": {\n        \"subreddit\": \"programming\",\n        \"title\": \"My Cool Project\",\n        \"type\": \"text\",\n        \"url\": \"\",\n        \"is_flair_required\": true,\n        \"flair\": {\n          \"id\": \"flair-12345\",\n          \"name\": \"Discussion\"\n        }\n      }\n    }]\n  }' \\\n  -i \"reddit-abc123\"\n```\n\n## Example: YouTube Playlists\n\n### 1. Get YouTube Settings\n\n```bash\npostiz integrations:settings youtube-123\n```\n\n**Output includes tools:**\n```json\n{\n  \"tools\": [\n    {\n      \"methodName\": \"getPlaylists\",\n      \"description\": \"Get your YouTube playlists\",\n      \"dataSchema\": []\n    },\n    {\n      \"methodName\": \"getCategories\",\n      \"description\": \"Get available video categories\",\n      \"dataSchema\": []\n    }\n  ]\n}\n```\n\n### 2. Get Playlists\n\n```bash\npostiz integrations:trigger youtube-123 getPlaylists\n```\n\n**Output:**\n```json\n{\n  \"output\": [\n    {\n      \"id\": \"PLxxxxxx\",\n      \"title\": \"My Tutorials\"\n    },\n    {\n      \"id\": \"PLyyyyyy\",\n      \"title\": \"Product Demos\"\n    }\n  ]\n}\n```\n\n### 3. Post to Specific Playlist\n\n```bash\npostiz posts:create \\\n  -c \"Video description\" \\\n  -p youtube \\\n  --settings '{\n    \"title\": \"My Video\",\n    \"type\": \"public\",\n    \"playlistId\": \"PLxxxxxx\"\n  }' \\\n  -i \"youtube-123\"\n```\n\n## Example: LinkedIn Companies\n\n### 1. Get LinkedIn Settings\n\n```bash\npostiz integrations:settings linkedin-123\n```\n\n**Output includes tools:**\n```json\n{\n  \"tools\": [\n    {\n      \"methodName\": \"getCompanies\",\n      \"description\": \"Get companies you can post to\",\n      \"dataSchema\": []\n    }\n  ]\n}\n```\n\n### 2. Get Companies\n\n```bash\npostiz integrations:trigger linkedin-123 getCompanies\n```\n\n**Output:**\n```json\n{\n  \"output\": [\n    {\n      \"id\": \"company-123\",\n      \"name\": \"My Company\"\n    },\n    {\n      \"id\": \"company-456\",\n      \"name\": \"Other Company\"\n    }\n  ]\n}\n```\n\n### 3. Post as Company\n\n```bash\npostiz posts:create \\\n  -c \"Company announcement\" \\\n  -p linkedin \\\n  --settings '{\n    \"companyId\": \"company-123\"\n  }' \\\n  -i \"linkedin-123\"\n```\n\n## Understanding Tools\n\n### Tool Structure\n\n```json\n{\n  \"methodName\": \"getFlairs\",\n  \"description\": \"Get available flairs for a subreddit\",\n  \"dataSchema\": [\n    {\n      \"key\": \"subreddit\",\n      \"description\": \"The subreddit name\",\n      \"type\": \"string\"\n    }\n  ]\n}\n```\n\n- **methodName** - Use this in `integrations:trigger`\n- **description** - What the tool does\n- **dataSchema** - Required input parameters\n\n### Calling Tools\n\n```bash\n# No parameters\npostiz integrations:trigger <integration-id> <methodName>\n\n# With parameters\npostiz integrations:trigger <integration-id> <methodName> -d '{\"key\":\"value\"}'\n```\n\n## Common Tool Methods\n\n### Reddit\n- `getFlairs` - Get flairs for a subreddit\n- `searchSubreddits` - Search for subreddits\n- `getSubreddits` - Get subscribed subreddits\n\n### YouTube\n- `getPlaylists` - Get your playlists\n- `getCategories` - Get video categories\n- `getChannels` - Get your channels\n\n### LinkedIn\n- `getCompanies` - Get companies you manage\n- `getOrganizations` - Get organizations\n\n### Twitter/X\n- `getListsowned` - Get your Twitter lists\n- `getCommunities` - Get communities you're in\n\n### Pinterest\n- `getBoards` - Get your Pinterest boards\n- `getBoardSections` - Get sections in a board\n\n## AI Agent Workflow\n\nFor AI agents, this enables dynamic discovery and usage:\n\n```javascript\n// 1. Get settings and tools\nconst settings = JSON.parse(\n  execSync(`postiz integrations:settings ${integrationId}`)\n);\n\n// 2. Check if tools are needed\nconst tools = settings.output.tools || [];\n\n// 3. Call tools to get required data\nfor (const tool of tools) {\n  if (needsThisTool(tool)) {\n    const data = buildDataForTool(tool.dataSchema);\n    const result = JSON.parse(\n      execSync(\n        `postiz integrations:trigger ${integrationId} ${tool.methodName} -d '${JSON.stringify(data)}'`\n      )\n    );\n\n    // Use result.output in your settings\n    updateSettings(result.output);\n  }\n}\n\n// 4. Create post with complete settings\nexecSync(`postiz posts:create -c \"${content}\" --settings '${JSON.stringify(settings)}' -i \"${integrationId}\"`);\n```\n\n## Error Handling\n\n### Tool Not Found\n\n```bash\npostiz integrations:trigger reddit-123 invalidMethod\n# ❌ Failed to trigger tool: Tool not found\n```\n\n### Missing Required Data\n\n```bash\npostiz integrations:trigger reddit-123 getFlairs\n# ❌ Missing required parameter: subreddit\n```\n\n### Integration Not Found\n\n```bash\npostiz integrations:trigger invalid-id getFlairs\n# ❌ Failed to trigger tool: Integration not found\n```\n\n## Tips\n\n1. **Always check tools first** - Run `integrations:settings` to see available tools\n2. **Read dataSchema** - Know what parameters each tool needs\n3. **Parse JSON output** - Use `jq` or similar to extract data\n4. **Cache results** - Tool results don't change often\n5. **For AI agents** - Automate the entire workflow\n\n## Complete Example Script\n\n```bash\n#!/bin/bash\nexport POSTIZ_API_KEY=your_key\nINTEGRATION_ID=\"reddit-abc123\"\n\n# 1. Get settings\necho \"📋 Getting settings...\"\nSETTINGS=$(postiz integrations:settings $INTEGRATION_ID)\necho $SETTINGS | jq '.output.tools'\n\n# 2. Get flairs\necho \"\"\necho \"🏷️  Getting flairs...\"\nFLAIRS=$(postiz integrations:trigger $INTEGRATION_ID getFlairs -d '{\"subreddit\":\"programming\"}')\nFLAIR_ID=$(echo $FLAIRS | jq -r '.output[0].id')\nFLAIR_NAME=$(echo $FLAIRS | jq -r '.output[0].name')\n\necho \"Selected flair: $FLAIR_NAME ($FLAIR_ID)\"\n\n# 3. Create post\necho \"\"\necho \"📝 Creating post...\"\npostiz posts:create \\\n  -c \"My post content\" \\\n  -p reddit \\\n  --settings \"{\n    \\\"subreddit\\\": [{\n      \\\"value\\\": {\n        \\\"subreddit\\\": \\\"programming\\\",\n        \\\"title\\\": \\\"My Post Title\\\",\n        \\\"type\\\": \\\"text\\\",\n        \\\"url\\\": \\\"\\\",\n        \\\"is_flair_required\\\": true,\n        \\\"flair\\\": {\n          \\\"id\\\": \\\"$FLAIR_ID\\\",\n          \\\"name\\\": \\\"$FLAIR_NAME\\\"\n        }\n      }\n    }]\n  }\" \\\n  -i \"$INTEGRATION_ID\"\n\necho \"✅ Done!\"\n```\n\n## Summary\n\n✅ **Discover available tools** with `integrations:settings`\n✅ **Call tools** to fetch required data with `integrations:trigger`\n✅ **Use tool results** in post settings\n✅ **Complete workflow** from discovery to posting\n✅ **Perfect for AI agents** - fully automated\n✅ **No guesswork** - know exactly what data you need\n\n**The CLI now supports the complete integration tools workflow!** 🎉\n"
  },
  {
    "path": "apps/cli/PROJECT_STRUCTURE.md",
    "content": "# Postiz CLI - Project Structure\n\n## Overview\n\nThe Postiz CLI is a complete command-line interface package for interacting with the Postiz social media scheduling API. It's designed for developers and AI agents to automate social media posting.\n\n## Directory Structure\n\n```\napps/cli/\n├── src/                          # Source code\n│   ├── index.ts                  # Main CLI entry point\n│   ├── api.ts                    # API client for Postiz API\n│   ├── config.ts                 # Configuration and environment handling\n│   └── commands/                 # Command implementations\n│       ├── posts.ts              # Posts management commands\n│       ├── integrations.ts       # Integrations listing\n│       └── upload.ts             # Media upload command\n│\n├── examples/                     # Usage examples\n│   ├── basic-usage.sh            # Shell script example\n│   └── ai-agent-example.js       # Node.js AI agent example\n│\n├── dist/                         # Build output (generated)\n│   ├── index.js                  # Compiled CLI executable\n│   └── index.js.map              # Source map\n│\n├── package.json                  # Package configuration\n├── tsconfig.json                 # TypeScript configuration\n├── tsup.config.ts                # Build configuration\n│\n├── README.md                     # Main documentation\n├── SKILL.md                      # AI agent usage guide\n├── QUICK_START.md                # Quick start guide\n├── CHANGELOG.md                  # Version history\n├── PROJECT_STRUCTURE.md          # This file\n│\n├── .gitignore                    # Git ignore rules\n└── .npmignore                    # npm publish ignore rules\n```\n\n## File Descriptions\n\n### Source Files\n\n#### `src/index.ts`\n- Main entry point for the CLI\n- Uses `yargs` for command parsing\n- Defines all available commands and their options\n- Contains help text and usage examples\n\n#### `src/api.ts`\n- API client class `PostizAPI`\n- Handles all HTTP requests to the Postiz API\n- Methods for:\n  - Creating posts\n  - Listing posts\n  - Deleting posts\n  - Uploading files\n  - Listing integrations\n- Error handling and response parsing\n\n#### `src/config.ts`\n- Configuration management\n- Environment variable handling\n- Validates required settings (API key)\n- Provides default values\n\n#### `src/commands/posts.ts`\n- Post management commands implementation\n- `createPost()` - Create new social media posts\n- `listPosts()` - List posts with filters\n- `deletePost()` - Delete posts by ID\n\n#### `src/commands/integrations.ts`\n- Integration management\n- `listIntegrations()` - Show connected accounts\n\n#### `src/commands/upload.ts`\n- Media upload functionality\n- `uploadFile()` - Upload images to Postiz\n\n### Configuration Files\n\n#### `package.json`\n- Package name: `postiz`\n- Version: `1.0.0`\n- Executable bin: `postiz` → `dist/index.js`\n- Scripts: `dev`, `build`, `start`, `publish`\n- Repository and metadata information\n\n#### `tsconfig.json`\n- Extends base config from monorepo\n- Target: ES2017\n- Module: CommonJS\n- Enables decorators and source maps\n\n#### `tsup.config.ts`\n- Build tool configuration\n- Entry point: `src/index.ts`\n- Output format: CommonJS\n- Adds shebang for Node.js execution\n- Generates source maps\n\n### Documentation Files\n\n#### `README.md`\n- Main package documentation\n- Installation instructions\n- Usage examples\n- API reference\n- Development guide\n\n#### `SKILL.md`\n- Comprehensive guide for AI agents\n- Usage patterns and workflows\n- Command examples\n- Best practices\n- Error handling\n\n#### `QUICK_START.md`\n- Fast onboarding guide\n- Installation steps\n- Basic commands\n- Common workflows\n- Troubleshooting\n\n#### `CHANGELOG.md`\n- Version history\n- Release notes\n- Feature additions\n- Bug fixes\n\n### Example Files\n\n#### `examples/basic-usage.sh`\n- Bash script example\n- Demonstrates basic CLI workflow\n- Shows integration listing, post creation, and deletion\n\n#### `examples/ai-agent-example.js`\n- Node.js script for AI agents\n- Programmatic CLI usage\n- Batch post creation\n- JSON parsing examples\n\n## Build Process\n\n### Development Build\n\n```bash\npnpm run dev\n```\n\n- Watches for file changes\n- Rebuilds automatically\n- Useful during development\n\n### Production Build\n\n```bash\npnpm run build\n```\n\n1. Cleans `dist/` directory\n2. Compiles TypeScript → JavaScript\n3. Bundles dependencies\n4. Adds shebang for executable\n5. Generates source maps\n6. Makes output executable\n\n### Output\n\n- `dist/index.js` - Main executable (~490KB)\n- `dist/index.js.map` - Source map (~920KB)\n\n## Commands Architecture\n\n### Command Flow\n\n```\nUser Input\n    ↓\nindex.ts (yargs parser)\n    ↓\nCommand Handler (posts.ts, integrations.ts, upload.ts)\n    ↓\nconfig.ts (get API key)\n    ↓\napi.ts (make API request)\n    ↓\nResponse / Error\n    ↓\nOutput to console\n```\n\n### Available Commands\n\n1. **posts:create**\n   - Options: `--content`, `--integrations`, `--schedule`, `--image`\n   - Handler: `commands/posts.ts::createPost()`\n\n2. **posts:list**\n   - Options: `--page`, `--limit`, `--search`\n   - Handler: `commands/posts.ts::listPosts()`\n\n3. **posts:delete**\n   - Positional: `<id>`\n   - Handler: `commands/posts.ts::deletePost()`\n\n4. **integrations:list**\n   - No options\n   - Handler: `commands/integrations.ts::listIntegrations()`\n\n5. **upload**\n   - Positional: `<file>`\n   - Handler: `commands/upload.ts::uploadFile()`\n\n## Environment Variables\n\n| Variable | Required | Default | Usage |\n|----------|----------|---------|-------|\n| `POSTIZ_API_KEY` | ✅ Yes | - | Authentication token |\n| `POSTIZ_API_URL` | ❌ No | `https://api.postiz.com` | Custom API endpoint |\n\n## Dependencies\n\n### Runtime Dependencies (from root)\n- `yargs` - CLI argument parsing\n- `node-fetch` - HTTP requests\n- Standard Node.js modules (`fs`, `path`)\n\n### Dev Dependencies\n- `tsup` - TypeScript bundler\n- `typescript` - Type checking\n- `@types/yargs` - TypeScript types\n\n## Integration Points\n\n### With Monorepo\n\n1. **Build Scripts**\n   - Added to root `package.json`\n   - `pnpm run build:cli` - Build the CLI\n   - `pnpm run publish-cli` - Publish to npm\n\n2. **TypeScript Config**\n   - Extends `tsconfig.base.json`\n   - Shares common compiler options\n\n3. **Dependencies**\n   - Uses shared dependencies from root\n   - No duplicate packages\n\n### With Postiz API\n\n1. **Endpoints Used**\n   - `POST /public/v1/posts` - Create post\n   - `GET /public/v1/posts` - List posts\n   - `DELETE /public/v1/posts/:id` - Delete post\n   - `GET /public/v1/integrations` - List integrations\n   - `POST /public/v1/upload` - Upload media\n\n2. **Authentication**\n   - API key via `Authorization` header\n   - Configured through environment variable\n\n## Publishing\n\n### To npm\n\n```bash\npnpm run publish-cli\n```\n\nThis will:\n1. Build the package\n2. Publish to npm with public access\n3. Include only `dist/`, `README.md`, and `SKILL.md`\n\n### Package Contents (via .npmignore)\n\n**Included:**\n- `dist/` - Compiled code\n- `README.md` - Documentation\n\n**Excluded:**\n- `src/` - Source code\n- `examples/` - Examples\n- Config files\n- Other markdown files\n\n## Testing\n\n### Manual Testing\n\n```bash\n# Test help\nnode dist/index.js --help\n\n# Test without API key (should error)\nnode dist/index.js posts:list\n\n# Test with API key (requires valid key)\nPOSTIZ_API_KEY=test node dist/index.js integrations:list\n```\n\n### Automated Testing (Future)\n\n- Unit tests for API client\n- Integration tests for commands\n- E2E tests with mock API\n\n## Future Enhancements\n\n1. **More Commands**\n   - Analytics retrieval\n   - Team management\n   - Settings configuration\n\n2. **Features**\n   - Interactive mode\n   - Config file support (~/.postizrc)\n   - Output formatting (JSON, table, CSV)\n   - Verbose/debug mode\n   - Batch operations from file\n\n3. **Developer Experience**\n   - TypeScript types export\n   - Programmatic API\n   - Plugin system\n   - Custom integrations\n\n## Support\n\n- **Issues:** https://github.com/gitroomhq/postiz-app/issues\n- **Docs:** See README.md, SKILL.md, QUICK_START.md\n- **Website:** https://postiz.com\n"
  },
  {
    "path": "apps/cli/PROVIDER_SETTINGS.md",
    "content": "# Provider-Specific Settings\n\nThe Postiz CLI supports platform-specific settings for each integration. Different platforms have different options and requirements.\n\n## How to Use Provider Settings\n\n### Method 1: Command Line Flags\n\n```bash\npostiz posts:create \\\n  -c \"Your content\" \\\n  -p <provider-type> \\\n  --settings '<json-settings>' \\\n  -i \"integration-id\"\n```\n\n### Method 2: JSON File\n\n```bash\npostiz posts:create --json post-with-settings.json\n```\n\nIn the JSON file, specify settings per integration:\n\n```json\n{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [{\n    \"integration\": { \"id\": \"reddit-123\" },\n    \"value\": [{ \"content\": \"Post content\", \"image\": [] }],\n    \"settings\": {\n      \"__type\": \"reddit\",\n      \"subreddit\": [{\n        \"value\": {\n          \"subreddit\": \"programming\",\n          \"title\": \"My Post Title\",\n          \"type\": \"text\",\n          \"url\": \"\",\n          \"is_flair_required\": false\n        }\n      }]\n    }\n  }]\n}\n```\n\n## Supported Platforms & Settings\n\n### Reddit (`reddit`)\n\n**Settings:**\n- `subreddit` (required): Subreddit name\n- `title` (required): Post title\n- `type` (required): `\"text\"` or `\"link\"`\n- `url` (required for links): URL if type is \"link\"\n- `is_flair_required` (boolean): Whether flair is required\n- `flair` (optional): Flair object with `id` and `name`\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"Post content here\" \\\n  -p reddit \\\n  --settings '{\n    \"subreddit\": [{\n      \"value\": {\n        \"subreddit\": \"programming\",\n        \"title\": \"Check out this cool project\",\n        \"type\": \"text\",\n        \"url\": \"\",\n        \"is_flair_required\": false\n      }\n    }]\n  }' \\\n  -i \"reddit-123\"\n```\n\n### YouTube (`youtube`)\n\n**Settings:**\n- `title` (required): Video title (2-100 characters)\n- `type` (required): `\"public\"`, `\"private\"`, or `\"unlisted\"`\n- `selfDeclaredMadeForKids` (optional): `\"yes\"` or `\"no\"`\n- `thumbnail` (optional): Thumbnail MediaDto object\n- `tags` (optional): Array of tag objects with `value` and `label`\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"Video description here\" \\\n  -p youtube \\\n  --settings '{\n    \"title\": \"My Awesome Video\",\n    \"type\": \"public\",\n    \"selfDeclaredMadeForKids\": \"no\",\n    \"tags\": [\n      {\"value\": \"tech\", \"label\": \"Tech\"},\n      {\"value\": \"tutorial\", \"label\": \"Tutorial\"}\n    ]\n  }' \\\n  -i \"youtube-123\"\n```\n\n### X / Twitter (`x`)\n\n**Settings:**\n- `community` (optional): X community URL (format: `https://x.com/i/communities/1234567890`)\n- `who_can_reply_post` (required): Who can reply\n  - `\"everyone\"` - Anyone can reply\n  - `\"following\"` - Only people you follow\n  - `\"mentionedUsers\"` - Only mentioned users\n  - `\"subscribers\"` - Only subscribers\n  - `\"verified\"` - Only verified users\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"Tweet content\" \\\n  -p x \\\n  --settings '{\n    \"who_can_reply_post\": \"everyone\"\n  }' \\\n  -i \"twitter-123\"\n```\n\n**With Community:**\n```bash\npostiz posts:create \\\n  -c \"Community tweet\" \\\n  -p x \\\n  --settings '{\n    \"community\": \"https://x.com/i/communities/1493446837214187523\",\n    \"who_can_reply_post\": \"everyone\"\n  }' \\\n  -i \"twitter-123\"\n```\n\n### LinkedIn (`linkedin`)\n\n**Settings:**\n- `post_as_images_carousel` (boolean): Post as image carousel\n- `carousel_name` (optional): Carousel name if posting as carousel\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"LinkedIn post\" \\\n  -m \"img1.jpg,img2.jpg,img3.jpg\" \\\n  -p linkedin \\\n  --settings '{\n    \"post_as_images_carousel\": true,\n    \"carousel_name\": \"Product Showcase\"\n  }' \\\n  -i \"linkedin-123\"\n```\n\n### Instagram (`instagram`)\n\n**Settings:**\n- `post_type` (required): `\"post\"` or `\"story\"`\n- `is_trial_reel` (optional): Boolean\n- `graduation_strategy` (optional): `\"MANUAL\"` or `\"SS_PERFORMANCE\"`\n- `collaborators` (optional): Array of collaborator objects with `label`\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"Instagram post\" \\\n  -m \"photo.jpg\" \\\n  -p instagram \\\n  --settings '{\n    \"post_type\": \"post\",\n    \"is_trial_reel\": false\n  }' \\\n  -i \"instagram-123\"\n```\n\n**Story Example:**\n```bash\npostiz posts:create \\\n  -c \"Story content\" \\\n  -m \"story-image.jpg\" \\\n  -p instagram \\\n  --settings '{\n    \"post_type\": \"story\"\n  }' \\\n  -i \"instagram-123\"\n```\n\n### TikTok (`tiktok`)\n\n**Settings:**\n- `title` (optional): Video title (max 90 characters)\n- `privacy_level` (required): Privacy level\n  - `\"PUBLIC_TO_EVERYONE\"`\n  - `\"MUTUAL_FOLLOW_FRIENDS\"`\n  - `\"FOLLOWER_OF_CREATOR\"`\n  - `\"SELF_ONLY\"`\n- `duet` (boolean): Allow duets\n- `stitch` (boolean): Allow stitch\n- `comment` (boolean): Allow comments\n- `autoAddMusic` (required): `\"yes\"` or `\"no\"`\n- `brand_content_toggle` (boolean): Brand content toggle\n- `brand_organic_toggle` (boolean): Brand organic toggle\n- `video_made_with_ai` (optional): Boolean\n- `content_posting_method` (required): `\"DIRECT_POST\"` or `\"UPLOAD\"`\n\n**Example:**\n```bash\npostiz posts:create \\\n  -c \"TikTok video description\" \\\n  -m \"video.mp4\" \\\n  -p tiktok \\\n  --settings '{\n    \"title\": \"Check this out!\",\n    \"privacy_level\": \"PUBLIC_TO_EVERYONE\",\n    \"duet\": true,\n    \"stitch\": true,\n    \"comment\": true,\n    \"autoAddMusic\": \"no\",\n    \"brand_content_toggle\": false,\n    \"brand_organic_toggle\": false,\n    \"content_posting_method\": \"DIRECT_POST\"\n  }' \\\n  -i \"tiktok-123\"\n```\n\n### Facebook (`facebook`)\n\nSettings available - check the DTO for specifics.\n\n### Pinterest (`pinterest`)\n\nSettings available - check the DTO for specifics.\n\n### Discord (`discord`)\n\nSettings available - check the DTO for specifics.\n\n### Slack (`slack`)\n\nSettings available - check the DTO for specifics.\n\n### Medium (`medium`)\n\nSettings available - check the DTO for specifics.\n\n### Dev.to (`devto`)\n\nSettings available - check the DTO for specifics.\n\n### Hashnode (`hashnode`)\n\nSettings available - check the DTO for specifics.\n\n### WordPress (`wordpress`)\n\nSettings available - check the DTO for specifics.\n\n## Platforms Without Specific Settings\n\nThese platforms use the default `EmptySettings`:\n- `threads`\n- `mastodon`\n- `bluesky`\n- `telegram`\n- `nostr`\n- `vk`\n\nFor these, you don't need to specify settings or can use:\n```bash\n-p threads  # or any of the above\n```\n\n## Using JSON Files for Complex Settings\n\nFor complex settings, it's easier to use JSON files:\n\n### Reddit Example\n\n**reddit-post.json:**\n```json\n{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [{\n    \"integration\": { \"id\": \"reddit-123\" },\n    \"value\": [{\n      \"content\": \"Check out this cool project!\",\n      \"image\": []\n    }],\n    \"settings\": {\n      \"__type\": \"reddit\",\n      \"subreddit\": [{\n        \"value\": {\n          \"subreddit\": \"programming\",\n          \"title\": \"My Cool Project - Built with TypeScript\",\n          \"type\": \"text\",\n          \"url\": \"\",\n          \"is_flair_required\": true,\n          \"flair\": {\n            \"id\": \"flair-123\",\n            \"name\": \"Project\"\n          }\n        }\n      }]\n    }\n  }]\n}\n```\n\n```bash\npostiz posts:create --json reddit-post.json\n```\n\n### YouTube Example\n\n**youtube-video.json:**\n```json\n{\n  \"type\": \"schedule\",\n  \"date\": \"2024-12-25T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [{\n    \"integration\": { \"id\": \"youtube-123\" },\n    \"value\": [{\n      \"content\": \"Full video description with timestamps...\",\n      \"image\": [{\n        \"id\": \"thumb1\",\n        \"path\": \"https://cdn.example.com/thumbnail.jpg\"\n      }]\n    }],\n    \"settings\": {\n      \"__type\": \"youtube\",\n      \"title\": \"How to Build a CLI Tool\",\n      \"type\": \"public\",\n      \"selfDeclaredMadeForKids\": \"no\",\n      \"tags\": [\n        { \"value\": \"programming\", \"label\": \"Programming\" },\n        { \"value\": \"typescript\", \"label\": \"TypeScript\" },\n        { \"value\": \"tutorial\", \"label\": \"Tutorial\" }\n      ]\n    }\n  }]\n}\n```\n\n```bash\npostiz posts:create --json youtube-video.json\n```\n\n### Multi-Platform with Different Settings\n\n**multi-platform-campaign.json:**\n```json\n{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"reddit-123\" },\n      \"value\": [{ \"content\": \"Reddit-specific content\", \"image\": [] }],\n      \"settings\": {\n        \"__type\": \"reddit\",\n        \"subreddit\": [{\n          \"value\": {\n            \"subreddit\": \"programming\",\n            \"title\": \"Post Title\",\n            \"type\": \"text\",\n            \"url\": \"\",\n            \"is_flair_required\": false\n          }\n        }]\n      }\n    },\n    {\n      \"integration\": { \"id\": \"twitter-123\" },\n      \"value\": [{ \"content\": \"Twitter-specific content\", \"image\": [] }],\n      \"settings\": {\n        \"__type\": \"x\",\n        \"who_can_reply_post\": \"everyone\"\n      }\n    },\n    {\n      \"integration\": { \"id\": \"linkedin-123\" },\n      \"value\": [\n        {\n          \"content\": \"LinkedIn post\",\n          \"image\": [\n            { \"id\": \"1\", \"path\": \"img1.jpg\" },\n            { \"id\": \"2\", \"path\": \"img2.jpg\" }\n          ]\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"linkedin\",\n        \"post_as_images_carousel\": true,\n        \"carousel_name\": \"Product Launch\"\n      }\n    }\n  ]\n}\n```\n\n## Tips\n\n1. **Use JSON files for complex settings** - Command-line JSON strings get messy fast\n2. **Validate your settings** - The API will return errors if settings are invalid\n3. **Check required fields** - Each platform has different required fields\n4. **Platform-specific content** - Different platforms may need different content/media\n5. **Test with drafts first** - Use `\"type\": \"draft\"` to test without posting\n\n## Finding Your Provider Type\n\nTo find the correct provider type for your integration:\n\n```bash\npostiz integrations:list\n```\n\nThis will show the `provider` field for each integration, which corresponds to the `__type` in settings.\n\n## Common Errors\n\n### Missing __type\n\n```json\n{\n  \"settings\": {\n    \"title\": \"My Video\"  // ❌ Missing __type\n  }\n}\n```\n\n**Fix:**\n```json\n{\n  \"settings\": {\n    \"__type\": \"youtube\",  // ✅ Add __type\n    \"title\": \"My Video\"\n  }\n}\n```\n\n### Wrong Provider Type\n\n```bash\n# ❌ Wrong\n-p twitter  # Should be \"x\"\n\n# ✅ Correct\n-p x\n```\n\n### Invalid Settings for Platform\n\nEach platform validates its own settings. Check the error message and refer to the platform's required fields above.\n\n## See Also\n\n- **EXAMPLES.md** - General usage examples\n- **COMMAND_LINE_GUIDE.md** - Command-line syntax\n- **SKILL.md** - AI agent patterns\n- Source DTOs in `libraries/nestjs-libraries/src/dtos/posts/providers-settings/`\n"
  },
  {
    "path": "apps/cli/PROVIDER_SETTINGS_SUMMARY.md",
    "content": "# Provider-Specific Settings - Quick Reference\n\n## ✅ What's Supported\n\nThe CLI now supports **platform-specific settings** for all 28+ integrations!\n\n## Supported Platforms\n\n### Platforms with Specific Settings\n\n| Platform | Type | Key Settings |\n|----------|------|--------------|\n| **Reddit** | `reddit` | subreddit, title, type, url, flair |\n| **YouTube** | `youtube` | title, type (public/private/unlisted), tags, thumbnail |\n| **X (Twitter)** | `x` | who_can_reply_post, community |\n| **LinkedIn** | `linkedin` | post_as_images_carousel, carousel_name |\n| **Instagram** | `instagram` | post_type (post/story), collaborators |\n| **TikTok** | `tiktok` | title, privacy_level, duet, stitch, comment, autoAddMusic |\n| **Facebook** | `facebook` | Platform-specific settings |\n| **Pinterest** | `pinterest` | Platform-specific settings |\n| **Discord** | `discord` | Platform-specific settings |\n| **Slack** | `slack` | Platform-specific settings |\n| **Medium** | `medium` | Platform-specific settings |\n| **Dev.to** | `devto` | Platform-specific settings |\n| **Hashnode** | `hashnode` | Platform-specific settings |\n| **WordPress** | `wordpress` | Platform-specific settings |\n| And 15+ more... | | See PROVIDER_SETTINGS.md |\n\n### Platforms with Default Settings\n\nThese use `EmptySettings` (no special configuration needed):\n- Threads, Mastodon, Bluesky, Telegram, Nostr, VK\n\n## Usage\n\n### Method 1: Command Line\n\n```bash\npostiz posts:create \\\n  -c \"Content\" \\\n  -p <provider-type> \\\n  --settings '<json-settings>' \\\n  -i \"integration-id\"\n```\n\n### Method 2: JSON File\n\n```json\n{\n  \"posts\": [{\n    \"integration\": { \"id\": \"integration-id\" },\n    \"value\": [...],\n    \"settings\": {\n      \"__type\": \"provider-type\",\n      ...\n    }\n  }]\n}\n```\n\n## Quick Examples\n\n### Reddit Post\n\n```bash\npostiz posts:create \\\n  -c \"Check out this project!\" \\\n  -p reddit \\\n  --settings '{\n    \"subreddit\": [{\n      \"value\": {\n        \"subreddit\": \"programming\",\n        \"title\": \"My Cool Project\",\n        \"type\": \"text\",\n        \"url\": \"\",\n        \"is_flair_required\": false\n      }\n    }]\n  }' \\\n  -i \"reddit-123\"\n```\n\n### YouTube Video\n\n```bash\npostiz posts:create \\\n  -c \"Full video description...\" \\\n  -p youtube \\\n  --settings '{\n    \"title\": \"How to Build a CLI\",\n    \"type\": \"public\",\n    \"tags\": [\n      {\"value\": \"tech\", \"label\": \"Tech\"},\n      {\"value\": \"tutorial\", \"label\": \"Tutorial\"}\n    ]\n  }' \\\n  -i \"youtube-123\"\n```\n\n### Twitter/X with Reply Controls\n\n```bash\npostiz posts:create \\\n  -c \"Important announcement!\" \\\n  -p x \\\n  --settings '{\n    \"who_can_reply_post\": \"verified\"\n  }' \\\n  -i \"twitter-123\"\n```\n\n### LinkedIn Carousel\n\n```bash\npostiz posts:create \\\n  -c \"Product showcase\" \\\n  -m \"img1.jpg,img2.jpg,img3.jpg\" \\\n  -p linkedin \\\n  --settings '{\n    \"post_as_images_carousel\": true,\n    \"carousel_name\": \"Product Launch\"\n  }' \\\n  -i \"linkedin-123\"\n```\n\n### Instagram Story\n\n```bash\npostiz posts:create \\\n  -c \"Story content\" \\\n  -m \"story-image.jpg\" \\\n  -p instagram \\\n  --settings '{\n    \"post_type\": \"story\"\n  }' \\\n  -i \"instagram-123\"\n```\n\n### TikTok Video\n\n```bash\npostiz posts:create \\\n  -c \"TikTok description #fyp\" \\\n  -m \"video.mp4\" \\\n  -p tiktok \\\n  --settings '{\n    \"privacy_level\": \"PUBLIC_TO_EVERYONE\",\n    \"duet\": true,\n    \"stitch\": true,\n    \"comment\": true,\n    \"autoAddMusic\": \"no\",\n    \"brand_content_toggle\": false,\n    \"brand_organic_toggle\": false,\n    \"content_posting_method\": \"DIRECT_POST\"\n  }' \\\n  -i \"tiktok-123\"\n```\n\n## JSON File Examples\n\nWe've created example JSON files for you:\n\n- **`reddit-post.json`** - Reddit post with subreddit settings\n- **`youtube-video.json`** - YouTube video with title, tags, thumbnail\n- **`tiktok-video.json`** - TikTok video with full settings\n- **`multi-platform-with-settings.json`** - Multi-platform campaign with different settings per platform\n\n## Finding Provider Types\n\n```bash\npostiz integrations:list\n```\n\nLook at the `provider` field - this is your provider type!\n\n## Common Provider Types\n\n- `reddit` - Reddit\n- `youtube` - YouTube\n- `x` - X (Twitter)\n- `linkedin` or `linkedin-page` - LinkedIn\n- `instagram` or `instagram-standalone` - Instagram\n- `tiktok` - TikTok\n- `facebook` - Facebook\n- `pinterest` - Pinterest\n- `discord` - Discord\n- `slack` - Slack\n- `threads` - Threads (no specific settings)\n- `bluesky` - Bluesky (no specific settings)\n- `mastodon` - Mastodon (no specific settings)\n\n## Documentation\n\n📖 **[PROVIDER_SETTINGS.md](./PROVIDER_SETTINGS.md)** - Complete documentation with all platform settings\n\nIncludes:\n- All available settings for each platform\n- Required vs optional fields\n- Validation rules\n- More examples\n- Common errors and solutions\n\n## Tips\n\n1. **Use JSON files for complex settings** - Easier to manage than command-line strings\n2. **Different settings per platform** - Each platform in a multi-platform post can have different settings\n3. **Validate before posting** - Use `\"type\": \"draft\"` to test\n4. **Check examples** - See `examples/` directory for working templates\n5. **Provider type matters** - Make sure `__type` matches your integration's provider\n\n## Summary\n\n✅ **28+ platforms supported**\n✅ **Platform-specific settings for Reddit, YouTube, TikTok, X, LinkedIn, Instagram, and more**\n✅ **Easy command-line interface**\n✅ **JSON file support for complex configs**\n✅ **Full type validation**\n✅ **Comprehensive examples included**\n\n**The CLI now supports the full power of each platform!** 🚀\n"
  },
  {
    "path": "apps/cli/PUBLISHING.md",
    "content": "# Publishing the Postiz CLI to npm\n\n## Quick Publish (Current Name: \"postiz\")\n\n```bash\n# From apps/cli directory\npnpm run build\npnpm publish --access public\n```\n\nThen users can install:\n```bash\nnpm install -g postiz\n# or\npnpm install -g postiz\n\n# And use:\npostiz --help\n```\n\n## Publishing with a Different Package Name\n\nIf you want to publish as a different npm package name (e.g., \"agent-postiz\"):\n\n### 1. Change Package Name\n\nEdit `apps/cli/package.json`:\n\n```json\n{\n  \"name\": \"agent-postiz\",  // ← Changed package name\n  \"version\": \"1.0.0\",\n  \"bin\": {\n    \"postiz\": \"./dist/index.js\"  // ← Keep command name!\n  }\n}\n```\n\n**Important:** The `bin` field determines the command name, NOT the package name!\n\n### 2. Publish\n\n```bash\ncd apps/cli\npnpm run build\npnpm publish --access public\n```\n\n### 3. Users Install\n\n```bash\nnpm install -g agent-postiz\n# or\npnpm install -g agent-postiz\n```\n\n### 4. Users Use\n\nEven though the package is called \"agent-postiz\", the command is still:\n\n```bash\npostiz --help  # ← Command name from \"bin\" field\npostiz posts:create -c \"Hello!\" -i \"twitter-123\"\n```\n\n## Package Name vs Command Name\n\n| Field | Purpose | Example |\n|-------|---------|---------|\n| `\"name\"` | npm package name (what you install) | `\"agent-postiz\"` |\n| `\"bin\"` | Command name (what you type) | `\"postiz\"` |\n\n**Examples:**\n\n1. **Same name:**\n   ```json\n   \"name\": \"postiz\",\n   \"bin\": { \"postiz\": \"./dist/index.js\" }\n   ```\n   Install: `npm i -g postiz`\n   Use: `postiz`\n\n2. **Different names:**\n   ```json\n   \"name\": \"agent-postiz\",\n   \"bin\": { \"postiz\": \"./dist/index.js\" }\n   ```\n   Install: `npm i -g agent-postiz`\n   Use: `postiz`\n\n3. **Multiple commands:**\n   ```json\n   \"name\": \"agent-postiz\",\n   \"bin\": {\n     \"postiz\": \"./dist/index.js\",\n     \"pz\": \"./dist/index.js\"\n   }\n   ```\n   Install: `npm i -g agent-postiz`\n   Use: `postiz` or `pz`\n\n## Publishing Checklist\n\n### Before First Publish\n\n- [ ] Verify package name is available on npm\n  ```bash\n  npm view postiz\n  # If error \"404 Not Found\" - name is available!\n  ```\n\n- [ ] Update version if needed\n  ```json\n  \"version\": \"1.0.0\"\n  ```\n\n- [ ] Review files to include\n  ```json\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"SKILL.md\"\n  ]\n  ```\n\n- [ ] Build the package\n  ```bash\n  pnpm run build\n  ```\n\n- [ ] Test locally\n  ```bash\n  pnpm link --global\n  postiz --help\n  ```\n\n### Publish to npm\n\n```bash\n# Login to npm (first time only)\nnpm login\n\n# From apps/cli\npnpm run build\npnpm publish --access public\n\n# Or use the root script\ncd /path/to/monorepo/root\npnpm run publish-cli\n```\n\n### After Publishing\n\nVerify it's published:\n```bash\nnpm view postiz\n# Should show your package info\n```\n\nTest installation:\n```bash\nnpm install -g postiz\npostiz --version\n```\n\n## Using from Monorepo Root\n\nThe root `package.json` already has:\n\n```json\n{\n  \"scripts\": {\n    \"publish-cli\": \"pnpm run --filter ./apps/cli publish\"\n  }\n}\n```\n\nSo you can publish from the root:\n\n```bash\n# From monorepo root\npnpm run publish-cli\n```\n\n## Version Updates\n\n### Patch Release (1.0.0 → 1.0.1)\n\n```bash\ncd apps/cli\nnpm version patch\npnpm publish --access public\n```\n\n### Minor Release (1.0.0 → 1.1.0)\n\n```bash\ncd apps/cli\nnpm version minor\npnpm publish --access public\n```\n\n### Major Release (1.0.0 → 2.0.0)\n\n```bash\ncd apps/cli\nnpm version major\npnpm publish --access public\n```\n\n## Scoped Packages\n\nIf you want to publish under an organization scope:\n\n```json\n{\n  \"name\": \"@yourorg/postiz\",\n  \"bin\": {\n    \"postiz\": \"./dist/index.js\"\n  }\n}\n```\n\nInstall:\n```bash\nnpm install -g @yourorg/postiz\n```\n\nUse:\n```bash\npostiz --help\n```\n\n## Testing Before Publishing\n\n### Test the Build\n\n```bash\npnpm run build\nnode dist/index.js --help\n```\n\n### Test Linking\n\n```bash\npnpm link --global\npostiz --help\npnpm unlink --global\n```\n\n### Test Publishing (Dry Run)\n\n```bash\nnpm publish --dry-run\n# Shows what would be published\n```\n\n### Test with `npm pack`\n\n```bash\nnpm pack\n# Creates a .tgz file\n\n# Test installing the tarball\nnpm install -g ./postiz-1.0.0.tgz\npostiz --help\nnpm uninstall -g postiz\n```\n\n## Continuous Publishing\n\n### Using GitHub Actions\n\nCreate `.github/workflows/publish-cli.yml`:\n\n```yaml\nname: Publish CLI to npm\n\non:\n  push:\n    tags:\n      - 'cli-v*'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: pnpm/action-setup@v2\n      - uses: actions/setup-node@v3\n        with:\n          node-version: '20'\n          registry-url: 'https://registry.npmjs.org'\n\n      - run: pnpm install\n      - run: pnpm run build:cli\n\n      - name: Publish to npm\n        run: pnpm --filter ./apps/cli publish --access public\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n```\n\nThen publish with:\n```bash\ngit tag cli-v1.0.0\ngit push origin cli-v1.0.0\n```\n\n## Common Issues\n\n### \"You do not have permission to publish\"\n\n- Make sure you're logged in: `npm login`\n- Check package name isn't taken: `npm view postiz`\n- If scoped, ensure org access: `npm org ls yourorg`\n\n### \"Package name too similar to existing package\"\n\n- Choose a more unique name\n- Or use a scoped package: `@yourorg/postiz`\n\n### \"Missing required files\"\n\n- Check `\"files\"` field in package.json\n- Run `npm pack` to see what would be included\n- Make sure `dist/` exists and is built\n\n### Command not found after install\n\n- Check `\"bin\"` field is correct\n- Ensure `dist/index.js` has shebang: `#!/usr/bin/env node`\n- Try reinstalling: `npm uninstall -g postiz && npm install -g postiz`\n\n## Recommended Names\n\nIf \"postiz\" is taken, consider:\n\n- `@postiz/cli`\n- `postiz-cli`\n- `postiz-agent`\n- `agent-postiz`\n- `@yourorg/postiz`\n\nRemember: The package name is just for installation. The command can still be `postiz`!\n\n## Summary\n\n✅ Current setup works perfectly!\n✅ `bin` field defines the command name\n✅ `name` field defines the npm package name\n✅ They can be different!\n\n**To publish now:**\n\n```bash\ncd apps/cli\npnpm run build\npnpm publish --access public\n```\n\n**Users install:**\n\n```bash\nnpm install -g postiz\n# or\npnpm install -g postiz\n```\n\n**Users use:**\n\n```bash\npostiz --help\npostiz posts:create -c \"Hello!\" -i \"twitter-123\"\n```\n\n🚀 **Ready to publish!**\n"
  },
  {
    "path": "apps/cli/QUICK_START.md",
    "content": "# Postiz CLI - Quick Start Guide\n\n## Installation\n\n### From Source (Development)\n\n```bash\n# Navigate to the monorepo root\ncd /path/to/gitroom\n\n# Install dependencies\npnpm install\n\n# Build the CLI\npnpm run build:cli\n\n# Test locally\nnode apps/cli/dist/index.js --help\n```\n\n### Global Installation (Development)\n\n```bash\n# From the CLI directory\ncd apps/cli\n\n# Link globally\npnpm link --global\n\n# Now you can use 'postiz' anywhere\npostiz --help\n```\n\n### From npm (Coming Soon)\n\n```bash\n# Once published\nnpm install -g postiz\n\n# Or with pnpm\npnpm add -g postiz\n```\n\n## Setup\n\n### 1. Get Your API Key\n\n1. Log in to your Postiz account at https://postiz.com\n2. Navigate to Settings → API Keys\n3. Generate a new API key\n\n### 2. Set Environment Variable\n\n```bash\n# Bash/Zsh\nexport POSTIZ_API_KEY=your_api_key_here\n\n# Fish\nset -x POSTIZ_API_KEY your_api_key_here\n\n# PowerShell\n$env:POSTIZ_API_KEY=\"your_api_key_here\"\n```\n\nTo make it permanent, add it to your shell profile:\n\n```bash\n# ~/.bashrc or ~/.zshrc\necho 'export POSTIZ_API_KEY=your_api_key_here' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n### 3. Verify Installation\n\n```bash\npostiz --help\n```\n\n## Basic Commands\n\n### Create a Post\n\n```bash\n# Simple post\npostiz posts:create -c \"Hello World!\" -i \"twitter-123\"\n\n# Post with multiple images\npostiz posts:create \\\n  -c \"Check these out!\" \\\n  -m \"img1.jpg,img2.jpg\" \\\n  -i \"twitter-123\"\n\n# Post with comments (each can have different media!)\npostiz posts:create \\\n  -c \"Main post\" -m \"main.jpg\" \\\n  -c \"First comment\" -m \"comment1.jpg\" \\\n  -c \"Second comment\" -m \"comment2.jpg\" \\\n  -i \"twitter-123\"\n\n# Scheduled post\npostiz posts:create \\\n  -c \"Future post\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -i \"twitter-123\"\n```\n\n### List Posts\n\n```bash\n# List all posts\npostiz posts:list\n\n# With pagination\npostiz posts:list -p 2 -l 20\n\n# Search\npostiz posts:list -s \"keyword\"\n```\n\n### Delete a Post\n\n```bash\npostiz posts:delete abc123xyz\n```\n\n### List Integrations\n\n```bash\npostiz integrations:list\n```\n\n### Upload Media\n\n```bash\npostiz upload ./path/to/image.png\n```\n\n## Common Workflows\n\n### 1. Check What's Connected\n\n```bash\n# See all your connected social media accounts\npostiz integrations:list\n```\n\nThe output will show integration IDs like:\n```json\n[\n  { \"id\": \"twitter-123\", \"provider\": \"twitter\" },\n  { \"id\": \"linkedin-456\", \"provider\": \"linkedin\" }\n]\n```\n\n### 2. Create Multi-Platform Post\n\n```bash\n# Use the integration IDs from step 1\npostiz posts:create \\\n  -c \"Posting to multiple platforms!\" \\\n  -i \"twitter-123,linkedin-456,facebook-789\"\n```\n\n### 3. Schedule Multiple Posts\n\n```bash\n# Morning post\npostiz posts:create -c \"Good morning!\" -s \"2024-01-15T09:00:00Z\"\n\n# Afternoon post\npostiz posts:create -c \"Lunch time update!\" -s \"2024-01-15T12:00:00Z\"\n\n# Evening post\npostiz posts:create -c \"Good night!\" -s \"2024-01-15T20:00:00Z\"\n```\n\n### 4. Upload and Post Image\n\n```bash\n# First upload the image\npostiz upload ./my-image.png\n\n# Copy the URL from the response, then create post\npostiz posts:create -c \"Check out this image!\" --image \"url-from-upload\"\n```\n\n## Tips & Tricks\n\n### Using with jq for JSON Parsing\n\n```bash\n# Get just the post IDs\npostiz posts:list | jq '.[] | .id'\n\n# Get integration names\npostiz integrations:list | jq '.[] | .provider'\n```\n\n### Script Automation\n\n```bash\n#!/bin/bash\n# Create a batch of posts\n\nfor hour in 09 12 15 18; do\n  postiz posts:create \\\n    -c \"Automated post at ${hour}:00\" \\\n    -s \"2024-01-15T${hour}:00:00Z\"\n  echo \"Created post for ${hour}:00\"\ndone\n```\n\n### Environment Variables\n\n```bash\n# Custom API endpoint (for self-hosted)\nexport POSTIZ_API_URL=https://your-instance.com\n\n# Use the CLI with custom endpoint\npostiz posts:list\n```\n\n## Troubleshooting\n\n### API Key Not Set\n\n```\n❌ Error: POSTIZ_API_KEY environment variable is required\n```\n\n**Solution:** Set the environment variable:\n```bash\nexport POSTIZ_API_KEY=your_key\n```\n\n### Command Not Found\n\n```\npostiz: command not found\n```\n\n**Solution:** Either:\n1. Use the full path: `node apps/cli/dist/index.js`\n2. Link globally: `cd apps/cli && pnpm link --global`\n3. Add to PATH: `export PATH=$PATH:/path/to/apps/cli/dist`\n\n### API Errors\n\n```\n❌ API Error (401): Unauthorized\n```\n\n**Solution:** Check your API key is valid and has proper permissions.\n\n```\n❌ API Error (404): Not Found\n```\n\n**Solution:** Verify the post ID exists when deleting.\n\n## Getting Help\n\n```bash\n# General help\npostiz --help\n\n# Command-specific help\npostiz posts:create --help\npostiz posts:list --help\npostiz posts:delete --help\n```\n\n## Next Steps\n\n- Read the full [README.md](./README.md) for detailed documentation\n- Check [SKILL.md](./SKILL.md) for AI agent integration patterns\n- See [examples/](./examples/) for more usage examples\n\n## Links\n\n- [Postiz Website](https://postiz.com)\n- [API Documentation](https://postiz.com/api-docs)\n- [GitHub Repository](https://github.com/gitroomhq/postiz-app)\n- [Report Issues](https://github.com/gitroomhq/postiz-app/issues)\n"
  },
  {
    "path": "apps/cli/README.md",
    "content": "# Postiz CLI\n\n**Social media automation CLI for AI agents** - Schedule posts across 28+ platforms programmatically.\n\nThe Postiz CLI provides a command-line interface to the Postiz API, enabling developers and AI agents to automate social media posting, manage content, and handle media uploads across platforms like Twitter/X, LinkedIn, Reddit, YouTube, TikTok, Instagram, Facebook, and more.\n\n---\n\n## Installation\n\n### From npm (Recommended)\n\n```bash\nnpm install -g postiz\n# or\npnpm install -g postiz\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/gitroomhq/postiz-app.git\ncd postiz-app/apps/cli\npnpm install\npnpm run build\npnpm link --global\n```\n\n### For Development\n\n```bash\ncd apps/cli\npnpm install\npnpm run build\npnpm link --global\n\n# Or run directly without linking\npnpm run start -- posts:list\n```\n\n---\n\n## Setup\n\n**Required:** Set your Postiz API key\n\n```bash\nexport POSTIZ_API_KEY=your_api_key_here\n```\n\n**Optional:** Custom API endpoint\n\n```bash\nexport POSTIZ_API_URL=https://your-custom-api.com\n```\n\n---\n\n## Commands\n\n### Discovery & Settings\n\n**List all connected integrations**\n```bash\npostiz integrations:list\n```\n\nReturns integration IDs, provider names, and metadata.\n\n**Get integration settings schema**\n```bash\npostiz integrations:settings <integration-id>\n```\n\nReturns character limits, required settings, and available tools for fetching dynamic data.\n\n**Trigger integration tools**\n```bash\npostiz integrations:trigger <integration-id> <method-name>\npostiz integrations:trigger <integration-id> <method-name> -d '{\"key\":\"value\"}'\n```\n\nFetch dynamic data like Reddit flairs, YouTube playlists, LinkedIn companies, etc.\n\n**Examples:**\n```bash\n# Get Reddit flairs\npostiz integrations:trigger reddit-123 getFlairs -d '{\"subreddit\":\"programming\"}'\n\n# Get YouTube playlists\npostiz integrations:trigger youtube-456 getPlaylists\n\n# Get LinkedIn companies\npostiz integrations:trigger linkedin-789 getCompanies\n```\n\n---\n\n### Creating Posts\n\n**Simple scheduled post**\n```bash\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -i \"integration-id\"\n```\n\n**Draft post**\n```bash\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -t draft -i \"integration-id\"\n```\n\n**Post with media**\n```bash\npostiz posts:create -c \"Content\" -m \"img1.jpg,img2.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"integration-id\"\n```\n\n**Post with comments** (each comment can have its own media)\n```bash\npostiz posts:create \\\n  -c \"Main post\" -m \"main.jpg\" \\\n  -c \"First comment\" -m \"comment1.jpg\" \\\n  -c \"Second comment\" -m \"comment2.jpg,comment3.jpg\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -i \"integration-id\"\n```\n\n**Multi-platform post**\n```bash\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-id,linkedin-id,facebook-id\"\n```\n\n**Platform-specific settings**\n```bash\npostiz posts:create \\\n  -c \"Content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"Post Title\",\"type\":\"text\"}}]}' \\\n  -i \"reddit-id\"\n```\n\n**Complex post from JSON file**\n```bash\npostiz posts:create --json post.json\n```\n\n**Options:**\n- `-c, --content` - Post/comment content (use multiple times for posts with comments)\n- `-s, --date` - Schedule date in ISO 8601 format (REQUIRED)\n- `-t, --type` - Post type: \"schedule\" or \"draft\" (default: \"schedule\")\n- `-m, --media` - Comma-separated media URLs for corresponding `-c`\n- `-i, --integrations` - Comma-separated integration IDs (required)\n- `-d, --delay` - Delay between comments in milliseconds (default: 5000)\n- `--settings` - Platform-specific settings as JSON string\n- `-j, --json` - Path to JSON file with full post structure\n- `--shortLink` - Use short links (default: true)\n\n---\n\n### Managing Posts\n\n**List posts**\n```bash\npostiz posts:list\npostiz posts:list --startDate \"2024-01-01T00:00:00Z\" --endDate \"2024-12-31T23:59:59Z\"\npostiz posts:list --customer \"customer-id\"\n```\n\nDefaults to last 30 days to next 30 days if dates not specified.\n\n**Delete post**\n```bash\npostiz posts:delete <post-id>\n```\n\n---\n\n### Media Upload\n\n**Upload file and get URL**\n```bash\npostiz upload <file-path>\n```\n\n**⚠️ IMPORTANT: Upload Files Before Posting**\n\nYou **must** upload media files to Postiz before using them in posts. Many platforms (especially TikTok, Instagram, and YouTube) require verified/trusted URLs and will reject external links.\n\n**Workflow:**\n1. Upload your file using `postiz upload`\n2. Extract the returned URL\n3. Use that URL in your post's `-m` parameter\n\n**Supported formats:**\n- **Images:** PNG, JPG, JPEG, GIF, WEBP, SVG, BMP, ICO\n- **Videos:** MP4, MOV, AVI, MKV, WEBM, FLV, WMV, M4V, MPEG, MPG, 3GP\n- **Audio:** MP3, WAV, OGG, AAC, FLAC, M4A\n- **Documents:** PDF, DOC, DOCX\n\n**Example:**\n```bash\n# 1. Upload the file first\nRESULT=$(postiz upload video.mp4)\nPATH=$(echo \"$RESULT\" | jq -r '.path')\n\n# 2. Use the Postiz URL in your post\npostiz posts:create -c \"Check out my video!\" -s \"2024-12-31T12:00:00Z\" -m \"$PATH\" -i \"tiktok-id\"\n```\n\n**Why this is required:**\n- **TikTok, Instagram, YouTube** only accept URLs from trusted domains\n- **Security:** Platforms verify media sources to prevent abuse\n- **Reliability:** Postiz ensures your media is always accessible\n\n---\n\n## Platform-Specific Features\n\n### Reddit\n```bash\n# Get available flairs\npostiz integrations:trigger reddit-id getFlairs -d '{\"subreddit\":\"programming\"}'\n\n# Post with subreddit and flair\npostiz posts:create \\\n  -c \"Content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"My Post\",\"type\":\"text\",\"is_flair_required\":true,\"flair\":{\"id\":\"flair-123\",\"name\":\"Discussion\"}}}]}' \\\n  -i \"reddit-id\"\n```\n\n### YouTube\n```bash\n# Get playlists\npostiz integrations:trigger youtube-id getPlaylists\n\n# Upload video FIRST (required!)\nVIDEO=$(postiz upload video.mp4)\nVIDEO_URL=$(echo \"$VIDEO\" | jq -r '.path')\n\n# Post with uploaded video URL\npostiz posts:create \\\n  -c \"Video description\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"title\":\"Video Title\",\"type\":\"public\",\"tags\":[{\"value\":\"tech\",\"label\":\"Tech\"}],\"playlistId\":\"playlist-id\"}' \\\n  -m \"$VIDEO_URL\" \\\n  -i \"youtube-id\"\n```\n\n### TikTok\n```bash\n# Upload video FIRST (TikTok only accepts verified URLs!)\nVIDEO=$(postiz upload video.mp4)\nVIDEO_URL=$(echo \"$VIDEO\" | jq -r '.path')\n\n# Post with uploaded video URL\npostiz posts:create \\\n  -c \"Video caption #fyp\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"privacy\":\"PUBLIC_TO_EVERYONE\",\"duet\":true,\"stitch\":true}' \\\n  -m \"$VIDEO_URL\" \\\n  -i \"tiktok-id\"\n```\n\n### LinkedIn\n```bash\n# Get companies you can post to\npostiz integrations:trigger linkedin-id getCompanies\n\n# Post as company\npostiz posts:create \\\n  -c \"Company announcement\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"companyId\":\"company-123\"}' \\\n  -i \"linkedin-id\"\n```\n\n### X (Twitter)\n```bash\n# Create thread\npostiz posts:create \\\n  -c \"Thread 1/3 🧵\" \\\n  -c \"Thread 2/3\" \\\n  -c \"Thread 3/3\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -d 2000 \\\n  -i \"twitter-id\"\n\n# With reply settings\npostiz posts:create \\\n  -c \"Tweet content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"who_can_reply_post\":\"everyone\"}' \\\n  -i \"twitter-id\"\n```\n\n### Instagram\n```bash\n# Upload image FIRST (Instagram requires verified URLs!)\nIMAGE=$(postiz upload image.jpg)\nIMAGE_URL=$(echo \"$IMAGE\" | jq -r '.path')\n\n# Regular post\npostiz posts:create \\\n  -c \"Caption #hashtag\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"post_type\":\"post\"}' \\\n  -m \"$IMAGE_URL\" \\\n  -i \"instagram-id\"\n\n# Story (upload first)\nSTORY=$(postiz upload story.jpg)\nSTORY_URL=$(echo \"$STORY\" | jq -r '.path')\n\npostiz posts:create \\\n  -c \"\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"post_type\":\"story\"}' \\\n  -m \"$STORY_URL\" \\\n  -i \"instagram-id\"\n```\n\n**See [PROVIDER_SETTINGS.md](./PROVIDER_SETTINGS.md) for all 28+ platforms.**\n\n---\n\n## Features for AI Agents\n\n### Discovery Workflow\nThe CLI enables dynamic discovery of integration capabilities:\n\n1. **List integrations** - Get available social media accounts\n2. **Get settings** - Retrieve character limits, required fields, and available tools\n3. **Trigger tools** - Fetch dynamic data (flairs, playlists, boards, etc.)\n4. **Create posts** - Use discovered data in posts\n\nThis allows AI agents to adapt to different platforms without hardcoded knowledge.\n\n### JSON Mode\nFor complex posts with multiple platforms and settings:\n\n```bash\npostiz posts:create --json complex-post.json\n```\n\nJSON structure:\n```json\n{\n  \"integrations\": [\"twitter-123\", \"linkedin-456\"],\n  \"posts\": [\n    {\n      \"provider\": \"twitter\",\n      \"post\": [\n        {\n          \"content\": \"Tweet version\",\n          \"image\": [\"twitter-image.jpg\"]\n        }\n      ]\n    },\n    {\n      \"provider\": \"linkedin\",\n      \"post\": [\n        {\n          \"content\": \"LinkedIn version with more context...\",\n          \"image\": [\"linkedin-image.jpg\"]\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"linkedin\",\n        \"companyId\": \"company-123\"\n      }\n    }\n  ]\n}\n```\n\n### All Output is JSON\nEvery command outputs JSON for easy parsing:\n\n```bash\nINTEGRATIONS=$(postiz integrations:list | jq -r '.')\nREDDIT_ID=$(echo \"$INTEGRATIONS\" | jq -r '.[] | select(.identifier==\"reddit\") | .id')\n```\n\n### Threading Support\nComments are automatically converted to threads/replies based on platform:\n- **Twitter/X**: Thread of tweets\n- **Reddit**: Comment replies\n- **LinkedIn**: Comment on post\n- **Instagram**: First comment\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  -c \"Comment 1\" \\\n  -c \"Comment 2\" \\\n  -i \"integration-id\"\n```\n\n---\n\n## Common Workflows\n\n### Reddit Post with Flair\n```bash\n#!/bin/bash\nREDDIT_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier==\"reddit\") | .id')\nFLAIRS=$(postiz integrations:trigger \"$REDDIT_ID\" getFlairs -d '{\"subreddit\":\"programming\"}')\nFLAIR_ID=$(echo \"$FLAIRS\" | jq -r '.output[0].id')\n\npostiz posts:create \\\n  -c \"My post content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings \"{\\\"subreddit\\\":[{\\\"value\\\":{\\\"subreddit\\\":\\\"programming\\\",\\\"title\\\":\\\"Post Title\\\",\\\"type\\\":\\\"text\\\",\\\"is_flair_required\\\":true,\\\"flair\\\":{\\\"id\\\":\\\"$FLAIR_ID\\\",\\\"name\\\":\\\"Discussion\\\"}}}]}\" \\\n  -i \"$REDDIT_ID\"\n```\n\n### YouTube Video Upload\n```bash\n#!/bin/bash\nVIDEO=$(postiz upload video.mp4)\nVIDEO_PATH=$(echo \"$VIDEO\" | jq -r '.path')\n\npostiz posts:create \\\n  -c \"Video description...\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"title\":\"My Video\",\"type\":\"public\",\"tags\":[{\"value\":\"tech\",\"label\":\"Tech\"}]}' \\\n  -m \"$VIDEO_PATH\" \\\n  -i \"youtube-id\"\n```\n\n### Multi-Platform Campaign\n```bash\n#!/bin/bash\npostiz posts:create \\\n  -c \"Same content everywhere\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -m \"image.jpg\" \\\n  -i \"twitter-id,linkedin-id,facebook-id\"\n```\n\n### Batch Scheduling\n```bash\n#!/bin/bash\nDATES=(\"2024-02-14T09:00:00Z\" \"2024-02-15T09:00:00Z\" \"2024-02-16T09:00:00Z\")\nCONTENT=(\"Monday motivation 💪\" \"Tuesday tips 💡\" \"Wednesday wisdom 🧠\")\n\nfor i in \"${!DATES[@]}\"; do\n  postiz posts:create \\\n    -c \"${CONTENT[$i]}\" \\\n    -s \"${DATES[$i]}\" \\\n    -i \"twitter-id\"\ndone\n```\n\n---\n\n## Documentation\n\n**For AI Agents:**\n- **[SKILL.md](./SKILL.md)** - Complete skill reference with patterns and examples\n\n**Deep-Dive Guides:**\n- **[HOW_TO_RUN.md](./HOW_TO_RUN.md)** - Installation and setup methods\n- **[COMMAND_LINE_GUIDE.md](./COMMAND_LINE_GUIDE.md)** - Complete command syntax reference\n- **[PROVIDER_SETTINGS.md](./PROVIDER_SETTINGS.md)** - All platform settings schemas\n- **[INTEGRATION_TOOLS_WORKFLOW.md](./INTEGRATION_TOOLS_WORKFLOW.md)** - Tools workflow guide\n- **[INTEGRATION_SETTINGS_DISCOVERY.md](./INTEGRATION_SETTINGS_DISCOVERY.md)** - Settings discovery\n- **[SUPPORTED_FILE_TYPES.md](./SUPPORTED_FILE_TYPES.md)** - Media format reference\n- **[PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md)** - Code architecture\n- **[PUBLISHING.md](./PUBLISHING.md)** - npm publishing guide\n\n**Examples:**\n- **[examples/EXAMPLES.md](./examples/EXAMPLES.md)** - Comprehensive examples\n- **[examples/](./examples/)** - Ready-to-use scripts and JSON files\n\n---\n\n## API Endpoints\n\nThe CLI interacts with these Postiz API endpoints:\n\n| Endpoint | Method | Purpose |\n|----------|--------|---------|\n| `/public/v1/posts` | POST | Create a post |\n| `/public/v1/posts` | GET | List posts |\n| `/public/v1/posts/:id` | DELETE | Delete a post |\n| `/public/v1/integrations` | GET | List integrations |\n| `/public/v1/integration-settings/:id` | GET | Get integration settings |\n| `/public/v1/integration-trigger/:id` | POST | Trigger integration tool |\n| `/public/v1/upload` | POST | Upload media |\n\n---\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `POSTIZ_API_KEY` | ✅ Yes | - | Your Postiz API key |\n| `POSTIZ_API_URL` | No | `https://api.postiz.com` | Custom API endpoint |\n\n---\n\n## Error Handling\n\nThe CLI provides clear error messages with exit codes:\n\n- **Exit code 0**: Success\n- **Exit code 1**: Error occurred\n\n**Common errors:**\n\n| Error | Solution |\n|-------|----------|\n| `POSTIZ_API_KEY is not set` | Set environment variable: `export POSTIZ_API_KEY=key` |\n| `Integration not found` | Run `integrations:list` to get valid IDs |\n| `startDate/endDate required` | Use ISO 8601 format: `\"2024-12-31T12:00:00Z\"` |\n| `Invalid settings` | Check `integrations:settings` for required fields |\n| `Tool not found` | Check available tools in `integrations:settings` output |\n| `Upload failed` | Verify file exists and format is supported |\n\n---\n\n## Development\n\n### Project Structure\n\n```\napps/cli/\n├── src/\n│   ├── index.ts              # CLI entry point with yargs\n│   ├── api.ts                # PostizAPI client class\n│   ├── config.ts             # Environment configuration\n│   └── commands/\n│       ├── posts.ts          # Post management commands\n│       ├── integrations.ts   # Integration commands\n│       └── upload.ts         # Media upload command\n├── examples/                 # Example scripts and JSON files\n├── package.json\n├── tsconfig.json\n├── tsup.config.ts            # Build configuration\n├── README.md                 # This file\n└── SKILL.md                  # AI agent reference\n```\n\n### Scripts\n\n```bash\npnpm run dev       # Watch mode for development\npnpm run build     # Build the CLI\npnpm run start     # Run the built CLI\n```\n\n### Building\n\nThe CLI uses `tsup` for bundling:\n\n```bash\npnpm run build\n```\n\nOutput in `dist/`:\n- `index.js` - Bundled executable with shebang\n- `index.js.map` - Source map\n\n---\n\n## Quick Reference\n\n```bash\n# Environment setup\nexport POSTIZ_API_KEY=your_key\n\n# Discovery\npostiz integrations:list                           # List integrations\npostiz integrations:settings <id>                  # Get settings\npostiz integrations:trigger <id> <method> -d '{}'  # Fetch data\n\n# Posting (date is required)\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" -i \"id\"                    # Simple\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" -t draft -i \"id\"          # Draft\npostiz posts:create -c \"text\" -m \"img.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"id\"      # With media\npostiz posts:create -c \"main\" -c \"comment\" -s \"2024-12-31T12:00:00Z\" -i \"id\"      # With comment\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" --settings '{}' -i \"id\"   # Platform-specific\npostiz posts:create --json file.json                                               # Complex\n\n# Management\npostiz posts:list                                  # List posts\npostiz posts:delete <id>                          # Delete post\npostiz upload <file>                              # Upload media\n\n# Help\npostiz --help                                     # Show help\npostiz posts:create --help                        # Command help\n```\n\n---\n\n## Contributing\n\nThis CLI is part of the [Postiz monorepo](https://github.com/gitroomhq/postiz-app).\n\nTo contribute:\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes in `apps/cli/`\n4. Run tests: `pnpm run build`\n5. Submit a pull request\n\n---\n\n## License\n\nAGPL-3.0\n\n---\n\n## Links\n\n- **Website:** [postiz.com](https://postiz.com)\n- **API Docs:** [postiz.com/api-docs](https://postiz.com/api-docs)\n- **GitHub:** [gitroomhq/postiz-app](https://github.com/gitroomhq/postiz-app)\n- **Issues:** [Report bugs](https://github.com/gitroomhq/postiz-app/issues)\n\n---\n\n## Supported Platforms\n\n28+ platforms including:\n\n| Platform | Integration Tools | Settings |\n|----------|------------------|----------|\n| Twitter/X | getLists, getCommunities | who_can_reply_post |\n| LinkedIn | getCompanies | companyId, carousel |\n| Reddit | getFlairs, searchSubreddits | subreddit, title, flair |\n| YouTube | getPlaylists, getCategories | title, type, tags, playlistId |\n| TikTok | - | privacy, duet, stitch |\n| Instagram | - | post_type (post/story) |\n| Facebook | getPages | - |\n| Pinterest | getBoards, getBoardSections | - |\n| Discord | getChannels | - |\n| Slack | getChannels | - |\n| And 18+ more... | | |\n\n**See [PROVIDER_SETTINGS.md](./PROVIDER_SETTINGS.md) for complete documentation.**\n"
  },
  {
    "path": "apps/cli/SKILL.md",
    "content": "| Property | Value |\n|----------|-------|\n| **name** | postiz |\n| **description** | Social media automation CLI for scheduling posts across 28+ platforms |\n| **allowed-tools** | Bash(postiz:*) |\n\n---\n\n## Core Workflow\n\nThe fundamental pattern for using Postiz CLI:\n\n1. **Discover** - List integrations and get their settings\n2. **Fetch** - Use integration tools to retrieve dynamic data (flairs, playlists, companies)\n3. **Prepare** - Upload media files if needed\n4. **Post** - Create posts with content, media, and platform-specific settings\n\n```bash\n# 1. Discover\npostiz integrations:list\npostiz integrations:settings <integration-id>\n\n# 2. Fetch (if needed)\npostiz integrations:trigger <integration-id> <method> -d '{\"key\":\"value\"}'\n\n# 3. Prepare\npostiz upload image.jpg\n\n# 4. Post\npostiz posts:create -c \"Content\" -m \"image.jpg\" -i \"<integration-id>\"\n```\n\n---\n\n## Essential Commands\n\n### Setup\n\n```bash\n# Required environment variable\nexport POSTIZ_API_KEY=your_api_key_here\n\n# Optional custom API URL\nexport POSTIZ_API_URL=https://custom-api-url.com\n```\n\n### Integration Discovery\n\n```bash\n# List all connected integrations\npostiz integrations:list\n\n# Get settings schema for specific integration\npostiz integrations:settings <integration-id>\n\n# Trigger integration tool to fetch dynamic data\npostiz integrations:trigger <integration-id> <method-name>\npostiz integrations:trigger <integration-id> <method-name> -d '{\"param\":\"value\"}'\n```\n\n### Creating Posts\n\n```bash\n# Simple post (date is REQUIRED)\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -i \"integration-id\"\n\n# Draft post\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -t draft -i \"integration-id\"\n\n# Post with media\npostiz posts:create -c \"Content\" -m \"img1.jpg,img2.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"integration-id\"\n\n# Post with comments (each with own media)\npostiz posts:create \\\n  -c \"Main post\" -m \"main.jpg\" \\\n  -c \"First comment\" -m \"comment1.jpg\" \\\n  -c \"Second comment\" -m \"comment2.jpg,comment3.jpg\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -i \"integration-id\"\n\n# Multi-platform post\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-id,linkedin-id,facebook-id\"\n\n# Platform-specific settings\npostiz posts:create \\\n  -c \"Content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"My Post\",\"type\":\"text\"}}]}' \\\n  -i \"reddit-id\"\n\n# Complex post from JSON file\npostiz posts:create --json post.json\n```\n\n### Managing Posts\n\n```bash\n# List posts (defaults to last 30 days to next 30 days)\npostiz posts:list\n\n# List posts in date range\npostiz posts:list --startDate \"2024-01-01T00:00:00Z\" --endDate \"2024-12-31T23:59:59Z\"\n\n# Delete post\npostiz posts:delete <post-id>\n```\n\n### Media Upload\n\n**⚠️ IMPORTANT:** Always upload files to Postiz before using them in posts. Many platforms (TikTok, Instagram, YouTube) **require verified URLs** and will reject external links.\n\n```bash\n# Upload file and get URL\npostiz upload image.jpg\n\n# Supports: images (PNG, JPG, GIF, WEBP, SVG), videos (MP4, MOV, AVI, MKV, WEBM),\n# audio (MP3, WAV, OGG, AAC), documents (PDF, DOC, DOCX)\n\n# Workflow: Upload → Extract URL → Use in post\nVIDEO=$(postiz upload video.mp4)\nVIDEO_PATH=$(echo \"$VIDEO\" | jq -r '.path')\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -m \"$VIDEO_PATH\" -i \"tiktok-id\"\n```\n\n---\n\n## Common Patterns\n\n### Pattern 1: Discover & Use Integration Tools\n\n**Reddit - Get flairs for a subreddit:**\n```bash\n# Get Reddit integration ID\nREDDIT_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier==\"reddit\") | .id')\n\n# Fetch available flairs\nFLAIRS=$(postiz integrations:trigger \"$REDDIT_ID\" getFlairs -d '{\"subreddit\":\"programming\"}')\nFLAIR_ID=$(echo \"$FLAIRS\" | jq -r '.output[0].id')\n\n# Use in post\npostiz posts:create \\\n  -c \"My post content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings \"{\\\"subreddit\\\":[{\\\"value\\\":{\\\"subreddit\\\":\\\"programming\\\",\\\"title\\\":\\\"Post Title\\\",\\\"type\\\":\\\"text\\\",\\\"is_flair_required\\\":true,\\\"flair\\\":{\\\"id\\\":\\\"$FLAIR_ID\\\",\\\"name\\\":\\\"Discussion\\\"}}}]}\" \\\n  -i \"$REDDIT_ID\"\n```\n\n**YouTube - Get playlists:**\n```bash\nYOUTUBE_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier==\"youtube\") | .id')\nPLAYLISTS=$(postiz integrations:trigger \"$YOUTUBE_ID\" getPlaylists)\nPLAYLIST_ID=$(echo \"$PLAYLISTS\" | jq -r '.output[0].id')\n\npostiz posts:create \\\n  -c \"Video description\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings \"{\\\"title\\\":\\\"My Video\\\",\\\"type\\\":\\\"public\\\",\\\"playlistId\\\":\\\"$PLAYLIST_ID\\\"}\" \\\n  -m \"video.mp4\" \\\n  -i \"$YOUTUBE_ID\"\n```\n\n**LinkedIn - Post as company:**\n```bash\nLINKEDIN_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier==\"linkedin\") | .id')\nCOMPANIES=$(postiz integrations:trigger \"$LINKEDIN_ID\" getCompanies)\nCOMPANY_ID=$(echo \"$COMPANIES\" | jq -r '.output[0].id')\n\npostiz posts:create \\\n  -c \"Company announcement\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings \"{\\\"companyId\\\":\\\"$COMPANY_ID\\\"}\" \\\n  -i \"$LINKEDIN_ID\"\n```\n\n### Pattern 2: Upload Media Before Posting\n\n```bash\n# Upload multiple files\nVIDEO_RESULT=$(postiz upload video.mp4)\nVIDEO_PATH=$(echo \"$VIDEO_RESULT\" | jq -r '.path')\n\nTHUMB_RESULT=$(postiz upload thumbnail.jpg)\nTHUMB_PATH=$(echo \"$THUMB_RESULT\" | jq -r '.path')\n\n# Use in post\npostiz posts:create \\\n  -c \"Check out my video!\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -m \"$VIDEO_PATH\" \\\n  -i \"tiktok-id\"\n```\n\n### Pattern 3: Twitter Thread\n\n```bash\npostiz posts:create \\\n  -c \"🧵 Thread starter (1/4)\" -m \"intro.jpg\" \\\n  -c \"Point one (2/4)\" -m \"point1.jpg\" \\\n  -c \"Point two (3/4)\" -m \"point2.jpg\" \\\n  -c \"Conclusion (4/4)\" -m \"outro.jpg\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -d 2000 \\\n  -i \"twitter-id\"\n```\n\n### Pattern 4: Multi-Platform Campaign\n\n```bash\n# Create JSON file with platform-specific content\ncat > campaign.json << 'EOF'\n{\n  \"integrations\": [\"twitter-123\", \"linkedin-456\", \"facebook-789\"],\n  \"posts\": [\n    {\n      \"provider\": \"twitter\",\n      \"post\": [\n        {\n          \"content\": \"Short tweet version #tech\",\n          \"image\": [\"twitter-image.jpg\"]\n        }\n      ]\n    },\n    {\n      \"provider\": \"linkedin\",\n      \"post\": [\n        {\n          \"content\": \"Professional LinkedIn version with more context...\",\n          \"image\": [\"linkedin-image.jpg\"]\n        }\n      ]\n    }\n  ]\n}\nEOF\n\npostiz posts:create --json campaign.json\n```\n\n### Pattern 5: Validate Settings Before Posting\n\n```javascript\nconst { execSync } = require('child_process');\n\nfunction validateAndPost(content, integrationId, settings) {\n  // Get integration settings\n  const settingsResult = execSync(\n    `postiz integrations:settings ${integrationId}`,\n    { encoding: 'utf-8' }\n  );\n  const schema = JSON.parse(settingsResult);\n\n  // Check character limit\n  if (content.length > schema.output.maxLength) {\n    console.warn(`Content exceeds ${schema.output.maxLength} chars, truncating...`);\n    content = content.substring(0, schema.output.maxLength - 3) + '...';\n  }\n\n  // Create post\n  const result = execSync(\n    `postiz posts:create -c \"${content}\" -s \"2024-12-31T12:00:00Z\" --settings '${JSON.stringify(settings)}' -i \"${integrationId}\"`,\n    { encoding: 'utf-8' }\n  );\n\n  return JSON.parse(result);\n}\n```\n\n### Pattern 6: Batch Scheduling\n\n```bash\n#!/bin/bash\n\n# Schedule posts for the week\nDATES=(\n  \"2024-02-14T09:00:00Z\"\n  \"2024-02-15T09:00:00Z\"\n  \"2024-02-16T09:00:00Z\"\n)\n\nCONTENT=(\n  \"Monday motivation 💪\"\n  \"Tuesday tips 💡\"\n  \"Wednesday wisdom 🧠\"\n)\n\nfor i in \"${!DATES[@]}\"; do\n  postiz posts:create \\\n    -c \"${CONTENT[$i]}\" \\\n    -s \"${DATES[$i]}\" \\\n    -i \"twitter-id\" \\\n    -m \"post-${i}.jpg\"\n  echo \"Scheduled: ${CONTENT[$i]} for ${DATES[$i]}\"\ndone\n```\n\n### Pattern 7: Error Handling & Retry\n\n```javascript\nconst { execSync } = require('child_process');\n\nasync function postWithRetry(content, integrationId, date, maxRetries = 3) {\n  for (let attempt = 1; attempt <= maxRetries; attempt++) {\n    try {\n      const result = execSync(\n        `postiz posts:create -c \"${content}\" -s \"${date}\" -i \"${integrationId}\"`,\n        { encoding: 'utf-8', stdio: 'pipe' }\n      );\n      console.log('✅ Post created successfully');\n      return JSON.parse(result);\n    } catch (error) {\n      console.error(`❌ Attempt ${attempt} failed: ${error.message}`);\n\n      if (attempt < maxRetries) {\n        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff\n        console.log(`⏳ Retrying in ${delay}ms...`);\n        await new Promise(resolve => setTimeout(resolve, delay));\n      } else {\n        throw new Error(`Failed after ${maxRetries} attempts`);\n      }\n    }\n  }\n}\n```\n\n---\n\n## Technical Concepts\n\n### Integration Tools Workflow\n\nMany integrations require dynamic data (IDs, tags, playlists) that can't be hardcoded. The tools workflow enables discovery and usage:\n\n1. **Check available tools** - `integrations:settings` returns a `tools` array\n2. **Review tool schema** - Each tool has `methodName`, `description`, and `dataSchema`\n3. **Trigger tool** - Call `integrations:trigger` with required parameters\n4. **Use output** - Tool returns data to use in post settings\n\n**Example tools by platform:**\n- **Reddit**: `getFlairs`, `searchSubreddits`, `getSubreddits`\n- **YouTube**: `getPlaylists`, `getCategories`, `getChannels`\n- **LinkedIn**: `getCompanies`, `getOrganizations`\n- **Twitter/X**: `getListsowned`, `getCommunities`\n- **Pinterest**: `getBoards`, `getBoardSections`\n\n### Provider Settings Structure\n\nPlatform-specific settings use a discriminator pattern with `__type` field:\n\n```json\n{\n  \"posts\": [\n    {\n      \"provider\": \"reddit\",\n      \"post\": [{ \"content\": \"...\", \"image\": [...] }],\n      \"settings\": {\n        \"__type\": \"reddit\",\n        \"subreddit\": [{\n          \"value\": {\n            \"subreddit\": \"programming\",\n            \"title\": \"Post Title\",\n            \"type\": \"text\",\n            \"url\": \"\",\n            \"is_flair_required\": false\n          }\n        }]\n      }\n    }\n  ]\n}\n```\n\nPass settings directly:\n```bash\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" --settings '{\"subreddit\":[...]}' -i \"reddit-id\"\n# Backend automatically adds \"__type\" based on integration ID\n```\n\n### Comments and Threading\n\nPosts can have comments (threads on Twitter/X, replies elsewhere). Each comment can have its own media:\n\n```bash\n# Using multiple -c and -m flags\npostiz posts:create \\\n  -c \"Main post\" -m \"image1.jpg,image2.jpg\" \\\n  -c \"Comment 1\" -m \"comment-img.jpg\" \\\n  -c \"Comment 2\" -m \"another.jpg,more.jpg\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -d 5000 \\  # Delay between comments in ms\n  -i \"integration-id\"\n```\n\nInternally creates:\n```json\n{\n  \"posts\": [{\n    \"value\": [\n      { \"content\": \"Main post\", \"image\": [\"image1.jpg\", \"image2.jpg\"] },\n      { \"content\": \"Comment 1\", \"image\": [\"comment-img.jpg\"], \"delay\": 5000 },\n      { \"content\": \"Comment 2\", \"image\": [\"another.jpg\", \"more.jpg\"], \"delay\": 5000 }\n    ]\n  }]\n}\n```\n\n### Date Handling\n\nAll dates use ISO 8601 format:\n- Schedule posts: `-s \"2024-12-31T12:00:00Z\"`\n- List posts: `--startDate \"2024-01-01T00:00:00Z\" --endDate \"2024-12-31T23:59:59Z\"`\n- Defaults: `posts:list` uses 30 days ago to 30 days from now\n\n### Media Upload Response\n\nUpload returns JSON with path and metadata:\n```json\n{\n  \"path\": \"https://cdn.postiz.com/uploads/abc123.jpg\",\n  \"size\": 123456,\n  \"type\": \"image/jpeg\"\n}\n```\n\nExtract path for use in posts:\n```bash\nRESULT=$(postiz upload image.jpg)\nPATH=$(echo \"$RESULT\" | jq -r '.path')\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -m \"$PATH\" -i \"integration-id\"\n```\n\n### JSON Mode vs CLI Flags\n\n**CLI flags** - Quick posts:\n```bash\npostiz posts:create -c \"Content\" -m \"img.jpg\" -i \"twitter-id\"\n```\n\n**JSON mode** - Complex posts with multiple platforms and settings:\n```bash\npostiz posts:create --json post.json\n```\n\nJSON mode supports:\n- Multiple platforms with different content per platform\n- Complex provider-specific settings\n- Scheduled posts\n- Posts with many comments\n- Custom delay between comments\n\n---\n\n## Platform-Specific Examples\n\n### Reddit\n```bash\npostiz posts:create \\\n  -c \"Post content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"My Title\",\"type\":\"text\",\"url\":\"\",\"is_flair_required\":false}}]}' \\\n  -i \"reddit-id\"\n```\n\n### YouTube\n```bash\n# Upload video first (required!)\nVIDEO=$(postiz upload video.mp4)\nVIDEO_URL=$(echo \"$VIDEO\" | jq -r '.path')\n\npostiz posts:create \\\n  -c \"Video description\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"title\":\"Video Title\",\"type\":\"public\",\"tags\":[{\"value\":\"tech\",\"label\":\"Tech\"}]}' \\\n  -m \"$VIDEO_URL\" \\\n  -i \"youtube-id\"\n```\n\n### TikTok\n```bash\n# Upload video first (TikTok only accepts verified URLs!)\nVIDEO=$(postiz upload video.mp4)\nVIDEO_URL=$(echo \"$VIDEO\" | jq -r '.path')\n\npostiz posts:create \\\n  -c \"Video caption #fyp\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"privacy\":\"PUBLIC_TO_EVERYONE\",\"duet\":true,\"stitch\":true}' \\\n  -m \"$VIDEO_URL\" \\\n  -i \"tiktok-id\"\n```\n\n### X (Twitter)\n```bash\npostiz posts:create \\\n  -c \"Tweet content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"who_can_reply_post\":\"everyone\"}' \\\n  -i \"twitter-id\"\n```\n\n### LinkedIn\n```bash\n# Personal post\npostiz posts:create -c \"Content\" -s \"2024-12-31T12:00:00Z\" -i \"linkedin-id\"\n\n# Company post\npostiz posts:create \\\n  -c \"Content\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"companyId\":\"company-123\"}' \\\n  -i \"linkedin-id\"\n```\n\n### Instagram\n```bash\n# Upload image first (Instagram requires verified URLs!)\nIMAGE=$(postiz upload image.jpg)\nIMAGE_URL=$(echo \"$IMAGE\" | jq -r '.path')\n\n# Regular post\npostiz posts:create \\\n  -c \"Caption #hashtag\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"post_type\":\"post\"}' \\\n  -m \"$IMAGE_URL\" \\\n  -i \"instagram-id\"\n\n# Story\nSTORY=$(postiz upload story.jpg)\nSTORY_URL=$(echo \"$STORY\" | jq -r '.path')\n\npostiz posts:create \\\n  -c \"\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  --settings '{\"post_type\":\"story\"}' \\\n  -m \"$STORY_URL\" \\\n  -i \"instagram-id\"\n```\n\n---\n\n## Supporting Resources\n\n**Deep-dive documentation:**\n- [HOW_TO_RUN.md](./HOW_TO_RUN.md) - Installation and setup methods\n- [COMMAND_LINE_GUIDE.md](./COMMAND_LINE_GUIDE.md) - Complete command syntax reference\n- [PROVIDER_SETTINGS.md](./PROVIDER_SETTINGS.md) - All 28+ platform settings schemas\n- [INTEGRATION_TOOLS_WORKFLOW.md](./INTEGRATION_TOOLS_WORKFLOW.md) - Complete tools workflow guide\n- [INTEGRATION_SETTINGS_DISCOVERY.md](./INTEGRATION_SETTINGS_DISCOVERY.md) - Settings discovery workflow\n- [SUPPORTED_FILE_TYPES.md](./SUPPORTED_FILE_TYPES.md) - All supported media formats\n- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - Code architecture\n- [PUBLISHING.md](./PUBLISHING.md) - npm publishing guide\n\n**Ready-to-use examples:**\n- [examples/EXAMPLES.md](./examples/EXAMPLES.md) - Comprehensive examples\n- [examples/basic-usage.sh](./examples/basic-usage.sh) - Shell script basics\n- [examples/ai-agent-example.js](./examples/ai-agent-example.js) - Node.js agent\n- [examples/post-with-comments.json](./examples/post-with-comments.json) - Threading example\n- [examples/multi-platform-with-settings.json](./examples/multi-platform-with-settings.json) - Campaign example\n- [examples/youtube-video.json](./examples/youtube-video.json) - YouTube with tags\n- [examples/reddit-post.json](./examples/reddit-post.json) - Reddit with subreddit\n- [examples/tiktok-video.json](./examples/tiktok-video.json) - TikTok with privacy\n\n---\n\n## Common Gotchas\n\n1. **API Key not set** - Always `export POSTIZ_API_KEY=key` before using CLI\n2. **Invalid integration ID** - Run `integrations:list` to get current IDs\n3. **Settings schema mismatch** - Check `integrations:settings` for required fields\n4. **Media MUST be uploaded to Postiz first** - ⚠️ **CRITICAL:** TikTok, Instagram, YouTube, and many platforms only accept verified URLs. Upload files via `postiz upload` first, then use the returned URL in `-m`. External URLs will be rejected!\n5. **JSON escaping in shell** - Use single quotes for JSON: `--settings '{...}'`\n6. **Date format** - Must be ISO 8601: `\"2024-12-31T12:00:00Z\"` and is REQUIRED\n7. **Tool not found** - Check available tools in `integrations:settings` output\n8. **Character limits** - Each platform has different limits, check `maxLength` in settings\n9. **Required settings** - Some platforms require specific settings (Reddit needs title, YouTube needs title)\n10. **Media MIME types** - CLI auto-detects from file extension, ensure correct extension\n\n---\n\n## Quick Reference\n\n```bash\n# Environment\nexport POSTIZ_API_KEY=key\n\n# Discovery\npostiz integrations:list                           # Get integration IDs\npostiz integrations:settings <id>                  # Get settings schema\npostiz integrations:trigger <id> <method> -d '{}'  # Fetch dynamic data\n\n# Posting (date is REQUIRED)\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" -i \"id\"                  # Simple\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" -t draft -i \"id\"        # Draft\npostiz posts:create -c \"text\" -m \"img.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"id\"    # With media\npostiz posts:create -c \"main\" -c \"comment\" -s \"2024-12-31T12:00:00Z\" -i \"id\"    # With comment\npostiz posts:create -c \"text\" -s \"2024-12-31T12:00:00Z\" --settings '{}' -i \"id\" # Platform-specific\npostiz posts:create --json file.json                                             # Complex\n\n# Management\npostiz posts:list                                  # List posts\npostiz posts:delete <id>                          # Delete post\npostiz upload <file>                              # Upload media\n\n# Help\npostiz --help                                     # Show help\npostiz posts:create --help                        # Command help\n```\n"
  },
  {
    "path": "apps/cli/SUMMARY.md",
    "content": "# Postiz CLI - Creation Summary\n\n## ✅ What Was Created\n\nA complete, production-ready CLI package for the Postiz API has been successfully created at `apps/cli/`.\n\n### Package Details\n\n- **Package Name:** `postiz`\n- **Version:** 1.0.0\n- **Executable:** `postiz` command\n- **Lines of Code:** 359 lines\n- **Build Size:** ~491KB (compressed)\n- **License:** AGPL-3.0\n\n## 📦 Package Structure\n\n```\napps/cli/\n├── src/                          # Source code (359 lines)\n│   ├── index.ts                  # CLI entry point with yargs\n│   ├── api.ts                    # Postiz API client\n│   ├── config.ts                 # Environment configuration\n│   └── commands/\n│       ├── posts.ts              # Post management\n│       ├── integrations.ts       # Integration listing\n│       └── upload.ts             # Media upload\n│\n├── examples/                     # Usage examples\n│   ├── basic-usage.sh            # Bash example\n│   └── ai-agent-example.js       # AI agent example\n│\n├── Documentation (5 files)\n│   ├── README.md                 # Main documentation\n│   ├── SKILL.md                  # AI agent guide\n│   ├── QUICK_START.md            # Quick start guide\n│   ├── CHANGELOG.md              # Version history\n│   └── PROJECT_STRUCTURE.md      # Architecture docs\n│\n└── Configuration\n    ├── package.json              # Package config\n    ├── tsconfig.json             # TypeScript config\n    ├── tsup.config.ts            # Build config\n    ├── .gitignore                # Git ignore\n    └── .npmignore                # npm ignore\n```\n\n## 🚀 Features Implemented\n\n### Commands\n\n1. **posts:create** - Create social media posts\n   - ✅ Content input\n   - ✅ Integration selection\n   - ✅ Scheduled posting\n   - ✅ Image attachment\n\n2. **posts:list** - List all posts\n   - ✅ Pagination support\n   - ✅ Search functionality\n   - ✅ Filtering options\n\n3. **posts:delete** - Delete posts by ID\n   - ✅ ID-based deletion\n   - ✅ Confirmation messages\n\n4. **integrations:list** - Show connected accounts\n   - ✅ List all integrations\n   - ✅ Show provider info\n\n5. **upload** - Upload media files\n   - ✅ Image upload support\n   - ✅ Multiple formats (PNG, JPG, GIF)\n\n### Technical Features\n\n- ✅ Environment variable configuration (POSTIZ_API_KEY)\n- ✅ Custom API URL support (POSTIZ_API_URL)\n- ✅ Comprehensive error handling\n- ✅ User-friendly error messages with emojis\n- ✅ JSON output for programmatic parsing\n- ✅ Executable shebang for direct execution\n- ✅ TypeScript with proper types\n- ✅ Source maps for debugging\n- ✅ Build optimization with tsup\n\n## 📚 Documentation Created\n\n1. **README.md** (Primary documentation)\n   - Installation instructions\n   - Usage examples\n   - API reference\n   - Development guide\n\n2. **SKILL.md** (AI Agent Guide)\n   - Comprehensive patterns for AI agents\n   - Usage examples\n   - Workflow suggestions\n   - Best practices\n   - Error handling\n\n3. **QUICK_START.md**\n   - Fast onboarding\n   - Common workflows\n   - Troubleshooting\n   - Tips & tricks\n\n4. **CHANGELOG.md**\n   - Version 1.0.0 release notes\n   - Feature list\n\n5. **PROJECT_STRUCTURE.md**\n   - Architecture overview\n   - File descriptions\n   - Build process\n   - Integration points\n\n## 🔧 Build System Integration\n\n### Root package.json Scripts Added\n\n```json\n{\n  \"build:cli\": \"rm -rf apps/cli/dist && pnpm --filter ./apps/cli run build\",\n  \"publish-cli\": \"pnpm run --filter ./apps/cli publish\"\n}\n```\n\n### CLI Package Scripts\n\n```json\n{\n  \"dev\": \"tsup --watch\",\n  \"build\": \"tsup\",\n  \"start\": \"node ./dist/index.js\",\n  \"publish\": \"tsup && pnpm publish --access public\"\n}\n```\n\n## 🎯 Usage Examples\n\n### Basic Usage\n\n```bash\n# Set API key\nexport POSTIZ_API_KEY=your_api_key\n\n# Create a post\npostiz posts:create -c \"Hello World!\" -i \"twitter-123\"\n\n# List posts\npostiz posts:list\n\n# Upload media\npostiz upload ./image.png\n```\n\n### AI Agent Usage\n\n```javascript\nconst { execSync } = require('child_process');\n\nfunction postToSocial(content) {\n  return execSync(`postiz posts:create -c \"${content}\"`, {\n    env: { ...process.env, POSTIZ_API_KEY: 'your_key' }\n  });\n}\n```\n\n## ✨ Example Files\n\n1. **basic-usage.sh**\n   - Shell script demonstration\n   - Complete workflow example\n   - Error handling\n\n2. **ai-agent-example.js**\n   - Node.js agent implementation\n   - Batch post creation\n   - JSON parsing\n\n## 🧪 Testing\n\n### Manual Testing Completed\n\n```bash\n✅ Build successful (173ms)\n✅ Help command works\n✅ Version command works (1.0.0)\n✅ Error handling works (API key validation)\n✅ All commands have help text\n✅ Examples are valid\n```\n\n### Test Results\n\n```\n✅ pnpm run build:cli - SUCCESS\n✅ postiz --help - SUCCESS\n✅ postiz --version - SUCCESS\n✅ postiz posts:create --help - SUCCESS\n✅ Error without API key - WORKS AS EXPECTED\n```\n\n## 📋 Checklist\n\n- ✅ CLI package created in apps/cli\n- ✅ Package name is \"postiz\"\n- ✅ Uses POSTIZ_API_KEY environment variable\n- ✅ Integrates with Postiz public API\n- ✅ Built for AI agent usage\n- ✅ SKILL.md created with comprehensive guide\n- ✅ README.md with full documentation\n- ✅ Build system configured\n- ✅ TypeScript compilation working\n- ✅ Executable binary generated\n- ✅ Examples provided\n- ✅ Error handling implemented\n- ✅ Help documentation complete\n\n## 🚦 Next Steps\n\n### To Use Locally\n\n```bash\n# Build the CLI\npnpm run build:cli\n\n# Test it\nnode apps/cli/dist/index.js --help\n\n# Link globally (optional)\ncd apps/cli\npnpm link --global\n\n# Use anywhere\npostiz --help\n```\n\n### To Publish to npm\n\n```bash\n# From monorepo root\npnpm run publish-cli\n\n# Or from apps/cli\ncd apps/cli\npnpm run publish\n```\n\n### To Use in AI Agents\n\n1. Install: `npm install -g postiz`\n2. Set API key: `export POSTIZ_API_KEY=your_key`\n3. Use commands programmatically\n4. Parse JSON output\n5. See SKILL.md for patterns\n\n## 📊 Statistics\n\n- **Total Files Created:** 18\n- **Source Code Files:** 6\n- **Documentation Files:** 5\n- **Example Files:** 2\n- **Config Files:** 5\n- **Total Lines of Code:** 359\n- **Build Time:** ~170ms\n- **Output Size:** 491KB\n\n## 🎉 Summary\n\nA complete, production-ready CLI tool for Postiz has been created with:\n\n- ✅ All requested features implemented\n- ✅ Comprehensive documentation for users and AI agents\n- ✅ Working examples\n- ✅ Proper build system\n- ✅ Ready for npm publishing\n- ✅ Integrated into monorepo\n\nThe CLI is ready to use and can be published to npm whenever you're ready!\n"
  },
  {
    "path": "apps/cli/SUPPORTED_FILE_TYPES.md",
    "content": "# Supported File Types for Upload\n\nThe Postiz CLI now correctly detects and uploads various media types.\n\n## How It Works\n\nThe CLI automatically detects the MIME type based on the file extension:\n\n```bash\npostiz upload video.mp4\n# ✅ Detected as: video/mp4\n\npostiz upload image.png\n# ✅ Detected as: image/png\n\npostiz upload audio.mp3\n# ✅ Detected as: audio/mpeg\n```\n\n## Supported File Types\n\n### Images\n\n| Extension | MIME Type | Supported |\n|-----------|-----------|-----------|\n| `.png` | `image/png` | ✅ Yes |\n| `.jpg`, `.jpeg` | `image/jpeg` | ✅ Yes |\n| `.gif` | `image/gif` | ✅ Yes |\n| `.webp` | `image/webp` | ✅ Yes |\n| `.svg` | `image/svg+xml` | ✅ Yes |\n| `.bmp` | `image/bmp` | ✅ Yes |\n| `.ico` | `image/x-icon` | ✅ Yes |\n\n**Examples:**\n```bash\npostiz upload photo.jpg\npostiz upload logo.png\npostiz upload animation.gif\npostiz upload icon.svg\n```\n\n### Videos\n\n| Extension | MIME Type | Supported |\n|-----------|-----------|-----------|\n| `.mp4` | `video/mp4` | ✅ Yes |\n| `.mov` | `video/quicktime` | ✅ Yes |\n| `.avi` | `video/x-msvideo` | ✅ Yes |\n| `.mkv` | `video/x-matroska` | ✅ Yes |\n| `.webm` | `video/webm` | ✅ Yes |\n| `.flv` | `video/x-flv` | ✅ Yes |\n| `.wmv` | `video/x-ms-wmv` | ✅ Yes |\n| `.m4v` | `video/x-m4v` | ✅ Yes |\n| `.mpeg`, `.mpg` | `video/mpeg` | ✅ Yes |\n| `.3gp` | `video/3gpp` | ✅ Yes |\n\n**Examples:**\n```bash\npostiz upload video.mp4\npostiz upload clip.mov\npostiz upload recording.webm\npostiz upload movie.mkv\n```\n\n### Audio\n\n| Extension | MIME Type | Supported |\n|-----------|-----------|-----------|\n| `.mp3` | `audio/mpeg` | ✅ Yes |\n| `.wav` | `audio/wav` | ✅ Yes |\n| `.ogg` | `audio/ogg` | ✅ Yes |\n| `.aac` | `audio/aac` | ✅ Yes |\n| `.flac` | `audio/flac` | ✅ Yes |\n| `.m4a` | `audio/mp4` | ✅ Yes |\n\n**Examples:**\n```bash\npostiz upload podcast.mp3\npostiz upload song.wav\npostiz upload audio.ogg\n```\n\n### Documents\n\n| Extension | MIME Type | Supported |\n|-----------|-----------|-----------|\n| `.pdf` | `application/pdf` | ✅ Yes |\n| `.doc` | `application/msword` | ✅ Yes |\n| `.docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | ✅ Yes |\n\n**Examples:**\n```bash\npostiz upload document.pdf\npostiz upload report.docx\n```\n\n### Other Files\n\nFor file types not listed above, the CLI uses:\n- MIME type: `application/octet-stream`\n- This is a generic binary file type\n\n## Usage Examples\n\n### Upload an Image\n\n```bash\npostiz upload ./images/photo.jpg\n```\n\nResponse:\n```json\n{\n  \"id\": \"upload-123\",\n  \"path\": \"https://cdn.postiz.com/uploads/photo.jpg\",\n  \"url\": \"https://cdn.postiz.com/uploads/photo.jpg\"\n}\n```\n\n### Upload a Video (MP4)\n\n```bash\npostiz upload ./videos/promo.mp4\n```\n\nResponse:\n```json\n{\n  \"id\": \"upload-456\",\n  \"path\": \"https://cdn.postiz.com/uploads/promo.mp4\",\n  \"url\": \"https://cdn.postiz.com/uploads/promo.mp4\"\n}\n```\n\n### Upload and Use in Post\n\n```bash\n# 1. Upload the file\nRESULT=$(postiz upload video.mp4)\necho $RESULT\n\n# 2. Extract the path (you'll need jq or similar)\nPATH=$(echo $RESULT | jq -r '.path')\n\n# 3. Use in a post\npostiz posts:create \\\n  -c \"Check out my video!\" \\\n  -m \"$PATH\" \\\n  -i \"tiktok-123\"\n```\n\n### Upload Multiple Files\n\n```bash\n# Upload images\npostiz upload image1.jpg\npostiz upload image2.png\npostiz upload image3.gif\n\n# Upload videos\npostiz upload video1.mp4\npostiz upload video2.mov\n```\n\n## What Changed (Fix)\n\n### Before (❌ Bug)\n\n```bash\npostiz upload video.mp4\n# ❌ Was detected as: image/jpeg (WRONG!)\n```\n\nThe problem: The CLI defaulted to `image/jpeg` for any unknown file type.\n\n### After (✅ Fixed)\n\n```bash\npostiz upload video.mp4\n# ✅ Correctly detected as: video/mp4\n\npostiz upload audio.mp3\n# ✅ Correctly detected as: audio/mpeg\n\npostiz upload document.pdf\n# ✅ Correctly detected as: application/pdf\n```\n\n## Platform-Specific Notes\n\n### TikTok\n- Supports: MP4, MOV, WEBM\n- Recommended: MP4\n\n### YouTube\n- Supports: MP4, MOV, AVI, WMV, FLV, 3GP, WEBM\n- Recommended: MP4\n\n### Instagram\n- Images: JPG, PNG\n- Videos: MP4, MOV\n- Recommended: MP4 for videos, JPG for images\n\n### Twitter/X\n- Images: PNG, JPG, GIF, WEBP\n- Videos: MP4, MOV\n- Max video size: 512MB\n\n### LinkedIn\n- Images: PNG, JPG, GIF\n- Videos: MP4, MOV, AVI\n- Documents: PDF, DOC, DOCX, PPT\n\n## Troubleshooting\n\n### \"Upload failed: Unsupported file type\"\n\nSome platforms may not accept certain file types. Check the platform's documentation.\n\n**Solution:** Convert the file to a supported format:\n\n```bash\n# Convert video to MP4\nffmpeg -i video.avi video.mp4\n\n# Then upload\npostiz upload video.mp4\n```\n\n### File Size Limits\n\nDifferent platforms have different file size limits:\n\n- **Twitter/X**: Max 512MB for videos\n- **Instagram**: Max 100MB for videos\n- **TikTok**: Max 287.6MB for videos\n- **YouTube**: Max 128GB (but 256GB for verified)\n\n### \"MIME type mismatch\"\n\nIf you renamed a file with the wrong extension:\n\n```bash\n# ❌ Wrong: PNG file renamed to .jpg\nmv image.png image.jpg\npostiz upload image.jpg  # Might fail\n\n# ✅ Correct: Keep original extension\npostiz upload image.png\n```\n\n## Testing File Upload\n\n```bash\n# Set API key\nexport POSTIZ_API_KEY=your_key\n\n# Test image upload\npostiz upload test-image.jpg\n\n# Test video upload\npostiz upload test-video.mp4\n\n# Test audio upload\npostiz upload test-audio.mp3\n```\n\n## Error Messages\n\n### File Not Found\n```\n❌ ENOENT: no such file or directory\n```\n\n**Solution:** Check the file path is correct.\n\n### No Permission\n```\n❌ EACCES: permission denied\n```\n\n**Solution:** Check file permissions:\n```bash\nchmod 644 your-file.mp4\n```\n\n### Invalid API Key\n```\n❌ Upload failed (401): Unauthorized\n```\n\n**Solution:** Set your API key:\n```bash\nexport POSTIZ_API_KEY=your_key\n```\n\n## Summary\n\n✅ **30+ file types supported**\n✅ **Automatic MIME type detection**\n✅ **Images, videos, audio, documents**\n✅ **Correct handling of MP4, MOV, MP3, etc.**\n✅ **No more defaulting to JPEG!**\n\n**The upload bug is fixed!** 🎉\n"
  },
  {
    "path": "apps/cli/SYNTAX_UPGRADE.md",
    "content": "# Postiz CLI - Improved Syntax! 🎉\n\n## What Changed\n\nThe CLI now supports a **much better** command-line syntax for creating posts with comments that have their own media.\n\n## New Syntax: Multiple `-c` and `-m` Flags\n\nInstead of using semicolon-separated strings (which break when you need semicolons in your content), you can now use multiple `-c` and `-m` flags:\n\n```bash\npostiz posts:create \\\n  -c \"main post content\" -m \"media1.png,media2.png\" \\\n  -c \"first comment\" -m \"media3.png\" \\\n  -c \"second comment; with semicolon!\" -m \"media4.png,media5.png\" \\\n  -i \"twitter-123\"\n```\n\n## The Problem We Solved\n\n### ❌ Old Approach (Problematic)\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  --comments \"Comment 1;Comment 2;Comment 3\" \\\n  -i \"twitter-123\"\n```\n\n**Issues:**\n1. ❌ Can't use semicolons in comment text\n2. ❌ Comments can't have their own media\n3. ❌ Less intuitive syntax\n4. ❌ Limited flexibility\n\n### ✅ New Approach (Better!)\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" -m \"main.jpg\" \\\n  -c \"Comment 1; with semicolon!\" -m \"comment1.jpg\" \\\n  -c \"Comment 2\" -m \"comment2.jpg\" \\\n  -c \"Comment 3\" \\\n  -i \"twitter-123\"\n```\n\n**Benefits:**\n1. ✅ Semicolons work fine in content\n2. ✅ Each comment can have different media\n3. ✅ More readable and intuitive\n4. ✅ Fully flexible\n\n## How It Works\n\n### Pairing Logic\n\nThe CLI pairs `-c` and `-m` flags in order:\n\n```bash\npostiz posts:create \\\n  -c \"Content 1\" -m \"media-for-content-1.jpg\" \\    # Pair 1\n  -c \"Content 2\" -m \"media-for-content-2.jpg\" \\    # Pair 2\n  -c \"Content 3\" -m \"media-for-content-3.jpg\" \\    # Pair 3\n  -i \"twitter-123\"\n```\n\n- **1st `-c`** = Main post\n- **2nd `-c`** = First comment (posted after delay)\n- **3rd `-c`** = Second comment (posted after delay)\n- Each `-m` is paired with the corresponding `-c` (in order)\n\n### Media is Optional\n\n```bash\npostiz posts:create \\\n  -c \"Post with media\" -m \"image.jpg\" \\\n  -c \"Comment without media\" \\\n  -c \"Another comment\" \\\n  -i \"twitter-123\"\n```\n\nResult:\n- Post with image\n- Text-only comment\n- Another text-only comment\n\n### Multiple Media per Post/Comment\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" -m \"img1.jpg,img2.jpg,img3.jpg\" \\\n  -c \"Comment\" -m \"img4.jpg,img5.jpg\" \\\n  -i \"twitter-123\"\n```\n\nResult:\n- Main post with 3 images\n- Comment with 2 images\n\n## Real Examples\n\n### Example 1: Product Launch\n\n```bash\npostiz posts:create \\\n  -c \"🚀 Launching ProductX today!\" \\\n  -m \"hero.jpg,features.jpg\" \\\n  -c \"⭐ Key features you'll love...\" \\\n  -m \"features-detail.jpg\" \\\n  -c \"💰 Special offer: 50% off!\" \\\n  -m \"discount.jpg\" \\\n  -i \"twitter-123,linkedin-456\"\n```\n\n### Example 2: Twitter Thread\n\n```bash\npostiz posts:create \\\n  -c \"🧵 Thread: How to X (1/5)\" -m \"intro.jpg\" \\\n  -c \"Step 1: ... (2/5)\" -m \"step1.jpg\" \\\n  -c \"Step 2: ... (3/5)\" -m \"step2.jpg\" \\\n  -c \"Step 3: ... (4/5)\" -m \"step3.jpg\" \\\n  -c \"Conclusion (5/5)\" -m \"done.jpg\" \\\n  -d 2000 \\\n  -i \"twitter-123\"\n```\n\n### Example 3: Tutorial with Screenshots\n\n```bash\npostiz posts:create \\\n  -c \"Tutorial: Feature X 📖\" \\\n  -m \"tutorial-cover.jpg\" \\\n  -c \"1. Open settings\" \\\n  -m \"settings-screenshot.jpg\" \\\n  -c \"2. Enable feature X\" \\\n  -m \"enable-screenshot.jpg\" \\\n  -c \"3. You're done! 🎉\" \\\n  -m \"success-screenshot.jpg\" \\\n  -i \"twitter-123\"\n```\n\n### Example 4: Content with Special Characters\n\n```bash\npostiz posts:create \\\n  -c \"Main post about programming\" \\\n  -c \"First tip: Use const; avoid var\" \\\n  -c \"Second tip: Functions should do one thing; keep it simple\" \\\n  -c \"Third tip: Comments should explain 'why'; not 'what'\" \\\n  -i \"twitter-123\"\n```\n\n**No escaping needed!** Semicolons work perfectly.\n\n## Options Reference\n\n| Option | Alias | Multiple? | Description |\n|--------|-------|-----------|-------------|\n| `--content` | `-c` | ✅ Yes | Post/comment content |\n| `--media` | `-m` | ✅ Yes | Comma-separated media URLs |\n| `--integrations` | `-i` | ❌ No | Integration IDs |\n| `--schedule` | `-s` | ❌ No | ISO 8601 date |\n| `--delay` | `-d` | ❌ No | Delay between comments (ms, default: 5000) |\n| `--shortLink` | - | ❌ No | Use URL shortener (default: true) |\n| `--json` | `-j` | ❌ No | Load from JSON file |\n\n## Delay Between Comments\n\nUse `-d` to control the delay between comments:\n\n```bash\npostiz posts:create \\\n  -c \"Main\" \\\n  -c \"Comment 1\" \\\n  -c \"Comment 2\" \\\n  -d 10000 \\    # 10 seconds between each\n  -i \"twitter-123\"\n```\n\n**Default:** 5000ms (5 seconds)\n\n## Command Line vs JSON\n\n### Use Command Line When:\n- ✅ Quick posts\n- ✅ Same content for all platforms\n- ✅ Simple structure\n- ✅ Dynamic/scripted content\n\n### Use JSON When:\n- ✅ Different content per platform\n- ✅ Very complex structures\n- ✅ Reusable templates\n- ✅ Integration with other tools\n\n## For AI Agents\n\n### Generating Commands\n\n```javascript\nfunction buildPostCommand(posts, integrationId) {\n  const parts = ['postiz posts:create'];\n\n  posts.forEach(post => {\n    parts.push(`-c \"${post.content.replace(/\"/g, '\\\\\"')}\"`);\n    if (post.media && post.media.length > 0) {\n      parts.push(`-m \"${post.media.join(',')}\"`);\n    }\n  });\n\n  parts.push(`-i \"${integrationId}\"`);\n\n  return parts.join(' \\\\\\n  ');\n}\n\n// Usage\nconst posts = [\n  { content: \"Main post\", media: [\"img1.jpg\", \"img2.jpg\"] },\n  { content: \"Comment; with semicolon!\", media: [\"img3.jpg\"] },\n  { content: \"Another comment\", media: [] }\n];\n\nconst command = buildPostCommand(posts, \"twitter-123\");\nconsole.log(command);\n```\n\nOutput:\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  -m \"img1.jpg,img2.jpg\" \\\n  -c \"Comment; with semicolon!\" \\\n  -m \"img3.jpg\" \\\n  -c \"Another comment\" \\\n  -i \"twitter-123\"\n```\n\n## Migration Guide\n\nIf you have existing scripts using the old syntax:\n\n### Before:\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  --comments \"Comment 1;Comment 2\" \\\n  --image \"main-image.jpg\" \\\n  -i \"twitter-123\"\n```\n\n### After:\n```bash\npostiz posts:create \\\n  -c \"Main post\" -m \"main-image.jpg\" \\\n  -c \"Comment 1\" \\\n  -c \"Comment 2\" \\\n  -i \"twitter-123\"\n```\n\n## Documentation\n\nSee these files for more details:\n\n- **COMMAND_LINE_GUIDE.md** - Comprehensive command-line guide\n- **command-line-examples.sh** - Executable examples\n- **EXAMPLES.md** - Full usage patterns\n- **SKILL.md** - AI agent integration\n- **README.md** - General documentation\n\n## Summary\n\n### ✅ You Can Now:\n\n1. **Use multiple `-c` flags** for main post + comments\n2. **Use multiple `-m` flags** to pair media with each `-c`\n3. **Use semicolons freely** in your content\n4. **Create complex threads** easily from command line\n5. **Each comment has its own media** array\n6. **More intuitive syntax** overall\n\n### 🎯 Perfect For:\n\n- Twitter threads\n- Product launches with follow-ups\n- Tutorials with screenshots\n- Event coverage\n- Multi-step announcements\n- Any post with comments that need their own media!\n\n**The CLI is now much more powerful and user-friendly!** 🚀\n"
  },
  {
    "path": "apps/cli/examples/COMMAND_LINE_GUIDE.md",
    "content": "# Postiz CLI - Command Line Guide\n\n## New Syntax: Multiple `-c` and `-m` Flags\n\nThe CLI now supports a much more intuitive syntax for creating posts with comments that have their own media.\n\n## Basic Syntax\n\n```bash\npostiz posts:create \\\n  -c \"content\" -m \"media\" \\    # Can be repeated multiple times\n  -c \"content\" -m \"media\" \\    # Each pair = one post/comment\n  -i \"integration-id\"\n```\n\n### How It Works\n\n- **First `-c`**: Main post content\n- **Subsequent `-c`**: Comments/replies\n- **Each `-m`**: Media for the corresponding `-c`\n- `-m` is optional (text-only posts/comments)\n- Order matters: `-c` and `-m` are paired in order\n\n## Examples\n\n### 1. Simple Post\n\n```bash\npostiz posts:create \\\n  -c \"Hello World!\" \\\n  -i \"twitter-123\"\n```\n\n### 2. Post with Multiple Images\n\n```bash\npostiz posts:create \\\n  -c \"Check out these photos!\" \\\n  -m \"photo1.jpg,photo2.jpg,photo3.jpg\" \\\n  -i \"twitter-123\"\n```\n\n**Result:**\n- Main post with 3 images\n\n### 3. Post with Comments, Each Having Their Own Media\n\n```bash\npostiz posts:create \\\n  -c \"Main post 🚀\" \\\n  -m \"main-image1.jpg,main-image2.jpg\" \\\n  -c \"First comment 📸\" \\\n  -m \"comment1-image.jpg\" \\\n  -c \"Second comment 🎨\" \\\n  -m \"comment2-img1.jpg,comment2-img2.jpg\" \\\n  -i \"twitter-123\"\n```\n\n**Result:**\n- Main post with 2 images\n- First comment (posted 5s later) with 1 image\n- Second comment (posted 10s later) with 2 images\n\n### 4. Comments Can Contain Semicolons! 🎉\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  -c \"First comment; with a semicolon!\" \\\n  -c \"Second comment; with multiple; semicolons; works fine!\" \\\n  -i \"twitter-123\"\n```\n\n**No escaping needed!** Each `-c` is a separate argument, so special characters work perfectly.\n\n### 5. Twitter Thread\n\n```bash\npostiz posts:create \\\n  -c \"🧵 Thread about X (1/5)\" \\\n  -m \"thread1.jpg\" \\\n  -c \"Key point 1 (2/5)\" \\\n  -m \"thread2.jpg\" \\\n  -c \"Key point 2 (3/5)\" \\\n  -m \"thread3.jpg\" \\\n  -c \"Key point 3 (4/5)\" \\\n  -m \"thread4.jpg\" \\\n  -c \"Conclusion 🎉 (5/5)\" \\\n  -m \"thread5.jpg\" \\\n  -d 2000 \\\n  -i \"twitter-123\"\n```\n\n**Result:** 5-part thread with 2-second delays between tweets\n\n### 6. Mix: Some with Media, Some Without\n\n```bash\npostiz posts:create \\\n  -c \"Amazing sunset! 🌅\" \\\n  -m \"sunset.jpg\" \\\n  -c \"Taken at 6:30 PM\" \\\n  -c \"Location: Santa Monica Beach\" \\\n  -c \"Camera: iPhone 15 Pro\" \\\n  -i \"twitter-123\"\n```\n\n**Result:**\n- Main post with 1 image\n- 3 text-only comments\n\n### 7. Multi-Platform with Same Content\n\n```bash\npostiz posts:create \\\n  -c \"Big announcement! 🎉\" \\\n  -m \"announcement.jpg\" \\\n  -c \"More details coming soon...\" \\\n  -i \"twitter-123,linkedin-456,facebook-789\"\n```\n\n**Result:** Same post + comment posted to all 3 platforms\n\n### 8. Scheduled Post with Follow-ups\n\n```bash\npostiz posts:create \\\n  -c \"Product launching today! 🚀\" \\\n  -m \"product-hero.jpg,product-features.jpg\" \\\n  -c \"Special launch offer: 50% off!\" \\\n  -m \"discount-banner.jpg\" \\\n  -c \"Limited to first 100 customers!\" \\\n  -s \"2024-12-25T09:00:00Z\" \\\n  -i \"twitter-123\"\n```\n\n**Result:** Scheduled main post with 2 follow-up comments\n\n### 9. Product Tutorial\n\n```bash\npostiz posts:create \\\n  -c \"Tutorial: How to Use Feature X 📖\" \\\n  -m \"tutorial-intro.jpg\" \\\n  -c \"Step 1: Open the settings menu\" \\\n  -m \"step1-screenshot.jpg\" \\\n  -c \"Step 2: Toggle the feature on\" \\\n  -m \"step2-screenshot.jpg\" \\\n  -c \"Step 3: Customize your preferences\" \\\n  -m \"step3-screenshot.jpg\" \\\n  -c \"That's it! You're all set 🎉\" \\\n  -d 3000 \\\n  -i \"twitter-123\"\n```\n\n## Options Reference\n\n| Flag | Alias | Description | Multiple? |\n|------|-------|-------------|-----------|\n| `--content` | `-c` | Post/comment content | ✅ Yes |\n| `--media` | `-m` | Comma-separated media URLs | ✅ Yes |\n| `--integrations` | `-i` | Comma-separated integration IDs | ❌ No |\n| `--schedule` | `-s` | ISO 8601 date (schedule post) | ❌ No |\n| `--delay` | `-d` | Delay between comments (ms) | ❌ No |\n| `--shortLink` | - | Use URL shortener | ❌ No |\n| `--json` | `-j` | Load from JSON file | ❌ No |\n\n## How `-c` and `-m` Pair Together\n\n```bash\npostiz posts:create \\\n  -c \"First content\"  -m \"first-media.jpg\" \\     # Pair 1 → Main post\n  -c \"Second content\" -m \"second-media.jpg\" \\    # Pair 2 → Comment 1\n  -c \"Third content\"  -m \"third-media.jpg\" \\     # Pair 3 → Comment 2\n  -i \"twitter-123\"\n```\n\n**Pairing logic:**\n- 1st `-c` pairs with 1st `-m` (if provided)\n- 2nd `-c` pairs with 2nd `-m` (if provided)\n- 3rd `-c` pairs with 3rd `-m` (if provided)\n- If no `-m` for a `-c`, it's text-only\n\n## Delay Between Comments\n\nUse `-d` or `--delay` to set the delay (in milliseconds) between comments:\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" \\\n  -c \"Comment 1\" \\\n  -c \"Comment 2\" \\\n  -d 10000 \\       # 10 seconds between each\n  -i \"twitter-123\"\n```\n\n**Default:** 5000ms (5 seconds)\n\n## Comparison: Old vs New Syntax\n\n### ❌ Old Way (Limited)\n\n```bash\n# Could only do simple comments without custom media\npostiz posts:create \\\n  -c \"Main post\" \\\n  --comments \"Comment 1;Comment 2;Comment 3\" \\\n  --image \"main-image.jpg\" \\\n  -i \"twitter-123\"\n```\n\n**Problems:**\n- Comments couldn't have their own media\n- Semicolons in content would break it\n- Less intuitive\n\n### ✅ New Way (Flexible)\n\n```bash\npostiz posts:create \\\n  -c \"Main post\" -m \"main.jpg\" \\\n  -c \"Comment 1; with semicolon!\" -m \"comment1.jpg\" \\\n  -c \"Comment 2\" -m \"comment2.jpg\" \\\n  -i \"twitter-123\"\n```\n\n**Benefits:**\n- ✅ Each comment can have its own media\n- ✅ Semicolons work fine\n- ✅ More readable\n- ✅ More flexible\n\n## When to Use JSON vs Command Line\n\n### Use Command Line (`-c` and `-m`) When:\n- ✅ Same content for all integrations\n- ✅ Simple, straightforward posts\n- ✅ Quick one-off posts\n- ✅ Scripting with dynamic content\n\n### Use JSON (`--json`) When:\n- ✅ Different content per platform\n- ✅ Complex settings or metadata\n- ✅ Reusable post templates\n- ✅ Very long or formatted content\n\n## Tips for AI Agents\n\n### Generate Commands Programmatically\n\n```javascript\nfunction createThreadCommand(tweets, integrationId) {\n  const parts = [\n    'postiz posts:create'\n  ];\n\n  tweets.forEach(tweet => {\n    parts.push(`-c \"${tweet.content}\"`);\n    if (tweet.media && tweet.media.length > 0) {\n      parts.push(`-m \"${tweet.media.join(',')}\"`);\n    }\n  });\n\n  parts.push(`-i \"${integrationId}\"`);\n\n  return parts.join(' \\\\\\n  ');\n}\n\nconst thread = [\n  { content: \"Tweet 1/3\", media: [\"img1.jpg\"] },\n  { content: \"Tweet 2/3\", media: [\"img2.jpg\"] },\n  { content: \"Tweet 3/3\", media: [\"img3.jpg\"] }\n];\n\nconst command = createThreadCommand(thread, \"twitter-123\");\nconsole.log(command);\n```\n\n### Escape Special Characters\n\nIn bash, you may need to escape some characters:\n\n```bash\n# Single quotes prevent interpolation\npostiz posts:create \\\n  -c 'Message with $variables and \"quotes\"' \\\n  -i \"twitter-123\"\n\n# Or use backslashes\npostiz posts:create \\\n  -c \"Message with \\$variables and \\\"quotes\\\"\" \\\n  -i \"twitter-123\"\n```\n\n## Error Handling\n\n### Missing Integration\n\n```bash\npostiz posts:create -c \"Post\" -m \"img.jpg\"\n# ❌ Error: --integrations is required when not using --json\n```\n\n**Fix:** Add `-i` flag\n\n### No Content\n\n```bash\npostiz posts:create -i \"twitter-123\"\n# ❌ Error: Either --content or --json is required\n```\n\n**Fix:** Add at least one `-c` flag\n\n### Mismatched Count (OK!)\n\n```bash\n# This is fine! Extra -m flags are ignored\npostiz posts:create \\\n  -c \"Post 1\" -m \"img1.jpg\" \\\n  -c \"Post 2\" \\\n  -c \"Post 3\" -m \"img3.jpg\" \\\n  -i \"twitter-123\"\n\n# Result:\n# - Post 1 with img1.jpg\n# - Post 2 with no media\n# - Post 3 with img3.jpg\n```\n\n## Full Example: Product Launch\n\n```bash\n#!/bin/bash\n\nexport POSTIZ_API_KEY=your_key\n\npostiz posts:create \\\n  -c \"🚀 Launching ProductX today!\" \\\n  -m \"https://cdn.example.com/hero.jpg,https://cdn.example.com/features.jpg\" \\\n  -c \"🎯 Key Features:\\n• AI-powered\\n• Cloud-native\\n• Open source\" \\\n  -m \"https://cdn.example.com/features-detail.jpg\" \\\n  -c \"💰 Special launch pricing: 50% off for early adopters!\" \\\n  -m \"https://cdn.example.com/pricing.jpg\" \\\n  -c \"🔗 Get started: https://example.com/productx\" \\\n  -s \"2024-12-25T09:00:00Z\" \\\n  -d 3600000 \\\n  -i \"twitter-123,linkedin-456,facebook-789\"\n\necho \"✅ Product launch scheduled!\"\n```\n\n## See Also\n\n- **EXAMPLES.md** - JSON file examples\n- **SKILL.md** - AI agent patterns\n- **README.md** - Full documentation\n- **examples/*.json** - Template files\n"
  },
  {
    "path": "apps/cli/examples/EXAMPLES.md",
    "content": "# Postiz CLI - Advanced Examples\n\nThis directory contains examples demonstrating the full capabilities of the Postiz CLI, including posts with comments and multiple media.\n\n## Understanding the Post Structure\n\nThe Postiz API supports a rich post structure:\n\n```typescript\n{\n  type: 'now' | 'schedule' | 'draft' | 'update',\n  date: string,              // ISO 8601 date\n  shortLink: boolean,        // Use URL shortener\n  tags: Tag[],              // Post tags\n  posts: [                  // Can post to multiple platforms at once\n    {\n      integration: { id: string },    // Platform integration ID\n      value: [                        // Main post + comments/thread\n        {\n          content: string,            // Post/comment text\n          image: MediaDto[],          // Multiple media attachments\n          delay?: number              // Delay in ms before posting (for comments)\n        },\n        // ... more comments\n      ],\n      settings: { __type: 'EmptySettings' }\n    }\n  ]\n}\n```\n\n## Simple Usage Examples\n\n### Basic Post\n\n```bash\npostiz posts:create \\\n  -c \"Hello World!\" \\\n  -i \"twitter-123\"\n```\n\n### Post with Multiple Images\n\n```bash\npostiz posts:create \\\n  -c \"Check out these images!\" \\\n  --image \"https://example.com/img1.jpg,https://example.com/img2.jpg,https://example.com/img3.jpg\" \\\n  -i \"twitter-123\"\n```\n\n### Post with Comments (Simple)\n\n```bash\npostiz posts:create \\\n  -c \"Main post content\" \\\n  --comments \"First comment;Second comment;Third comment\" \\\n  -i \"twitter-123\"\n```\n\n### Scheduled Post\n\n```bash\npostiz posts:create \\\n  -c \"Future post\" \\\n  -s \"2024-12-31T12:00:00Z\" \\\n  -i \"twitter-123,linkedin-456\"\n```\n\n## Advanced JSON Examples\n\nFor complex posts with comments that have their own media, use JSON files:\n\n### 1. Post with Comments and Media\n\n**File:** `post-with-comments.json`\n\n```bash\npostiz posts:create --json examples/post-with-comments.json\n```\n\nThis creates:\n- Main post with 2 images\n- First comment with 1 image (posted 5s after main)\n- Second comment with 2 images (posted 10s after main)\n\n### 2. Multi-Platform Campaign\n\n**File:** `multi-platform-post.json`\n\n```bash\npostiz posts:create --json examples/multi-platform-post.json\n```\n\nThis creates:\n- Twitter post with main + comment\n- LinkedIn post with single content\n- Facebook post with main + comment\nAll scheduled for the same time with platform-specific content and media!\n\n### 3. Twitter Thread\n\n**File:** `thread-post.json`\n\n```bash\npostiz posts:create --json examples/thread-post.json\n```\n\nThis creates a 5-part Twitter thread, with each tweet having its own image and a 2-second delay between tweets.\n\n## JSON File Structure Explained\n\n### Basic Structure\n\n```json\n{\n  \"type\": \"now\",                    // \"now\", \"schedule\", \"draft\", \"update\"\n  \"date\": \"2024-01-15T12:00:00Z\",  // When to post (ISO 8601)\n  \"shortLink\": true,                // Enable URL shortening\n  \"tags\": [],                       // Array of tags\n  \"posts\": [...]                    // Array of posts\n}\n```\n\n### Post Structure\n\n```json\n{\n  \"integration\": {\n    \"id\": \"twitter-123\"              // Get this from integrations:list\n  },\n  \"value\": [                         // Array of content (main + comments)\n    {\n      \"content\": \"Post text\",        // The actual content\n      \"image\": [                     // Array of media\n        {\n          \"id\": \"unique-id\",         // Unique identifier\n          \"path\": \"https://...\"      // URL to the image\n        }\n      ],\n      \"delay\": 5000                  // Optional delay in milliseconds\n    }\n  ],\n  \"settings\": {\n    \"__type\": \"EmptySettings\"        // Platform-specific settings\n  }\n}\n```\n\n## Use Cases\n\n### 1. Product Launch Campaign\n\nCreate a coordinated multi-platform launch:\n\n```json\n{\n  \"type\": \"schedule\",\n  \"date\": \"2024-03-15T09:00:00Z\",\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"twitter-id\" },\n      \"value\": [\n        { \"content\": \"🚀 Launching today!\", \"image\": [...] },\n        { \"content\": \"Special features:\", \"image\": [...], \"delay\": 3600000 },\n        { \"content\": \"Get it now:\", \"image\": [...], \"delay\": 7200000 }\n      ]\n    },\n    {\n      \"integration\": { \"id\": \"linkedin-id\" },\n      \"value\": [\n        { \"content\": \"Professional announcement...\", \"image\": [...] }\n      ]\n    }\n  ]\n}\n```\n\n### 2. Tutorial Series\n\nCreate an educational thread:\n\n```json\n{\n  \"type\": \"now\",\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"twitter-id\" },\n      \"value\": [\n        { \"content\": \"🧵 How to X (1/5)\", \"image\": [...] },\n        { \"content\": \"Step 1: ... (2/5)\", \"image\": [...], \"delay\": 2000 },\n        { \"content\": \"Step 2: ... (3/5)\", \"image\": [...], \"delay\": 2000 },\n        { \"content\": \"Step 3: ... (4/5)\", \"image\": [...], \"delay\": 2000 },\n        { \"content\": \"Conclusion (5/5)\", \"image\": [...], \"delay\": 2000 }\n      ]\n    }\n  ]\n}\n```\n\n### 3. Event Coverage\n\nLive event updates with media:\n\n```json\n{\n  \"type\": \"now\",\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"twitter-id\" },\n      \"value\": [\n        {\n          \"content\": \"📍 Event starting now!\",\n          \"image\": [\n            { \"id\": \"1\", \"path\": \"venue-photo.jpg\" }\n          ]\n        },\n        {\n          \"content\": \"First speaker taking stage\",\n          \"image\": [\n            { \"id\": \"2\", \"path\": \"speaker-photo.jpg\" }\n          ],\n          \"delay\": 1800000\n        }\n      ]\n    }\n  ]\n}\n```\n\n## Getting Integration IDs\n\nBefore creating posts, get your integration IDs:\n\n```bash\npostiz integrations:list\n```\n\nOutput:\n```json\n[\n  { \"id\": \"abc-123-twitter\", \"provider\": \"twitter\", \"name\": \"@myaccount\" },\n  { \"id\": \"def-456-linkedin\", \"provider\": \"linkedin\", \"name\": \"My Company\" }\n]\n```\n\nUse these IDs in your `integration.id` fields.\n\n## Tips for AI Agents\n\n1. **Use JSON for complex posts** - If you need comments with media, always use JSON files\n2. **Delays matter** - Use appropriate delays between comments (Twitter: 2-5s, others: 30s-1min)\n3. **Image IDs** - Generate unique IDs for each image (can use UUIDs or random strings)\n4. **Validate before sending** - Check that all integration IDs exist\n5. **Test with \"draft\" type** - Use `\"type\": \"draft\"` to create without posting\n\n## Automation Scripts\n\n### Batch Create from Directory\n\n```bash\n#!/bin/bash\n# Create posts from all JSON files in a directory\n\nfor file in posts/*.json; do\n  echo \"Creating post from $file...\"\n  postiz posts:create --json \"$file\"\n  sleep 2\ndone\n```\n\n### Generate JSON Programmatically\n\n```javascript\nconst fs = require('fs');\n\nfunction createThreadPost(tweets, integrationId) {\n  return {\n    type: 'now',\n    date: new Date().toISOString(),\n    shortLink: true,\n    tags: [],\n    posts: [{\n      integration: { id: integrationId },\n      value: tweets.map((tweet, i) => ({\n        content: tweet.content,\n        image: tweet.images || [],\n        delay: i === 0 ? undefined : 2000\n      })),\n      settings: { __type: 'EmptySettings' }\n    }]\n  };\n}\n\nconst thread = createThreadPost([\n  { content: 'Tweet 1', images: [...] },\n  { content: 'Tweet 2', images: [...] },\n  { content: 'Tweet 3', images: [...] }\n], 'twitter-123');\n\nfs.writeFileSync('thread.json', JSON.stringify(thread, null, 2));\n```\n\n## Error Handling\n\nCommon errors and solutions:\n\n1. **Invalid integration ID** - Run `integrations:list` to get valid IDs\n2. **Invalid image path** - Ensure images are accessible URLs or uploaded to Postiz first\n3. **Missing required fields** - Check that `type`, `date`, `shortLink`, `tags`, and `posts` are all present\n4. **Invalid date format** - Use ISO 8601 format: `YYYY-MM-DDTHH:mm:ssZ`\n\n## Further Reading\n\n- See `SKILL.md` for AI agent patterns\n- See `README.md` for installation and setup\n- See `QUICK_START.md` for basic usage\n"
  },
  {
    "path": "apps/cli/examples/ai-agent-example.js",
    "content": "#!/usr/bin/env node\n\n/**\n * Example: Using Postiz CLI from an AI Agent (Node.js)\n *\n * This demonstrates how AI agents can programmatically use the Postiz CLI\n * to schedule social media posts.\n */\n\nconst { execSync } = require('child_process');\n\n// Configuration\nconst POSTIZ_API_KEY = process.env.POSTIZ_API_KEY;\n\nif (!POSTIZ_API_KEY) {\n  console.error('❌ POSTIZ_API_KEY environment variable is required');\n  process.exit(1);\n}\n\n/**\n * Execute a Postiz CLI command\n */\nfunction runPostizCommand(command) {\n  try {\n    const output = execSync(`postiz ${command}`, {\n      env: { ...process.env, POSTIZ_API_KEY },\n      encoding: 'utf-8',\n    });\n    return JSON.parse(output);\n  } catch (error) {\n    console.error(`Command failed: ${command}`);\n    console.error(error.message);\n    throw error;\n  }\n}\n\n/**\n * Main AI Agent workflow\n */\nasync function main() {\n  console.log('🤖 AI Agent: Starting social media scheduling workflow...\\n');\n\n  try {\n    // Step 1: Get available integrations\n    console.log('📋 Fetching connected integrations...');\n    const integrations = runPostizCommand('integrations:list');\n    console.log(`Found ${integrations.length || 0} integrations\\n`);\n\n    // Step 2: Create multiple scheduled posts\n    const posts = [\n      {\n        content: '🌅 Good morning! Starting the day with positive energy.',\n        schedule: getScheduledTime(9, 0), // 9 AM\n      },\n      {\n        content: '☕ Midday motivation: Keep pushing towards your goals!',\n        schedule: getScheduledTime(12, 0), // 12 PM\n      },\n      {\n        content: '🌙 Evening reflection: What did you accomplish today?',\n        schedule: getScheduledTime(20, 0), // 8 PM\n      },\n    ];\n\n    console.log('📝 Creating scheduled posts...');\n    for (let i = 0; i < posts.length; i++) {\n      const post = posts[i];\n      console.log(`  ${i + 1}. Creating post scheduled for ${post.schedule}...`);\n\n      const command = `posts:create -c \"${post.content}\" -s \"${post.schedule}\"`;\n      const result = runPostizCommand(command);\n\n      console.log(`  ✅ Post created with ID: ${result.id || 'unknown'}`);\n    }\n\n    console.log('\\n📊 Checking created posts...');\n    const postsList = runPostizCommand('posts:list -l 5');\n    console.log(`Total recent posts: ${postsList.total || 0}\\n`);\n\n    console.log('✅ AI Agent workflow completed successfully!');\n  } catch (error) {\n    console.error('\\n❌ AI Agent workflow failed:', error.message);\n    process.exit(1);\n  }\n}\n\n/**\n * Helper: Get ISO 8601 timestamp for today at specific time\n */\nfunction getScheduledTime(hours, minutes) {\n  const date = new Date();\n  date.setHours(hours, minutes, 0, 0);\n\n  // If time already passed today, schedule for tomorrow\n  if (date < new Date()) {\n    date.setDate(date.getDate() + 1);\n  }\n\n  return date.toISOString();\n}\n\n// Run the agent\nmain().catch(console.error);\n"
  },
  {
    "path": "apps/cli/examples/basic-usage.sh",
    "content": "#!/bin/bash\n\n# Basic Postiz CLI Usage Example\n# Make sure to set your API key first: export POSTIZ_API_KEY=your_key\n\necho \"🚀 Postiz CLI Example Workflow\"\necho \"\"\n\n# Check if API key is set\nif [ -z \"$POSTIZ_API_KEY\" ]; then\n    echo \"❌ POSTIZ_API_KEY is not set!\"\n    echo \"Set it with: export POSTIZ_API_KEY=your_api_key\"\n    exit 1\nfi\n\necho \"✅ API key is set\"\necho \"\"\n\n# 1. List integrations\necho \"📋 Step 1: Listing connected integrations...\"\npostiz integrations:list\necho \"\"\n\n# 2. Create a post\necho \"📝 Step 2: Creating a test post...\"\npostiz posts:create \\\n  -c \"Hello from Postiz CLI! This is an automated test post.\" \\\n  -s \"$(date -u -v+1H +%Y-%m-%dT%H:%M:%SZ)\" # Schedule 1 hour from now\necho \"\"\n\n# 3. List posts\necho \"📋 Step 3: Listing recent posts...\"\npostiz posts:list -l 5\necho \"\"\n\necho \"✅ Example workflow completed!\"\necho \"\"\necho \"💡 Tips:\"\necho \"  - Use -i flag to specify integrations when creating posts\"\necho \"  - Upload images with: postiz upload ./path/to/image.png\"\necho \"  - Delete posts with: postiz posts:delete <post-id>\"\necho \"  - Get help: postiz --help\"\n"
  },
  {
    "path": "apps/cli/examples/command-line-examples.sh",
    "content": "#!/bin/bash\n\n# Postiz CLI - Command Line Examples\n# Demonstrating the new -c and -m flag syntax\n\necho \"🚀 Postiz CLI Command Line Examples\"\necho \"\"\n\n# Make sure API key is set\nif [ -z \"$POSTIZ_API_KEY\" ]; then\n    echo \"❌ POSTIZ_API_KEY is not set!\"\n    echo \"Set it with: export POSTIZ_API_KEY=your_api_key\"\n    exit 1\nfi\n\necho \"✅ API key is set\"\necho \"\"\n\n# Example 1: Simple post\necho \"📝 Example 1: Simple post\"\necho \"Command:\"\necho 'postiz posts:create -c \"Hello World!\" -i \"twitter-123\"'\necho \"\"\n\n# Example 2: Post with multiple images\necho \"📸 Example 2: Post with multiple images\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Check out these amazing photos!\" \\'\necho '  -m \"photo1.jpg,photo2.jpg,photo3.jpg\" \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 3: Post with comments, each having their own media\necho \"💬 Example 3: Post with comments, each having different media\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Main post content 🚀\" \\'\necho '  -m \"main-image1.jpg,main-image2.jpg\" \\'\necho '  -c \"First comment with its own image 📸\" \\'\necho '  -m \"comment1-image.jpg\" \\'\necho '  -c \"Second comment with different images 🎨\" \\'\necho '  -m \"comment2-image1.jpg,comment2-image2.jpg\" \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 4: Comments with semicolons (no escaping needed!)\necho \"🎯 Example 4: Comments can contain semicolons!\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Main post\" \\'\necho '  -c \"First comment; notice the semicolon!\" \\'\necho '  -c \"Second comment; with multiple; semicolons; works fine!\" \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 5: Twitter thread with custom delay\necho \"🧵 Example 5: Twitter thread with 2-second delays\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"🧵 How to use Postiz CLI (1/5)\" \\'\necho '  -m \"thread-intro.jpg\" \\'\necho '  -c \"Step 1: Install the CLI (2/5)\" \\'\necho '  -m \"step1-screenshot.jpg\" \\'\necho '  -c \"Step 2: Set your API key (3/5)\" \\'\necho '  -m \"step2-screenshot.jpg\" \\'\necho '  -c \"Step 3: Create your first post (4/5)\" \\'\necho '  -m \"step3-screenshot.jpg\" \\'\necho '  -c \"You'\\''re all set! 🎉 (5/5)\" \\'\necho '  -m \"done.jpg\" \\'\necho '  -d 2000 \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 6: Scheduled post with comments\necho \"⏰ Example 6: Scheduled post with follow-up comments\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Product launch! 🚀\" \\'\necho '  -m \"product-hero.jpg,product-features.jpg\" \\'\necho '  -c \"Special launch offer - 50% off!\" \\'\necho '  -m \"discount-banner.jpg\" \\'\necho '  -c \"Limited time only!\" \\'\necho '  -s \"2024-12-25T09:00:00Z\" \\'\necho '  -i \"twitter-123,linkedin-456\"'\necho \"\"\n\n# Example 7: Multi-platform with same content\necho \"🌐 Example 7: Multi-platform posting\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Exciting announcement! 🎉\" \\'\necho '  -m \"announcement.jpg\" \\'\necho '  -c \"More details in the comments...\" \\'\necho '  -m \"details-infographic.jpg\" \\'\necho '  -i \"twitter-123,linkedin-456,facebook-789\"'\necho \"\"\n\n# Example 8: Comments without media\necho \"💭 Example 8: Main post with media, comments without media\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Check out this amazing view! 🏔️\" \\'\necho '  -m \"mountain-photo.jpg\" \\'\necho '  -c \"Taken at sunrise this morning\" \\'\necho '  -c \"Location: Swiss Alps\" \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 9: Product tutorial series\necho \"📚 Example 9: Product tutorial series\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Tutorial: Getting Started with Our Product 📖\" \\'\necho '  -m \"tutorial-cover.jpg\" \\'\necho '  -c \"1. First, download and install the app\" \\'\necho '  -m \"install-screen.jpg\" \\'\necho '  -c \"2. Create your account and set up your profile\" \\'\necho '  -m \"signup-screen.jpg\" \\'\necho '  -c \"3. You'\\''re ready to go! Start creating your first project\" \\'\necho '  -m \"dashboard-screen.jpg\" \\'\necho '  -d 3000 \\'\necho '  -i \"twitter-123\"'\necho \"\"\n\n# Example 10: Event coverage\necho \"📍 Example 10: Live event coverage\"\necho \"Command:\"\necho 'postiz posts:create \\'\necho '  -c \"Conference 2024 is starting! 🎤\" \\'\necho '  -m \"venue-photo.jpg\" \\'\necho '  -c \"First speaker: Jane Doe talking about AI\" \\'\necho '  -m \"speaker1-photo.jpg\" \\'\necho '  -c \"Second speaker: John Smith on cloud architecture\" \\'\necho '  -m \"speaker2-photo.jpg\" \\'\necho '  -c \"Networking break! Great conversations happening\" \\'\necho '  -m \"networking-photo.jpg\" \\'\necho '  -d 30000 \\'\necho '  -i \"twitter-123,linkedin-456\"'\necho \"\"\n\necho \"💡 Tips:\"\necho \"  - Use multiple -c flags for main post + comments\"\necho \"  - Use -m flags to specify media for each -c\"\necho \"  - First -c is the main post, subsequent ones are comments\"\necho \"  - -m is optional, can be omitted for text-only comments\"\necho \"  - Use -d to set delay between comments (in milliseconds)\"\necho \"  - Semicolons and special characters work fine in -c content!\"\necho \"\"\necho \"📖 For more examples, see:\"\necho \"  - examples/EXAMPLES.md - Comprehensive guide\"\necho \"  - examples/*.json - JSON file examples\"\necho \"  - SKILL.md - AI agent patterns\"\n"
  },
  {
    "path": "apps/cli/examples/multi-platform-post.json",
    "content": "{\n  \"type\": \"schedule\",\n  \"date\": \"2024-12-25T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [\n    {\n      \"value\": \"holiday\",\n      \"label\": \"Holiday\"\n    },\n    {\n      \"value\": \"marketing\",\n      \"label\": \"Marketing\"\n    }\n  ],\n  \"posts\": [\n    {\n      \"integration\": {\n        \"id\": \"twitter-integration-id\"\n      },\n      \"value\": [\n        {\n          \"content\": \"Happy Holidays! 🎄 Check out our special offers!\",\n          \"image\": [\n            {\n              \"id\": \"holiday1\",\n              \"path\": \"https://example.com/holiday-twitter.jpg\"\n            }\n          ]\n        },\n        {\n          \"content\": \"Limited time offer - 50% off! 🎁\",\n          \"image\": [],\n          \"delay\": 3600000\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"EmptySettings\"\n      }\n    },\n    {\n      \"integration\": {\n        \"id\": \"linkedin-integration-id\"\n      },\n      \"value\": [\n        {\n          \"content\": \"Season's greetings from our team! We're offering exclusive holiday promotions.\",\n          \"image\": [\n            {\n              \"id\": \"holiday2\",\n              \"path\": \"https://example.com/holiday-linkedin.jpg\"\n            }\n          ]\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"EmptySettings\"\n      }\n    },\n    {\n      \"integration\": {\n        \"id\": \"facebook-integration-id\"\n      },\n      \"value\": [\n        {\n          \"content\": \"🎅 Happy Holidays! Special announcement in the comments!\",\n          \"image\": [\n            {\n              \"id\": \"holiday3\",\n              \"path\": \"https://example.com/holiday-facebook-main.jpg\"\n            }\n          ]\n        },\n        {\n          \"content\": \"Our holiday sale is now live! Visit our website for amazing deals 🎁\",\n          \"image\": [\n            {\n              \"id\": \"holiday4\",\n              \"path\": \"https://example.com/holiday-sale-banner.jpg\"\n            }\n          ],\n          \"delay\": 300000\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"EmptySettings\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/cli/examples/multi-platform-with-settings.json",
    "content": "{\n  \"type\": \"schedule\",\n  \"date\": \"2024-03-15T09:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [\n    { \"value\": \"product-launch\", \"label\": \"Product Launch\" }\n  ],\n  \"posts\": [\n    {\n      \"integration\": { \"id\": \"reddit-integration-id\" },\n      \"value\": [{\n        \"content\": \"We're launching our new CLI tool today!\\n\\nIt's designed to make social media scheduling effortless for developers and AI agents. Built with TypeScript, supports 28+ platforms, and has a clean, intuitive API.\\n\\nFeatures:\\n- Multi-platform posting\\n- Thread creation\\n- Scheduled posts\\n- Comments with media\\n- Provider-specific settings\\n\\nTry it out and let us know what you think!\",\n        \"image\": [\n          { \"id\": \"r1\", \"path\": \"https://cdn.example.com/reddit-screenshot.jpg\" }\n        ]\n      }],\n      \"settings\": {\n        \"__type\": \"reddit\",\n        \"subreddit\": [{\n          \"value\": {\n            \"subreddit\": \"programming\",\n            \"title\": \"Launching Postiz CLI - Social Media Automation for Developers\",\n            \"type\": \"text\",\n            \"url\": \"\",\n            \"is_flair_required\": false\n          }\n        }]\n      }\n    },\n    {\n      \"integration\": { \"id\": \"twitter-integration-id\" },\n      \"value\": [\n        {\n          \"content\": \"🚀 Launching Postiz CLI today!\\n\\nFinally, a developer-friendly way to automate social media. Built with TypeScript, supports 28+ platforms.\\n\\n✨ Features in thread below 👇\",\n          \"image\": [\n            { \"id\": \"t1\", \"path\": \"https://cdn.example.com/twitter-banner.jpg\" }\n          ]\n        },\n        {\n          \"content\": \"1️⃣ Multi-platform posting\\nPost to Twitter, LinkedIn, Reddit, TikTok, YouTube, and 23 more platforms with a single command\",\n          \"image\": [\n            { \"id\": \"t2\", \"path\": \"https://cdn.example.com/multi-platform.jpg\" }\n          ],\n          \"delay\": 3000\n        },\n        {\n          \"content\": \"2️⃣ Thread creation\\nEasily create Twitter threads, each tweet with its own media\",\n          \"image\": [\n            { \"id\": \"t3\", \"path\": \"https://cdn.example.com/threads.jpg\" }\n          ],\n          \"delay\": 3000\n        },\n        {\n          \"content\": \"3️⃣ Provider-specific settings\\nReddit subreddits, YouTube visibility, TikTok privacy - all configurable\\n\\nGet started: https://github.com/yourrepo\",\n          \"image\": [],\n          \"delay\": 3000\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"x\",\n        \"who_can_reply_post\": \"everyone\"\n      }\n    },\n    {\n      \"integration\": { \"id\": \"linkedin-integration-id\" },\n      \"value\": [{\n        \"content\": \"Excited to announce the launch of Postiz CLI! 🎉\\n\\nAs developers, we know how time-consuming social media management can be. That's why we built a powerful CLI tool that makes scheduling posts across 28+ platforms effortless.\\n\\nKey features:\\n• Multi-platform support (Twitter, LinkedIn, Reddit, TikTok, YouTube, and more)\\n• Thread and carousel creation\\n• Scheduled posting with precise timing\\n• Provider-specific settings and customization\\n• Built for AI agents and automation\\n\\nWhether you're managing a personal brand, running marketing campaigns, or building AI-powered social media tools, Postiz CLI has you covered.\\n\\nCheck it out and let us know your thoughts!\",\n        \"image\": [\n          { \"id\": \"l1\", \"path\": \"https://cdn.example.com/linkedin-slide1.jpg\" },\n          { \"id\": \"l2\", \"path\": \"https://cdn.example.com/linkedin-slide2.jpg\" },\n          { \"id\": \"l3\", \"path\": \"https://cdn.example.com/linkedin-slide3.jpg\" }\n        ]\n      }],\n      \"settings\": {\n        \"__type\": \"linkedin\",\n        \"post_as_images_carousel\": true,\n        \"carousel_name\": \"Postiz CLI Launch\"\n      }\n    },\n    {\n      \"integration\": { \"id\": \"instagram-integration-id\" },\n      \"value\": [{\n        \"content\": \"🚀 New launch alert!\\n\\nPostiz CLI is here - automate your social media like a pro.\\n\\n✨ 28+ platforms\\n📅 Scheduled posting\\n🧵 Thread creation\\n⚙️ Full customization\\n\\nLink in bio! #developer #automation #socialmedia #tech\",\n        \"image\": [\n          { \"id\": \"i1\", \"path\": \"https://cdn.example.com/instagram-post.jpg\" }\n        ]\n      }],\n      \"settings\": {\n        \"__type\": \"instagram\",\n        \"post_type\": \"post\",\n        \"is_trial_reel\": false\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/cli/examples/post-with-comments.json",
    "content": "{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [\n    {\n      \"integration\": {\n        \"id\": \"your-integration-id-here\"\n      },\n      \"value\": [\n        {\n          \"content\": \"This is the main post content 🚀\",\n          \"image\": [\n            {\n              \"id\": \"img1\",\n              \"path\": \"https://example.com/main-image.jpg\"\n            },\n            {\n              \"id\": \"img2\",\n              \"path\": \"https://example.com/secondary-image.jpg\"\n            }\n          ]\n        },\n        {\n          \"content\": \"This is the first comment with its own media 📸\",\n          \"image\": [\n            {\n              \"id\": \"img3\",\n              \"path\": \"https://example.com/comment1-image.jpg\"\n            }\n          ],\n          \"delay\": 5000\n        },\n        {\n          \"content\": \"This is the second comment with different media 🎨\",\n          \"image\": [\n            {\n              \"id\": \"img4\",\n              \"path\": \"https://example.com/comment2-image1.jpg\"\n            },\n            {\n              \"id\": \"img5\",\n              \"path\": \"https://example.com/comment2-image2.jpg\"\n            }\n          ],\n          \"delay\": 10000\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"EmptySettings\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/cli/examples/reddit-post.json",
    "content": "{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [{\n    \"integration\": {\n      \"id\": \"your-reddit-integration-id\"\n    },\n    \"value\": [{\n      \"content\": \"I built a CLI tool for Postiz that makes social media scheduling super easy!\\n\\nYou can create posts, schedule them, and even post to multiple platforms at once. It supports comments with their own media, threads, and much more.\\n\\nCheck it out and let me know what you think!\",\n      \"image\": []\n    }],\n    \"settings\": {\n      \"__type\": \"reddit\",\n      \"subreddit\": [{\n        \"value\": {\n          \"subreddit\": \"programming\",\n          \"title\": \"Built a CLI tool for social media scheduling with TypeScript\",\n          \"type\": \"text\",\n          \"url\": \"\",\n          \"is_flair_required\": false\n        }\n      }]\n    }\n  }]\n}\n"
  },
  {
    "path": "apps/cli/examples/thread-post.json",
    "content": "{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [\n    {\n      \"integration\": {\n        \"id\": \"twitter-integration-id\"\n      },\n      \"value\": [\n        {\n          \"content\": \"🧵 Thread: How to use Postiz CLI for automated social media posting (1/5)\",\n          \"image\": [\n            {\n              \"id\": \"tutorial1\",\n              \"path\": \"https://example.com/tutorial-intro.jpg\"\n            }\n          ]\n        },\n        {\n          \"content\": \"Step 1: Install the CLI and set your API key\\n\\nexport POSTIZ_API_KEY=your_key\\npnpm install -g postiz (2/5)\",\n          \"image\": [\n            {\n              \"id\": \"tutorial2\",\n              \"path\": \"https://example.com/tutorial-install.jpg\"\n            }\n          ],\n          \"delay\": 2000\n        },\n        {\n          \"content\": \"Step 2: List your connected integrations to get their IDs\\n\\npostiz integrations:list (3/5)\",\n          \"image\": [\n            {\n              \"id\": \"tutorial3\",\n              \"path\": \"https://example.com/tutorial-integrations.jpg\"\n            }\n          ],\n          \"delay\": 2000\n        },\n        {\n          \"content\": \"Step 3: Create your first post\\n\\npostiz posts:create -c \\\"Hello World!\\\" -i \\\"twitter-123\\\" (4/5)\",\n          \"image\": [\n            {\n              \"id\": \"tutorial4\",\n              \"path\": \"https://example.com/tutorial-create.jpg\"\n            }\n          ],\n          \"delay\": 2000\n        },\n        {\n          \"content\": \"That's it! You can now automate your social media posts with ease. Check out our docs for more advanced features! 🚀 (5/5)\",\n          \"image\": [\n            {\n              \"id\": \"tutorial5\",\n              \"path\": \"https://example.com/tutorial-done.jpg\"\n            }\n          ],\n          \"delay\": 2000\n        }\n      ],\n      \"settings\": {\n        \"__type\": \"EmptySettings\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/cli/examples/tiktok-video.json",
    "content": "{\n  \"type\": \"now\",\n  \"date\": \"2024-01-15T12:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [],\n  \"posts\": [{\n    \"integration\": {\n      \"id\": \"your-tiktok-integration-id\"\n    },\n    \"value\": [{\n      \"content\": \"Quick tip: Automate your social media with this CLI tool! 🚀\\n\\n#coding #programming #typescript #developer #tech\",\n      \"image\": [{\n        \"id\": \"video1\",\n        \"path\": \"https://cdn.example.com/tiktok-video.mp4\"\n      }]\n    }],\n    \"settings\": {\n      \"__type\": \"tiktok\",\n      \"title\": \"Automate Social Media with CLI\",\n      \"privacy_level\": \"PUBLIC_TO_EVERYONE\",\n      \"duet\": true,\n      \"stitch\": true,\n      \"comment\": true,\n      \"autoAddMusic\": \"no\",\n      \"brand_content_toggle\": false,\n      \"brand_organic_toggle\": false,\n      \"video_made_with_ai\": false,\n      \"content_posting_method\": \"DIRECT_POST\"\n    }\n  }]\n}\n"
  },
  {
    "path": "apps/cli/examples/youtube-video.json",
    "content": "{\n  \"type\": \"schedule\",\n  \"date\": \"2024-12-25T09:00:00Z\",\n  \"shortLink\": true,\n  \"tags\": [\n    { \"value\": \"tutorial\", \"label\": \"Tutorial\" },\n    { \"value\": \"tech\", \"label\": \"Tech\" }\n  ],\n  \"posts\": [{\n    \"integration\": {\n      \"id\": \"your-youtube-integration-id\"\n    },\n    \"value\": [{\n      \"content\": \"In this video, I'll show you how to build a powerful CLI tool for social media automation.\\n\\n⏱️ Timestamps:\\n0:00 - Introduction\\n2:15 - Setting up the project\\n5:30 - Building the API client\\n10:45 - Creating commands\\n15:20 - Testing and deployment\\n\\n📚 Resources:\\n- GitHub: https://github.com/yourrepo\\n- Documentation: https://docs.example.com\\n\\n🔔 Subscribe for more TypeScript tutorials!\",\n      \"image\": [{\n        \"id\": \"thumbnail1\",\n        \"path\": \"https://cdn.example.com/thumbnail.jpg\"\n      }]\n    }],\n    \"settings\": {\n      \"__type\": \"youtube\",\n      \"title\": \"Building a Social Media CLI Tool with TypeScript\",\n      \"type\": \"public\",\n      \"selfDeclaredMadeForKids\": \"no\",\n      \"tags\": [\n        { \"value\": \"typescript\", \"label\": \"TypeScript\" },\n        { \"value\": \"cli\", \"label\": \"CLI\" },\n        { \"value\": \"tutorial\", \"label\": \"Tutorial\" },\n        { \"value\": \"programming\", \"label\": \"Programming\" },\n        { \"value\": \"nodejs\", \"label\": \"Node.js\" }\n      ]\n    }\n  }]\n}\n"
  },
  {
    "path": "apps/cli/package.json",
    "content": "{\n  \"name\": \"postiz\",\n  \"version\": \"2.0.5\",\n  \"description\": \"Postiz CLI - Command line interface for the Postiz social media scheduling API\",\n  \"main\": \"dist/index.js\",\n  \"bin\": {\n    \"postiz\": \"./dist/index.js\"\n  },\n  \"scripts\": {\n    \"dev\": \"tsup --watch\",\n    \"build\": \"tsup\",\n    \"start\": \"node ./dist/index.js\",\n    \"publish\": \"tsup && pnpm publish --access public --no-git-checks\"\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"SKILL.md\"\n  ],\n  \"keywords\": [\n    \"postiz\",\n    \"cli\",\n    \"social media\",\n    \"scheduling\",\n    \"automation\",\n    \"ai-agent\",\n    \"command-line\"\n  ],\n  \"author\": \"Nevo David\",\n  \"license\": \"AGPL-3.0\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/gitroomhq/postiz-app.git\",\n    \"directory\": \"apps/cli\"\n  },\n  \"homepage\": \"https://postiz.com\",\n  \"bugs\": {\n    \"url\": \"https://github.com/gitroomhq/postiz-app/issues\"\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/api.ts",
    "content": "import fetch, { FormData } from 'node-fetch';\n\nexport interface PostizConfig {\n  apiKey: string;\n  apiUrl?: string;\n}\n\nexport class PostizAPI {\n  private apiKey: string;\n  private apiUrl: string;\n\n  constructor(config: PostizConfig) {\n    this.apiKey = config.apiKey;\n    this.apiUrl = config.apiUrl || 'https://api.postiz.com';\n  }\n\n  private async request(endpoint: string, options: any = {}) {\n    const url = `${this.apiUrl}${endpoint}`;\n    const headers = {\n      'Content-Type': 'application/json',\n      Authorization: this.apiKey,\n      ...options.headers,\n    };\n\n    try {\n      const response = await fetch(url, {\n        ...options,\n        headers,\n      });\n\n      if (!response.ok) {\n        const error = await response.text();\n        throw new Error(`API Error (${response.status}): ${error}`);\n      }\n\n      return await response.json();\n    } catch (error: any) {\n      throw new Error(`Request failed: ${error.message}`);\n    }\n  }\n\n  async createPost(data: any) {\n    return this.request('/public/v1/posts', {\n      method: 'POST',\n      body: JSON.stringify(data),\n    });\n  }\n\n  async listPosts(filters: any = {}) {\n    const queryString = new URLSearchParams(\n      Object.entries(filters).reduce((acc, [key, value]) => {\n        if (value !== undefined && value !== null) {\n          acc[key] = String(value);\n        }\n        return acc;\n      }, {} as Record<string, string>)\n    ).toString();\n\n    const endpoint = queryString\n      ? `/public/v1/posts?${queryString}`\n      : '/public/v1/posts';\n\n    return this.request(endpoint, {\n      method: 'GET',\n    });\n  }\n\n  async deletePost(id: string) {\n    return this.request(`/public/v1/posts/${id}`, {\n      method: 'DELETE',\n    });\n  }\n\n  async upload(file: Buffer, filename: string) {\n    const formData = new FormData();\n    const extension = filename.split('.').pop()?.toLowerCase() || '';\n\n    // Determine MIME type based on file extension\n    const mimeTypes: Record<string, string> = {\n      // Images\n      'png': 'image/png',\n      'jpg': 'image/jpeg',\n      'jpeg': 'image/jpeg',\n      'gif': 'image/gif',\n      'webp': 'image/webp',\n      'svg': 'image/svg+xml',\n      'bmp': 'image/bmp',\n      'ico': 'image/x-icon',\n\n      // Videos\n      'mp4': 'video/mp4',\n      'mov': 'video/quicktime',\n      'avi': 'video/x-msvideo',\n      'mkv': 'video/x-matroska',\n      'webm': 'video/webm',\n      'flv': 'video/x-flv',\n      'wmv': 'video/x-ms-wmv',\n      'm4v': 'video/x-m4v',\n      'mpeg': 'video/mpeg',\n      'mpg': 'video/mpeg',\n      '3gp': 'video/3gpp',\n\n      // Audio\n      'mp3': 'audio/mpeg',\n      'wav': 'audio/wav',\n      'ogg': 'audio/ogg',\n      'aac': 'audio/aac',\n      'flac': 'audio/flac',\n      'm4a': 'audio/mp4',\n\n      // Documents\n      'pdf': 'application/pdf',\n      'doc': 'application/msword',\n      'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n    };\n\n    const type = mimeTypes[extension] || 'application/octet-stream';\n\n    const blob = new Blob([file], { type });\n    formData.append('file', blob, filename);\n\n    const url = `${this.apiUrl}/public/v1/upload`;\n    const response = await fetch(url, {\n      method: 'POST',\n      // @ts-ignore\n      body: formData,\n      headers: {\n        Authorization: this.apiKey,\n      },\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Upload failed (${response.status}): ${error}`);\n    }\n\n    return await response.json();\n  }\n\n  async listIntegrations() {\n    return this.request('/public/v1/integrations', {\n      method: 'GET',\n    });\n  }\n\n  async getIntegrationSettings(integrationId: string) {\n    return this.request(`/public/v1/integration-settings/${integrationId}`, {\n      method: 'GET',\n    });\n  }\n\n  async triggerIntegrationTool(\n    integrationId: string,\n    methodName: string,\n    data: Record<string, string>\n  ) {\n    return this.request(`/public/v1/integration-trigger/${integrationId}`, {\n      method: 'POST',\n      body: JSON.stringify({ methodName, data }),\n    });\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/commands/integrations.ts",
    "content": "import { PostizAPI } from '../api';\nimport { getConfig } from '../config';\n\nexport async function listIntegrations() {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  try {\n    const result = await api.listIntegrations();\n    console.log('🔌 Connected Integrations:');\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to list integrations:', error.message);\n    process.exit(1);\n  }\n}\n\nexport async function getIntegrationSettings(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  if (!args.id) {\n    console.error('❌ Integration ID is required');\n    process.exit(1);\n  }\n\n  try {\n    const result = await api.getIntegrationSettings(args.id);\n    console.log(`⚙️  Settings for integration: ${args.id}`);\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to get integration settings:', error.message);\n    process.exit(1);\n  }\n}\n\nexport async function triggerIntegrationTool(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  if (!args.id) {\n    console.error('❌ Integration ID is required');\n    process.exit(1);\n  }\n\n  if (!args.method) {\n    console.error('❌ Method name is required');\n    process.exit(1);\n  }\n\n  // Parse data from JSON string or use empty object\n  let data: Record<string, string> = {};\n  if (args.data) {\n    try {\n      data = JSON.parse(args.data);\n    } catch (error: any) {\n      console.error('❌ Failed to parse data JSON:', error.message);\n      process.exit(1);\n    }\n  }\n\n  try {\n    const result = await api.triggerIntegrationTool(args.id, args.method, data);\n    console.log(`🔧 Tool result for ${args.method}:`);\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to trigger tool:', error.message);\n    process.exit(1);\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/commands/posts.ts",
    "content": "import { PostizAPI } from '../api';\nimport { getConfig } from '../config';\nimport { readFileSync, existsSync } from 'fs';\n\nexport async function createPost(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  // Support both simple and complex post creation\n  let postData: any;\n\n  if (args.json) {\n    // Load from JSON file for complex posts with comments and media\n    try {\n      const jsonPath = args.json;\n      if (!existsSync(jsonPath)) {\n        console.error(`❌ JSON file not found: ${jsonPath}`);\n        process.exit(1);\n      }\n      const jsonContent = readFileSync(jsonPath, 'utf-8');\n      postData = JSON.parse(jsonContent);\n    } catch (error: any) {\n      console.error('❌ Failed to parse JSON file:', error.message);\n      process.exit(1);\n    }\n  } else {\n    const integrations = args.integrations\n      ? args.integrations.split(',').map((id: string) => id.trim())\n      : [];\n\n    if (integrations.length === 0) {\n      console.error('❌ At least one integration ID is required');\n      console.error('Use -i or --integrations to specify integration IDs');\n      console.error('Run \"postiz integrations:list\" to see available integrations');\n      process.exit(1);\n    }\n\n    // Support multiple -c and -m flags\n    // Normalize to arrays\n    const contents = Array.isArray(args.content) ? args.content : [args.content];\n    const medias = Array.isArray(args.media) ? args.media : (args.media ? [args.media] : []);\n\n    if (!contents[0]) {\n      console.error('❌ At least one -c/--content is required');\n      process.exit(1);\n    }\n\n    // Build value array by pairing contents with their media\n    const values = contents.map((content: string, index: number) => {\n      const mediaForThisContent = medias[index];\n      const images = mediaForThisContent\n        ? mediaForThisContent.split(',').map((img: string) => ({\n            id: Math.random().toString(36).substring(7),\n            path: img.trim(),\n          }))\n        : [];\n\n      return {\n        content: content,\n        image: images,\n        // Add delay for all items except the first (main post)\n        ...(index > 0 && { delay: args.delay || 5000 }),\n      };\n    });\n\n    // Parse provider-specific settings if provided\n    // Note: __type is automatically added by the backend based on integration ID\n    let settings: any = undefined;\n\n    if (args.settings) {\n      try {\n        settings = typeof args.settings === 'string'\n          ? JSON.parse(args.settings)\n          : args.settings;\n      } catch (error: any) {\n        console.error('❌ Failed to parse settings JSON:', error.message);\n        process.exit(1);\n      }\n    }\n\n    // Build the proper post structure\n    postData = {\n      type: args.type || 'schedule', // 'schedule' or 'draft'\n      date: args.date, // Required date field\n      shortLink: args.shortLink !== false,\n      tags: [],\n      posts: integrations.map((integrationId: string) => ({\n        integration: { id: integrationId },\n        value: values,\n        settings: settings,\n      })),\n    };\n  }\n\n  try {\n    const result = await api.createPost(postData);\n    console.log('✅ Post created successfully!');\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to create post:', error.message);\n    process.exit(1);\n  }\n}\n\nexport async function listPosts(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  // Set default date range: last 30 days to 30 days in the future\n  const defaultStartDate = new Date();\n  defaultStartDate.setDate(defaultStartDate.getDate() - 30);\n\n  const defaultEndDate = new Date();\n  defaultEndDate.setDate(defaultEndDate.getDate() + 30);\n\n  // Only send fields that are in GetPostsDto\n  const filters: any = {\n    startDate: args.startDate || defaultStartDate.toISOString(),\n    endDate: args.endDate || defaultEndDate.toISOString(),\n  };\n\n  // customer is optional in the DTO\n  if (args.customer) {\n    filters.customer = args.customer;\n  }\n\n  try {\n    const result = await api.listPosts(filters);\n    console.log('📋 Posts:');\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to list posts:', error.message);\n    process.exit(1);\n  }\n}\n\nexport async function deletePost(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  if (!args.id) {\n    console.error('❌ Post ID is required');\n    process.exit(1);\n  }\n\n  try {\n    await api.deletePost(args.id);\n    console.log(`✅ Post ${args.id} deleted successfully!`);\n  } catch (error: any) {\n    console.error('❌ Failed to delete post:', error.message);\n    process.exit(1);\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/commands/upload.ts",
    "content": "import { PostizAPI } from '../api';\nimport { getConfig } from '../config';\nimport { readFileSync } from 'fs';\n\nexport async function uploadFile(args: any) {\n  const config = getConfig();\n  const api = new PostizAPI(config);\n\n  if (!args.file) {\n    console.error('❌ File path is required');\n    process.exit(1);\n  }\n\n  try {\n    const fileBuffer = readFileSync(args.file);\n    const filename = args.file.split('/').pop() || 'file';\n\n    const result = await api.upload(fileBuffer, filename);\n    console.log('✅ File uploaded successfully!');\n    console.log(JSON.stringify(result, null, 2));\n    return result;\n  } catch (error: any) {\n    console.error('❌ Failed to upload file:', error.message);\n    process.exit(1);\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/config.ts",
    "content": "import { PostizConfig } from './api';\n\nexport function getConfig(): PostizConfig {\n  const apiKey = process.env.POSTIZ_API_KEY;\n  const apiUrl = process.env.POSTIZ_API_URL;\n\n  if (!apiKey) {\n    console.error('❌ Error: POSTIZ_API_KEY environment variable is required');\n    console.error('Please set it using: export POSTIZ_API_KEY=your_api_key');\n    process.exit(1);\n  }\n\n  return {\n    apiKey,\n    apiUrl,\n  };\n}\n"
  },
  {
    "path": "apps/cli/src/index.ts",
    "content": "import yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { createPost, listPosts, deletePost } from './commands/posts';\nimport { listIntegrations, getIntegrationSettings, triggerIntegrationTool } from './commands/integrations';\nimport { uploadFile } from './commands/upload';\nimport type { Argv } from 'yargs';\n\nyargs(hideBin(process.argv))\n  .scriptName('postiz')\n  .usage('$0 <command> [options]')\n  .command(\n    'posts:create',\n    'Create a new post',\n    (yargs: Argv) => {\n      return yargs\n        .option('content', {\n          alias: 'c',\n          describe: 'Post/comment content (can be used multiple times)',\n          type: 'string',\n        })\n        .option('media', {\n          alias: 'm',\n          describe: 'Comma-separated media URLs for the corresponding -c (can be used multiple times)',\n          type: 'string',\n        })\n        .option('integrations', {\n          alias: 'i',\n          describe: 'Comma-separated list of integration IDs',\n          type: 'string',\n        })\n        .option('date', {\n          alias: 's',\n          describe: 'Schedule date (ISO 8601 format) - REQUIRED',\n          type: 'string',\n        })\n        .option('type', {\n          alias: 't',\n          describe: 'Post type: \"schedule\" or \"draft\"',\n          type: 'string',\n          choices: ['schedule', 'draft'],\n          default: 'schedule',\n        })\n        .option('delay', {\n          alias: 'd',\n          describe: 'Delay in milliseconds between comments (default: 5000)',\n          type: 'number',\n          default: 5000,\n        })\n        .option('json', {\n          alias: 'j',\n          describe: 'Path to JSON file with full post structure',\n          type: 'string',\n        })\n        .option('shortLink', {\n          describe: 'Use short links',\n          type: 'boolean',\n          default: true,\n        })\n        .option('settings', {\n          describe: 'Platform-specific settings as JSON string',\n          type: 'string',\n        })\n        .check((argv) => {\n          if (!argv.json && !argv.content) {\n            throw new Error('Either --content or --json is required');\n          }\n          if (!argv.json && !argv.integrations) {\n            throw new Error('--integrations is required when not using --json');\n          }\n          if (!argv.json && !argv.date) {\n            throw new Error('--date is required when not using --json');\n          }\n          return true;\n        })\n        .example(\n          '$0 posts:create -c \"Hello World!\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-123\"',\n          'Simple scheduled post'\n        )\n        .example(\n          '$0 posts:create -c \"Draft post\" -s \"2024-12-31T12:00:00Z\" -t draft -i \"twitter-123\"',\n          'Create draft post'\n        )\n        .example(\n          '$0 posts:create -c \"Main post\" -m \"img1.jpg,img2.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-123\"',\n          'Post with multiple images'\n        )\n        .example(\n          '$0 posts:create -c \"Main post\" -m \"img1.jpg\" -c \"First comment\" -m \"img2.jpg\" -c \"Second comment\" -m \"img3.jpg,img4.jpg\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-123\"',\n          'Post with comments, each having their own media'\n        )\n        .example(\n          '$0 posts:create -c \"Main\" -c \"Comment with semicolon; see?\" -c \"Another!\" -s \"2024-12-31T12:00:00Z\" -i \"twitter-123\"',\n          'Comments can contain semicolons'\n        )\n        .example(\n          '$0 posts:create -c \"Thread 1/3\" -c \"Thread 2/3\" -c \"Thread 3/3\" -d 2000 -s \"2024-12-31T12:00:00Z\" -i \"twitter-123\"',\n          'Twitter thread with 2s delay'\n        )\n        .example(\n          '$0 posts:create --json ./post.json',\n          'Complex post from JSON file'\n        )\n        .example(\n          '$0 posts:create -c \"Post to subreddit\" -s \"2024-12-31T12:00:00Z\" --settings \\'{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"My Title\",\"type\":\"text\",\"url\":\"\",\"is_flair_required\":false}}]}\\' -i \"reddit-123\"',\n          'Reddit post with specific subreddit settings'\n        )\n        .example(\n          '$0 posts:create -c \"Video description\" -s \"2024-12-31T12:00:00Z\" --settings \\'{\"title\":\"My Video\",\"type\":\"public\",\"tags\":[{\"value\":\"tech\",\"label\":\"Tech\"}]}\\' -i \"youtube-123\"',\n          'YouTube post with title and tags'\n        )\n        .example(\n          '$0 posts:create -c \"Tweet content\" -s \"2024-12-31T12:00:00Z\" --settings \\'{\"who_can_reply_post\":\"everyone\"}\\' -i \"twitter-123\"',\n          'X (Twitter) post with reply settings'\n        );\n    },\n    createPost as any\n  )\n  .command(\n    'posts:list',\n    'List all posts',\n    (yargs: Argv) => {\n      return yargs\n        .option('startDate', {\n          describe: 'Start date (ISO 8601 format). Default: 30 days ago',\n          type: 'string',\n        })\n        .option('endDate', {\n          describe: 'End date (ISO 8601 format). Default: 30 days from now',\n          type: 'string',\n        })\n        .option('customer', {\n          describe: 'Customer ID (optional)',\n          type: 'string',\n        })\n        .example('$0 posts:list', 'List all posts (last 30 days to next 30 days)')\n        .example(\n          '$0 posts:list --startDate \"2024-01-01T00:00:00Z\" --endDate \"2024-12-31T23:59:59Z\"',\n          'List posts for a specific date range'\n        )\n        .example(\n          '$0 posts:list --customer \"customer-id\"',\n          'List posts for a specific customer'\n        );\n    },\n    listPosts as any\n  )\n  .command(\n    'posts:delete <id>',\n    'Delete a post',\n    (yargs: Argv) => {\n      return yargs\n        .positional('id', {\n          describe: 'Post ID to delete',\n          type: 'string',\n        })\n        .example('$0 posts:delete abc123', 'Delete post with ID abc123');\n    },\n    deletePost as any\n  )\n  .command(\n    'integrations:list',\n    'List all connected integrations',\n    {},\n    listIntegrations as any\n  )\n  .command(\n    'integrations:settings <id>',\n    'Get settings schema for a specific integration',\n    (yargs: Argv) => {\n      return yargs\n        .positional('id', {\n          describe: 'Integration ID',\n          type: 'string',\n        })\n        .example(\n          '$0 integrations:settings reddit-123',\n          'Get settings schema for Reddit integration'\n        )\n        .example(\n          '$0 integrations:settings youtube-456',\n          'Get settings schema for YouTube integration'\n        );\n    },\n    getIntegrationSettings as any\n  )\n  .command(\n    'integrations:trigger <id> <method>',\n    'Trigger an integration tool to fetch additional data',\n    (yargs: Argv) => {\n      return yargs\n        .positional('id', {\n          describe: 'Integration ID',\n          type: 'string',\n        })\n        .positional('method', {\n          describe: 'Method name from the integration tools',\n          type: 'string',\n        })\n        .option('data', {\n          alias: 'd',\n          describe: 'Data to pass to the tool as JSON string',\n          type: 'string',\n        })\n        .example(\n          '$0 integrations:trigger reddit-123 getSubreddits',\n          'Get list of subreddits'\n        )\n        .example(\n          '$0 integrations:trigger reddit-123 searchSubreddits -d \\'{\"query\":\"programming\"}\\'',\n          'Search for subreddits'\n        )\n        .example(\n          '$0 integrations:trigger youtube-123 getPlaylists',\n          'Get YouTube playlists'\n        );\n    },\n    triggerIntegrationTool as any\n  )\n  .command(\n    'upload <file>',\n    'Upload a file',\n    (yargs: Argv) => {\n      return yargs\n        .positional('file', {\n          describe: 'File path to upload',\n          type: 'string',\n        })\n        .example('$0 upload ./image.png', 'Upload an image');\n    },\n    uploadFile as any\n  )\n  .demandCommand(1, 'You need at least one command')\n  .help()\n  .alias('h', 'help')\n  .version()\n  .alias('v', 'version')\n  .epilogue(\n    'For more information, visit: https://postiz.com\\n\\nSet your API key: export POSTIZ_API_KEY=your_api_key'\n  )\n  .parse();\n"
  },
  {
    "path": "apps/cli/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"rootDir\": \"../../\",\n    \"incremental\": false\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "apps/cli/tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs'],\n  dts: false, // Disable DTS generation to avoid type issues\n  splitting: false,\n  sourcemap: true,\n  clean: true,\n  outDir: 'dist',\n  banner: {\n    js: '#!/usr/bin/env node',\n  },\n});\n"
  },
  {
    "path": "apps/commands/.gitignore",
    "content": "dist/\nnode_modules/\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n"
  },
  {
    "path": "apps/commands/nest-cli.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"monorepo\": false,\n  \"sourceRoot\": \"src\",\n  \"entryFile\": \"../../dist/commands/apps/commands/src/main\",\n  \"language\": \"ts\",\n  \"generateOptions\": {\n    \"spec\": false\n  },\n  \"compilerOptions\": {\n    \"manualRestart\": true,\n    \"tsConfigPath\": \"./tsconfig.build.json\",\n    \"webpack\": false,\n    \"deleteOutDir\": true,\n    \"assets\": [],\n    \"watchAssets\": false,\n    \"plugins\": []\n  }\n}\n"
  },
  {
    "path": "apps/commands/package.json",
    "content": "{\n  \"name\": \"postiz-command\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"dotenv -e ../../.env -- nest start --watch --entryFile=./apps/command/src/main\",\n    \"build\": \"cross-env NODE_ENV=production nest build\",\n    \"start\": \"node ./dist/apps/command/src/main.js\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "apps/commands/src/command.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { CommandModule as ExternalCommandModule } from 'nestjs-command';\nimport { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module';\nimport { RefreshTokens } from './tasks/refresh.tokens';\nimport { ConfigurationTask } from './tasks/configuration';\nimport { AgentRun } from './tasks/agent.run';\nimport { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module';\n\n@Module({\n  imports: [ExternalCommandModule, DatabaseModule, AgentModule],\n  controllers: [],\n  providers: [RefreshTokens, ConfigurationTask, AgentRun],\n  get exports() {\n    return [...this.imports, ...this.providers];\n  },\n})\nexport class CommandModule {}\n"
  },
  {
    "path": "apps/commands/src/main.ts",
    "content": "import { NestFactory } from '@nestjs/core';\nimport { CommandModule } from './command.module';\nimport { CommandService } from 'nestjs-command';\n\nasync function bootstrap() {\n  // some comment again\n  const app = await NestFactory.createApplicationContext(CommandModule, {\n    logger: ['error'],\n  });\n\n  try {\n    await app.select(CommandModule).get(CommandService).exec();\n    await app.close();\n  } catch (error) {\n    console.error(error);\n    await app.close();\n    process.exit(1);\n  }\n}\n\nbootstrap();\n"
  },
  {
    "path": "apps/commands/src/tasks/agent.run.ts",
    "content": "import { Command } from 'nestjs-command';\nimport { Injectable } from '@nestjs/common';\nimport { AgentGraphService } from '@gitroom/nestjs-libraries/agent/agent.graph.service';\n\n@Injectable()\nexport class AgentRun {\n  constructor(private _agentGraphService: AgentGraphService) {}\n  @Command({\n    command: 'run:agent',\n    describe: 'Run the agent',\n  })\n  async agentRun() {\n    console.log(await this._agentGraphService.createGraph('hello', true));\n  }\n}\n"
  },
  {
    "path": "apps/commands/src/tasks/configuration.ts",
    "content": "import { Command } from 'nestjs-command';\nimport { Injectable } from '@nestjs/common';\nimport { ConfigurationChecker } from '@gitroom/helpers/configuration/configuration.checker';\n\n@Injectable()\nexport class ConfigurationTask {\n  @Command({\n    command: 'config:check',\n    describe: 'Checks your configuration (.env) file for issues.',\n  })\n  create() {\n    const checker = new ConfigurationChecker();\n    checker.readEnvFromProcess();\n    checker.check();\n\n    if (checker.hasIssues()) {\n      for (const issue of checker.getIssues()) {\n        console.warn('Configuration issue:', issue);\n      }\n\n      console.error(\n        'Configuration check complete, issues: ',\n        checker.getIssuesCount()\n      );\n    } else {\n      console.log('Configuration check complete, no issues found.');\n    }\n\n    console.log('Press Ctrl+C to exit.');\n    return true;\n  }\n}\n"
  },
  {
    "path": "apps/commands/src/tasks/refresh.tokens.ts",
    "content": "import { Command } from 'nestjs-command';\nimport { Injectable } from '@nestjs/common';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\n\n@Injectable()\nexport class RefreshTokens {\n  constructor(private _integrationService: IntegrationService) {}\n  @Command({\n    command: 'refresh',\n    describe: 'Refresh all tokens',\n  })\n  async refresh() {\n    await this._integrationService.refreshTokens();\n    return true;\n  }\n}\n"
  },
  {
    "path": "apps/commands/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"],\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"resolveJsonModule\": true,\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"ES2021\",\n    \"sourceMap\": true,\n    \"incremental\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"strictBindCallApply\": false,\n    \"forceConsistentCasingInFileNames\": false,\n    \"noFallthroughCasesInSwitch\": false,\n    \"outDir\": \"./dist\"\n  }\n}\n"
  },
  {
    "path": "apps/commands/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"esModuleInterop\": true\n  }\n}\n"
  },
  {
    "path": "apps/extension/.gitignore",
    "content": "\n# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,visualstudiocode,sublimetext,node,react,windows,macos,linux\n# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,visualstudiocode,sublimetext,node,react,windows,macos,linux\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n### react ###\n.DS_*\n**/*.backup.*\n**/*.back.*\n\nnode_modules\n\n*.sublime*\n\npsd\nthumb\nsketch\n\n### SublimeText ###\n# Cache files for Sublime Text\n*.tmlanguage.cache\n*.tmPreferences.cache\n*.stTheme.cache\n\n# Workspace files are user-specific\n*.sublime-workspace\n\n# Project files should be checked into the repository, unless a significant\n# proportion of contributors will probably not be using Sublime Text\n# *.sublime-project\n\n# SFTP configuration file\nsftp-config.json\nsftp-config-alt*.json\n\n# Package control specific files\nPackage Control.last-run\nPackage Control.ca-list\nPackage Control.ca-bundle\nPackage Control.system-ca-bundle\nPackage Control.cache/\nPackage Control.ca-certs/\nPackage Control.merged-ca-bundle\nPackage Control.user-ca-bundle\noscrypto-ca-bundle.crt\nbh_unicode_properties.cache\n\n# Sublime-github package stores a github token in this file\n# https://packagecontrol.io/packages/sublime-github\nGitHub.sublime-settings\n\n### VisualStudioCode ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n\n# Local History for Visual Studio Code\n.history/\n\n# Built Visual Studio Code Extensions\n*.vsix\n\n### VisualStudioCode Patch ###\n# Ignore all local history of files\n.history\n.ionide\n\n# Support for Project snippet scope\n\n### WebStorm+all ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n\n# AWS User-specific\n.idea/**/aws.xml\n\n# Generated files\n.idea/**/contentModel.xml\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# Gradle and Maven with auto-import\n# When using Gradle or Maven with auto-import, you should exclude module files,\n# since they will be recreated, and may cause churn.  Uncomment if using\n# auto-import.\n# .idea/artifacts\n# .idea/compiler.xml\n# .idea/jarRepositories.xml\n# .idea/modules.xml\n# .idea/*.iml\n# .idea/modules\n# *.iml\n# *.ipr\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# SonarLint plugin\n.idea/sonarlint/\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n### WebStorm+all Patch ###\n# Ignore everything but code style settings and run configurations\n# that are supposed to be shared within teams.\n\n.idea/*\n\n!.idea/codeStyles\n!.idea/runConfigurations\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,visualstudiocode,sublimetext,node,react,windows,macos,linux\n\n# testing\n/coverage\n\n# etc\n.idea\n\n#generated manifest\npublic/manifest.json\n\nextension.zip"
  },
  {
    "path": "apps/extension/custom-vite-plugins.ts",
    "content": "import fs from 'fs';\nimport { resolve } from 'path';\nimport type { PluginOption } from 'vite';\n\n// plugin to remove dev icons from prod build\nexport function stripDevIcons(isDev: boolean) {\n  if (isDev) return null;\n\n  return {\n    name: 'strip-dev-icons',\n    resolveId(source: string) {\n      return source === 'virtual-module' ? source : null;\n    },\n    renderStart(outputOptions: any, inputOptions: any) {\n      const outDir = outputOptions.dir;\n      fs.rm(resolve(outDir, 'dev-icon-32.png'), () =>\n        console.log(`Deleted dev-icon-32.png from prod build`)\n      );\n      fs.rm(resolve(outDir, 'dev-icon-128.png'), () =>\n        console.log(`Deleted dev-icon-128.png from prod build`)\n      );\n    },\n  };\n}\n\n// plugin to support i18n\nexport function crxI18n(options: {\n  localize: boolean;\n  src: string;\n}): PluginOption {\n  if (!options.localize) return null;\n\n  const getJsonFiles = (dir: string): Array<string> => {\n    const files = fs.readdirSync(dir, { recursive: true }) as string[];\n    return files.filter((file) => !!file && file.endsWith('.json'));\n  };\n  const entry = resolve(__dirname, options.src);\n  const localeFiles = getJsonFiles(entry);\n  const files = localeFiles.map((file) => {\n    return {\n      id: '',\n      fileName: file,\n      source: fs.readFileSync(resolve(entry, file)),\n    };\n  });\n  return {\n    name: 'crx-i18n',\n    enforce: 'pre',\n    buildStart: {\n      order: 'post',\n      handler() {\n        files.forEach((file) => {\n          const refId = this.emitFile({\n            type: 'asset',\n            source: file.source,\n            fileName: '_locales/' + file.fileName,\n          });\n          file.id = refId;\n        });\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "apps/extension/manifest.dev.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"Postiz\",\n  \"version\": \"2.0.0\",\n  \"key\": \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqtH6qclAsfFf6qbUKfPmhBbfycGrt13+0h6ti/olniCGnjQjhkVVTnURfLFz+v+842Ee+pAS5HBEXo57dQ9xUtwFGXnavVR+myjN+Un9NIfFyYmYEBvLrinclsMJBwWMM8JkhxKuaOagxp1hqGgNAO4C0bzE3YN/SPoTjNpGU8TGm/ENZ/TDUneZyyVM5HEEmOTZEmjmy9FJaxbzGmZ2rixNO45pkjXMFp8+/XrFSNiCqNZt6LQNIqL5SfVIRUKGBjE3OG/gtahVToBdlXi5yzP1uYE0Qs4grJ/T1rUUzTXFAQa7heWA9mskf0xAMEtTSED4N9bZ4sF8cf5J+SGGlwIDAQAB\",\n  \"description\": \"Postiz browser extension for social media scheduling\",\n  \"icons\": {\n    \"32\": \"icon-32.png\",\n    \"128\": \"icon-128.png\"\n  },\n  \"permissions\": [\n    \"cookies\",\n    \"alarms\",\n    \"storage\"\n  ],\n  \"host_permissions\": [\n    \"*://*.skool.com/*\"\n  ],\n  \"background\": {\n    \"service_worker\": \"background.js\",\n    \"type\": \"module\"\n  },\n  \"externally_connectable\": {\n    \"matches\": [\n      \"http://localhost/*\",\n      \"https://localhost/*\",\n      \"https://*.postiz.com/*\"\n    ]\n  }\n}\n"
  },
  {
    "path": "apps/extension/manifest.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"Postiz\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Postiz browser extension for social media scheduling\",\n  \"icons\": {\n    \"32\": \"icon-32.png\",\n    \"128\": \"icon-128.png\"\n  },\n  \"permissions\": [\n    \"cookies\",\n    \"alarms\",\n    \"storage\"\n  ],\n  \"host_permissions\": [\n    \"*://*.skool.com/*\"\n  ],\n  \"background\": {\n    \"service_worker\": \"background.js\",\n    \"type\": \"module\"\n  },\n  \"externally_connectable\": {\n    \"matches\": [\n      \"http://localhost/*\",\n      \"https://localhost/*\",\n      \"https://*.postiz.com/*\"\n    ]\n  }\n}\n"
  },
  {
    "path": "apps/extension/package.json",
    "content": "{\n  \"name\": \"postiz-extension\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Postiz browser extension for cookie-based platform authentication\",\n  \"scripts\": {\n    \"build\": \"rm -rf dist && vite build && cp manifest.json dist/manifest.json && cd dist && zip -r ../extension.zip .\",\n    \"dev\": \"rm -rf dist && HOT_RELOAD_EXTENSION_VITE_PORT=8081 NODE_ENV=development dotenv -e ../../.env -- vite build --config vite.config.chrome.ts --mode development --watch\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "apps/extension/src/background.ts",
    "content": "import { ExtensionRequest, GetCookiesResponse, ProviderInfo, StoredRefreshEntry } from './types/messages';\nimport { getAllProviders, getProvider } from './providers/provider.registry';\nimport { CookieProvider } from './providers/cookie-provider.interface';\n\nconst EXTENSION_VERSION = '2.0.0';\nconst REFRESH_ALARM_NAME = 'cookie-refresh';\nconst STORAGE_KEY = 'refreshEntries';\n\nconst ALLOWED_ORIGIN_PATTERNS = [\n  /^https?:\\/\\/localhost(:\\d+)?$/,\n  /^https?:\\/\\/([a-z0-9-]+\\.)*postiz\\.com$/,\n];\n\nfunction isOriginAllowed(origin: string | undefined): boolean {\n  if (!origin) return false;\n  return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));\n}\n\nasync function extractCookies(provider: CookieProvider): Promise<GetCookiesResponse> {\n  const allCookies = await chrome.cookies.getAll({ url: provider.url });\n\n  const extracted: Record<string, string> = {};\n  const missingRequired: string[] = [];\n\n  for (const def of provider.cookies) {\n    const found = allCookies.find((c) => c.name === def.name);\n    if (found) {\n      extracted[def.name] = found.value;\n    } else if (def.required) {\n      missingRequired.push(def.name);\n    }\n  }\n\n  if (missingRequired.length > 0) {\n    return {\n      success: false,\n      provider: provider.identifier,\n      error: `Missing required cookies: ${missingRequired.join(', ')}. User may need to log in to ${provider.name}.`,\n      missingCookies: missingRequired,\n    };\n  }\n\n  return {\n    success: true,\n    provider: provider.identifier,\n    cookies: extracted,\n  };\n}\n\n// --- Refresh Token Storage Helpers ---\n\nasync function getStoredEntries(): Promise<Record<string, StoredRefreshEntry>> {\n  const result = await chrome.storage.local.get(STORAGE_KEY);\n  return result[STORAGE_KEY] || {};\n}\n\nasync function setStoredEntries(entries: Record<string, StoredRefreshEntry>): Promise<void> {\n  await chrome.storage.local.set({ [STORAGE_KEY]: entries });\n}\n\nasync function ensureAlarm(): Promise<void> {\n  const existing = await chrome.alarms.get(REFRESH_ALARM_NAME);\n  if (!existing) {\n    chrome.alarms.create(REFRESH_ALARM_NAME, { periodInMinutes: 1440 });\n  }\n}\n\nasync function clearAlarmIfEmpty(): Promise<void> {\n  const entries = await getStoredEntries();\n  if (Object.keys(entries).length === 0) {\n    await chrome.alarms.clear(REFRESH_ALARM_NAME);\n  }\n}\n\n// --- Background Cookie Refresh ---\n\nasync function refreshAllCookies(): Promise<void> {\n  const entries = await getStoredEntries();\n  for (const [integrationId, entry] of Object.entries(entries)) {\n    try {\n      const provider = getProvider(entry.provider);\n      if (!provider) continue;\n\n      const cookieResult = await extractCookies(provider);\n      if (!cookieResult.success) continue;\n\n      const base64Cookies = btoa(JSON.stringify(cookieResult.cookies));\n\n      await fetch(`${entry.backendUrl}/integrations/extension-refresh`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ jwt: entry.jwt, cookies: base64Cookies }),\n      });\n    } catch {\n      // Silently skip — will retry next cycle\n    }\n  }\n}\n\n// --- Alarm Listener ---\n\nchrome.alarms.onAlarm.addListener((alarm) => {\n  if (alarm.name === REFRESH_ALARM_NAME) {\n    refreshAllCookies();\n  }\n});\n\n// --- Ensure alarm on startup ---\n\n(async () => {\n  const entries = await getStoredEntries();\n  if (Object.keys(entries).length > 0) {\n    await ensureAlarm();\n  }\n})();\n\n// --- Message Listener ---\n\nchrome.runtime.onMessageExternal.addListener(\n  (\n    message: ExtensionRequest,\n    sender: chrome.runtime.MessageSender,\n    sendResponse: (response: unknown) => void\n  ) => {\n    const origin = sender.origin ?? sender.url;\n    if (!isOriginAllowed(origin)) {\n      sendResponse({ error: 'Unauthorized origin' });\n      return true;\n    }\n\n    switch (message.type) {\n      case 'PING': {\n        sendResponse({ status: 'ok', version: EXTENSION_VERSION });\n        break;\n      }\n\n      case 'GET_PROVIDERS': {\n        const providers = getAllProviders();\n        const providerInfos: ProviderInfo[] = providers.map((p) => ({\n          identifier: p.identifier,\n          name: p.name,\n          url: p.url,\n          cookieNames: p.cookies.map((c) => c.name),\n        }));\n        sendResponse({ providers: providerInfos });\n        break;\n      }\n\n      case 'GET_COOKIES': {\n        const provider = getProvider(message.provider);\n        if (!provider) {\n          sendResponse({\n            success: false,\n            provider: message.provider,\n            error: `Unknown provider: ${message.provider}`,\n          });\n          break;\n        }\n\n        extractCookies(provider)\n          .then((result) => sendResponse(result))\n          .catch((err) =>\n            sendResponse({\n              success: false,\n              provider: message.provider,\n              error: `Failed to extract cookies: ${err.message}`,\n            })\n          );\n\n        return true;\n      }\n\n      case 'STORE_REFRESH_TOKEN': {\n        (async () => {\n          const entries = await getStoredEntries();\n          entries[message.integrationId] = {\n            jwt: message.jwt,\n            backendUrl: message.backendUrl,\n            provider: message.provider,\n          };\n          await setStoredEntries(entries);\n          await ensureAlarm();\n          sendResponse({ success: true });\n        })().catch(() => sendResponse({ success: false }));\n\n        return true;\n      }\n\n      case 'REMOVE_REFRESH_TOKEN': {\n        (async () => {\n          const entries = await getStoredEntries();\n          delete entries[message.integrationId];\n          await setStoredEntries(entries);\n          await clearAlarmIfEmpty();\n          sendResponse({ success: true });\n        })().catch(() => sendResponse({ success: false }));\n\n        return true;\n      }\n\n      default: {\n        sendResponse({ error: `Unknown message type: ${(message as any).type}` });\n        break;\n      }\n    }\n\n    return true;\n  }\n);\n"
  },
  {
    "path": "apps/extension/src/providers/cookie-provider.interface.ts",
    "content": "export interface CookieDefinition {\n  /** The cookie name to extract, e.g., 'client_id' */\n  name: string;\n  /** Whether this cookie must exist for the extraction to be considered successful */\n  required: boolean;\n}\n\nexport interface CookieProvider {\n  /** Unique identifier used in messages, e.g., 'skool' */\n  identifier: string;\n  /** Human-readable name, e.g., 'Skool' */\n  name: string;\n  /** URL to query cookies for, e.g., 'https://www.skool.com' — passed to chrome.cookies.getAll({ url }) */\n  url: string;\n  /** URL pattern for host_permissions in manifest, e.g., '*://*.skool.com/*' */\n  hostPermission: string;\n  /** List of cookies to extract from this site */\n  cookies: CookieDefinition[];\n}\n"
  },
  {
    "path": "apps/extension/src/providers/list/skool.provider.ts",
    "content": "import { CookieProvider } from '../cookie-provider.interface';\n\nexport const skoolProvider: CookieProvider = {\n  identifier: 'skool',\n  name: 'Skool',\n  url: 'https://www.skool.com',\n  hostPermission: '*://*.skool.com/*',\n  cookies: [\n    { name: 'client_id', required: true },\n    { name: 'auth_token', required: true },\n  ],\n};\n"
  },
  {
    "path": "apps/extension/src/providers/provider.registry.ts",
    "content": "import { CookieProvider } from './cookie-provider.interface';\nimport { skoolProvider } from './list/skool.provider';\n\nexport const providers: CookieProvider[] = [\n  skoolProvider,\n];\n\nconst providerMap = new Map<string, CookieProvider>(\n  providers.map((p) => [p.identifier, p])\n);\n\nexport function getAllProviders(): CookieProvider[] {\n  return providers;\n}\n\nexport function getProvider(identifier: string): CookieProvider | undefined {\n  return providerMap.get(identifier);\n}\n"
  },
  {
    "path": "apps/extension/src/types/messages.ts",
    "content": "// ---- Request Types ----\n\nexport interface PingRequest {\n  type: 'PING';\n}\n\nexport interface GetProvidersRequest {\n  type: 'GET_PROVIDERS';\n}\n\nexport interface GetCookiesRequest {\n  type: 'GET_COOKIES';\n  provider: string;\n}\n\nexport interface StoreRefreshTokenRequest {\n  type: 'STORE_REFRESH_TOKEN';\n  provider: string;\n  integrationId: string;\n  jwt: string;\n  backendUrl: string;\n}\n\nexport interface RemoveRefreshTokenRequest {\n  type: 'REMOVE_REFRESH_TOKEN';\n  integrationId: string;\n}\n\nexport type ExtensionRequest =\n  | PingRequest\n  | GetProvidersRequest\n  | GetCookiesRequest\n  | StoreRefreshTokenRequest\n  | RemoveRefreshTokenRequest;\n\n// ---- Response Types ----\n\nexport interface PingResponse {\n  status: 'ok';\n  version: string;\n}\n\nexport interface ProviderInfo {\n  identifier: string;\n  name: string;\n  url: string;\n  cookieNames: string[];\n}\n\nexport interface GetProvidersResponse {\n  providers: ProviderInfo[];\n}\n\nexport interface GetCookiesSuccessResponse {\n  success: true;\n  provider: string;\n  cookies: Record<string, string>;\n}\n\nexport interface GetCookiesErrorResponse {\n  success: false;\n  provider: string;\n  error: string;\n  missingCookies?: string[];\n}\n\nexport type GetCookiesResponse =\n  | GetCookiesSuccessResponse\n  | GetCookiesErrorResponse;\n\nexport interface StoredRefreshEntry {\n  jwt: string;\n  backendUrl: string;\n  provider: string;\n}\n\nexport interface ErrorResponse {\n  error: string;\n}\n\nexport type ExtensionResponse =\n  | PingResponse\n  | GetProvidersResponse\n  | GetCookiesResponse\n  | ErrorResponse;\n"
  },
  {
    "path": "apps/extension/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"types\": [\"vite/client\", \"node\", \"chrome\"],\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\n    \"src\",\n    \"utils\",\n    \"vite.config.base.ts\",\n    \"vite.config.chrome.ts\"\n  ]\n}\n"
  },
  {
    "path": "apps/extension/vite.config.base.ts",
    "content": "import react from '@vitejs/plugin-react';\nimport { resolve } from 'path';\nimport { ManifestV3Export } from '@crxjs/vite-plugin';\nimport { defineConfig, BuildOptions } from 'vite';\nimport tsconfigPaths from 'vite-tsconfig-paths';\nimport { stripDevIcons, crxI18n } from './custom-vite-plugins';\nimport manifest from './manifest.json';\nimport devManifest from './manifest.dev.json';\nimport pkg from './package.json';\nimport { providers } from './src/providers/provider.registry';\n\nconst isDev = process.env.NODE_ENV === 'development';\n// set this flag to true, if you want localization support\nconst localize = false;\n\nconst merge = isDev ? devManifest : ({} as ManifestV3Export);\n\nexport const baseManifest = {\n  ...manifest,\n  host_permissions: [\n    import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*',\n    (import.meta.env?.NEXT_PUBLIC_BACKEND_URL || process?.env?.NEXT_PUBLIC_BACKEND_URL || '') + '/*',\n    ...providers.map(p => p.hostPermission)\n  ],\n  permissions: [...(manifest.permissions || [])],\n  version: pkg.version,\n  ...merge,\n  ...(localize\n    ? {\n        name: '__MSG_extName__',\n        description: '__MSG_extDescription__',\n        default_locale: 'en',\n      }\n    : {}),\n} as ManifestV3Export;\n\nexport const baseBuildOptions: BuildOptions = {\n  sourcemap: isDev,\n  emptyOutDir: !isDev,\n};\n\nexport default defineConfig({\n  envPrefix: ['NEXT_PUBLIC_', 'FRONTEND_URL', 'NEXT_PUBLIC_BACKEND_URL'],\n  plugins: [\n    tsconfigPaths(),\n    react(),\n    stripDevIcons(isDev),\n    crxI18n({ localize, src: './src/locales' }),\n  ],\n  publicDir: resolve(__dirname, 'public'),\n});\n"
  },
  {
    "path": "apps/extension/vite.config.chrome.ts",
    "content": "import { resolve } from 'path';\nimport { mergeConfig, defineConfig } from 'vite';\nimport { crx, ManifestV3Export } from '@crxjs/vite-plugin';\nimport baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base';\nimport hotReloadExtension from 'hot-reload-extension-vite';\n\nconst outDir = resolve(__dirname, 'dist');\nconst isDev = process.env.NODE_ENV === 'development';\n\nexport default mergeConfig(\n  baseConfig,\n  defineConfig({\n    plugins: [\n      crx({\n        manifest: {\n          ...baseManifest,\n          background: {\n            service_worker: 'src/background.ts',\n            type: 'module',\n          },\n        } as ManifestV3Export,\n        browser: 'chrome',\n        contentScripts: {\n          injectCss: true,\n        },\n      }),\n      ...(isDev\n        ? [\n            hotReloadExtension({\n              log: true,\n              backgroundPath: 'src/background.ts',\n            }),\n          ]\n        : []),\n    ],\n    build: {\n      ...baseBuildOptions,\n      outDir,\n      ...(isDev\n        ? {\n            rollupOptions: {\n              output: {\n                entryFileNames: 'assets/[name].js',\n                chunkFileNames: 'assets/[name].js',\n                assetFileNames: 'assets/[name][extname]',\n              },\n            },\n          }\n        : {}),\n    },\n  })\n);\n"
  },
  {
    "path": "apps/extension/vite.config.ts",
    "content": "import { resolve } from 'path';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  build: {\n    outDir: resolve(__dirname, 'dist'),\n    emptyOutDir: true,\n    lib: {\n      entry: resolve(__dirname, 'src/background.ts'),\n      formats: ['es'],\n      fileName: () => 'background.js',\n    },\n    rollupOptions: {\n      output: {\n        entryFileNames: 'background.js',\n      },\n    },\n    target: 'esnext',\n    minify: false,\n    sourcemap: process.env.NODE_ENV === 'development',\n  },\n  publicDir: resolve(__dirname, 'public'),\n});\n"
  },
  {
    "path": "apps/frontend/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "apps/frontend/README.md",
    "content": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.\n"
  },
  {
    "path": "apps/frontend/next.config.js",
    "content": "// @ts-check\nimport { withSentryConfig } from '@sentry/nextjs';\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  experimental: {\n    proxyTimeout: 90_000,\n  },\n  // Document-Policy header for browser profiling\n  async headers() {\n    return [{\n      source: \"/:path*\",\n      headers: [{\n        key: \"Document-Policy\",\n        value: \"js-profiling\",\n      }, ],\n    }, ];\n  },\n  reactStrictMode: false,\n  transpilePackages: ['crypto-hash'],\n  // Enable production sourcemaps for Sentry\n  productionBrowserSourceMaps: true,\n  \n  // Custom webpack config to ensure sourcemaps are generated properly\n  webpack: (config, { buildId, dev, isServer, defaultLoaders }) => {\n    // Enable sourcemaps for both client and server in production\n    if (!dev) {\n      config.devtool = isServer ? 'source-map' : 'hidden-source-map';\n    }\n    \n    return config;\n  },\n  \n  images: {\n    remotePatterns: [\n      {\n        protocol: 'http',\n        hostname: '**',\n      },\n      {\n        protocol: 'https',\n        hostname: '**',\n      },\n    ],\n  },\n  async redirects() {\n    return [\n      {\n        source: '/api/uploads/:path*',\n        destination:\n          process.env.STORAGE_PROVIDER === 'local' ? '/uploads/:path*' : '/404',\n        permanent: true,\n      },\n    ];\n  },\n  async rewrites() {\n    return [\n      {\n        source: '/uploads/:path*',\n        destination:\n          process.env.STORAGE_PROVIDER === 'local'\n            ? '/api/uploads/:path*'\n            : '/404',\n      },\n    ];\n  },\n};\n\nexport default withSentryConfig(nextConfig, {\n  org: process.env.SENTRY_ORG,\n  project: process.env.SENTRY_PROJECT,\n  authToken: process.env.SENTRY_AUTH_TOKEN,\n\n  // Sourcemap configuration optimized for monorepo\n  sourcemaps: {\n    disable: false,\n    // More comprehensive asset patterns for monorepo\n    assets: [\n      \".next/static/**/*.js\",\n      \".next/static/**/*.js.map\",\n      \".next/server/**/*.js\",\n      \".next/server/**/*.js.map\",\n    ],\n    ignore: [\n      \"**/node_modules/**\",\n      \"**/*hot-update*\",\n      \"**/_buildManifest.js\",\n      \"**/_ssgManifest.js\",\n      \"**/*.test.js\",\n      \"**/*.spec.js\",\n    ],\n    deleteSourcemapsAfterUpload: true,\n  },\n\n  // Release configuration\n  release: {\n    create: true,\n    finalize: true,\n    // Use git commit hash for releases in monorepo\n    name: process.env.VERCEL_GIT_COMMIT_SHA || process.env.GITHUB_SHA || undefined,\n  },\n\n  // NextJS specific optimizations for monorepo\n  widenClientFileUpload: true,\n\n  // Additional configuration\n  telemetry: false,\n  silent: process.env.NODE_ENV === 'production',\n  debug: process.env.NODE_ENV === 'development',\n\n  // Error handling for CI/CD\n  errorHandler: (error) => {\n    console.warn(\"Sentry build error occurred:\", error.message);\n    console.warn(\"This might be due to missing Sentry environment variables or network issues\");\n    // Don't fail the build if Sentry upload fails in monorepo context\n    return;\n  },\n});\n"
  },
  {
    "path": "apps/frontend/package.json",
    "content": "{\n  \"name\": \"postiz-frontend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"dotenv -e ../../.env -- next dev -p 4200\",\n    \"build\": \"next build\",\n    \"build:sentry\": \"dotenv -e ../../.env -- next build\",\n    \"start\": \"dotenv -e ../../.env -- next start -p 4200\",\n    \"pm2\": \"pm2 start pnpm --name frontend -- start\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "apps/frontend/postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "apps/frontend/public/.gitkeep",
    "content": ""
  },
  {
    "path": "apps/frontend/public/f.js",
    "content": "/**\n * Copyright (c) 2017-present, Facebook, Inc. All rights reserved.\n *\n * You are hereby granted a non-exclusive, worldwide, royalty-free license to use,\n * copy, modify, and distribute this software in source code or binary form for use\n * in connection with the web services and APIs provided by Facebook.\n *\n * As with any software that integrates with the Facebook platform, your use of\n * this software is subject to the Facebook Platform Policy\n * [http://developers.facebook.com/policy/]. This copyright notice shall be\n * included in all copies or substantial portions of the software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nfbq.version = '2.9.179';\nfbq._releaseSegment = 'stable';\nfbq.pendingConfigs = ['global_config'];\nfbq.__openBridgeRollout = 1.0;\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    var g = (function () {\n        function a(a, b) {\n          var c = [],\n            d = !0,\n            e = !1,\n            f = void 0;\n          try {\n            for (\n              var g =\n                  a[\n                    typeof Symbol === 'function'\n                      ? Symbol.iterator\n                      : '@@iterator'\n                  ](),\n                a;\n              !(d = (a = g.next()).done);\n              d = !0\n            ) {\n              c.push(a.value);\n              if (b && c.length === b) break;\n            }\n          } catch (a) {\n            (e = !0), (f = a);\n          } finally {\n            try {\n              !d && g['return'] && g['return']();\n            } finally {\n              if (e) throw f;\n            }\n          }\n          return c;\n        }\n        return function (b, c) {\n          if (Array.isArray(b)) return b;\n          else if (\n            (typeof Symbol === 'function' ? Symbol.iterator : '@@iterator') in\n            Object(b)\n          )\n            return a(b, c);\n          else\n            throw new TypeError(\n              'Invalid attempt to destructure non-iterable instance'\n            );\n        };\n      })(),\n      h = (function () {\n        function a(a, b) {\n          for (var c = 0; c < b.length; c++) {\n            var d = b[c];\n            d.enumerable = d.enumerable || !1;\n            d.configurable = !0;\n            'value' in d && (d.writable = !0);\n            Object.defineProperty(a, d.key, d);\n          }\n        }\n        return function (b, c, d) {\n          c && a(b.prototype, c);\n          d && a(b, d);\n          return b;\n        };\n      })(),\n      i =\n        typeof Symbol === 'function' &&\n        typeof (typeof Symbol === 'function'\n          ? Symbol.iterator\n          : '@@iterator') === 'symbol'\n          ? function (a) {\n              return typeof a;\n            }\n          : function (a) {\n              return a &&\n                typeof Symbol === 'function' &&\n                a.constructor === Symbol &&\n                a !==\n                  (typeof Symbol === 'function'\n                    ? Symbol.prototype\n                    : '@@prototype')\n                ? 'symbol'\n                : typeof a;\n            };\n    function j(a, b) {\n      if (!a)\n        throw new ReferenceError(\n          \"this hasn't been initialised - super() hasn't been called\"\n        );\n      return b && (typeof b === 'object' || typeof b === 'function') ? b : a;\n    }\n    function k(a, b) {\n      if (typeof b !== 'function' && b !== null)\n        throw new TypeError(\n          'Super expression must either be null or a function, not ' + typeof b\n        );\n      a.prototype = Object.create(b && b.prototype, {\n        constructor: {\n          value: a,\n          enumerable: !1,\n          writable: !0,\n          configurable: !0,\n        },\n      });\n      b &&\n        (Object.setPrototypeOf\n          ? Object.setPrototypeOf(a, b)\n          : (a.__proto__ = b));\n    }\n    function l(a, b, c) {\n      b in a\n        ? Object.defineProperty(a, b, {\n            value: c,\n            enumerable: !0,\n            configurable: !0,\n            writable: !0,\n          })\n        : (a[b] = c);\n      return a;\n    }\n    function m(a) {\n      if (Array.isArray(a)) {\n        for (var b = 0, c = Array(a.length); b < a.length; b++) c[b] = a[b];\n        return c;\n      } else return Array.from(a);\n    }\n    function n(a, b) {\n      if (!(a instanceof b))\n        throw new TypeError('Cannot call a class as a function');\n    }\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('generateUUID', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          function a() {\n            var a = new Date().getTime(),\n              b = 'xxxxxxxsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(\n                /[xy]/g,\n                function (b) {\n                  var c = (a + Math.random() * 16) % 16 | 0;\n                  a = Math.floor(a / 16);\n                  return (b == 'x' ? c : (c & 3) | 8).toString(16);\n                }\n              );\n            return b;\n          }\n          j.exports = a;\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsConvertNodeToHTMLElement', function () {\n      return (function (f, g, h, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          function a(a) {\n            if (\n              (typeof HTMLElement === 'undefined'\n                ? 'undefined'\n                : i(HTMLElement)) === 'object'\n            )\n              return a instanceof HTMLElement;\n            else\n              return (\n                a !== null &&\n                (typeof a === 'undefined' ? 'undefined' : i(a)) === 'object' &&\n                a.nodeType === Node.ELEMENT_NODE &&\n                typeof a.nodeName === 'string'\n              );\n          }\n          function b(b) {\n            return !a(b) ? null : b;\n          }\n          k.exports = b;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsEventValidation', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = a.logUserError,\n            c = /^[+-]?\\d+(\\.\\d+)?$/,\n            d = 'number',\n            e = 'currency_code',\n            g = {\n              AED: 1,\n              ARS: 1,\n              AUD: 1,\n              BOB: 1,\n              BRL: 1,\n              CAD: 1,\n              CHF: 1,\n              CLP: 1,\n              CNY: 1,\n              COP: 1,\n              CRC: 1,\n              CZK: 1,\n              DKK: 1,\n              EUR: 1,\n              GBP: 1,\n              GTQ: 1,\n              HKD: 1,\n              HNL: 1,\n              HUF: 1,\n              IDR: 1,\n              ILS: 1,\n              INR: 1,\n              ISK: 1,\n              JPY: 1,\n              KRW: 1,\n              MOP: 1,\n              MXN: 1,\n              MYR: 1,\n              NIO: 1,\n              NOK: 1,\n              NZD: 1,\n              PEN: 1,\n              PHP: 1,\n              PLN: 1,\n              PYG: 1,\n              QAR: 1,\n              RON: 1,\n              RUB: 1,\n              SAR: 1,\n              SEK: 1,\n              SGD: 1,\n              THB: 1,\n              TRY: 1,\n              TWD: 1,\n              USD: 1,\n              UYU: 1,\n              VEF: 1,\n              VND: 1,\n              ZAR: 1,\n            };\n          a = {\n            value: {\n              isRequired: !0,\n              type: d,\n            },\n            currency: {\n              isRequired: !0,\n              type: e,\n            },\n          };\n          var h = {\n              AddPaymentInfo: {},\n              AddToCart: {},\n              AddToWishlist: {},\n              CompleteRegistration: {},\n              Contact: {},\n              CustomEvent: {\n                validationSchema: {\n                  event: {\n                    isRequired: !0,\n                  },\n                },\n              },\n              CustomizeProduct: {},\n              Donate: {},\n              FindLocation: {},\n              InitiateCheckout: {},\n              Lead: {},\n              PageView: {},\n              PixelInitialized: {},\n              Purchase: {\n                validationSchema: a,\n              },\n              Schedule: {},\n              Search: {},\n              StartTrial: {},\n              SubmitApplication: {},\n              Subscribe: {},\n              ViewContent: {},\n            },\n            i = {\n              agent: !0,\n              automaticmatchingconfig: !0,\n              codeless: !0,\n              tracksingleonly: !0,\n              'cbdata.onetrustid': !0,\n            },\n            j = Object.prototype.hasOwnProperty;\n          function l() {\n            return {\n              error: null,\n              warnings: [],\n            };\n          }\n          function m(a) {\n            return {\n              error: a,\n              warnings: [],\n            };\n          }\n          function n(a) {\n            return {\n              error: null,\n              warnings: a,\n            };\n          }\n          function o(a) {\n            if (a) {\n              a = a.toLowerCase();\n              var b = i[a];\n              if (b !== !0)\n                return m({\n                  metadata: a,\n                  type: 'UNSUPPORTED_METADATA_ARGUMENT',\n                });\n            }\n            return l();\n          }\n          function p(a) {\n            var b =\n              arguments.length > 1 && arguments[1] !== void 0\n                ? arguments[1]\n                : {};\n            if (!a)\n              return m({\n                type: 'NO_EVENT_NAME',\n              });\n            var c = h[a];\n            return !c\n              ? n([\n                  {\n                    eventName: a,\n                    type: 'NONSTANDARD_EVENT',\n                  },\n                ])\n              : q(a, b, c);\n          }\n          function q(a, b, f) {\n            f = f.validationSchema;\n            var h = [];\n            for (var i in f)\n              if (j.call(f, i)) {\n                var k = f[i],\n                  l = b[i];\n                if (k) {\n                  if (k.isRequired != null && !j.call(b, i))\n                    return m({\n                      eventName: a,\n                      param: i,\n                      type: 'REQUIRED_PARAM_MISSING',\n                    });\n                  if (k.type != null && typeof k.type === 'string') {\n                    var o = !0;\n                    switch (k.type) {\n                      case d:\n                        k =\n                          (typeof l === 'string' || typeof l === 'number') &&\n                          c.test('' + l);\n                        k &&\n                          Number(l) < 0 &&\n                          h.push({\n                            eventName: a ? a : 'null',\n                            param: i,\n                            type: 'NEGATIVE_EVENT_PARAM',\n                          });\n                        o = k;\n                        break;\n                      case e:\n                        o = typeof l === 'string' && !!g[l.toUpperCase()];\n                        break;\n                    }\n                    if (!o)\n                      return m({\n                        eventName: a,\n                        param: i,\n                        type: 'INVALID_PARAM',\n                      });\n                  }\n                }\n              }\n            return n(h);\n          }\n          function r(a, c) {\n            a = p(a, c);\n            a.error && b(a.error);\n            if (a.warnings)\n              for (c = 0; c < a.warnings.length; c++) b(a.warnings[c]);\n            return a;\n          }\n          k.exports = {\n            validateEvent: p,\n            validateEventAndLog: r,\n            validateMetadata: o,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsActionIDConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a.coerce;\n            a = a.Typed;\n            a = a.objectWithFields({\n              portNumber: a.withValidation({\n                def: a.number(),\n                validators: [\n                  function (a) {\n                    return a > 0;\n                  },\n                ],\n              }),\n              ttlInHour: a.withValidation({\n                def: a.number(),\n                validators: [\n                  function (a) {\n                    return a > 0;\n                  },\n                ],\n              }),\n              rtcPortNumbers: a.withValidation({\n                def: a.arrayOf(a.number()),\n                validators: [\n                  function (a) {\n                    return a.every(function (a) {\n                      return a > 0;\n                    });\n                  },\n                ],\n              }),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsBaseEvent', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.map,\n            c = a.keys;\n          a = (function () {\n            function a(b) {\n              n(this, a),\n                (this._regKey = 0),\n                (this._subscriptions = {}),\n                (this._coerceArgs = b || null);\n            }\n            h(a, [\n              {\n                key: 'listen',\n                value: function (a) {\n                  var b = this,\n                    c = '' + this._regKey++;\n                  this._subscriptions[c] = a;\n                  return function () {\n                    delete b._subscriptions[c];\n                  };\n                },\n              },\n              {\n                key: 'listenOnce',\n                value: function (a) {\n                  var b = null,\n                    c = function () {\n                      b && b();\n                      b = null;\n                      return a.apply(void 0, arguments);\n                    };\n                  b = this.listen(c);\n                  return b;\n                },\n              },\n              {\n                key: 'trigger',\n                value: function () {\n                  var a = this;\n                  for (\n                    var d = arguments.length, e = Array(d), f = 0;\n                    f < d;\n                    f++\n                  )\n                    e[f] = arguments[f];\n                  return b(c(this._subscriptions), function (b) {\n                    if (b in a._subscriptions && a._subscriptions[b] != null) {\n                      var c;\n                      return (c = a._subscriptions)[b].apply(c, e);\n                    } else return null;\n                  });\n                },\n              },\n              {\n                key: 'triggerWeakly',\n                value: function () {\n                  var a =\n                    this._coerceArgs != null\n                      ? this._coerceArgs.apply(this, arguments)\n                      : null;\n                  return a == null ? [] : this.trigger.apply(this, m(a));\n                },\n              },\n            ]);\n            return a;\n          })();\n          l.exports = a;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsBatcher', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsConfigStore'),\n            b = 1e3,\n            c = 10;\n          function d() {\n            var b = a.get(null, 'batching');\n            return b != null ? b.maxBatchSize : c;\n          }\n          function e() {\n            var c = a.get(null, 'batching');\n            return c != null ? c.batchWaitTimeMs : b;\n          }\n          var i = (function () {\n            function a(b) {\n              n(this, a),\n                (this._waitHandle = null),\n                (this._data = []),\n                (this._cb = b);\n            }\n            h(a, [\n              {\n                key: 'addToBatch',\n                value: function (a) {\n                  var b = this;\n                  this._waitHandle == null &&\n                    (this._waitHandle = g.setTimeout(function () {\n                      (b._waitHandle = null), b.forceEndBatch();\n                    }, e()));\n                  this._data.push(a);\n                  this._data.length >= d() && this.forceEndBatch();\n                },\n              },\n              {\n                key: 'forceEndBatch',\n                value: function () {\n                  this._waitHandle != null &&\n                    (g.clearTimeout(this._waitHandle),\n                    (this._waitHandle = null)),\n                    this._data.length > 0 && this._cb(this._data),\n                    (this._data = []);\n                },\n              },\n            ]);\n            return a;\n          })();\n          l.exports = i;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsBrowserPropertiesConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              delayInMs: b.allowNull(b.number()),\n              enableEventSuppression: b.allowNull(b['boolean']()),\n              enableBackupTimeout: b.allowNull(b['boolean']()),\n              experiment: b.allowNull(b.string()),\n              fbcParamsConfig: b.allowNull(\n                b.objectWithFields({\n                  params: b.arrayOf(\n                    b.objectWithFields({\n                      ebp_path: b.string(),\n                      prefix: b.string(),\n                      query: b.string(),\n                    })\n                  ),\n                })\n              ),\n              enableFbcParamSplit: b.allowNull(b['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsBufferConfigTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.Typed;\n          a.coerce;\n          a = b.objectWithFields({\n            delayInMs: b.number(),\n            experimentName: b.allowNull(b.string()),\n            enableMultiEid: b.allowNull(b['boolean']()),\n          });\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsCCRuleEvaluatorConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              ccRules: b.allowNull(\n                b.arrayOf(\n                  b.allowNull(\n                    b.objectWithFields({\n                      id: b.allowNull(b.stringOrNumber()),\n                      rule: b.allowNull(b.objectOrString()),\n                    })\n                  )\n                )\n              ),\n              wcaRules: b.allowNull(\n                b.arrayOf(\n                  b.allowNull(\n                    b.objectWithFields({\n                      id: b.allowNull(b.stringOrNumber()),\n                      rule: b.allowNull(b.objectOrString()),\n                    })\n                  )\n                )\n              ),\n              valueRules: b.allowNull(\n                b.arrayOf(\n                  b.allowNull(\n                    b.objectWithFields({\n                      id: b.allowNull(b.string()),\n                      rule: b.allowNull(b.object()),\n                    })\n                  )\n                )\n              ),\n              blacklistedIframeReferrers: b.allowNull(b.mapOf(b['boolean']())),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsClientHintConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              delayInMs: b.allowNull(b.number()),\n              disableBackupTimeout: b.allowNull(b['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsClientSidePixelForkingConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a.coerce;\n            a = a.Typed;\n            a = a.objectWithFields({\n              forkedPixelIds: a.allowNull(a.arrayOf(a.string())),\n              forkedPixelIdsInBrowserChannel: a.allowNull(\n                a.arrayOf(a.string())\n              ),\n              forkedPixelIdsInServerChannel: a.allowNull(a.arrayOf(a.string())),\n              forkedPixelsInBrowserChannel: a.arrayOf(\n                a.objectWithFields({\n                  destination_pixel_id: a.string(),\n                  domains: a.allowNull(a.arrayOf(a.string())),\n                })\n              ),\n              forkedPixelsInServerChannel: a.arrayOf(\n                a.objectWithFields({\n                  destination_pixel_id: a.string(),\n                  domains: a.allowNull(a.arrayOf(a.string())),\n                })\n              ),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'signalsFBEventsCoerceAutomaticMatchingConfig',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.coerce;\n            a = a.Typed;\n            var c = a.objectWithFields({\n              selectedMatchKeys: a.arrayOf(a.string()),\n            });\n            k.exports = function (a) {\n              return b(a, c);\n            };\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'signalsFBEventsCoerceBatchingConfig',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed,\n              c = a.coerce,\n              d = a.enforce,\n              e = function (a) {\n                var e = c(\n                  a,\n                  b.objectWithFields({\n                    max_batch_size: b.number(),\n                    wait_time_ms: b.number(),\n                  })\n                );\n                return e != null\n                  ? {\n                      batchWaitTimeMs: e.wait_time_ms,\n                      maxBatchSize: e.max_batch_size,\n                    }\n                  : d(\n                      a,\n                      b.objectWithFields({\n                        batchWaitTimeMs: b.number(),\n                        maxBatchSize: b.number(),\n                      })\n                    );\n              };\n            k.exports = function (a) {\n              return c(a, e);\n            };\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'signalsFBEventsCoerceInferedEventsConfig',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.coerce;\n            a = a.Typed;\n            var c = a.objectWithFields({\n              buttonSelector: a.allowNull(a.string()),\n              disableRestrictedData: a.allowNull(a['boolean']()),\n            });\n            k.exports = function (a) {\n              return b(a, c);\n            };\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'signalsFBEventsCoerceParameterExtractors',\n      function () {\n        return (function (g, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n              b = a.filter,\n              c = a.map,\n              d = f.getFbeventsModules(\n                'signalsFBEventsCoerceStandardParameter'\n              );\n            function e(a) {\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              var b = a.domain_uri,\n                c = a.event_type,\n                d = a.extractor_type;\n              a = a.id;\n              b = typeof b === 'string' ? b : null;\n              c = c != null && typeof c === 'string' && c !== '' ? c : null;\n              a = a != null && typeof a === 'string' && a !== '' ? a : null;\n              d =\n                d === 'CONSTANT_VALUE' ||\n                d === 'CSS' ||\n                d === 'GLOBAL_VARIABLE' ||\n                d === 'GTM' ||\n                d === 'JSON_LD' ||\n                d === 'META_TAG' ||\n                d === 'OPEN_GRAPH' ||\n                d === 'RDFA' ||\n                d === 'SCHEMA_DOT_ORG' ||\n                d === 'URI'\n                  ? d\n                  : null;\n              return b != null && c != null && a != null && d != null\n                ? {\n                    domain_uri: b,\n                    event_type: c,\n                    extractor_type: d,\n                    id: a,\n                  }\n                : null;\n            }\n            function g(a) {\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              a = a.extractor_config;\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              var b = a.parameter_type;\n              a = a.value;\n              b = d(b);\n              a = a != null && typeof a === 'string' && a !== '' ? a : null;\n              return b != null && a != null\n                ? {\n                    parameter_type: b,\n                    value: a,\n                  }\n                : null;\n            }\n            function h(a) {\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              var b = a.parameter_type;\n              a = a.selector;\n              b = d(b);\n              a = a != null && typeof a === 'string' && a !== '' ? a : null;\n              return b != null && a != null\n                ? {\n                    parameter_type: b,\n                    selector: a,\n                  }\n                : null;\n            }\n            function j(a) {\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              a = a.extractor_config;\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              a = a.parameter_selectors;\n              if (Array.isArray(a)) {\n                a = c(a, h);\n                var d = b(a, Boolean);\n                if (a.length === d.length)\n                  return {\n                    parameter_selectors: d,\n                  };\n              }\n              return null;\n            }\n            function k(a) {\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              a = a.extractor_config;\n              if (\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              var b = a.context,\n                c = a.parameter_type;\n              a = a.value;\n              b = b != null && typeof b === 'string' && b !== '' ? b : null;\n              c = d(c);\n              a = a != null && typeof a === 'string' && a !== '' ? a : null;\n              return b != null && c != null && a != null\n                ? {\n                    context: b,\n                    parameter_type: c,\n                    value: a,\n                  }\n                : null;\n            }\n            function m(a) {\n              var b = e(a);\n              if (\n                b == null ||\n                a == null ||\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object'\n              )\n                return null;\n              var c = b.domain_uri,\n                d = b.event_type,\n                f = b.extractor_type;\n              b = b.id;\n              if (f === 'CSS') {\n                var h = j(a);\n                if (h != null)\n                  return {\n                    domain_uri: c,\n                    event_type: d,\n                    extractor_config: h,\n                    extractor_type: 'CSS',\n                    id: b,\n                  };\n              }\n              if (f === 'CONSTANT_VALUE') {\n                h = g(a);\n                if (h != null)\n                  return {\n                    domain_uri: c,\n                    event_type: d,\n                    extractor_config: h,\n                    extractor_type: 'CONSTANT_VALUE',\n                    id: b,\n                  };\n              }\n              if (f === 'GLOBAL_VARIABLE')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'GLOBAL_VARIABLE',\n                  id: b,\n                };\n              if (f === 'GTM')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'GTM',\n                  id: b,\n                };\n              if (f === 'JSON_LD')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'JSON_LD',\n                  id: b,\n                };\n              if (f === 'META_TAG')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'META_TAG',\n                  id: b,\n                };\n              if (f === 'OPEN_GRAPH')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'OPEN_GRAPH',\n                  id: b,\n                };\n              if (f === 'RDFA')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'RDFA',\n                  id: b,\n                };\n              if (f === 'SCHEMA_DOT_ORG')\n                return {\n                  domain_uri: c,\n                  event_type: d,\n                  extractor_type: 'SCHEMA_DOT_ORG',\n                  id: b,\n                };\n              if (f === 'URI') {\n                h = k(a);\n                if (h != null)\n                  return {\n                    domain_uri: c,\n                    event_type: d,\n                    extractor_config: h,\n                    extractor_type: 'URI',\n                    id: b,\n                  };\n              }\n              return null;\n            }\n            l.exports = m;\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsCoercePixelID', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = a.logUserError;\n          a = f.getFbeventsModules('SignalsFBEventsTyped');\n          var c = a.Typed,\n            d = a.coerce;\n          function e(a) {\n            a = d(a, c.fbid());\n            if (a == null) {\n              var e = JSON.stringify(a);\n              b({\n                pixelID: e != null ? e : 'undefined',\n                type: 'INVALID_PIXEL_ID',\n              });\n              return null;\n            }\n            return a;\n          }\n          k.exports = e;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsCoercePrimitives', function () {\n      return (function (g, h, j, k) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsUtils'),\n            c = b.filter,\n            d = b.map,\n            e = b.reduce;\n          function g(a) {\n            return Object.values(a);\n          }\n          function h(a) {\n            return typeof a === 'boolean' ? a : null;\n          }\n          function j(a) {\n            return typeof a === 'number' ? a : null;\n          }\n          function k(a) {\n            return typeof a === 'string' ? a : null;\n          }\n          function n(a) {\n            return (typeof a === 'undefined' ? 'undefined' : i(a)) ===\n              'object' &&\n              !Array.isArray(a) &&\n              a != null\n              ? a\n              : null;\n          }\n          function o(a) {\n            return Array.isArray(a) ? a : null;\n          }\n          function p(a, b) {\n            return g(a).includes(b) ? b : null;\n          }\n          function q(a, b) {\n            a = o(a);\n            return a == null\n              ? null\n              : c(d(a, b), function (a) {\n                  return a != null;\n                });\n          }\n          function r(a, b) {\n            var c = o(a);\n            if (c == null) return null;\n            a = q(a, b);\n            return a == null ? null : a.length === c.length ? a : null;\n          }\n          function s(b, c) {\n            var d = n(b);\n            if (d == null) return null;\n            b = e(\n              Object.keys(d),\n              function (b, e) {\n                var f = c(d[e]);\n                return f == null ? b : a({}, b, l({}, e, f));\n              },\n              {}\n            );\n            return Object.keys(d).length === Object.keys(b).length ? b : null;\n          }\n          function t(a) {\n            var b = function (b) {\n              return a(b);\n            };\n            b.nullable = !0;\n            return b;\n          }\n          function u(b, c) {\n            var d = n(b);\n            if (d == null) return null;\n            b = Object.keys(c).reduce(function (b, e) {\n              if (b == null) return null;\n              var f = c[e],\n                g = d[e];\n              if (f.nullable === !0 && g == null)\n                return a({}, b, l({}, e, null));\n              f = f(g);\n              return f == null ? null : a({}, b, l({}, e, f));\n            }, {});\n            return b != null ? Object.freeze(b) : null;\n          }\n          m.exports = {\n            coerceArray: o,\n            coerceArrayFilteringNulls: q,\n            coerceArrayOf: r,\n            coerceBoolean: h,\n            coerceEnum: p,\n            coerceMapOf: s,\n            coerceNullableField: t,\n            coerceNumber: j,\n            coerceObject: n,\n            coerceObjectWithFields: u,\n            coerceString: k,\n          };\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'signalsFBEventsCoerceStandardParameter',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsUtils');\n            a = a.FBSet;\n            var b = new a([\n              'content_category',\n              'content_ids',\n              'content_name',\n              'content_type',\n              'currency',\n              'contents',\n              'num_items',\n              'order_id',\n              'predicted_ltv',\n              'search_string',\n              'status',\n              'subscription_id',\n              'value',\n              'id',\n              'item_price',\n              'quantity',\n              'ct',\n              'db',\n              'em',\n              'external_id',\n              'fn',\n              'ge',\n              'ln',\n              'namespace',\n              'ph',\n              'st',\n              'zp',\n            ]);\n            function c(a) {\n              return typeof a === 'string' && b.has(a) ? a : null;\n            }\n            k.exports = c;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsConfigLoadedEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('signalsFBEventsCoercePixelID');\n          function c(a) {\n            a = b(a);\n            return a != null ? [a] : null;\n          }\n          a = new a(c);\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsConfigStore', function () {\n      return (function (g, i, j, k) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules(\n              'signalsFBEventsCoerceAutomaticMatchingConfig'\n            ),\n            b = f.getFbeventsModules('signalsFBEventsCoerceBatchingConfig'),\n            c = f.getFbeventsModules(\n              'signalsFBEventsCoerceInferedEventsConfig'\n            ),\n            d = f.getFbeventsModules('signalsFBEventsCoercePixelID'),\n            e = f.getFbeventsModules('SignalsFBEventsLogging'),\n            g = e.logError,\n            i = f.getFbeventsModules('SignalsFBEventsQE');\n          e = f.getFbeventsModules(\n            'SignalsFBEventsBrowserPropertiesConfigTypedef'\n          );\n          var j = f.getFbeventsModules('SignalsFBEventsBufferConfigTypedef'),\n            k = f.getFbeventsModules(\n              'SignalsFBEventsESTRuleEngineConfigTypedef'\n            ),\n            o = f.getFbeventsModules(\n              'SignalsFBEventsDataProcessingOptionsConfigTypedef'\n            ),\n            p = f.getFbeventsModules(\n              'SignalsFBEventsDefaultCustomDataConfigTypedef'\n            ),\n            q = f.getFbeventsModules('SignalsFBEventsMicrodataConfigTypedef'),\n            r = f.getFbeventsModules('SignalsFBEventsOpenBridgeConfigTypedef'),\n            s = f.getFbeventsModules(\n              'SignalsFBEventsParallelFireConfigTypedef'\n            ),\n            t = f.getFbeventsModules('SignalsFBEventsProhibitedSourcesTypedef'),\n            u = f.getFbeventsModules('SignalsFBEventsTyped'),\n            v = u.Typed,\n            w = u.coerce;\n          u = f.getFbeventsModules('SignalsFBEventsUnwantedDataTypedef');\n          var x = f.getFbeventsModules(\n              'SignalsFBEventsEventValidationConfigTypedef'\n            ),\n            y = f.getFbeventsModules(\n              'SignalsFBEventsProtectedDataModeConfigTypedef'\n            ),\n            z = f.getFbeventsModules('SignalsFBEventsClientHintConfigTypedef'),\n            A = f.getFbeventsModules(\n              'SignalsFBEventsCCRuleEvaluatorConfigTypedef'\n            ),\n            B = f.getFbeventsModules(\n              'SignalsFBEventsRestrictedDomainsConfigTypedef'\n            ),\n            C = f.getFbeventsModules(\n              'SignalsFBEventsIABPCMAEBridgeConfigTypedef'\n            ),\n            D = f.getFbeventsModules(\n              'SignalsFBEventsCookieDeprecationLabelConfigTypedef'\n            ),\n            E = f.getFbeventsModules(\n              'SignalsFBEventsUnwantedEventsConfigTypedef'\n            ),\n            F = f.getFbeventsModules(\n              'SignalsFBEventsUnwantedEventNamesConfigTypedef'\n            ),\n            G = f.getFbeventsModules(\n              'SignalsFBEventsUnwantedParamsConfigTypedef'\n            ),\n            H = f.getFbeventsModules(\n              'SignalsFBEventsStandardParamChecksConfigTypedef'\n            ),\n            I = f.getFbeventsModules(\n              'SignalsFBEventsClientSidePixelForkingConfigTypedef'\n            ),\n            J = f.getFbeventsModules('SignalsFBEventsCookieConfigTypedef'),\n            K = f.getFbeventsModules('SignalsFBEventsActionIDConfigTypedef'),\n            L = f.getFbeventsModules('SignalsFBEventsGatingConfigTypedef'),\n            M = f.getFbeventsModules(\n              'SignalsFBEventsProhibitedPixelConfigTypedef'\n            ),\n            N = 'global',\n            O = {\n              automaticMatching: a,\n              openbridge: r,\n              batching: b,\n              inferredEvents: c,\n              microdata: q,\n              prohibitedSources: t,\n              unwantedData: u,\n              dataProcessingOptions: o,\n              parallelfire: s,\n              buffer: j,\n              browserProperties: e,\n              defaultCustomData: p,\n              estRuleEngine: k,\n              eventValidation: x,\n              protectedDataMode: y,\n              clientHint: z,\n              ccRuleEvaluator: A,\n              restrictedDomains: B,\n              IABPCMAEBridge: C,\n              cookieDeprecationLabel: D,\n              unwantedEvents: E,\n              unwantedEventNames: F,\n              unwantedParams: G,\n              standardParamChecks: H,\n              clientSidePixelForking: I,\n              cookie: J,\n              actionID: K,\n              gating: L,\n              prohibitedPixels: M,\n            };\n          a = (function () {\n            function a() {\n              var b;\n              n(this, a);\n              this._configStore =\n                ((b = {\n                  automaticMatching: {},\n                  batching: {},\n                  inferredEvents: {},\n                  microdata: {},\n                  prohibitedSources: {},\n                  unwantedData: {},\n                  dataProcessingOptions: {},\n                  openbridge: {},\n                  parallelfire: {},\n                  buffer: {},\n                  defaultCustomData: {},\n                  estRuleEngine: {},\n                }),\n                l(b, 'defaultCustomData', {}),\n                l(b, 'browserProperties', {}),\n                l(b, 'eventValidation', {}),\n                l(b, 'protectedDataMode', {}),\n                l(b, 'clientHint', {}),\n                l(b, 'ccRuleEvaluator', {}),\n                l(b, 'restrictedDomains', {}),\n                l(b, 'IABPCMAEBridge', {}),\n                l(b, 'cookieDeprecationLabel', {}),\n                l(b, 'unwantedEvents', {}),\n                l(b, 'unwantedParams', {}),\n                l(b, 'standardParamChecks', {}),\n                l(b, 'unwantedEventNames', {}),\n                l(b, 'clientSidePixelForking', {}),\n                l(b, 'cookie', {}),\n                l(b, 'actionID', {}),\n                l(b, 'gating', {}),\n                l(b, 'prohibitedPixels', {}),\n                b);\n            }\n            h(a, [\n              {\n                key: 'set',\n                value: function (a, b, c) {\n                  a = a == null ? N : d(a);\n                  if (a == null) return;\n                  b = w(b, v.string());\n                  if (b == null) return;\n                  if (this._configStore[b] == null) return;\n                  this._configStore[b][a] = O[b] != null ? O[b](c) : c;\n                },\n              },\n              {\n                key: 'setExperimental',\n                value: function (a) {\n                  a = w(\n                    a,\n                    v.objectWithFields({\n                      config: v.object(),\n                      experimentName: v.string(),\n                      pixelID: d,\n                      pluginName: v.string(),\n                    })\n                  );\n                  if (a == null) return;\n                  var b = a.config,\n                    c = a.experimentName,\n                    e = a.pixelID;\n                  a = a.pluginName;\n                  i.isInTest(c) && this.set(e, a, b);\n                },\n              },\n              {\n                key: 'get',\n                value: function (a, b) {\n                  return this._configStore[b][a != null ? a : N];\n                },\n              },\n              {\n                key: 'getWithGlobalFallback',\n                value: function (a, b) {\n                  var c = N;\n                  b = this._configStore[b];\n                  a != null &&\n                    Object.prototype.hasOwnProperty.call(b, a) &&\n                    (c = a);\n                  return b[c];\n                },\n              },\n              {\n                key: 'getAutomaticMatchingConfig',\n                value: function (a) {\n                  g(new Error('Calling legacy api getAutomaticMatchingConfig'));\n                  return this.get(a, 'automaticMatching');\n                },\n              },\n              {\n                key: 'getInferredEventsConfig',\n                value: function (a) {\n                  g(new Error('Calling legacy api getInferredEventsConfig'));\n                  return this.get(a, 'inferredEvents');\n                },\n              },\n            ]);\n            return a;\n          })();\n          m.exports = new a();\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsCookieConfigTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.Typed;\n          a.coerce;\n          a = b.objectWithFields({\n            fbcParamsConfig: b.allowNull(\n              b.objectWithFields({\n                params: b.arrayOf(\n                  b.objectWithFields({\n                    ebp_path: b.string(),\n                    prefix: b.string(),\n                    query: b.string(),\n                  })\n                ),\n              })\n            ),\n            enableFbcParamSplit: b.allowNull(b['boolean']()),\n          });\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsCookieDeprecationLabelConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              delayInMs: b.allowNull(b.number()),\n              disableBackupTimeout: b.allowNull(b['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsDataProcessingOptionsConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              dataProcessingOptions: a.withValidation({\n                def: a.arrayOf(a.string()),\n                validators: [\n                  function (a) {\n                    return a.reduce(function (a, b) {\n                      return a === !0 && b === 'LDU';\n                    }, !0);\n                  },\n                ],\n              }),\n              dataProcessingCountry: a.withValidation({\n                def: a.allowNull(a.number()),\n                validators: [\n                  function (a) {\n                    return a === null || a === 0 || a === 1;\n                  },\n                ],\n              }),\n              dataProcessingState: a.withValidation({\n                def: a.allowNull(a.number()),\n                validators: [\n                  function (a) {\n                    return a === null || a === 0 || a === 1e3;\n                  },\n                ],\n              }),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsDefaultCustomDataConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              enable_order_id: b['boolean'](),\n              enable_value: b['boolean'](),\n              enable_currency: b['boolean'](),\n              enable_contents: b['boolean'](),\n              enable_content_ids: b['boolean'](),\n              enable_content_type: b['boolean'](),\n              experiment: b.allowNull(b.string()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsDoAutomaticMatching', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.keys,\n            c = f.getFbeventsModules('SignalsFBEventsConfigStore');\n          a = f.getFbeventsModules('SignalsFBEventsEvents');\n          var d = a.piiAutomatched;\n          function e(a, e, f, g) {\n            a = g != null ? g : c.get(e.id, 'automaticMatching');\n            if (b(f).length > 0 && a != null) {\n              g = a.selectedMatchKeys;\n              for (a in f)\n                g.indexOf(a) >= 0 && (e.userDataFormFields[a] = f[a]);\n              d.trigger(e);\n            }\n          }\n          k.exports = e;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsESTRuleEngineConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              experimentName: b.allowNull(b.string()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsEvents', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsConfigLoadedEvent'),\n            c = f.getFbeventsModules('SignalsFBEventsFiredEvent'),\n            d = f.getFbeventsModules('SignalsFBEventsGetCustomParametersEvent'),\n            e = f.getFbeventsModules('SignalsFBEventsGetIWLParametersEvent'),\n            g = f.getFbeventsModules('SignalsFBEventsIWLBootStrapEvent'),\n            h = f.getFbeventsModules('SignalsFBEventsPIIAutomatchedEvent'),\n            i = f.getFbeventsModules('SignalsFBEventsPIIConflictingEvent'),\n            j = f.getFbeventsModules('SignalsFBEventsPIIInvalidatedEvent'),\n            l = f.getFbeventsModules('SignalsFBEventsPluginLoadedEvent'),\n            m = f.getFbeventsModules('SignalsFBEventsSetEventIDEvent'),\n            n = f.getFbeventsModules('SignalsFBEventsSetIWLExtractorsEvent'),\n            o = f.getFbeventsModules('SignalsFBEventsSetESTRules'),\n            p = f.getFbeventsModules('SignalsFBEventsSetCCRules'),\n            q = f.getFbeventsModules(\n              'SignalsFBEventsValidateCustomParametersEvent'\n            ),\n            r = f.getFbeventsModules(\n              'SignalsFBEventsLateValidateCustomParametersEvent'\n            ),\n            s = f.getFbeventsModules(\n              'SignalsFBEventsValidateUrlParametersEvent'\n            ),\n            t = f.getFbeventsModules('SignalsFBEventsGetAemResultEvent'),\n            u = f.getFbeventsModules(\n              'SignalsFBEventsValidateGetClickIDFromBrowserProperties'\n            ),\n            v = f.getFbeventsModules('SignalsFBEventsExtractPII'),\n            w = f.getFbeventsModules('SignalsFBEventsSetFBPEvent');\n          b = {\n            configLoaded: b,\n            execEnd: new a(),\n            fired: c,\n            getCustomParameters: d,\n            getIWLParameters: e,\n            iwlBootstrap: g,\n            piiAutomatched: h,\n            piiConflicting: i,\n            piiInvalidated: j,\n            pluginLoaded: l,\n            setEventId: m,\n            setIWLExtractors: n,\n            setESTRules: o,\n            setCCRules: p,\n            validateCustomParameters: q,\n            lateValidateCustomParameters: r,\n            validateUrlParameters: s,\n            getAemResult: t,\n            getClickIDFromBrowserProperties: u,\n            extractPii: v,\n            setFBP: w,\n          };\n          k.exports = b;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsEventValidationConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              unverifiedEventNames: b.allowNull(b.arrayOf(b.string())),\n              enableEventSanitization: b.allowNull(b['boolean']()),\n              restrictedEventNames: b.allowNull(b.arrayOf(b.string())),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsExperimentNames', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          j.exports = {\n            BATCHING_EXPERIMENT: 'batching',\n            SEND_XHR_EXPERIMENT: 'send_xhr',\n            USE_FBC_AS_CACHE_KEY_EXPERIMENT: 'use_fbc_as_cache_key',\n            NETWORK_RETRY_EXPERIMENT: 'network_retry_when_not_success',\n            BUFFER_EVENTS_EXPERIMENT: 'buffer_events',\n            NO_OP_EXPERIMENT: 'no_op_exp',\n            NO_CD_FILTERED_PARAMS: 'no_cd_filtered_params',\n            LOWER_MICRODATA_DELAY: 'lower_microdata_delay',\n          };\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsExperimentsTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.Typed;\n          a.coerce;\n          a.enforce;\n          a = b.arrayOf(\n            b.objectWithFields({\n              allocation: b.number(),\n              code: b.string(),\n              name: b.string(),\n              passRate: b.number(),\n            })\n          );\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsExtractPII', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n            c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.Typed,\n            e = c.coerce;\n          function g(a, c, f) {\n            c = e(a, b);\n            f = d.allowNull(d.object());\n            a = d.allowNull(d.object());\n            return c != null\n              ? [\n                  {\n                    pixel: c,\n                    form: f,\n                    button: a,\n                  },\n                ]\n              : null;\n          }\n          c = new a(g);\n          k.exports = c;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsFBQ', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsEventValidation'),\n            c = f.getFbeventsModules('SignalsFBEventsConfigStore'),\n            d = f.getFbeventsModules('SignalsFBEventsEvents'),\n            e = d.configLoaded,\n            k = f.getFbeventsModules('SignalsFBEventsFireLock'),\n            o = f.getFbeventsModules('SignalsFBEventsJSLoader');\n          d = f.getFbeventsModules('SignalsFBEventsLogging');\n          var p = f.getFbeventsModules('SignalsFBEventsOptIn'),\n            q = f.getFbeventsModules('SignalsFBEventsUtils'),\n            r = f.getFbeventsModules('signalsFBEventsGetIsIosInAppBrowser'),\n            s = f.getFbeventsModules('SignalsFBEventsURLUtil'),\n            t = s.getURLParameter,\n            u = f.getFbeventsModules('SignalsFBEventsGetValidUrl'),\n            v = f.getFbeventsModules('SignalsFBEventsResolveLink');\n          s = f.getFbeventsModules('SignalsPixelCookieUtils');\n          var w = s.CLICK_ID_PARAMETER,\n            x = s.readPackedCookie,\n            y = s.CLICKTHROUGH_COOKIE_NAME;\n          s = f.getFbeventsModules('SignalsFBEventsExperimentNames');\n          var z = s.USE_FBC_AS_CACHE_KEY_EXPERIMENT,\n            A = f.getFbeventsModules('SignalsFBEventsQE'),\n            B = f.getFbeventsModules('SignalsFBEventsModuleEncodings'),\n            C = f.getFbeventsModules('SignalsParamList'),\n            D = f.getFbeventsModules('signalsFBEventsSendEvent'),\n            E = q.each,\n            F = q.keys,\n            G = q.map,\n            H = q.some,\n            I = d.logError,\n            J = d.logUserError,\n            K = {\n              AutomaticMatching: !0,\n              AutomaticMatchingForPartnerIntegrations: !0,\n              DefaultCustomData: !0,\n              Buffer: !0,\n              CommonIncludes: !0,\n              FirstPartyCookies: !0,\n              IWLBootstrapper: !0,\n              IWLParameters: !0,\n              IdentifyIntegration: !0,\n              InferredEvents: !0,\n              Microdata: !0,\n              MicrodataJsonLd: !0,\n              OpenBridge: !0,\n              ParallelFire: !0,\n              ProhibitedSources: !0,\n              Timespent: !0,\n              UnwantedData: !0,\n              LocalComputation: !0,\n              IABPCMAEBridge: !0,\n              AEM: !0,\n              BrowserProperties: !0,\n              ESTRuleEngine: !0,\n              EventValidation: !0,\n              ProtectedDataMode: !0,\n              PrivacySandbox: !0,\n              ClientHint: !0,\n              CCRuleEvaluator: !0,\n              ProhibitedPixels: !0,\n              LastExternalReferrer: !0,\n              CookieDeprecationLabel: !0,\n              UnwantedEvents: !0,\n              UnwantedEventNames: !0,\n              UnwantedParams: !0,\n              StandardParamChecks: !0,\n              ShopifyAppIntegratedPixel: !0,\n              clientSidePixelForking: !0,\n              ShadowTest: !0,\n              ActionID: !0,\n              TopicsAPI: !0,\n              Gating: !0,\n              AutomaticParameters: !0,\n            },\n            L = {\n              Track: 0,\n              TrackCustom: 4,\n              TrackSingle: 1,\n              TrackSingleCustom: 2,\n              TrackSingleSystem: 3,\n              TrackSystem: 5,\n            };\n          s = ['InferredEvents', 'Microdata'];\n          var M = {\n              AutomaticSetup: s,\n            },\n            N = {\n              AutomaticMatching: ['inferredevents', 'identity'],\n              AutomaticMatchingForPartnerIntegrations: [\n                'automaticmatchingforpartnerintegrations',\n              ],\n              CommonIncludes: ['commonincludes'],\n              DefaultCustomData: ['defaultcustomdata'],\n              FirstPartyCookies: ['cookie'],\n              IWLBootstrapper: ['iwlbootstrapper'],\n              IWLParameters: ['iwlparameters'],\n              ESTRuleEngine: ['estruleengine'],\n              IdentifyIntegration: ['identifyintegration'],\n              Buffer: ['buffer'],\n              InferredEvents: ['inferredevents', 'identity'],\n              Microdata: ['microdata', 'identity'],\n              MicrodataJsonLd: ['jsonld_microdata'],\n              ParallelFire: ['parallelfire'],\n              ProhibitedSources: ['prohibitedsources'],\n              Timespent: ['timespent'],\n              UnwantedData: ['unwanteddata'],\n              LocalComputation: ['localcomputation'],\n              IABPCMAEBridge: ['iabpcmaebridge'],\n              AEM: ['aem'],\n              BrowserProperties: ['browserproperties'],\n              EventValidation: ['eventvalidation'],\n              ProtectedDataMode: ['protecteddatamode'],\n              PrivacySandbox: ['privacysandbox'],\n              ClientHint: ['clienthint'],\n              CCRuleEvaluator: ['ccruleevaluator'],\n              ProhibitedPixels: ['prohibitedpixels'],\n              LastExternalReferrer: ['lastexternalreferrer'],\n              CookieDeprecationLabel: ['cookiedeprecationlabel'],\n              UnwantedEvents: ['unwantedevents'],\n              UnwantedEventNames: ['unwantedeventnames'],\n              UnwantedParams: ['unwantedparams'],\n              ShopifyAppIntegratedPixel: ['shopifyappintegratedpixel'],\n              clientSidePixelForking: ['clientsidepixelforking'],\n              actionID: ['actionid'],\n              TopicsAPI: ['topicsapi'],\n              Gating: ['gating'],\n              AutomaticParameters: ['automaticparameters'],\n            };\n          function O(a) {\n            return !!(K[a] || M[a]);\n          }\n          var P = function (a, b, c, d, e, f) {\n            var g = new C(function (a) {\n              return a;\n            });\n            g.append('v', b);\n            g.append('r', c);\n            d === !0 && g.append('no_min', !0);\n            e != null && e != '' && g.append('domain', e);\n            f != null && r() && e != '' && g.append('fbc', f);\n            B.addEncodings(g);\n            return (\n              o.CONFIG.CDN_BASE_URL +\n              'signals/config/' +\n              a +\n              '?' +\n              g.toQueryString()\n            );\n          };\n          function Q(a, b, c, d, e, f) {\n            o.loadJSFile(P(a, b, c, e, d, f));\n          }\n          q = (function () {\n            function d(a, b) {\n              var e = this;\n              n(this, d);\n              this.VALID_FEATURES = K;\n              this.optIns = new p(M);\n              this.configsLoaded = {};\n              this.locks = k.global;\n              this.pluginConfig = c;\n              this.disableFirstPartyCookies = !1;\n              this.disableAutoConfig = !1;\n              this.disableErrorLogging = !1;\n              this.VERSION = a.version;\n              this.RELEASE_SEGMENT = a._releaseSegment;\n              this.pixelsByID = b;\n              this.fbq = a;\n              E(a.pendingConfigs || [], function (a) {\n                return e.locks.lockConfig(a);\n              });\n            }\n            h(d, [\n              {\n                key: 'optIn',\n                value: function (a, b) {\n                  var c = this,\n                    d =\n                      arguments.length > 2 && arguments[2] !== void 0\n                        ? arguments[2]\n                        : !1;\n                  if (typeof b !== 'string' || !O(b))\n                    throw new Error(\n                      'Invalid Argument: \"' +\n                        b +\n                        '\" is not a valid opt-in feature'\n                    );\n                  O(b) &&\n                    (this.optIns.optIn(a, b, d),\n                    E([b].concat(m(M[b] || [])), function (a) {\n                      N[a] &&\n                        E(N[a], function (a) {\n                          return c.fbq.loadPlugin(a);\n                        });\n                    }));\n                  return this;\n                },\n              },\n              {\n                key: 'optOut',\n                value: function (a, b) {\n                  this.optIns.optOut(a, b);\n                  return this;\n                },\n              },\n              {\n                key: 'consent',\n                value: function (a) {\n                  a === 'revoke'\n                    ? this.locks.lockConsent()\n                    : a === 'grant'\n                    ? this.locks.unlockConsent()\n                    : J({\n                        action: a,\n                        type: 'INVALID_CONSENT_ACTION',\n                      });\n                  return this;\n                },\n              },\n              {\n                key: 'setUserProperties',\n                value: function (b, c) {\n                  var d = this.pluginConfig.get(null, 'dataProcessingOptions');\n                  if (d != null && d.dataProcessingOptions.includes('LDU'))\n                    return;\n                  if (\n                    !Object.prototype.hasOwnProperty.call(this.pixelsByID, b)\n                  ) {\n                    J({\n                      pixelID: b,\n                      type: 'PIXEL_NOT_INITIALIZED',\n                    });\n                    return;\n                  }\n                  this.trackSingleSystem(\n                    'user_properties',\n                    b,\n                    'UserProperties',\n                    a({}, c)\n                  );\n                },\n              },\n              {\n                key: 'trackSingle',\n                value: function (a, c, d, e) {\n                  b.validateEventAndLog(c, d);\n                  return this.trackSingleGeneric(a, c, d, L.TrackSingle, e);\n                },\n              },\n              {\n                key: 'trackSingleCustom',\n                value: function (a, b, c, d) {\n                  return this.trackSingleGeneric(\n                    a,\n                    b,\n                    c,\n                    L.TrackSingleCustom,\n                    d\n                  );\n                },\n              },\n              {\n                key: 'trackSingleSystem',\n                value: function (a, b, c, d, e) {\n                  return this.trackSingleGeneric(\n                    b,\n                    c,\n                    d,\n                    L.TrackSingleSystem,\n                    e || null,\n                    a\n                  );\n                },\n              },\n              {\n                key: 'trackSingleGeneric',\n                value: function (b, c, d, e, f, g) {\n                  b = typeof b === 'string' ? b : b.id;\n                  if (\n                    !Object.prototype.hasOwnProperty.call(this.pixelsByID, b)\n                  ) {\n                    var h = {\n                      pixelID: b,\n                      type: 'PIXEL_NOT_INITIALIZED',\n                    };\n                    g == null ? J(h) : I(new Error(h.type + ' ' + h.pixelID));\n                    return this;\n                  }\n                  h = this.getDefaultSendData(b, c, f);\n                  h.customData = d;\n                  g != null &&\n                    (h.customParameters = {\n                      es: g,\n                    });\n                  h.customParameters = a({}, h.customParameters, {\n                    tm: '' + e,\n                  });\n                  this.fire(h, !1);\n                  return this;\n                },\n              },\n              {\n                key: '_validateSend',\n                value: function (a, c) {\n                  if (!a.eventName || !a.eventName.length)\n                    throw new Error('Event name not specified');\n                  if (!a.pixelId || !a.pixelId.length)\n                    throw new Error('PixelId not specified');\n                  a.set &&\n                    E(\n                      G(F(a.set), function (a) {\n                        return b.validateMetadata(a);\n                      }),\n                      function (a) {\n                        if (a.error) throw new Error(a.error);\n                        a.warnings.length && E(a.warnings, J);\n                      }\n                    );\n                  if (c) {\n                    c = b.validateEvent(a.eventName, a.customData || {});\n                    if (c.error) throw new Error(c.error);\n                    c.warnings && c.warnings.length && E(c.warnings, J);\n                  }\n                  return this;\n                },\n              },\n              {\n                key: '_argsHasAnyUserData',\n                value: function (a) {\n                  var b = a.userData != null && F(a.userData).length > 0;\n                  a =\n                    a.userDataFormFields != null &&\n                    F(a.userDataFormFields).length > 0;\n                  return b || a;\n                },\n              },\n              {\n                key: 'fire',\n                value: function (a) {\n                  var b =\n                    arguments.length > 1 && arguments[1] !== void 0\n                      ? arguments[1]\n                      : !1;\n                  this._validateSend(a, b);\n                  if (\n                    (this._argsHasAnyUserData(a) &&\n                      !this.fbq.loadPlugin('identity')) ||\n                    this.locks.isLocked()\n                  ) {\n                    g.fbq('fire', a);\n                    return this;\n                  }\n                  var c = a.customParameters,\n                    d = '';\n                  c && c.es && typeof c.es === 'string' && (d = c.es);\n                  a.customData = a.customData || {};\n                  var e = this.fbq.getEventCustomParameters(\n                      this.getPixel(a.pixelId),\n                      a.eventName,\n                      a.customData,\n                      d,\n                      a.eventData\n                    ),\n                    f = a.eventData.eventID;\n                  e.append('eid', f);\n                  c &&\n                    E(F(c), function (a) {\n                      if (e.containsKey(a))\n                        throw new Error(\n                          'Custom parameter ' + a + ' already specified.'\n                        );\n                      e.append(a, c[a]);\n                    });\n                  D({\n                    customData: a.customData,\n                    customParams: e,\n                    eventName: a.eventName,\n                    id: a.pixelId,\n                    piiTranslator: null,\n                  });\n                  return this;\n                },\n              },\n              {\n                key: 'callMethod',\n                value: function (a) {\n                  var b = a[0];\n                  a = Array.prototype.slice.call(a, 1);\n                  if (typeof b !== 'string') {\n                    J({\n                      type: 'FBQ_NO_METHOD_NAME',\n                    });\n                    return;\n                  }\n                  if (typeof this[b] === 'function')\n                    try {\n                      this[b].apply(this, a);\n                    } catch (a) {\n                      I(a);\n                    }\n                  else\n                    J({\n                      method: b,\n                      type: 'INVALID_FBQ_METHOD',\n                    });\n                },\n              },\n              {\n                key: 'getDefaultSendData',\n                value: function (a, b, c) {\n                  var d = this.getPixel(a);\n                  c = {\n                    eventData: c || {},\n                    eventName: b,\n                    pixelId: a,\n                  };\n                  d &&\n                    (d.userData && (c.userData = d.userData),\n                    d.agent != null && d.agent !== ''\n                      ? (c.set = {\n                          agent: d.agent,\n                        })\n                      : this.fbq.agent != null &&\n                        this.fbq.agent !== '' &&\n                        (c.set = {\n                          agent: this.fbq.agent,\n                        }));\n                  return c;\n                },\n              },\n              {\n                key: 'getOptedInPixels',\n                value: function (a) {\n                  var b = this;\n                  return this.optIns.listPixelIds(a).map(function (a) {\n                    return b.pixelsByID[a];\n                  });\n                },\n              },\n              {\n                key: 'getPixel',\n                value: function (a) {\n                  return this.pixelsByID[a];\n                },\n              },\n              {\n                key: 'getFBCWithAEMPayload',\n                value: function () {\n                  if (!A.isInTest(z) || r() === !1) return '';\n                  var a = t(g.location.href, w);\n                  (a == null || a.trim() == '') && (a = t(i.referrer, w));\n                  if (a != null && a.includes('_aem_')) {\n                    a = a.split('_aem_');\n                    if (a.length === 2) return a[1];\n                  }\n                  a = x(y);\n                  if (a == null) return '';\n                  a = a.payload;\n                  if (a == null) return '';\n                  a = a.split('_aem_');\n                  return a.length !== 2 ? '' : a[1];\n                },\n              },\n              {\n                key: 'loadConfig',\n                value: function (a) {\n                  if (\n                    this.fbq.disableConfigLoading === !0 ||\n                    Object.prototype.hasOwnProperty.call(this.configsLoaded, a)\n                  )\n                    return;\n                  this.locks.lockConfig(a);\n                  if (\n                    !this.fbq.pendingConfigs ||\n                    H(this.fbq.pendingConfigs, function (b) {\n                      return b === a;\n                    }) === !1\n                  ) {\n                    var b = j.href,\n                      c = i.referrer;\n                    b = v(b, c, {\n                      google: !0,\n                    });\n                    c = u(b);\n                    b = '';\n                    c != null && (b = c.hostname);\n                    Q(\n                      a,\n                      this.VERSION,\n                      this.RELEASE_SEGMENT != null\n                        ? this.RELEASE_SEGMENT\n                        : 'stable',\n                      b,\n                      this.fbq._no_min,\n                      this.getFBCWithAEMPayload()\n                    );\n                  }\n                },\n              },\n              {\n                key: 'configLoaded',\n                value: function (a) {\n                  (this.configsLoaded[a] = !0),\n                    e.trigger(a),\n                    this.locks.releaseConfig(a);\n                },\n              },\n            ]);\n            return d;\n          })();\n          l.exports = q;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsFillParamList', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsParamList'),\n            c = f.getFbeventsModules('SignalsFBEventsQE'),\n            d = g.top !== g;\n          function e(e) {\n            var f = e.customData,\n              j = e.customParams,\n              k = e.eventName,\n              l = e.id,\n              m = e.piiTranslator,\n              n = e.documentLink,\n              o = e.referrerLink,\n              p = e.timestamp;\n            f = f != null ? a({}, f) : null;\n            var q = i.href;\n            Object.prototype.hasOwnProperty.call(e, 'documentLink')\n              ? (q = n)\n              : (e.documentLink = q);\n            n = h.referrer;\n            Object.prototype.hasOwnProperty.call(e, 'referrerLink')\n              ? (n = o)\n              : (e.referrerLink = n);\n            o = new b(m);\n            o.append('id', l);\n            o.append('ev', k);\n            o.append('dl', q);\n            o.append('rl', n);\n            o.append('if', d);\n            o.append('ts', p);\n            o.append('cd', f);\n            o.append('sw', g.screen.width);\n            o.append('sh', g.screen.height);\n            j && o.addRange(j);\n            e = c.get();\n            e != null && o.append('exp', c.getCode());\n            return o;\n          }\n          k.exports = e;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsFilterProtectedModeEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent');\n            f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            var b = f.getFbeventsModules('SignalsFBEventsTyped');\n            b = b.Typed;\n            var c = f.getFbeventsModules('SignalsFBEventsMessageParamsTypedef');\n            a = new a(b.tuple([c]));\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsFiredEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsParamList');\n          function c(a, c) {\n            var d = null;\n            (a === 'GET' || a === 'POST' || a === 'BEACON') && (d = a);\n            a = c instanceof b ? c : null;\n            return d != null && a != null ? [d, a] : null;\n          }\n          a = new a(c);\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsFireEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsEvents'),\n            b = a.fired;\n          a.setEventId;\n          var c = f.getFbeventsModules('SignalsFBEventsQE');\n          a = f.getFbeventsModules('SignalsFBEventsExperimentNames');\n          var d = a.NO_OP_EXPERIMENT,\n            e = f.getFbeventsModules('signalsFBEventsSendBeacon');\n          f.getFbeventsModules('signalsFBEventsSendBeaconWithParamsInURL');\n          var g = f.getFbeventsModules('signalsFBEventsSendGET'),\n            h = f.getFbeventsModules('signalsFBEventsSendFormPOST'),\n            i = f.getFbeventsModules('signalsFBEventsSendFetch'),\n            j = f.getFbeventsModules('SignalsFBEventsForkEvent'),\n            l = f.getFbeventsModules('signalsFBEventsSendBatch'),\n            m = f.getFbeventsModules('SignalsFBEventsGetTimingsEvent'),\n            n = f.getFbeventsModules('signalsFBEventsGetIsChrome'),\n            o = f.getFbeventsModules('signalsFBEventsFillParamList'),\n            p = 'SubscribedButtonClick';\n          function q(a) {\n            j.trigger(a);\n            var f = a.eventName;\n            a = o(a);\n            m.trigger(a);\n            var k = !n();\n            c.isInTest(d);\n            if (c.isInTest('send_events_in_batch')) {\n              l(a);\n              return;\n            }\n            if (i(a)) {\n              b.trigger('FETCH', a);\n              return;\n            }\n            if (k && f === p && e(a)) {\n              b.trigger('BEACON', a);\n              return;\n            }\n            if (g(a)) {\n              b.trigger('GET', a);\n              return;\n            }\n            if (k && e(a)) {\n              b.trigger('BEACON', a);\n              return;\n            }\n            h(a);\n            b.trigger('POST', a);\n          }\n          k.exports = q;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsFireLock', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.each,\n            c = a.keys;\n          a = (function () {\n            function a() {\n              n(this, a), (this._locks = {}), (this._callbacks = []);\n            }\n            h(a, [\n              {\n                key: 'lock',\n                value: function (a) {\n                  this._locks[a] = !0;\n                },\n              },\n              {\n                key: 'release',\n                value: function (a) {\n                  Object.prototype.hasOwnProperty.call(this._locks, a) &&\n                    (delete this._locks[a],\n                    c(this._locks).length === 0 &&\n                      b(this._callbacks, function (b) {\n                        return b(a);\n                      }));\n                },\n              },\n              {\n                key: 'onUnlocked',\n                value: function (a) {\n                  this._callbacks.push(a);\n                },\n              },\n              {\n                key: 'isLocked',\n                value: function () {\n                  return c(this._locks).length > 0;\n                },\n              },\n              {\n                key: 'lockPlugin',\n                value: function (a) {\n                  this.lock('plugin:' + a);\n                },\n              },\n              {\n                key: 'releasePlugin',\n                value: function (a) {\n                  this.release('plugin:' + a);\n                },\n              },\n              {\n                key: 'lockConfig',\n                value: function (a) {\n                  this.lock('config:' + a);\n                },\n              },\n              {\n                key: 'releaseConfig',\n                value: function (a) {\n                  this.release('config:' + a);\n                },\n              },\n              {\n                key: 'lockConsent',\n                value: function () {\n                  this.lock('consent');\n                },\n              },\n              {\n                key: 'unlockConsent',\n                value: function () {\n                  this.release('consent');\n                },\n              },\n            ]);\n            return a;\n          })();\n          a.global = new a();\n          l.exports = a;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsForkEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsParamList');\n          f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n          var c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.Typed;\n          c.coerce;\n          c = d.objectWithFields({\n            customData: d.allowNull(d.object()),\n            customParams: function (a) {\n              return a instanceof b ? a : void 0;\n            },\n            eventName: d.string(),\n            id: d.string(),\n            piiTranslator: function (a) {\n              return typeof a === 'function' ? a : void 0;\n            },\n            documentLink: d.allowNull(d.string()),\n            referrerLink: d.allowNull(d.string()),\n          });\n          a = new a(d.tuple([c]));\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsGatingConfigTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped');\n          a.coerce;\n          a = a.Typed;\n          a = a.objectWithFields({\n            gatings: a.arrayOf(\n              a.allowNull(\n                a.objectWithFields({\n                  name: a.allowNull(a.string()),\n                  passed: a.allowNull(a['boolean']()),\n                })\n              )\n            ),\n          });\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsGetAemResultEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent');\n          function b(a, b, c) {\n            a = a != null && typeof a === 'number' && a !== -1 ? a : null;\n            b = b != null && typeof b === 'number' && b !== -1 ? b : null;\n            c = c != null && typeof c === 'string' && c !== '' ? c : null;\n            return a !== null && b !== null && c !== null ? [a, b, c] : null;\n          }\n          a = new a(b);\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsGetCustomParametersEvent',\n      function () {\n        return (function (g, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n              c = f.getFbeventsModules('SignalsFBEventsTyped'),\n              d = c.Typed,\n              e = c.coerce;\n            function g(a, c, f, g, h) {\n              a = e(a, b);\n              c = e(c, d.string());\n              var j = {};\n              f != null &&\n                (typeof f === 'undefined' ? 'undefined' : i(f)) === 'object' &&\n                (j = f);\n              f = g != null && typeof g === 'string' ? g : null;\n              g = {};\n              h != null &&\n                (typeof h === 'undefined' ? 'undefined' : i(h)) === 'object' &&\n                (g = h);\n              return a != null && c != null ? [a, c, j, f, g] : null;\n            }\n            c = new a(g);\n            l.exports = c;\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsGetIsChrome', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          function a() {\n            var a = f.chrome,\n              b = f.navigator,\n              c = b.vendor,\n              d = f.opr !== void 0,\n              e = b.userAgent.indexOf('Edg') > -1;\n            b = b.userAgent.match('CriOS');\n            return (\n              !b &&\n              a !== null &&\n              a !== void 0 &&\n              c === 'Google Inc.' &&\n              d === !1 &&\n              e === !1\n            );\n          }\n          j.exports = a;\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'signalsFBEventsGetIsIosInAppBrowser',\n      function () {\n        return (function (f, g, h, i) {\n          var j = {\n            exports: {},\n          };\n          j.exports;\n          (function () {\n            'use strict';\n\n            function a() {\n              var a = f.navigator,\n                b = a.userAgent.indexOf('AppleWebKit'),\n                c = a.userAgent.indexOf('FBIOS'),\n                d = a.userAgent.indexOf('Instagram');\n              a = a.userAgent.indexOf('MessengerLiteForiOS');\n              return b !== null && (c != -1 || d != -1 || a != -1);\n            }\n            function b(b) {\n              return a();\n            }\n            j.exports = b;\n          })();\n          return j.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsGetIWLParametersEvent',\n      function () {\n        return (function (g, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsConvertNodeToHTMLElement'),\n              c = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n              d = f.getFbeventsModules('SignalsFBEventsTyped'),\n              e = d.coerce;\n            function g() {\n              for (var a = arguments.length, d = Array(a), f = 0; f < a; f++)\n                d[f] = arguments[f];\n              var g = d[0];\n              if (\n                g == null ||\n                (typeof g === 'undefined' ? 'undefined' : i(g)) !== 'object'\n              )\n                return null;\n              var h = g.unsafePixel,\n                j = g.unsafeTarget,\n                k = e(h, c),\n                l = j instanceof Node ? b(j) : null;\n              return k != null && l != null\n                ? [\n                    {\n                      pixel: k,\n                      target: l,\n                    },\n                  ]\n                : null;\n            }\n            l.exports = new a(g);\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsGetTimingsEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsParamList');\n          function c(a) {\n            a = a instanceof b ? a : null;\n            return a != null ? [a] : null;\n          }\n          a = new a(c);\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsGetValidUrl', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          j.exports = function (a) {\n            if (a == null) return null;\n            try {\n              a = new URL(a);\n              return a;\n            } catch (a) {\n              return null;\n            }\n          };\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsGuardrail', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsGuardrailTypedef');\n          f.getFbeventsModules('SignalsFBEventsExperimentsTypedef');\n          f.getFbeventsModules('SignalsFBEventsLegacyExperimentGroupsTypedef');\n          f.getFbeventsModules('SignalsFBEventsTypeVersioning');\n          var c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.coerce;\n          c = f.getFbeventsModules('SignalsFBEventsUtils');\n          c.reduce;\n          var e = function () {\n              return Math.random();\n            },\n            g = {};\n          function i(a) {\n            var b = a.passRate;\n            a.name;\n            b != null && (a.passed = e() < b);\n          }\n          c = (function () {\n            function c() {\n              n(this, c);\n            }\n            h(c, [\n              {\n                key: 'setGuardrails',\n                value: function (c) {\n                  c = d(c, b);\n                  if (c != null) {\n                    this._guardrails = c;\n                    c = !0;\n                    var e = !1,\n                      f = void 0;\n                    try {\n                      for (\n                        var h =\n                            this._guardrails[\n                              typeof Symbol === 'function'\n                                ? Symbol.iterator\n                                : '@@iterator'\n                            ](),\n                          i;\n                        !(c = (i = h.next()).done);\n                        c = !0\n                      ) {\n                        i = i.value;\n                        if (i.name != null) {\n                          var j = i.name,\n                            k = {\n                              passed: null,\n                            };\n                          k = a({}, k, i);\n                          g[j] = k;\n                        }\n                      }\n                    } catch (a) {\n                      (e = !0), (f = a);\n                    } finally {\n                      try {\n                        !c && h['return'] && h['return']();\n                      } finally {\n                        if (e) throw f;\n                      }\n                    }\n                  }\n                },\n              },\n              {\n                key: 'eval',\n                value: function (a, b) {\n                  a = g[a];\n                  if (!a) return !1;\n                  if (a.enableForPixels && a.enableForPixels.includes(b))\n                    return !0;\n                  if (a.passed != null) return a.passed;\n                  i(a);\n                  return a.passed != null ? a.passed : !1;\n                },\n              },\n              {\n                key: 'enable',\n                value: function (a) {\n                  var b = g[a];\n                  if (b != null) b.passed = !0;\n                  else {\n                    b = {\n                      passed: !0,\n                    };\n                    g[a] = b;\n                  }\n                },\n              },\n              {\n                key: 'disable',\n                value: function (a) {\n                  var b = g[a];\n                  if (b != null) b.passed = !1;\n                  else {\n                    b = {\n                      passed: !1,\n                    };\n                    g[a] = b;\n                  }\n                },\n              },\n            ]);\n            return c;\n          })();\n          l.exports = new c();\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsGuardrailTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.Typed;\n          a.coerce;\n          a.enforce;\n          a = b.arrayOf(\n            b.objectWithFields({\n              name: b.allowNull(b.string()),\n              passRate: b.allowNull(b.number()),\n              enableForPixels: b.allowNull(b.arrayOf(b.string())),\n              code: b.allowNull(b.string()),\n              passed: b.allowNull(b['boolean']()),\n            })\n          );\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsIABPCMAEBridgeConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              enableAutoEventId: b.allowNull(b['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsInjectMethod', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('signalsFBEventsMakeSafe');\n          function b(b, c, d) {\n            var e = b[c],\n              f = a(d);\n            b[c] = function () {\n              for (var a = arguments.length, b = Array(a), c = 0; c < a; c++)\n                b[c] = arguments[c];\n              var d = e.apply(this, b);\n              f.apply(this, b);\n              return d;\n            };\n          }\n          k.exports = b;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsIWLBootStrapEvent', function () {\n      return (function (g, h, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('signalsFBEventsCoercePixelID');\n          function c() {\n            for (var a = arguments.length, c = Array(a), d = 0; d < a; d++)\n              c[d] = arguments[d];\n            var e = c[0];\n            if (\n              e == null ||\n              (typeof e === 'undefined' ? 'undefined' : i(e)) !== 'object'\n            )\n              return null;\n            var f = e.graphToken,\n              g = e.pixelID,\n              h = b(g);\n            return f != null && typeof f === 'string' && h != null\n              ? [\n                  {\n                    graphToken: f,\n                    pixelID: h,\n                  },\n                ]\n              : null;\n          }\n          a = new a(c);\n          l.exports = a;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsJSLoader', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          var a = {\n            CDN_BASE_URL: 'https://connect.facebook.net/',\n          };\n          function b() {\n            var b = g.getElementsByTagName('script');\n            for (var c = 0; c < b.length; c++) {\n              var d = b[c];\n              if (d && d.src && d.src.indexOf(a.CDN_BASE_URL) !== -1) return d;\n            }\n            return null;\n          }\n          var c = d();\n          function d() {\n            try {\n              if (f.trustedTypes && f.trustedTypes.createPolicy) {\n                var b = f.trustedTypes;\n                return b.createPolicy('connect.facebook.net/fbevents', {\n                  createScriptURL: function (b) {\n                    if (!b.startsWith(a.CDN_BASE_URL))\n                      throw new Error('Disallowed script URL');\n                    return b;\n                  },\n                });\n              }\n            } catch (a) {}\n            return null;\n          }\n          function e(a) {\n            var d = g.createElement('script');\n            c != null ? (d.src = c.createScriptURL(a)) : (d.src = a);\n            d.async = !0;\n            a = b();\n            a && a.parentNode\n              ? a.parentNode.insertBefore(d, a)\n              : g.head && g.head.firstChild && g.head.appendChild(d);\n          }\n          j.exports = {\n            CONFIG: a,\n            loadJSFile: e,\n          };\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsLateValidateCustomParametersEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsFBEventsTyped'),\n              c = b.coerce,\n              d = b.Typed;\n            f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            b = f.getFbeventsModules('SignalsFBEventsCoercePrimitives');\n            b.coerceString;\n            function e() {\n              for (var a = arguments.length, b = Array(a), e = 0; e < a; e++)\n                b[e] = arguments[e];\n              return c(b, d.tuple([d.string(), d.object(), d.string()]));\n            }\n            b = new a(e);\n            k.exports = b;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsLegacyExperimentGroupsTypedef',\n      function () {\n        return (function (g, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            var c = a.enforce;\n            a = f.getFbeventsModules('SignalsFBEventsTypeVersioning');\n            a = a.upgrade;\n            function d(a) {\n              return a != null &&\n                (typeof a === 'undefined' ? 'undefined' : i(a)) === 'object'\n                ? Object.values(a)\n                : null;\n            }\n            var e = function (a) {\n              a = Array.isArray(a) ? a : d(a);\n              return c(\n                a,\n                b.arrayOf(\n                  b.objectWithFields({\n                    code: b.string(),\n                    name: b.string(),\n                    passRate: b.number(),\n                    range: b.tuple([b.number(), b.number()]),\n                  })\n                )\n              );\n            };\n            function g(a) {\n              var b = a.name,\n                c = a.code,\n                d = a.range;\n              a = a.passRate;\n              return {\n                allocation: d[1] - d[0],\n                code: c,\n                name: b,\n                passRate: a,\n              };\n            }\n            l.exports = a(e, function (a) {\n              return a.map(g);\n            });\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsLogging', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.isArray,\n            c = a.isInstanceOf,\n            d = a.map,\n            e = f.getFbeventsModules('SignalsParamList'),\n            h = f.getFbeventsModules('signalsFBEventsSendGET'),\n            i = f.getFbeventsModules('SignalsFBEventsJSLoader'),\n            j = !1;\n          function l() {\n            j = !0;\n          }\n          var m = !0;\n          function n() {\n            m = !1;\n          }\n          var o = !1;\n          function p() {\n            o = !0;\n          }\n          var q = 'console',\n            r = 'warn',\n            s = [];\n          function t(a) {\n            g[q] && g[q][r] && (g[q][r](a), o && s.push(a));\n          }\n          var u = !1;\n          function v() {\n            u = !0;\n          }\n          function w(a) {\n            if (u) return;\n            t('[Meta Pixel] - ' + a);\n          }\n          var x = 'Meta Pixel Error',\n            y = function () {\n              g.postMessage != null && g.postMessage.apply(g, arguments);\n            },\n            z = {};\n          function A(a) {\n            switch (a.type) {\n              case 'FBQ_NO_METHOD_NAME':\n                return 'You must provide an argument to fbq().';\n              case 'INVALID_FBQ_METHOD':\n                var b = a.method;\n                return '\"fbq(\\'' + b + '\\', ...);\" is not a valid fbq command.';\n              case 'INVALID_FBQ_METHOD_PARAMETER':\n                b = a.invalidParamName;\n                var c = a.invalidParamValue,\n                  d = a.method,\n                  e = a.params;\n                return (\n                  'Call to \"fbq(\\'' +\n                  d +\n                  \"', \" +\n                  C(e) +\n                  ');\" with parameter \"' +\n                  b +\n                  '\" has an invalid value of \"' +\n                  B(c) +\n                  '\"'\n                );\n              case 'INVALID_PIXEL_ID':\n                d = a.pixelID;\n                return 'Invalid PixelID: ' + d + '.';\n              case 'DUPLICATE_PIXEL_ID':\n                e = a.pixelID;\n                return 'Duplicate Pixel ID: ' + e + '.';\n              case 'SET_METADATA_ON_UNINITIALIZED_PIXEL_ID':\n                b = a.metadataValue;\n                c = a.pixelID;\n                return (\n                  'Trying to set argument ' +\n                  b +\n                  ' for uninitialized Pixel ID ' +\n                  c +\n                  '.'\n                );\n              case 'CONFLICTING_VERSIONS':\n                return 'Multiple pixels with conflicting versions were detected on this page.';\n              case 'MULTIPLE_PIXELS':\n                return 'Multiple pixels were detected on this page.';\n              case 'UNSUPPORTED_METADATA_ARGUMENT':\n                d = a.metadata;\n                return 'Unsupported metadata argument: ' + d + '.';\n              case 'REQUIRED_PARAM_MISSING':\n                e = a.param;\n                b = a.eventName;\n                return (\n                  \"Required parameter '\" +\n                  e +\n                  \"' is missing for event '\" +\n                  b +\n                  \"'.\"\n                );\n              case 'INVALID_PARAM':\n                c = a.param;\n                d = a.eventName;\n                return (\n                  \"Parameter '\" + c + \"' is invalid for event '\" + d + \"'.\"\n                );\n              case 'NO_EVENT_NAME':\n                return 'Missing event name. Track events must be logged with an event name fbq(\"track\", eventName)';\n              case 'NONSTANDARD_EVENT':\n                e = a.eventName;\n                return (\n                  \"You are sending a non-standard event '\" +\n                  e +\n                  \"'. The preferred way to send these events is using trackCustom. See 'https://developers.facebook.com/docs/ads-for-websites/pixel-events/#events' for more information.\"\n                );\n              case 'NEGATIVE_EVENT_PARAM':\n                b = a.param;\n                c = a.eventName;\n                return (\n                  \"Parameter '\" + b + \"' is negative for event '\" + c + \"'.\"\n                );\n              case 'PII_INVALID_TYPE':\n                d = a.key_type;\n                e = a.key_val;\n                return (\n                  'An invalid ' +\n                  d +\n                  \" was specified for '\" +\n                  e +\n                  \"'. This data will not be sent with any events for this Pixel.\"\n                );\n              case 'PII_UNHASHED_PII':\n                b = a.key;\n                return (\n                  \"The value for the '\" +\n                  b +\n                  \"' key appeared to be PII. This data will not be sent with any events for this Pixel.\"\n                );\n              case 'INVALID_CONSENT_ACTION':\n                c = a.action;\n                return (\n                  '\"fbq(\\'' +\n                  c +\n                  \"', ...);\\\" is not a valid fbq('consent', ...) action. Valid actions are 'revoke' and 'grant'.\"\n                );\n              case 'INVALID_JSON_LD':\n                d = a.jsonLd;\n                return (\n                  \"Unable to parse JSON-LD tag. Malformed JSON found: '\" +\n                  d +\n                  \"'.\"\n                );\n              case 'SITE_CODELESS_OPT_OUT':\n                e = a.pixelID;\n                return (\n                  'Unable to open Codeless events interface for pixel as the site has opted out. Pixel ID: ' +\n                  e +\n                  '.'\n                );\n              case 'PIXEL_NOT_INITIALIZED':\n                b = a.pixelID;\n                return 'Pixel ' + b + ' not found';\n              case 'UNWANTED_CUSTOM_DATA':\n                return 'Removed parameters from custom data due to potential violations. Go to Events Manager to learn more.';\n              case 'UNWANTED_URL_DATA':\n                return 'Removed URL query parameters due to potential violations.';\n              case 'UNWANTED_EVENT_NAME':\n                return 'Blocked Event due to potential violations.';\n              case 'UNVERIFIED_EVENT':\n                return 'You are attempting to send an unverified event. The event was suppressed. Go to Events Manager to learn more.';\n              case 'RESTRICTED_EVENT':\n                return 'You are attempting to send a restricted event. The event was suppressed. Go to Events Manager to learn more.';\n              case 'INVALID_PARAM_FORMAT':\n                c = a.invalidParamName;\n                return (\n                  'Invalid parameter format for ' +\n                  c +\n                  '. Please refer https://developers.facebook.com/docs/meta-pixel/reference/ for valid parameter specifications.'\n                );\n              default:\n                F(\n                  new Error(\n                    'INVALID_USER_ERROR - ' + a.type + ' - ' + JSON.stringify(a)\n                  )\n                );\n                return 'Invalid User Error.';\n            }\n          }\n          var B = function (a) {\n              if (typeof a === 'string') return \"'\" + a + \"'\";\n              else if (typeof a == 'undefined') return 'undefined';\n              else if (a === null) return 'null';\n              else if (\n                !b(a) &&\n                a.constructor != null &&\n                a.constructor.name != null\n              )\n                return a.constructor.name;\n              try {\n                return JSON.stringify(a) || 'undefined';\n              } catch (a) {\n                return 'undefined';\n              }\n            },\n            C = function (a) {\n              return d(a, B).join(', ');\n            };\n          function D(a, b) {\n            try {\n              var d = g.fbq.instance.pluginConfig.get(\n                null,\n                'dataProcessingOptions'\n              );\n              if (d != null && d.dataPrivacyOptions.includes('LDU')) return;\n              d = Math.random();\n              var f =\n                g.fbq && g.fbq._releaseSegment\n                  ? g.fbq._releaseSegment\n                  : 'unknown';\n              if (\n                (!g.fbq || !g.fbq.disableErrorLogging) &&\n                ((m && d < 0.01) || f === 'canary')\n              ) {\n                d = new e(null);\n                d.append('p', 'pixel');\n                d.append(\n                  'v',\n                  g.fbq && g.fbq.version ? g.fbq.version : 'unknown'\n                );\n                d.append('e', a.toString());\n                c(a, Error) &&\n                  (d.append('f', a.fileName),\n                  d.append('s', a.stackTrace || a.stack));\n                d.append('ue', b ? '1' : '0');\n                d.append('rs', f);\n                h(d, {\n                  url: i.CONFIG.CDN_BASE_URL + '/log/error',\n                  ignoreRequestLengthCheck: !0,\n                });\n              }\n            } catch (a) {}\n          }\n          function E(a) {\n            var b = JSON.stringify(a);\n            if (!Object.prototype.hasOwnProperty.call(z, b)) z[b] = !0;\n            else return;\n            b = A(a);\n            w(b);\n            y(\n              {\n                action: 'FB_LOG',\n                logMessage: b,\n                logType: x,\n              },\n              '*'\n            );\n            D(new Error(b), !0);\n          }\n          function F(a) {\n            D(a, !1), j && w(a.toString());\n          }\n          a = {\n            consoleWarn: t,\n            disableAllLogging: v,\n            disableSampling: n,\n            enableVerboseDebugLogging: l,\n            logError: F,\n            logUserError: E,\n            enableBufferedLoggedWarnings: p,\n            bufferedLoggedWarnings: s,\n          };\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsMakeSafe', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = a.logError;\n          function c(a) {\n            return function () {\n              try {\n                for (var c = arguments.length, d = Array(c), e = 0; e < c; e++)\n                  d[e] = arguments[e];\n                a.apply(this, d);\n              } catch (a) {\n                b(a);\n              }\n              return;\n            };\n          }\n          k.exports = c;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsMessageParamsTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            var b = f.getFbeventsModules('SignalsParamList');\n            a = a.objectWithFields({\n              customData: a.allowNull(a.object()),\n              customParams: function (a) {\n                return a instanceof b ? a : void 0;\n              },\n              eventName: a.string(),\n              id: a.string(),\n              piiTranslator: function (a) {\n                return typeof a === 'function' ? a : void 0;\n              },\n              documentLink: a.allowNull(a.string()),\n              referrerLink: a.allowNull(a.string()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsMicrodataConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              waitTimeMs: a.allowNull(\n                a.withValidation({\n                  def: a.number(),\n                  validators: [\n                    function (a) {\n                      return a > 0 && a < 1e4;\n                    },\n                  ],\n                })\n              ),\n              disableMicrodataEvent: a.allowNull(a['boolean']()),\n              enablePageHash: a.allowNull(a['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsMobileAppBridge', function () {\n      return (function (g, h, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTelemetry'),\n            b = f.getFbeventsModules('SignalsFBEventsUtils'),\n            c = b.each,\n            d = 'fbmq-0.1',\n            e = {\n              AddPaymentInfo: 'fb_mobile_add_payment_info',\n              AddToCart: 'fb_mobile_add_to_cart',\n              AddToWishlist: 'fb_mobile_add_to_wishlist',\n              CompleteRegistration: 'fb_mobile_complete_registration',\n              InitiateCheckout: 'fb_mobile_initiated_checkout',\n              Other: 'other',\n              Purchase: 'fb_mobile_purchase',\n              Search: 'fb_mobile_search',\n              ViewContent: 'fb_mobile_content_view',\n            },\n            h = {\n              content_ids: 'fb_content_id',\n              content_type: 'fb_content_type',\n              currency: 'fb_currency',\n              num_items: 'fb_num_items',\n              search_string: 'fb_search_string',\n              value: '_valueToSum',\n              contents: 'fb_content',\n            },\n            j = {};\n          function k(a) {\n            return 'fbmq_' + a[1];\n          }\n          function m(a) {\n            if (\n              Object.prototype.hasOwnProperty.call(j, [0]) &&\n              Object.prototype.hasOwnProperty.call(j[a[0]], a[1])\n            )\n              return !0;\n            var b = g[k(a)];\n            b = b && b.getProtocol.call && b.getProtocol() === d ? b : null;\n            b !== null && ((j[a[0]] = j[a[0]] || {}), (j[a[0]][a[1]] = b));\n            return b !== null;\n          }\n          function n(a) {\n            var b = [];\n            a = j[a.id] || {};\n            for (var c in a)\n              Object.prototype.hasOwnProperty.call(a, c) && b.push(a[c]);\n            return b;\n          }\n          function o(a) {\n            return n(a).length > 0;\n          }\n          function p(a) {\n            return Object.prototype.hasOwnProperty.call(e, a) ? e[a] : a;\n          }\n          function q(a) {\n            return Object.prototype.hasOwnProperty.call(h, a) ? h[a] : a;\n          }\n          function r(a) {\n            if (typeof a === 'string') return a;\n            if (typeof a === 'number') return isNaN(a) ? void 0 : a;\n            try {\n              return JSON.stringify(a);\n            } catch (a) {}\n            return a.toString && a.toString.call ? a.toString() : void 0;\n          }\n          function s(a) {\n            var b = {};\n            if (\n              a != null &&\n              (typeof a === 'undefined' ? 'undefined' : i(a)) === 'object'\n            )\n              for (var c in a)\n                if (Object.prototype.hasOwnProperty.call(a, c)) {\n                  var d = r(a[c]);\n                  d != null && (b[q(c)] = d);\n                }\n            return b;\n          }\n          var t = 0;\n          function u() {\n            var b = t;\n            t = 0;\n            a.logMobileNativeForwarding(b);\n          }\n          function v(a, b, d) {\n            c(n(a), function (c) {\n              return c.sendEvent(a.id, p(b), JSON.stringify(s(d)));\n            }),\n              t++,\n              setTimeout(u, 0);\n          }\n          l.exports = {\n            pixelHasActiveBridge: o,\n            registerBridge: m,\n            sendEvent: v,\n          };\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsModuleEncodings', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.coerce,\n            c = f.getFbeventsModules('SignalsFBEventsModuleEncodingsTypedef');\n          f.getFbeventsModules('SignalsParamList');\n          a = f.getFbeventsModules('SignalsFBEventsTyped');\n          var d = a.Typed;\n          a = f.getFbeventsModules('SignalsFBEventsUtils');\n          var i = a.map,\n            j = a.keys,\n            k = a.filter;\n          f.getFbeventsModules('SignalsFBEventsQE');\n          f.getFbeventsModules('SignalsFBEventsGuardrail');\n          a = (function () {\n            function a() {\n              n(this, a);\n            }\n            h(a, [\n              {\n                key: 'setModuleEncodings',\n                value: function (a) {\n                  a = b(a, c);\n                  a != null && (this.moduleEncodings = a);\n                },\n              },\n              {\n                key: 'addEncodings',\n                value: function (a) {\n                  var c = this;\n                  if (g.fbq == null || g.fbq.__fbeventsResolvedModules == null)\n                    return;\n                  if (this.moduleEncodings == null) return;\n                  var f = b(g.fbq.__fbeventsResolvedModules, d.object());\n                  if (f == null) return;\n                  f = k(\n                    i(j(f), function (a) {\n                      return c.moduleEncodings.map != null &&\n                        a in c.moduleEncodings.map\n                        ? c.moduleEncodings.map[a]\n                        : null;\n                    }),\n                    function (a) {\n                      return a != null;\n                    }\n                  );\n                  f.length > 0 &&\n                    (this.moduleEncodings.hash != null &&\n                      a.append('hme', this.moduleEncodings.hash),\n                    a.append('ex_m', f.join(',')));\n                },\n              },\n            ]);\n            return a;\n          })();\n          l.exports = new a();\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsModuleEncodingsTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              map: a.allowNull(a.object()),\n              hash: a.allowNull(a.string()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsNetworkConfig', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          var a = {\n            ENDPOINT: 'https://www.facebook.com/tr/',\n            INSTAGRAM_TRIGGER_ATTRIBUTION: 'https://www.instagram.com/tr/',\n            AEM_ENDPOINT:\n              'https://www.facebook.com/.well-known/aggregated-event-measurement/',\n            GPS_ENDPOINT:\n              'https://www.facebook.com/privacy_sandbox/pixel/register/trigger/',\n            TOPICS_API_ENDPOINT:\n              'https://www.facebook.com/privacy_sandbox/topics/registration/',\n          };\n          j.exports = a;\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsOpenBridgeConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              endpoints: b.arrayOf(\n                b.objectWithFields({\n                  targetDomain: b.allowNull(b.string()),\n                  endpoint: b.allowNull(b.string()),\n                  usePathCookie: b.allowNull(b['boolean']()),\n                  fallbackDomain: b.allowNull(b.string()),\n                })\n              ),\n              eventsFilter: b.allowNull(\n                b.objectWithFields({\n                  filteringMode: b.allowNull(b.string()),\n                  eventNames: b.allowNull(b.arrayOf(b.string())),\n                })\n              ),\n              additionalUserData: b.allowNull(\n                b.objectWithFields({\n                  sendFBLoginID: b.allowNull(b['boolean']()),\n                })\n              ),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsOptIn', function () {\n      return (function (g, i, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.each,\n            c = a.filter,\n            d = a.keys,\n            e = a.some;\n          function g(a) {\n            b(d(a), function (b) {\n              if (\n                e(a[b], function (b) {\n                  return Object.prototype.hasOwnProperty.call(a, b);\n                })\n              )\n                throw new Error(\n                  'Circular subOpts are not allowed. ' +\n                    b +\n                    ' depends on another subOpt'\n                );\n            });\n          }\n          a = (function () {\n            function a() {\n              var b =\n                arguments.length > 0 && arguments[0] !== void 0\n                  ? arguments[0]\n                  : {};\n              n(this, a);\n              this._opts = {};\n              this._subOpts = b;\n              g(this._subOpts);\n            }\n            h(a, [\n              {\n                key: '_getOpts',\n                value: function (a) {\n                  return [].concat(\n                    m(\n                      Object.prototype.hasOwnProperty.call(this._subOpts, a)\n                        ? this._subOpts[a]\n                        : []\n                    ),\n                    [a]\n                  );\n                },\n              },\n              {\n                key: '_setOpt',\n                value: function (a, b, c) {\n                  b = this._opts[b] || (this._opts[b] = {});\n                  b[a] = c;\n                },\n              },\n              {\n                key: 'optIn',\n                value: function (a, c) {\n                  var d = this,\n                    e =\n                      arguments.length > 2 && arguments[2] !== void 0\n                        ? arguments[2]\n                        : !1;\n                  b(this._getOpts(c), function (b) {\n                    var f = e == !0 && d.isOptedOut(a, c);\n                    f || d._setOpt(a, b, !0);\n                  });\n                  return this;\n                },\n              },\n              {\n                key: 'optOut',\n                value: function (a, c) {\n                  var d = this;\n                  b(this._getOpts(c), function (b) {\n                    return d._setOpt(a, b, !1);\n                  });\n                  return this;\n                },\n              },\n              {\n                key: 'isOptedIn',\n                value: function (a, b) {\n                  return this._opts[b] != null && this._opts[b][a] === !0;\n                },\n              },\n              {\n                key: 'isOptedOut',\n                value: function (a, b) {\n                  return this._opts[b] != null && this._opts[b][a] === !1;\n                },\n              },\n              {\n                key: 'listPixelIds',\n                value: function (a) {\n                  var b = this._opts[a];\n                  return b != null\n                    ? c(d(b), function (a) {\n                        return b[a] === !0;\n                      })\n                    : [];\n                },\n              },\n            ]);\n            return a;\n          })();\n          l.exports = a;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsParallelFireConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              target: a.string(),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsPIIAutomatchedEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n            c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.coerce;\n          function e(a) {\n            a = d(a, b);\n            return a != null ? [a] : null;\n          }\n          c = new a(e);\n          k.exports = c;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPIIConflictingEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n            c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.coerce;\n          function e(a) {\n            a = d(a, b);\n            return a != null ? [a] : null;\n          }\n          c = new a(e);\n          k.exports = c;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPIIInvalidatedEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsPixelTypedef'),\n            c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.coerce;\n          function e(a) {\n            a = d(a, b);\n            return a != null ? [a] : null;\n          }\n          k.exports = new a(e);\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPixelCookie', function () {\n      return (function (i, j, k, l) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = a.logError,\n            c = 'fb',\n            d = 4;\n          a = (function () {\n            function a(b) {\n              n(this, a),\n                typeof b === 'string'\n                  ? this.maybeUpdatePayload(b)\n                  : ((this.subdomainIndex = b.subdomainIndex),\n                    (this.creationTime = b.creationTime),\n                    (this.payload = b.payload));\n            }\n            h(\n              a,\n              [\n                {\n                  key: 'pack',\n                  value: function () {\n                    return [\n                      c,\n                      this.subdomainIndex,\n                      this.creationTime,\n                      this.payload,\n                    ].join('.');\n                  },\n                },\n                {\n                  key: 'maybeUpdatePayload',\n                  value: function (a) {\n                    if (this.payload === null || this.payload !== a) {\n                      this.payload = a;\n                      a = Date.now();\n                      this.creationTime =\n                        typeof a === 'number' ? a : new Date().getTime();\n                    }\n                  },\n                },\n              ],\n              [\n                {\n                  key: 'unpack',\n                  value: function (e) {\n                    try {\n                      e = e.split('.');\n                      if (e.length !== d) return null;\n                      var f = g(e, 4),\n                        h = f[0],\n                        i = f[1],\n                        j = f[2];\n                      f = f[3];\n                      if (h !== c)\n                        throw new Error(\n                          \"Unexpected version number '\" + e[0] + \"'\"\n                        );\n                      h = parseInt(i, 10);\n                      if (isNaN(h))\n                        throw new Error(\n                          \"Illegal subdomain index '\" + e[1] + \"'\"\n                        );\n                      i = parseInt(j, 10);\n                      if (isNaN(i))\n                        throw new Error(\"Illegal creation time '\" + e[2] + \"'\");\n                      if (f == null || f === '')\n                        throw new Error('Empty cookie payload');\n                      return new a({\n                        creationTime: i,\n                        payload: f,\n                        subdomainIndex: h,\n                      });\n                    } catch (a) {\n                      b(a);\n                      return null;\n                    }\n                  },\n                },\n              ]\n            );\n            return a;\n          })();\n          m.exports = a;\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPixelTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped');\n          a = a.Typed;\n          a = a.objectWithFields({\n            eventCount: a.number(),\n            id: a.fbid(),\n            userData: a.mapOf(a.string()),\n            userDataFormFields: a.mapOf(a.string()),\n          });\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPlugin', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          var a = function a(b) {\n            n(this, a),\n              (this.__fbEventsPlugin = 1),\n              (this.plugin = b),\n              (this.__fbEventsPlugin = 1);\n          };\n          j.exports = a;\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPluginLoadedEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent');\n          function b(a) {\n            a = a != null && typeof a === 'string' ? a : null;\n            return a != null ? [a] : null;\n          }\n          k.exports = new a(b);\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPluginManager', function () {\n      return (function (g, j, k, l) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsConfigStore'),\n            b = f.getFbeventsModules('SignalsFBEventsEvents'),\n            c = b.pluginLoaded,\n            d = f.getFbeventsModules('SignalsFBEventsJSLoader');\n          b = f.getFbeventsModules('SignalsFBEventsLogging');\n          var e = b.logError,\n            g = f.getFbeventsModules('SignalsFBEventsPlugin');\n          function j(a) {\n            return 'fbevents.plugins.' + a;\n          }\n          function k(a, b) {\n            if (a === 'fbevents') return new g(function () {});\n            if (b instanceof g) return b;\n            if (\n              b == null ||\n              (typeof b === 'undefined' ? 'undefined' : i(b)) !== 'object'\n            ) {\n              e(new Error('Invalid plugin registered ' + a));\n              return new g(function () {});\n            }\n            var c = b.__fbEventsPlugin;\n            b = b.plugin;\n            if (c !== 1 || typeof b !== 'function') {\n              e(new Error('Invalid plugin registered ' + a));\n              return new g(function () {});\n            }\n            return new g(b);\n          }\n          b = (function () {\n            function b(a, c) {\n              n(this, b),\n                (this._loadedPlugins = {}),\n                (this._instance = a),\n                (this._lock = c);\n            }\n            h(b, [\n              {\n                key: 'registerPlugin',\n                value: function (b, d) {\n                  if (\n                    Object.prototype.hasOwnProperty.call(this._loadedPlugins, b)\n                  )\n                    return;\n                  this._loadedPlugins[b] = k(b, d);\n                  this._loadedPlugins[b].plugin(f, this._instance, a);\n                  c.trigger(b);\n                  this._lock.releasePlugin(b);\n                },\n              },\n              {\n                key: 'loadPlugin',\n                value: function (a) {\n                  if (/^[a-zA-Z]\\w+$/.test(a) === !1)\n                    throw new Error('Invalid plugin name: ' + a);\n                  var b = j(a);\n                  if (this._loadedPlugins[b]) return !0;\n                  if (f.fbIsModuleLoaded(b)) {\n                    this.registerPlugin(b, f.getFbeventsModules(b));\n                    return !0;\n                  }\n                  a =\n                    d.CONFIG.CDN_BASE_URL +\n                    'signals/plugins/' +\n                    a +\n                    '.js?v=' +\n                    f.version;\n                  if (!this._loadedPlugins[b]) {\n                    this._lock.lockPlugin(b);\n                    d.loadJSFile(a);\n                    return !0;\n                  }\n                  return !1;\n                },\n              },\n            ]);\n            return b;\n          })();\n          m.exports = b;\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsProcessCCRulesEvent', function () {\n      return (function (g, h, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            c = f.getFbeventsModules('SignalsParamList');\n          function d(b, d) {\n            b = b instanceof c ? b : null;\n            d =\n              (typeof d === 'undefined' ? 'undefined' : i(d)) === 'object'\n                ? a({}, d)\n                : null;\n            return b != null ? [b, d] : null;\n          }\n          b = new b(d);\n          l.exports = b;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsProhibitedPixelConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a.coerce;\n            a = a.Typed;\n            a = a.objectWithFields({\n              lockWebpage: a.allowNull(a['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsProhibitedSourcesTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              prohibitedSources: b.arrayOf(\n                b.objectWithFields({\n                  domain: b.allowNull(b.string()),\n                })\n              ),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsProtectedDataModeConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              standardParams: b.mapOf(b['boolean']()),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsQE', function () {\n      return (function (i, j, k, l) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsExperimentsTypedef'),\n            b = f.getFbeventsModules(\n              'SignalsFBEventsLegacyExperimentGroupsTypedef'\n            ),\n            c = f.getFbeventsModules('SignalsFBEventsTypeVersioning'),\n            d = f.getFbeventsModules('SignalsFBEventsTyped'),\n            e = d.coerce;\n          d = f.getFbeventsModules('SignalsFBEventsUtils');\n          var i = d.reduce,\n            j = function () {\n              return Math.random();\n            };\n          function k(a) {\n            var b = i(\n                a,\n                function (b, c, a) {\n                  if (a === 0) {\n                    b.push([0, c.allocation]);\n                    return b;\n                  }\n                  a = g(b[a - 1], 2);\n                  a[0];\n                  a = a[1];\n                  b.push([a, a + c.allocation]);\n                  return b;\n                },\n                []\n              ),\n              c = j();\n            for (var d = 0; d < a.length; d++) {\n              var e = a[d],\n                f = e.passRate,\n                h = e.code;\n              e = e.name;\n              var k = g(b[d], 2),\n                l = k[0];\n              k = k[1];\n              if (c >= l && c < k) {\n                l = j() < f;\n                return {\n                  code: h,\n                  isInExperimentGroup: l,\n                  name: e,\n                };\n              }\n            }\n            return null;\n          }\n          d = (function () {\n            function d() {\n              n(this, d),\n                (this._result = null),\n                (this._hasRolled = !1),\n                (this._isExposed = !1),\n                (this.CONTROL = 'CONTROL'),\n                (this.TEST = 'TEST'),\n                (this.UNASSIGNED = 'UNASSIGNED');\n            }\n            h(d, [\n              {\n                key: 'setExperiments',\n                value: function (d) {\n                  d = e(d, c.waterfall([b, a]));\n                  d != null &&\n                    ((this._experiments = d),\n                    (this._hasRolled = !1),\n                    (this._result = null),\n                    (this._isExposed = !1));\n                },\n              },\n              {\n                key: 'get',\n                value: function (a) {\n                  if (!this._hasRolled) {\n                    var b = this._experiments;\n                    if (b == null) return null;\n                    b = k(b);\n                    b != null && (this._result = b);\n                    this._hasRolled = !0;\n                  }\n                  if (a == null || a === '') return this._result;\n                  return this._result != null && this._result.name === a\n                    ? this._result\n                    : null;\n                },\n              },\n              {\n                key: 'getCode',\n                value: function () {\n                  var a = this.get();\n                  if (a == null) return '';\n                  var b = 0;\n                  a.isInExperimentGroup && (b |= 1);\n                  this._isExposed && (b |= 2);\n                  return a.code + b.toString();\n                },\n              },\n              {\n                key: 'getAssignmentFor',\n                value: function (a) {\n                  var b = this.get();\n                  if (b != null && b.name === a) {\n                    this._isExposed = !0;\n                    return b.isInExperimentGroup ? this.TEST : this.CONTROL;\n                  }\n                  return this.UNASSIGNED;\n                },\n              },\n              {\n                key: 'isInTest',\n                value: function (a) {\n                  var b = this.get();\n                  if (b != null && b.name === a) {\n                    this._isExposed = !0;\n                    return b.isInExperimentGroup;\n                  }\n                  return !1;\n                },\n              },\n            ]);\n            return d;\n          })();\n          m.exports = new d();\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'signalsFBEventsResolveLegacyArguments',\n      function () {\n        return (function (f, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = 'report';\n            function b(a) {\n              var b = g(a, 1);\n              b = b[0];\n              return a.length === 1 && Array.isArray(b)\n                ? {\n                    args: b,\n                    isLegacySyntax: !0,\n                  }\n                : {\n                    args: a,\n                    isLegacySyntax: !1,\n                  };\n            }\n            function c(b) {\n              var c = g(b, 2),\n                d = c[0];\n              c = c[1];\n              if (typeof d === 'string' && d.slice(0, a.length) === a) {\n                d = d.slice(a.length);\n                if (d === 'CustomEvent') {\n                  c != null &&\n                    (typeof c === 'undefined' ? 'undefined' : i(c)) ===\n                      'object' &&\n                    typeof c.event === 'string' &&\n                    (d = c.event);\n                  return ['trackCustom', d].concat(b.slice(1));\n                }\n                return ['track', d].concat(b.slice(1));\n              }\n              return b;\n            }\n            function d(a) {\n              a = b(a);\n              var d = a.args;\n              a = a.isLegacySyntax;\n              d = c(d);\n              return {\n                args: d,\n                isLegacySyntax: a,\n              };\n            }\n            l.exports = d;\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsResolveLink', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsGetValidUrl'),\n            b = f.getFbeventsModules('SignalsFBEventsUtils'),\n            c = b.each,\n            d = b.keys;\n          k.exports = function (b, e, f) {\n            var h = g.top !== g;\n            if (h && e != null && e.length > 0) {\n              if (f != null) {\n                h = !1;\n                var i = a(e);\n                if (i != null) {\n                  var j = i.origin;\n                  c(d(f), function (a) {\n                    a != null && j.indexOf(a) >= 0 && (h = !0);\n                  });\n                }\n                if (i == null || h) return b;\n              }\n              return e;\n            } else return b != null && b.length > 0 ? b : e;\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsRestrictedDomainsConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              restrictedDomains: b.allowNull(\n                b.arrayOf(b.allowNull(b.string()))\n              ),\n              blacklistedIframeReferrers: b.allowNull(b.mapOf(b['boolean']())),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsSendBatch', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBatcher'),\n            b = f.getFbeventsModules('SignalsFBEventsLogging'),\n            c = b.logError;\n          b = f.getFbeventsModules('SignalsFBEventsUtils');\n          var d = b.map,\n            e = f.getFbeventsModules('SignalsParamList'),\n            h = f.getFbeventsModules('signalsFBEventsSendBeacon'),\n            i = f.getFbeventsModules('signalsFBEventsSendGET');\n          f.getFbeventsModules('signalsFBEventsSendXHR');\n          var j = f.getFbeventsModules('signalsFBEventsSendFetch'),\n            l = f.getFbeventsModules('signalsFBEventsSendFormPOST');\n          b = f.getFbeventsModules('SignalsFBEventsEvents');\n          var m = b.fired,\n            n = f.getFbeventsModules('signalsFBEventsGetIsChrome');\n          function o(a, b) {\n            var c = !0,\n              d = !1,\n              e = void 0;\n            try {\n              for (\n                var f =\n                    b[\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    ](),\n                  b;\n                !(c = (b = f.next()).done);\n                c = !0\n              ) {\n                b = b.value;\n                m.trigger(a, b);\n              }\n            } catch (a) {\n              (d = !0), (e = a);\n            } finally {\n              try {\n                !c && f['return'] && f['return']();\n              } finally {\n                if (d) throw e;\n              }\n            }\n          }\n          function p(a) {\n            var b = d(a, function (a) {\n              return a.toQueryString();\n            });\n            b = new e().appendHash({\n              batch: 1,\n              events: b,\n            });\n            var f = !n();\n            if (j(b)) {\n              o('FETCH', a);\n              return;\n            }\n            if (f && h(b)) {\n              o('BEACON', a);\n              return;\n            }\n            if (i(b)) {\n              o('GET', a);\n              return;\n            }\n            if (f && h(b)) {\n              o('BEACON', a);\n              return;\n            }\n            l(b);\n            o('POST', a);\n            c(new Error('could not send batch'));\n          }\n          var q = new a(p);\n          function r(a) {\n            q.addToBatch(a);\n          }\n          g.addEventListener(\n            'onpagehide' in g ? 'pagehide' : 'unload',\n            function () {\n              return q.forceEndBatch();\n            }\n          );\n          k.exports = r;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsSendBeacon', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          f.getFbeventsModules('SignalsFBEventsQE');\n          var a = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n            b = f.getFbeventsModules('SignalsFBEventsLogging'),\n            c = b.logError;\n          function d(b, d) {\n            try {\n              if (!g.navigator || !g.navigator.sendBeacon) return !1;\n              d = d || {};\n              d = d.url;\n              d = d === void 0 ? a.ENDPOINT : d;\n              b.replaceEntry('rqm', 'SB');\n              return g.navigator.sendBeacon(d, b.toFormData());\n            } catch (a) {\n              a instanceof Error && c(new Error('[SendBeacon]:' + a.message));\n              return !1;\n            }\n          }\n          k.exports = d;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'signalsFBEventsSendBeaconWithParamsInURL',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n              b = f.getFbeventsModules('SignalsFBEventsLogging'),\n              c = b.logError,\n              d = 2048;\n            function e(b, e) {\n              try {\n                if (!g.navigator || !g.navigator.sendBeacon) return !1;\n                e = e || {};\n                e = e.url;\n                e = e === void 0 ? a.ENDPOINT : e;\n                b.replaceEntry('rqm', 'SB');\n                b = b.toQueryString();\n                e = e + '?' + b;\n                return e.length > d ? !1 : g.navigator.sendBeacon(e);\n              } catch (a) {\n                a instanceof Error &&\n                  c(new Error('[SendBeaconWithParamsInURL]:' + a.message));\n                return !1;\n              }\n            }\n            k.exports = e;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsSendCloudbridgeEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent');\n            f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            var b = f.getFbeventsModules('SignalsFBEventsTyped');\n            b = b.Typed;\n            var c = f.getFbeventsModules('SignalsFBEventsMessageParamsTypedef');\n            a = new a(b.tuple([c]));\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('signalsFBEventsSendEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsEvents');\n          b.fired;\n          var c = b.setEventId,\n            d = f.getFbeventsModules('SignalsParamList'),\n            e = f.getFbeventsModules('SignalsFBEventsSendEventEvent'),\n            h = f.getFbeventsModules('SignalsFBEventsSendCloudbridgeEvent'),\n            i = f.getFbeventsModules('SignalsFBEventsFilterProtectedModeEvent'),\n            j = f.getFbeventsModules('SignalsFBEventsProcessCCRulesEvent'),\n            l = f.getFbeventsModules(\n              'SignalsFBEventsLateValidateCustomParametersEvent'\n            );\n          b = f.getFbeventsModules('SignalsFBEventsUtils');\n          var m = b.some,\n            n = b.each,\n            o = b.keys;\n          f.getFbeventsModules('SignalsFBEventsNetworkConfig');\n          f.getFbeventsModules('generateUUID');\n          var p = f.getFbeventsModules('SignalsFBEventsSetFilteredEventName'),\n            q = f.getFbeventsModules('signalsFBEventsFillParamList'),\n            r = f.getFbeventsModules('signalsFBEventsFireEvent');\n          b = f.getFbeventsModules('SignalsFBEventsExperimentNames');\n          b.BATCHING_EXPERIMENT;\n          b.SEND_XHR_EXPERIMENT;\n          g.top !== g;\n          function s(b) {\n            b.customData = a({}, b.customData);\n            b.timestamp = new Date().valueOf();\n            var f = null;\n            b.customParams != null && (f = b.customParams.get('eid'));\n            if (f == null || f === '') {\n              b.customParams = b.customParams || new d();\n              f = b.customParams;\n              b.id != null && c.trigger(String(b.id), f);\n            }\n            f = j.trigger(q(b), b.customData);\n            f != null &&\n              n(f, function (a) {\n                a != null &&\n                  n(o(a), function (c) {\n                    (b.customParams = b.customParams || new d()),\n                      b.customParams.append(c, a[c]);\n                  });\n              });\n            l.trigger(String(b.id), b.customData || {}, b.eventName);\n            f = p.trigger(q(b));\n            f != null &&\n              n(f, function (a) {\n                a != null &&\n                  n(o(a), function (c) {\n                    (b.customParams = b.customParams || new d()),\n                      b.customParams.append(c, a[c]);\n                  });\n              });\n            i.trigger(b);\n            f = e.trigger(b);\n            if (\n              m(f, function (a) {\n                return a;\n              })\n            )\n              return;\n            f = h.trigger(b);\n            if (\n              m(f, function (a) {\n                return a;\n              })\n            )\n              return;\n            f =\n              Object.prototype.hasOwnProperty.call(b, 'customData') &&\n              typeof b.customData !== 'undefined' &&\n              b.customData !== null;\n            f || (b.customData = {});\n            r(b);\n          }\n          k.exports = s;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsSendEventEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsParamList');\n          f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n          var c = f.getFbeventsModules('SignalsFBEventsTyped'),\n            d = c.Typed;\n          c.coerce;\n          c = d.objectWithFields({\n            customData: d.allowNull(d.object()),\n            customParams: function (a) {\n              return a instanceof b ? a : void 0;\n            },\n            eventName: d.string(),\n            id: d.string(),\n            piiTranslator: function (a) {\n              return typeof a === 'function' ? a : void 0;\n            },\n            documentLink: d.allowNull(d.string()),\n            referrerLink: d.allowNull(d.string()),\n          });\n          a = new a(d.tuple([c]));\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsSendFetch', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          var a = f.getFbeventsModules('SignalsFBEventsQE'),\n            b = f.getFbeventsModules('SignalsFBEventsGuardrail'),\n            c = f.getFbeventsModules('SignalsFBEventsNetworkConfig');\n          function d(d, e, f) {\n            if (!('fetch' in g && typeof g.fetch === 'function')) return !1;\n            if (!a.isInTest('use_keepalive') && !b.eval('use_keepalive_on'))\n              return !1;\n            f = e || {};\n            e = f.url;\n            f = e === void 0 ? c.ENDPOINT : e;\n            d.replaceEntry('rqm', 'fetch');\n            e = {\n              method: 'POST',\n              body: d.toFormData(),\n              keepalive: !0,\n            };\n            g.fetch(f, e);\n            return !0;\n          }\n          k.exports = d;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsSendFormPOST', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n            b = f.getFbeventsModules('SignalsFBEventsUtils'),\n            c = b.listenOnce;\n          b = f.getFbeventsModules('SignalsFBEventsLogging');\n          var d = b.logError;\n          function e(b, e) {\n            try {\n              b.replaceEntry('rqm', 'formPOST');\n              var f = 'fb' + Math.random().toString().replace('.', ''),\n                i = h.createElement('form');\n              i.method = 'post';\n              i.action = e != null ? e : a.ENDPOINT;\n              i.target = f;\n              i.acceptCharset = 'utf-8';\n              i.style.display = 'none';\n              e = !!(g.attachEvent && !g.addEventListener);\n              var j = h.createElement('iframe');\n              e && (j.name = f);\n              j.src = 'about:blank';\n              j.id = f;\n              j.name = f;\n              i.appendChild(j);\n              c(j, 'load', function () {\n                b.each(function (a, b) {\n                  var c = h.createElement('input');\n                  c.name = decodeURIComponent(a);\n                  c.value = b;\n                  i.appendChild(c);\n                }),\n                  c(j, 'load', function () {\n                    i.parentNode && i.parentNode.removeChild(i);\n                  }),\n                  i.submit();\n              });\n              h.body != null && h.body.appendChild(i);\n              return !0;\n            } catch (a) {\n              a instanceof Error && d(new Error('[POST]:' + a.message));\n              return !0;\n            }\n          }\n          k.exports = e;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsSendGET', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n            b = f.getFbeventsModules(\n              'SignalsFBEventsShouldRestrictReferrerEvent'\n            ),\n            c = f.getFbeventsModules('SignalsFBEventsUtils'),\n            d = c.some,\n            e = 2048;\n          function g(c, f) {\n            try {\n              var g = f || {},\n                h = g.ignoreRequestLengthCheck;\n              h = h === void 0 ? !1 : h;\n              var i = g.url;\n              i = i === void 0 ? a.ENDPOINT : i;\n              g = g.attributionReporting;\n              g = g === void 0 ? !1 : g;\n              c.replaceEntry('rqm', h ? 'FGET' : 'GET');\n              var j = c.toQueryString();\n              i = i + '?' + j;\n              if (h || i.length < e) {\n                j = new Image();\n                f != null &&\n                  f.errorHandler != null &&\n                  (j.onerror = f.errorHandler);\n                h = b.trigger(c);\n                d(h, function (a) {\n                  return a;\n                }) && (j.referrerPolicy = 'origin');\n                g && j.setAttribute('attributionsrc', '');\n                j.src = i;\n                return !0;\n              }\n              return !1;\n            } catch (a) {\n              return !1;\n            }\n          }\n          k.exports = g;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsSendXHR', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n            b = f.getFbeventsModules('SignalsParamList'),\n            c = f.getFbeventsModules('SignalsFBEventsLogging'),\n            d = c.logError,\n            e = {\n              UNSENT: 0,\n              OPENED: 1,\n              HEADERS_RECEIVED: 2,\n              LOADING: 3,\n              DONE: 4,\n            },\n            g =\n              typeof XMLHttpRequest !== 'undefined' &&\n              'withCredentials' in new XMLHttpRequest();\n          function h(a, b, c) {\n            var f = new XMLHttpRequest();\n            f.withCredentials = !0;\n            f.open('POST', b);\n            f.onreadystatechange = function () {\n              if (f.readyState !== e.DONE) return;\n              f.status !== 200 &&\n                (c != null\n                  ? c()\n                  : d(\n                      new Error(\n                        'Error sending XHR ' + f.status + ' - ' + f.statusText\n                      )\n                    ));\n            };\n            f.send(a);\n          }\n          function i(c) {\n            var d =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : a.ENDPOINT,\n              e = arguments[2];\n            if (!g) return !1;\n            c instanceof b && c.replaceEntry('rqm', 'xhr');\n            var f = c instanceof b ? c.toFormData() : c;\n            h(f, d, e);\n            return !0;\n          }\n          k.exports = i;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsSetCCRules', function () {\n      return (function (g, h, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsUtils');\n          b.filter;\n          b.map;\n          b = f.getFbeventsModules('SignalsFBEventsTyped');\n          var c = b.coerce;\n          b = b.Typed;\n          f.getFbeventsModules('signalsFBEventsCoerceParameterExtractors');\n          var d = f.getFbeventsModules('signalsFBEventsCoercePixelID'),\n            e = b.arrayOf(\n              b.objectWithFields({\n                id: b.number(),\n                rule: b.string(),\n              })\n            );\n          function g() {\n            for (var a = arguments.length, b = Array(a), f = 0; f < a; f++)\n              b[f] = arguments[f];\n            var g = b[0];\n            if (\n              g == null ||\n              (typeof g === 'undefined' ? 'undefined' : i(g)) !== 'object'\n            )\n              return null;\n            var h = g.pixelID,\n              j = g.rules,\n              k = d(h);\n            if (k == null) return null;\n            var l = c(j, e);\n            return [\n              {\n                rules: l,\n                pixelID: k,\n              },\n            ];\n          }\n          b = new a(g);\n          l.exports = b;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsSetESTRules', function () {\n      return (function (g, h, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsFBEventsUtils');\n          b.filter;\n          b.map;\n          b = f.getFbeventsModules('SignalsFBEventsTyped');\n          var c = b.coerce;\n          b = b.Typed;\n          f.getFbeventsModules('signalsFBEventsCoerceParameterExtractors');\n          var d = f.getFbeventsModules('signalsFBEventsCoercePixelID'),\n            e = b.arrayOf(\n              b.objectWithFields({\n                condition: b.objectOrString(),\n                derived_event_name: b.string(),\n                rule_status: b.allowNull(b.string()),\n                transformations: b.allowNull(b.array()),\n                rule_id: b.allowNull(b.string()),\n              })\n            );\n          function g() {\n            for (var a = arguments.length, b = Array(a), f = 0; f < a; f++)\n              b[f] = arguments[f];\n            var g = b[0];\n            if (\n              g == null ||\n              (typeof g === 'undefined' ? 'undefined' : i(g)) !== 'object'\n            )\n              return null;\n            var h = g.pixelID,\n              j = g.rules,\n              k = d(h);\n            if (k == null) return null;\n            var l = c(j, e);\n            return [\n              {\n                rules: l,\n                pixelID: k,\n              },\n            ];\n          }\n          b = new a(g);\n          l.exports = b;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsSetEventIDEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('SignalsParamList');\n          f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n          var c = f.getFbeventsModules('SignalsFBEventsTyped');\n          c.coerce;\n          var d = f.getFbeventsModules('signalsFBEventsCoercePixelID');\n          function e(a, c) {\n            a = d(a);\n            c = c instanceof b ? c : null;\n            return a != null && c != null ? [a, c] : null;\n          }\n          c = new a(e);\n          k.exports = c;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsSetFBPEvent', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n            b = f.getFbeventsModules('signalsFBEventsCoercePixelID');\n          function c(a, c) {\n            a = b(a);\n            c = c != null && typeof c === 'string' && c !== '' ? c : null;\n            return [a, c];\n          }\n          a = new a(c);\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsSetFilteredEventName',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsParamList');\n            f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            var c = f.getFbeventsModules('SignalsFBEventsTyped');\n            c.Typed;\n            c.coerce;\n            function d(a) {\n              a = a instanceof b ? a : null;\n              return a != null ? [a] : null;\n            }\n            c = new a(d);\n            k.exports = c;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsSetIWLExtractorsEvent',\n      function () {\n        return (function (g, h, j, k) {\n          var l = {\n            exports: {},\n          };\n          l.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsFBEventsUtils'),\n              c = b.filter,\n              d = b.map,\n              e = f.getFbeventsModules(\n                'signalsFBEventsCoerceParameterExtractors'\n              ),\n              g = f.getFbeventsModules('signalsFBEventsCoercePixelID');\n            function h() {\n              for (var a = arguments.length, b = Array(a), f = 0; f < a; f++)\n                b[f] = arguments[f];\n              var h = b[0];\n              if (\n                h == null ||\n                (typeof h === 'undefined' ? 'undefined' : i(h)) !== 'object'\n              )\n                return null;\n              var j = h.pixelID,\n                k = h.extractors,\n                l = g(j),\n                m = Array.isArray(k) ? d(k, e) : null,\n                n = m != null ? c(m, Boolean) : null;\n              return n != null &&\n                m != null &&\n                n.length === m.length &&\n                l != null\n                ? [\n                    {\n                      extractors: n,\n                      pixelID: l,\n                    },\n                  ]\n                : null;\n            }\n            b = new a(h);\n            l.exports = b;\n          })();\n          return l.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsShouldRestrictReferrerEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsParamList'),\n              b = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              c = f.getFbeventsModules('SignalsFBEventsTyped');\n            c.coerce;\n            c.Typed;\n            f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            c = f.getFbeventsModules('SignalsFBEventsCoercePrimitives');\n            c.coerceString;\n            function d(b) {\n              b = b instanceof a ? b : null;\n              return b != null ? [b] : null;\n            }\n            c = new b(d);\n            k.exports = c;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsStandardParamChecksConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n              b = a.Typed;\n            a.coerce;\n            a = b.objectWithFields({\n              standardParamChecks: b.allowNull(\n                b.mapOf(\n                  b.allowNull(\n                    b.arrayOf(\n                      b.allowNull(\n                        b.objectWithFields({\n                          require_exact_match: b['boolean'](),\n                          potential_matches: b.allowNull(b.arrayOf(b.string())),\n                        })\n                      )\n                    )\n                  )\n                )\n              ),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsTelemetry', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = f.getFbeventsModules('SignalsParamList');\n          f.getFbeventsModules('SignalsFBEventsQE');\n          var c = f.getFbeventsModules('signalsFBEventsSendGET');\n          f.getFbeventsModules('signalsFBEventsSendXHR');\n          f.getFbeventsModules('signalsFBEventsSendBeacon');\n          var d = 0.01,\n            e = Math.random(),\n            h =\n              g.fbq && g.fbq._releaseSegment\n                ? g.fbq._releaseSegment\n                : 'unknown',\n            i = e < d || h === 'canary',\n            j = 'https://connect.facebook.net/log/fbevents_telemetry/';\n          function l(d) {\n            var e =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : 0,\n              f =\n                arguments.length > 2 && arguments[2] !== void 0\n                  ? arguments[2]\n                  : !1;\n            if (!f && !i) return;\n            try {\n              var k = new b(null);\n              k.append('v', g.fbq && g.fbq.version ? g.fbq.version : 'unknown');\n              k.append('rs', h);\n              k.append('e', d);\n              k.append('p', e);\n              c(k, {\n                ignoreRequestLengthCheck: !0,\n                url: j,\n              });\n            } catch (b) {\n              a.logError(b);\n            }\n          }\n          function m(a) {\n            l('FBMQ_FORWARDED', a, !0);\n          }\n          k.exports = {\n            logMobileNativeForwarding: m,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsTyped', function () {\n      return (function (g, h, m, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsUtils');\n          b.filter;\n          b.map;\n          var c = b.reduce;\n          b = f.getFbeventsModules('SignalsFBEventsUtils');\n          var d = b.isSafeInteger,\n            g = (function (b) {\n              k(a, b);\n              function a() {\n                var b =\n                  arguments.length > 0 && arguments[0] !== void 0\n                    ? arguments[0]\n                    : '';\n                n(this, a);\n                var c = j(\n                  this,\n                  (a.__proto__ || Object.getPrototypeOf(a)).call(this, b)\n                );\n                c.name = 'FBEventsCoercionError';\n                return c;\n              }\n              return a;\n            })(Error);\n          function h(a) {\n            return Object.values(a);\n          }\n          function m() {\n            return function (a) {\n              if (typeof a !== 'boolean') throw new g();\n              return a;\n            };\n          }\n          function o() {\n            return function (a) {\n              if (typeof a !== 'number') throw new g();\n              return a;\n            };\n          }\n          function p() {\n            return function (a) {\n              if (typeof a !== 'string') throw new g();\n              return a;\n            };\n          }\n          function q() {\n            return function (a) {\n              if (typeof a !== 'string' && typeof a !== 'number') throw new g();\n              return a;\n            };\n          }\n          function r() {\n            return function (a) {\n              if (\n                (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object' ||\n                Array.isArray(a) ||\n                a == null\n              )\n                throw new g();\n              return a;\n            };\n          }\n          function s() {\n            return function (a) {\n              if (\n                ((typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object' &&\n                  typeof a !== 'string') ||\n                Array.isArray(a) ||\n                a == null\n              )\n                throw new g();\n              return a;\n            };\n          }\n          function t() {\n            return function (a) {\n              if (typeof a !== 'function' || a == null) throw new g();\n              return a;\n            };\n          }\n          function u() {\n            return function (a) {\n              if (a == null || !Array.isArray(a)) throw new g();\n              return a;\n            };\n          }\n          function v(a) {\n            return function (b) {\n              if (h(a).includes(b)) return b;\n              throw new g();\n            };\n          }\n          function w(a) {\n            return function (b) {\n              return B(b, I.array()).map(a);\n            };\n          }\n          function x(b) {\n            return function (e) {\n              var d = B(e, I.object());\n              return c(\n                Object.keys(d),\n                function (c, e) {\n                  return a({}, c, l({}, e, b(d[e])));\n                },\n                {}\n              );\n            };\n          }\n          function y(a) {\n            return function (b) {\n              return b == null ? null : a(b);\n            };\n          }\n          function z(b) {\n            return function (e) {\n              var d = B(e, I.object());\n              e = c(\n                Object.keys(b),\n                function (c, e) {\n                  if (c == null) return null;\n                  var f = b[e],\n                    g = d[e];\n                  f = f(g);\n                  return a({}, c, l({}, e, f));\n                },\n                {}\n              );\n              return e;\n            };\n          }\n          function A(a, b) {\n            try {\n              return b(a);\n            } catch (a) {\n              if (a.name === 'FBEventsCoercionError') return null;\n              throw a;\n            }\n          }\n          function B(a, b) {\n            return b(a);\n          }\n          function C(a) {\n            return function (b) {\n              b = B(b, I.string());\n              if (a.test(b)) return b;\n              throw new g();\n            };\n          }\n          function D(a) {\n            if (!a) throw new g();\n          }\n          function E(a) {\n            return function (b) {\n              b = B(b, u());\n              D(b.length === a.length);\n              return b.map(function (b, c) {\n                return B(b, a[c]);\n              });\n            };\n          }\n          function F(a) {\n            var b = a.def,\n              c = a.validators;\n            return function (a) {\n              var d = B(a, b);\n              c.forEach(function (a) {\n                if (!a(d)) throw new g();\n              });\n              return d;\n            };\n          }\n          var G = /^[1-9][0-9]{0,25}$/;\n          function H() {\n            return F({\n              def: function (a) {\n                var b = A(a, I.number());\n                if (b != null) {\n                  I.assert(d(b));\n                  return '' + b;\n                }\n                return B(a, I.string());\n              },\n              validators: [\n                function (a) {\n                  return G.test(a);\n                },\n              ],\n            });\n          }\n          var I = {\n            allowNull: y,\n            array: u,\n            arrayOf: w,\n            assert: D,\n            boolean: m,\n            enumeration: v,\n            fbid: H,\n            mapOf: x,\n            matches: C,\n            number: o,\n            object: r,\n            objectOrString: s,\n            objectWithFields: z,\n            string: p,\n            stringOrNumber: q,\n            tuple: E,\n            withValidation: F,\n            func: t,\n          };\n          e.exports = {\n            Typed: I,\n            coerce: A,\n            enforce: B,\n            FBEventsCoercionError: g,\n          };\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsTypeVersioning', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          var a = f.getFbeventsModules('SignalsFBEventsTyped');\n          a.coerce;\n          var b = a.enforce,\n            c = a.FBEventsCoercionError;\n          function d(a) {\n            return function (d) {\n              for (var e = 0; e < a.length; e++) {\n                var f = a[e];\n                try {\n                  return b(d, f);\n                } catch (a) {\n                  if (a.name === 'FBEventsCoercionError') continue;\n                  throw a;\n                }\n              }\n              throw new c();\n            };\n          }\n          function e(a, c) {\n            return function (d) {\n              return c(b(d, a));\n            };\n          }\n          a = {\n            waterfall: d,\n            upgrade: e,\n          };\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsUnwantedDataTypedef', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsTyped'),\n            b = a.Typed;\n          a.coerce;\n          a = b.objectWithFields({\n            blacklisted_keys: b.allowNull(\n              b.mapOf(b.mapOf(b.arrayOf(b.string())))\n            ),\n            sensitive_keys: b.allowNull(\n              b.mapOf(b.mapOf(b.arrayOf(b.string())))\n            ),\n          });\n          k.exports = a;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsUnwantedEventNamesConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              unwantedEventNames: a.allowNull(a.mapOf(a.allowNull(a.number()))),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsUnwantedEventsConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              restrictedEventNames: a.allowNull(\n                a.mapOf(a.allowNull(a.number()))\n              ),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsUnwantedParamsConfigTypedef',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsTyped');\n            a = a.Typed;\n            a = a.objectWithFields({\n              unwantedParams: a.allowNull(a.arrayOf(a.string())),\n            });\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsFBEventsURLUtil', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          function a(a, b) {\n            b = new RegExp(\n              '[?#&]' + b.replace(/[\\[\\]]/g, '\\\\$&') + '(=([^&#]*)|&|#|$)'\n            );\n            b = b.exec(a);\n            if (!b) return null;\n            return !b[2] ? '' : decodeURIComponent(b[2].replace(/\\+/g, ' '));\n          }\n          function b(b) {\n            var c;\n            c = a(f.location.href, b);\n            if (c != null) return c;\n            c = a(g.referrer, b);\n            return c;\n          }\n          j.exports = {\n            getURLParameter: a,\n            maybeGetParamFromUrlForEbp: b,\n          };\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsUtils', function () {\n      return (function (f, g, j, k) {\n        var l = {\n          exports: {},\n        };\n        l.exports;\n        (function () {\n          'use strict';\n\n          var a = Object.prototype.toString,\n            b = !('addEventListener' in g);\n          function c(a, b) {\n            return b != null && a instanceof b;\n          }\n          function d(b) {\n            return Array.isArray\n              ? Array.isArray(b)\n              : a.call(b) === '[object Array]';\n          }\n          function e(a) {\n            return (\n              typeof a === 'number' ||\n              (typeof a === 'string' && /^\\d+$/.test(a))\n            );\n          }\n          function f(a) {\n            return (\n              a != null &&\n              (typeof a === 'undefined' ? 'undefined' : i(a)) === 'object' &&\n              d(a) === !1\n            );\n          }\n          function j(a) {\n            return (\n              f(a) === !0 &&\n              Object.prototype.toString.call(a) === '[object Object]'\n            );\n          }\n          function k(a) {\n            if (j(a) === !1) return !1;\n            a = a.constructor;\n            if (typeof a !== 'function') return !1;\n            a = a.prototype;\n            if (j(a) === !1) return !1;\n            return Object.prototype.hasOwnProperty.call(a, 'isPrototypeOf') ===\n              !1\n              ? !1\n              : !0;\n          }\n          var m =\n            Number.isInteger ||\n            function (a) {\n              return (\n                typeof a === 'number' && isFinite(a) && Math.floor(a) === a\n              );\n            };\n          function o(a) {\n            return m(a) && a >= 0 && a <= Number.MAX_SAFE_INTEGER;\n          }\n          function p(a, c, d) {\n            var e = b ? 'on' + c : c;\n            c = b ? a.attachEvent : a.addEventListener;\n            var f = b ? a.detachEvent : a.removeEventListener,\n              g = function b() {\n                f && f.call(a, e, b, !1), d();\n              };\n            c && c.call(a, e, g, !1);\n          }\n          var q = Object.prototype.hasOwnProperty,\n            r = !{\n              toString: null,\n            }.propertyIsEnumerable('toString'),\n            s = [\n              'toString',\n              'toLocaleString',\n              'valueOf',\n              'hasOwnProperty',\n              'isPrototypeOf',\n              'propertyIsEnumerable',\n              'constructor',\n            ],\n            t = s.length;\n          function u(a) {\n            if (\n              (typeof a === 'undefined' ? 'undefined' : i(a)) !== 'object' &&\n              (typeof a !== 'function' || a === null)\n            )\n              throw new TypeError('Object.keys called on non-object');\n            var b = [];\n            for (var c in a) q.call(a, c) && b.push(c);\n            if (r) for (c = 0; c < t; c++) q.call(a, s[c]) && b.push(s[c]);\n            return b;\n          }\n          function v(a, b) {\n            if (a == null) throw new TypeError(' array is null or not defined');\n            a = Object(a);\n            var c = a.length >>> 0;\n            if (typeof b !== 'function')\n              throw new TypeError(b + ' is not a function');\n            var d = new Array(c),\n              e = 0;\n            while (e < c) {\n              var f;\n              e in a && ((f = a[e]), (f = b(f, e, a)), (d[e] = f));\n              e++;\n            }\n            return d;\n          }\n          function w(a, b, c, d) {\n            if (a == null) throw new TypeError(' array is null or not defined');\n            if (typeof b !== 'function')\n              throw new TypeError(b + ' is not a function');\n            var e = Object(a),\n              f = e.length >>> 0,\n              g = 0;\n            if (c != null || d === !0) d = c;\n            else {\n              while (g < f && !(g in e)) g++;\n              if (g >= f)\n                throw new TypeError(\n                  'Reduce of empty array with no initial value'\n                );\n              d = e[g++];\n            }\n            while (g < f) g in e && (d = b(d, e[g], g, a)), g++;\n            return d;\n          }\n          function x(a) {\n            if (typeof a !== 'function') throw new TypeError();\n            var b = Object(this),\n              c = b.length >>> 0,\n              d = arguments.length >= 2 ? arguments[1] : void 0;\n            for (var e = 0; e < c; e++)\n              if (e in b && a.call(d, b[e], e, b)) return !0;\n            return !1;\n          }\n          function y(a) {\n            return u(a).length === 0;\n          }\n          function z(a) {\n            if (this === void 0 || this === null) throw new TypeError();\n            var b = Object(this),\n              c = b.length >>> 0;\n            if (typeof a !== 'function') throw new TypeError();\n            var d = [],\n              e = arguments.length >= 2 ? arguments[1] : void 0;\n            for (var f = 0; f < c; f++)\n              if (f in b) {\n                var g = b[f];\n                a.call(e, g, f, b) && d.push(g);\n              }\n            return d;\n          }\n          function A(a, b) {\n            try {\n              return b(a);\n            } catch (a) {\n              if (a instanceof TypeError)\n                if (B.test(a)) return null;\n                else if (C.test(a)) return void 0;\n              throw a;\n            }\n          }\n          var B = /^null | null$|^[^(]* null /i,\n            C = /^undefined | undefined$|^[^(]* undefined /i;\n          A['default'] = A;\n          var D = (function () {\n            function a(b) {\n              n(this, a), (this.items = b || []);\n            }\n            h(a, [\n              {\n                key: 'has',\n                value: function (a) {\n                  return x.call(this.items, function (b) {\n                    return b === a;\n                  });\n                },\n              },\n              {\n                key: 'add',\n                value: function (a) {\n                  this.items.push(a);\n                },\n              },\n            ]);\n            return a;\n          })();\n          function E(a) {\n            return a;\n          }\n          function F(a, b) {\n            return a == null || b == null ? !1 : a.indexOf(b) >= 0;\n          }\n          function G(a, b) {\n            return a == null || b == null ? !1 : a.indexOf(b) === 0;\n          }\n          D = {\n            FBSet: D,\n            castTo: E,\n            each: function (a, b) {\n              v.call(this, a, b);\n            },\n            filter: function (a, b) {\n              return z.call(a, b);\n            },\n            idx: A,\n            isArray: d,\n            isEmptyObject: y,\n            isInstanceOf: c,\n            isInteger: m,\n            isNumber: e,\n            isObject: f,\n            isPlainObject: k,\n            isSafeInteger: o,\n            keys: u,\n            listenOnce: p,\n            map: v,\n            reduce: w,\n            some: function (a, b) {\n              return x.call(a, b);\n            },\n            stringIncludes: F,\n            stringStartsWith: G,\n          };\n          l.exports = D;\n        })();\n        return l.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEventsValidateCustomParametersEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsFBEventsTyped'),\n              c = b.coerce,\n              d = b.Typed,\n              e = f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            b = f.getFbeventsModules('SignalsFBEventsCoercePrimitives');\n            b.coerceString;\n            function g() {\n              for (var a = arguments.length, b = Array(a), f = 0; f < a; f++)\n                b[f] = arguments[f];\n              return c(b, d.tuple([e, d.object(), d.string()]));\n            }\n            b = new a(g);\n            k.exports = b;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsValidateGetClickIDFromBrowserProperties',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent');\n            function b(a) {\n              return a != null && typeof a === 'string' && a !== '' ? a : null;\n            }\n            a = new a(b);\n            k.exports = a;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'SignalsFBEventsValidateUrlParametersEvent',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsBaseEvent'),\n              b = f.getFbeventsModules('SignalsFBEventsTyped'),\n              c = b.coerce,\n              d = b.Typed,\n              e = f.getFbeventsModules('SignalsFBEventsPixelTypedef');\n            b = f.getFbeventsModules('SignalsFBEventsCoercePrimitives');\n            b.coerceString;\n            f.getFbeventsModules('SignalsParamList');\n            function g() {\n              for (var a = arguments.length, b = Array(a), f = 0; f < a; f++)\n                b[f] = arguments[f];\n              return c(\n                b,\n                d.tuple([e, d.mapOf(d.string()), d.string(), d.object()])\n              );\n            }\n            b = new a(g);\n            k.exports = b;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('SignalsParamList', function () {\n      return (function (f, j, k, l) {\n        var m = {\n          exports: {},\n        };\n        m.exports;\n        (function () {\n          'use strict';\n\n          var a = 'deep',\n            b = 'shallow',\n            c = ['eid'];\n          function d(a) {\n            return JSON === void 0 || JSON === null || !JSON.stringify\n              ? Object.prototype.toString.call(a)\n              : JSON.stringify(a);\n          }\n          function e(a) {\n            if (a === null || a === void 0) return !0;\n            a = typeof a === 'undefined' ? 'undefined' : i(a);\n            return a === 'number' || a === 'boolean' || a === 'string';\n          }\n          var f = (function () {\n            function f(a) {\n              n(this, f), (this._params = new Map()), (this._piiTranslator = a);\n            }\n            h(\n              f,\n              [\n                {\n                  key: 'containsKey',\n                  value: function (a) {\n                    return this._params.has(a);\n                  },\n                },\n                {\n                  key: 'get',\n                  value: function (a) {\n                    a = this._params.get(a);\n                    return a == null || a.length === 0 ? null : a[a.length - 1];\n                  },\n                },\n                {\n                  key: 'getAllParams',\n                  value: function () {\n                    var a = [],\n                      b = !0,\n                      c = !1,\n                      d = void 0;\n                    try {\n                      for (\n                        var e = this._params\n                            .entries()\n                            [\n                              typeof Symbol === 'function'\n                                ? Symbol.iterator\n                                : '@@iterator'\n                            ](),\n                          f;\n                        !(b = (f = e.next()).done);\n                        b = !0\n                      ) {\n                        f = f.value;\n                        f = g(f, 2);\n                        var h = f[0];\n                        f = f[1];\n                        var i = !0,\n                          j = !1,\n                          k = void 0;\n                        try {\n                          for (\n                            var l =\n                                f[\n                                  typeof Symbol === 'function'\n                                    ? Symbol.iterator\n                                    : '@@iterator'\n                                ](),\n                              f;\n                            !(i = (f = l.next()).done);\n                            i = !0\n                          ) {\n                            f = f.value;\n                            a.push({\n                              name: h,\n                              value: f,\n                            });\n                          }\n                        } catch (a) {\n                          (j = !0), (k = a);\n                        } finally {\n                          try {\n                            !i && l['return'] && l['return']();\n                          } finally {\n                            if (j) throw k;\n                          }\n                        }\n                      }\n                    } catch (a) {\n                      (c = !0), (d = a);\n                    } finally {\n                      try {\n                        !b && e['return'] && e['return']();\n                      } finally {\n                        if (c) throw d;\n                      }\n                    }\n                    return a;\n                  },\n                },\n                {\n                  key: 'replaceEntry',\n                  value: function (a, b) {\n                    this._removeKey(a), this.append(a, b);\n                  },\n                },\n                {\n                  key: 'replaceObjectEntry',\n                  value: function (a, b) {\n                    this._removeObjectKey(a, b), this.append(a, b);\n                  },\n                },\n                {\n                  key: 'addRange',\n                  value: function (a) {\n                    this.addParams(a.getAllParams());\n                  },\n                },\n                {\n                  key: 'addParams',\n                  value: function (a) {\n                    for (var c = 0; c < a.length; c++) {\n                      var d = a[c];\n                      this._append(\n                        {\n                          name: d.name,\n                          value: d.value,\n                        },\n                        b,\n                        !1\n                      );\n                    }\n                    return this;\n                  },\n                },\n                {\n                  key: 'append',\n                  value: function (b, c) {\n                    var d =\n                      arguments.length > 2 && arguments[2] !== void 0\n                        ? arguments[2]\n                        : !1;\n                    this._append(\n                      {\n                        name: encodeURIComponent(b),\n                        value: c,\n                      },\n                      a,\n                      d\n                    );\n                    return this;\n                  },\n                },\n                {\n                  key: 'appendHash',\n                  value: function (b) {\n                    var c =\n                      arguments.length > 1 && arguments[1] !== void 0\n                        ? arguments[1]\n                        : !1;\n                    for (var d in b)\n                      Object.prototype.hasOwnProperty.call(b, d) &&\n                        this._append(\n                          {\n                            name: encodeURIComponent(d),\n                            value: b[d],\n                          },\n                          a,\n                          c\n                        );\n                    return this;\n                  },\n                },\n                {\n                  key: '_removeKey',\n                  value: function (a) {\n                    this._params['delete'](a);\n                  },\n                },\n                {\n                  key: '_removeObjectKey',\n                  value: function (a, b) {\n                    for (var c in b)\n                      if (Object.prototype.hasOwnProperty.call(b, c)) {\n                        var d = a + '[' + encodeURIComponent(c) + ']';\n                        this._removeKey(d);\n                      }\n                  },\n                },\n                {\n                  key: '_append',\n                  value: function (b, f, g) {\n                    var h = b.name;\n                    b = b.value;\n                    if (b != null)\n                      for (var i = 0; i < c.length; i++) {\n                        var j = c[i];\n                        j === h && this._removeKey(h);\n                      }\n                    e(b)\n                      ? this._appendPrimitive(h, b, g)\n                      : f === a\n                      ? this._appendObject(h, b, g)\n                      : this._appendPrimitive(h, d(b), g);\n                  },\n                },\n                {\n                  key: '_translateValue',\n                  value: function (a, b, c) {\n                    if (typeof b === 'boolean') return b ? 'true' : 'false';\n                    if (!c) return '' + b;\n                    if (!this._piiTranslator) throw new Error();\n                    return this._piiTranslator(a, '' + b);\n                  },\n                },\n                {\n                  key: '_appendPrimitive',\n                  value: function (a, b, c) {\n                    if (b != null) {\n                      b = this._translateValue(a, b, c);\n                      if (b != null) {\n                        c = this._params.get(a);\n                        c != null\n                          ? (c.push(b), this._params.set(a, c))\n                          : this._params.set(a, [b]);\n                      }\n                    }\n                  },\n                },\n                {\n                  key: '_appendObject',\n                  value: function (a, c, d) {\n                    var e = null;\n                    for (var f in c)\n                      if (Object.prototype.hasOwnProperty.call(c, f)) {\n                        var g = a + '[' + encodeURIComponent(f) + ']';\n                        try {\n                          this._append(\n                            {\n                              name: g,\n                              value: c[f],\n                            },\n                            b,\n                            d\n                          );\n                        } catch (a) {\n                          e == null && (e = a);\n                        }\n                      }\n                    if (e != null) throw e;\n                  },\n                },\n                {\n                  key: 'each',\n                  value: function (a) {\n                    var b = !0,\n                      c = !1,\n                      d = void 0;\n                    try {\n                      for (\n                        var e = this._params\n                            .entries()\n                            [\n                              typeof Symbol === 'function'\n                                ? Symbol.iterator\n                                : '@@iterator'\n                            ](),\n                          f;\n                        !(b = (f = e.next()).done);\n                        b = !0\n                      ) {\n                        f = f.value;\n                        f = g(f, 2);\n                        var h = f[0];\n                        f = f[1];\n                        var i = !0,\n                          j = !1,\n                          k = void 0;\n                        try {\n                          for (\n                            var l =\n                                f[\n                                  typeof Symbol === 'function'\n                                    ? Symbol.iterator\n                                    : '@@iterator'\n                                ](),\n                              f;\n                            !(i = (f = l.next()).done);\n                            i = !0\n                          ) {\n                            f = f.value;\n                            a(h, f);\n                          }\n                        } catch (a) {\n                          (j = !0), (k = a);\n                        } finally {\n                          try {\n                            !i && l['return'] && l['return']();\n                          } finally {\n                            if (j) throw k;\n                          }\n                        }\n                      }\n                    } catch (a) {\n                      (c = !0), (d = a);\n                    } finally {\n                      try {\n                        !b && e['return'] && e['return']();\n                      } finally {\n                        if (c) throw d;\n                      }\n                    }\n                  },\n                },\n                {\n                  key: 'toQueryString',\n                  value: function () {\n                    var a = [];\n                    this.each(function (b, c) {\n                      a.push(b + '=' + encodeURIComponent(c));\n                    });\n                    return a.join('&');\n                  },\n                },\n                {\n                  key: 'toFormData',\n                  value: function () {\n                    var a = new FormData();\n                    this.each(function (b, c) {\n                      a.append(b, c);\n                    });\n                    return a;\n                  },\n                },\n              ],\n              [\n                {\n                  key: 'fromHash',\n                  value: function (a, b) {\n                    return new f(b).appendHash(a);\n                  },\n                },\n              ]\n            );\n            return f;\n          })();\n          m.exports = f;\n        })();\n        return m.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsPixelCookieUtils', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsPixelCookie'),\n            b = f.getFbeventsModules('signalsFBEventsGetIsChrome'),\n            c = f.getFbeventsModules('SignalsFBEventsLogging'),\n            d = c.logError,\n            e = f.getFbeventsModules('SignalsFBEventsQE'),\n            i = 90 * 24 * 60 * 60 * 1e3;\n          c = '_fbc';\n          var j = 'fbc',\n            l = '_fbp',\n            m = 'fbp',\n            n = 'fbclid',\n            o = [\n              {\n                prefix: '',\n                query: 'fbclid',\n                ebp_path: 'clickID',\n              },\n            ],\n            p = {\n              params: o,\n            },\n            q = !1;\n          function r(a) {\n            return new Date(Date.now() + Math.round(a)).toUTCString();\n          }\n          function s(a) {\n            var b = [];\n            try {\n              var c = h.cookie.split(';');\n              a = '^\\\\s*' + a + '=\\\\s*(.*?)\\\\s*$';\n              a = new RegExp(a);\n              for (var e = 0; e < c.length; e++) {\n                var f = c[e].match(a);\n                f && b.push(f[1]);\n              }\n              return b &&\n                Object.prototype.hasOwnProperty.call(b, 0) &&\n                typeof b[0] === 'string'\n                ? b[0]\n                : '';\n            } catch (a) {\n              d('Fail to read from cookie: ' + a.message);\n              return '';\n            }\n          }\n          function t(b) {\n            b = s(b);\n            return typeof b !== 'string' || b === '' ? null : a.unpack(b);\n          }\n          function u(a, b) {\n            return a.slice(a.length - 1 - b).join('.');\n          }\n          function v(a, c, e) {\n            var f = r(i);\n            try {\n              c = encodeURIComponent(c);\n              h.cookie =\n                a +\n                '=' +\n                c +\n                ';' +\n                ('expires=' + f + ';') +\n                ('domain=.' + e + ';') +\n                ('' + (b() ? 'SameSite=Lax;' : '')) +\n                'path=/';\n            } catch (a) {\n              d('Fail to write cookie: ' + a.message);\n            }\n          }\n          function w(a, b) {\n            var c = g.location.hostname;\n            c = c.split('.');\n            if (b.subdomainIndex == null)\n              throw new Error('Subdomain index not set on cookie.');\n            c = u(c, b.subdomainIndex);\n            v(a, b.pack(), c);\n            return b;\n          }\n          function x(b, c) {\n            var d = g.location.hostname;\n            d = d.split('.');\n            c = new a(c);\n            for (var f = 0; f < d.length; f++) {\n              var h = u(d, f);\n              c.subdomainIndex = f;\n              v(b, c.pack(), h);\n              h = s(b);\n              if (e.isInTest('fix_fbc_fbp_update')) {\n                if (h != null && h != '' && a.unpack(h) != null) return c;\n              } else if (h !== '') return c;\n            }\n            return c;\n          }\n          k.exports = {\n            readPackedCookie: t,\n            writeNewCookie: x,\n            writeExistingCookie: w,\n            CLICK_ID_PARAMETER: n,\n            CLICKTHROUGH_COOKIE_NAME: c,\n            CLICKTHROUGH_COOKIE_PARAM: j,\n            DOMAIN_SCOPED_BROWSER_ID_COOKIE_NAME: l,\n            DOMAIN_SCOPED_BROWSER_ID_COOKIE_PARAM: m,\n            DEFAULT_FBC_PARAMS: o,\n            DEFAULT_FBC_PARAM_CONFIG: p,\n            DEFAULT_ENABLE_FBC_PARAM_SPLIT: q,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEvents.plugins.commonincludes',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsPlugin');\n            k.exports = new a(function (a, b) {});\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    e.exports = f.getFbeventsModules('SignalsFBEvents.plugins.commonincludes');\n    f.registerPlugin &&\n      f.registerPlugin('fbevents.plugins.commonincludes', e.exports);\n    f.ensureModuleRegistered('fbevents.plugins.commonincludes', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    var g = (function () {\n        function a(a, b) {\n          var c = [],\n            d = !0,\n            e = !1,\n            f = void 0;\n          try {\n            for (\n              var g =\n                  a[\n                    typeof Symbol === 'function'\n                      ? Symbol.iterator\n                      : '@@iterator'\n                  ](),\n                a;\n              !(d = (a = g.next()).done);\n              d = !0\n            ) {\n              c.push(a.value);\n              if (b && c.length === b) break;\n            }\n          } catch (a) {\n            (e = !0), (f = a);\n          } finally {\n            try {\n              !d && g['return'] && g['return']();\n            } finally {\n              if (e) throw f;\n            }\n          }\n          return c;\n        }\n        return function (b, c) {\n          if (Array.isArray(b)) return b;\n          else if (\n            (typeof Symbol === 'function' ? Symbol.iterator : '@@iterator') in\n            Object(b)\n          )\n            return a(b, c);\n          else\n            throw new TypeError(\n              'Invalid attempt to destructure non-iterable instance'\n            );\n        };\n      })(),\n      h =\n        typeof Symbol === 'function' &&\n        typeof (typeof Symbol === 'function'\n          ? Symbol.iterator\n          : '@@iterator') === 'symbol'\n          ? function (a) {\n              return typeof a;\n            }\n          : function (a) {\n              return a &&\n                typeof Symbol === 'function' &&\n                a.constructor === Symbol &&\n                a !==\n                  (typeof Symbol === 'function'\n                    ? Symbol.prototype\n                    : '@@prototype')\n                ? 'symbol'\n                : typeof a;\n            };\n    function i(a, b) {\n      if (!(a instanceof b))\n        throw new TypeError('Cannot call a class as a function');\n    }\n    function j(a, b) {\n      if (!a)\n        throw new ReferenceError(\n          \"this hasn't been initialised - super() hasn't been called\"\n        );\n      return b && (typeof b === 'object' || typeof b === 'function') ? b : a;\n    }\n    function k(a, b) {\n      if (typeof b !== 'function' && b !== null)\n        throw new TypeError(\n          'Super expression must either be null or a function, not ' + typeof b\n        );\n      a.prototype = Object.create(b && b.prototype, {\n        constructor: {\n          value: a,\n          enumerable: !1,\n          writable: !0,\n          configurable: !0,\n        },\n      });\n      b &&\n        (Object.setPrototypeOf\n          ? Object.setPrototypeOf(a, b)\n          : (a.__proto__ = b));\n    }\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('normalizeSignalsFBEventsEmailType', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsValidationUtils'),\n            b = a.looksLikeHashed,\n            c = a.trim,\n            d =\n              /^[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+(:?\\.[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+)*@(?:[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?$/i;\n          function e(a) {\n            return d.test(a);\n          }\n          function g(a) {\n            var d = null;\n            if (a != null)\n              if (b(a)) d = a;\n              else {\n                a = c(a.toLowerCase());\n                d = e(a) ? a : null;\n              }\n            return d;\n          }\n          k.exports = g;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('normalizeSignalsFBEventsEnumType', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsShared'),\n            b = a.unicodeSafeTruncate;\n          a = f.getFbeventsModules('SignalsFBEventsValidationUtils');\n          var c = a.looksLikeHashed,\n            d = a.trim;\n          function e(a) {\n            var e =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : {},\n              f = null,\n              g = e.caseInsensitive,\n              h = e.lowercase,\n              i = e.options,\n              j = e.truncate,\n              k = e.uppercase;\n            if (a != null && i != null && Array.isArray(i) && i.length)\n              if (typeof a === 'string' && c(a)) f = a;\n              else {\n                var l = d(String(a));\n                h === !0 && (l = l.toLowerCase());\n                k === !0 && (l = l.toUpperCase());\n                j != null && j !== 0 && (l = b(l, j));\n                if (g === !0) {\n                  var m = l.toLowerCase();\n                  for (var n = 0; n < i.length; ++n)\n                    if (m === i[n].toLowerCase()) {\n                      l = i[n];\n                      break;\n                    }\n                }\n                f = i.indexOf(l) > -1 ? l : null;\n              }\n            return f;\n          }\n          k.exports = e;\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'normalizeSignalsFBEventsPhoneNumberType',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsValidationUtils'),\n              b = f.getFbeventsModules('SignalsFBEventsUtils');\n            b = b.stringStartsWith;\n            var c = a.looksLikeHashed;\n            f.getFbeventsModules('SignalsFBEventsQE');\n            var d = /^0*/,\n              e = /[\\-@#<>\\'\\\",; ]|\\(|\\)|\\+|[a-z]/gi;\n            b = /^1\\(?\\d{3}\\)?\\d{7}$/;\n            a = /^47\\d{8}$/;\n            b = /^\\d{1,4}\\(?\\d{2,3}\\)?\\d{4,}$/;\n            function g(a) {\n              var b = null;\n              if (a != null)\n                if (c(a)) b = a;\n                else {\n                  a = String(a);\n                  b = a.replace(e, '').replace(d, '');\n                }\n              return b;\n            }\n            k.exports = g;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered(\n      'normalizeSignalsFBEventsPostalCodeType',\n      function () {\n        return (function (g, h, i, j) {\n          var k = {\n            exports: {},\n          };\n          k.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('SignalsFBEventsValidationUtils'),\n              b = a.looksLikeHashed,\n              c = a.trim;\n            function d(a) {\n              var d = null;\n              if (a != null && typeof a === 'string')\n                if (b(a)) d = a;\n                else {\n                  a = c(String(a).toLowerCase().split('-', 1)[0]);\n                  a.length >= 2 && (d = a);\n                }\n              return d;\n            }\n            k.exports = d;\n          })();\n          return k.exports;\n        })(a, b, c, d);\n      }\n    );\n    f.ensureModuleRegistered('normalizeSignalsFBEventsStringType', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsShared'),\n            b = a.unicodeSafeTruncate;\n          a = f.getFbeventsModules('SignalsFBEventsValidationUtils');\n          var c = a.looksLikeHashed,\n            d = a.strip;\n          function e(a) {\n            var e =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : {},\n              f = null;\n            if (a != null)\n              if (c(a) && typeof a === 'string')\n                e.rejectHashed !== !0 && (f = a);\n              else {\n                var g = String(a);\n                e.strip != null && (g = d(g, e.strip));\n                e.lowercase === !0\n                  ? (g = g.toLowerCase())\n                  : e.uppercase === !0 && (g = g.toUpperCase());\n                e.truncate != null &&\n                  e.truncate !== 0 &&\n                  (g = b(g, e.truncate));\n                e.test != null && e.test !== ''\n                  ? (f = new RegExp(e.test).test(g) ? g : null)\n                  : (f = g);\n              }\n            return f;\n          }\n          function g(a) {\n            return e(a, {\n              strip: 'whitespace_and_punctuation',\n            });\n          }\n          function h(a) {\n            return e(a, {\n              truncate: 2,\n              strip: 'all_non_latin_alpha_numeric',\n              test: '^[a-z]+',\n            });\n          }\n          function i(a) {\n            return e(a, {\n              strip: 'all_non_latin_alpha_numeric',\n              test: '^[a-z]+',\n            });\n          }\n          k.exports = {\n            normalize: e,\n            normalizeName: g,\n            normalizeCity: i,\n            normalizeState: h,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('sha256_with_dependencies_new', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          function a(a) {\n            var b = '',\n              c = void 0,\n              d;\n            for (var e = 0; e < a.length; e++)\n              (c = a.charCodeAt(e)),\n                (d = e + 1 < a.length ? a.charCodeAt(e + 1) : 0),\n                c >= 55296 &&\n                  c <= 56319 &&\n                  d >= 56320 &&\n                  d <= 57343 &&\n                  ((c = 65536 + ((c & 1023) << 10) + (d & 1023)), e++),\n                c <= 127\n                  ? (b += String.fromCharCode(c))\n                  : c <= 2047\n                  ? (b += String.fromCharCode(\n                      192 | ((c >>> 6) & 31),\n                      128 | (c & 63)\n                    ))\n                  : c <= 65535\n                  ? (b += String.fromCharCode(\n                      224 | ((c >>> 12) & 15),\n                      128 | ((c >>> 6) & 63),\n                      128 | (c & 63)\n                    ))\n                  : c <= 2097151 &&\n                    (b += String.fromCharCode(\n                      240 | ((c >>> 18) & 7),\n                      128 | ((c >>> 12) & 63),\n                      128 | ((c >>> 6) & 63),\n                      128 | (c & 63)\n                    ));\n            return b;\n          }\n          function b(a, b) {\n            return (b >>> a) | (b << (32 - a));\n          }\n          function c(a, b, c) {\n            return (a & b) ^ (~a & c);\n          }\n          function d(a, b, c) {\n            return (a & b) ^ (a & c) ^ (b & c);\n          }\n          function e(a) {\n            return b(2, a) ^ b(13, a) ^ b(22, a);\n          }\n          function f(a) {\n            return b(6, a) ^ b(11, a) ^ b(25, a);\n          }\n          function g(a) {\n            return b(7, a) ^ b(18, a) ^ (a >>> 3);\n          }\n          function h(a) {\n            return b(17, a) ^ b(19, a) ^ (a >>> 10);\n          }\n          function i(a, b) {\n            return (a[b & 15] +=\n              h(a[(b + 14) & 15]) + a[(b + 9) & 15] + g(a[(b + 1) & 15]));\n          }\n          var k = [\n              1116352408, 1899447441, 3049323471, 3921009573, 961987163,\n              1508970993, 2453635748, 2870763221, 3624381080, 310598401,\n              607225278, 1426881987, 1925078388, 2162078206, 2614888103,\n              3248222580, 3835390401, 4022224774, 264347078, 604807628,\n              770255983, 1249150122, 1555081692, 1996064986, 2554220882,\n              2821834349, 2952996808, 3210313671, 3336571891, 3584528711,\n              113926993, 338241895, 666307205, 773529912, 1294757372,\n              1396182291, 1695183700, 1986661051, 2177026350, 2456956037,\n              2730485921, 2820302411, 3259730800, 3345764771, 3516065817,\n              3600352804, 4094571909, 275423344, 430227734, 506948616,\n              659060556, 883997877, 958139571, 1322822218, 1537002063,\n              1747873779, 1955562222, 2024104815, 2227730452, 2361852424,\n              2428436474, 2756734187, 3204031479, 3329325298,\n            ],\n            l = new Array(8),\n            m = new Array(2),\n            n = new Array(64),\n            o = new Array(16),\n            p = '0123456789abcdef';\n          function q(a, b) {\n            var c = (a & 65535) + (b & 65535);\n            a = (a >> 16) + (b >> 16) + (c >> 16);\n            return (a << 16) | (c & 65535);\n          }\n          function r() {\n            (m[0] = m[1] = 0),\n              (l[0] = 1779033703),\n              (l[1] = 3144134277),\n              (l[2] = 1013904242),\n              (l[3] = 2773480762),\n              (l[4] = 1359893119),\n              (l[5] = 2600822924),\n              (l[6] = 528734635),\n              (l[7] = 1541459225);\n          }\n          function s() {\n            var a = void 0,\n              b = void 0,\n              g = void 0,\n              h = void 0,\n              j = void 0,\n              m = void 0,\n              p = void 0,\n              r = void 0,\n              s = void 0,\n              t = void 0;\n            g = l[0];\n            h = l[1];\n            j = l[2];\n            m = l[3];\n            p = l[4];\n            r = l[5];\n            s = l[6];\n            t = l[7];\n            for (var u = 0; u < 16; u++)\n              o[u] =\n                n[(u << 2) + 3] |\n                (n[(u << 2) + 2] << 8) |\n                (n[(u << 2) + 1] << 16) |\n                (n[u << 2] << 24);\n            for (u = 0; u < 64; u++)\n              (a = t + f(p) + c(p, r, s) + k[u]),\n                u < 16 ? (a += o[u]) : (a += i(o, u)),\n                (b = e(g) + d(g, h, j)),\n                (t = s),\n                (s = r),\n                (r = p),\n                (p = q(m, a)),\n                (m = j),\n                (j = h),\n                (h = g),\n                (g = q(a, b));\n            l[0] += g;\n            l[1] += h;\n            l[2] += j;\n            l[3] += m;\n            l[4] += p;\n            l[5] += r;\n            l[6] += s;\n            l[7] += t;\n          }\n          function t(a, b) {\n            var c = void 0,\n              d,\n              e = 0;\n            d = (m[0] >> 3) & 63;\n            var f = b & 63;\n            (m[0] += b << 3) < b << 3 && m[1]++;\n            m[1] += b >> 29;\n            for (c = 0; c + 63 < b; c += 64) {\n              for (var g = d; g < 64; g++) n[g] = a.charCodeAt(e++);\n              s();\n              d = 0;\n            }\n            for (g = 0; g < f; g++) n[g] = a.charCodeAt(e++);\n          }\n          function u() {\n            var a = (m[0] >> 3) & 63;\n            n[a++] = 128;\n            if (a <= 56) for (var b = a; b < 56; b++) n[b] = 0;\n            else {\n              for (b = a; b < 64; b++) n[b] = 0;\n              s();\n              for (a = 0; a < 56; a++) n[a] = 0;\n            }\n            n[56] = (m[1] >>> 24) & 255;\n            n[57] = (m[1] >>> 16) & 255;\n            n[58] = (m[1] >>> 8) & 255;\n            n[59] = m[1] & 255;\n            n[60] = (m[0] >>> 24) & 255;\n            n[61] = (m[0] >>> 16) & 255;\n            n[62] = (m[0] >>> 8) & 255;\n            n[63] = m[0] & 255;\n            s();\n          }\n          function v() {\n            var a = '';\n            for (var b = 0; b < 8; b++)\n              for (var c = 28; c >= 0; c -= 4) a += p.charAt((l[b] >>> c) & 15);\n            return a;\n          }\n          function w(a) {\n            var b = 0;\n            for (var c = 0; c < 8; c++)\n              for (var d = 28; d >= 0; d -= 4)\n                a[b++] = p.charCodeAt((l[c] >>> d) & 15);\n          }\n          function x(a, b) {\n            r();\n            t(a, a.length);\n            u();\n            if (b) w(b);\n            else return v();\n          }\n          function y(b) {\n            var c =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : !0,\n              d = arguments[2];\n            if (b === null || b === void 0) return null;\n            var e = b;\n            c && (e = a(b));\n            return x(e, d);\n          }\n          j.exports = y;\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsNormalizers', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('normalizeSignalsFBEventsStringType');\n          a = a.normalize;\n          k.exports = {\n            email: f.getFbeventsModules('normalizeSignalsFBEventsEmailType'),\n            enum: f.getFbeventsModules('normalizeSignalsFBEventsEnumType'),\n            postal_code: f.getFbeventsModules(\n              'normalizeSignalsFBEventsPostalCodeType'\n            ),\n            phone_number: f.getFbeventsModules(\n              'normalizeSignalsFBEventsPhoneNumberType'\n            ),\n            string: a,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsPixelPIISchema', function () {\n      return (function (f, g, h, i) {\n        var j = {\n          exports: {},\n        };\n        j.exports;\n        (function () {\n          'use strict';\n\n          j.exports = {\n            default: {\n              type: 'string',\n              typeParams: {\n                lowercase: !0,\n                strip: 'whitespace_only',\n              },\n            },\n            ph: {\n              type: 'phone_number',\n            },\n            em: {\n              type: 'email',\n            },\n            fn: {\n              type: 'string',\n              typeParams: {\n                lowercase: !0,\n                strip: 'whitespace_and_punctuation',\n              },\n            },\n            ln: {\n              type: 'string',\n              typeParams: {\n                lowercase: !0,\n                strip: 'whitespace_and_punctuation',\n              },\n            },\n            zp: {\n              type: 'postal_code',\n            },\n            ct: {\n              type: 'string',\n              typeParams: {\n                lowercase: !0,\n                strip: 'all_non_latin_alpha_numeric',\n                test: '^[a-z]+',\n              },\n            },\n            st: {\n              type: 'string',\n              typeParams: {\n                lowercase: !0,\n                truncate: 2,\n                strip: 'all_non_latin_alpha_numeric',\n                test: '^[a-z]+',\n              },\n            },\n            dob: {\n              type: 'date',\n            },\n            doby: {\n              type: 'string',\n              typeParams: {\n                test: '^[0-9]{4,4}$',\n              },\n            },\n            ge: {\n              type: 'enum',\n              typeParams: {\n                lowercase: !0,\n                options: ['f', 'm'],\n              },\n            },\n            dobm: {\n              type: 'string',\n              typeParams: {\n                test: '^(0?[1-9]|1[012])$|^jan|^feb|^mar|^apr|^may|^jun|^jul|^aug|^sep|^oct|^nov|^dec',\n              },\n            },\n            dobd: {\n              type: 'string',\n              typeParams: {\n                test: '^(([0]?[1-9])|([1-2][0-9])|(3[01]))$',\n              },\n            },\n          };\n        })();\n        return j.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsShared', function () {\n      return (function (f, g, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          k.exports = (function (a) {\n            var b = {};\n            function c(d) {\n              if (b[d]) return b[d].exports;\n              var e = (b[d] = {\n                i: d,\n                l: !1,\n                exports: {},\n              });\n              return (\n                a[d].call(e.exports, e, e.exports, c), (e.l = !0), e.exports\n              );\n            }\n            return (\n              (c.m = a),\n              (c.c = b),\n              (c.d = function (a, b, d) {\n                c.o(a, b) ||\n                  Object.defineProperty(a, b, {\n                    enumerable: !0,\n                    get: d,\n                  });\n              }),\n              (c.r = function (a) {\n                'undefined' != typeof Symbol &&\n                  (typeof Symbol === 'function'\n                    ? Symbol.toStringTag\n                    : '@@toStringTag') &&\n                  Object.defineProperty(\n                    a,\n                    typeof Symbol === 'function'\n                      ? Symbol.toStringTag\n                      : '@@toStringTag',\n                    {\n                      value: 'Module',\n                    }\n                  ),\n                  Object.defineProperty(a, '__esModule', {\n                    value: !0,\n                  });\n              }),\n              (c.t = function (a, b) {\n                if ((1 & b && (a = c(a)), 8 & b)) return a;\n                if (\n                  4 & b &&\n                  'object' == (typeof a === 'undefined' ? 'undefined' : h(a)) &&\n                  a &&\n                  a.__esModule\n                )\n                  return a;\n                var d = Object.create(null);\n                if (\n                  (c.r(d),\n                  Object.defineProperty(d, 'default', {\n                    enumerable: !0,\n                    value: a,\n                  }),\n                  2 & b && 'string' != typeof a)\n                )\n                  for (b in a)\n                    c.d(\n                      d,\n                      b,\n                      function (b) {\n                        return a[b];\n                      }.bind(null, b)\n                    );\n                return d;\n              }),\n              (c.n = function (a) {\n                var b =\n                  a && a.__esModule\n                    ? function () {\n                        return a['default'];\n                      }\n                    : function () {\n                        return a;\n                      };\n                return c.d(b, 'a', b), b;\n              }),\n              (c.o = function (a, b) {\n                return Object.prototype.hasOwnProperty.call(a, b);\n              }),\n              (c.p = ''),\n              c((c.s = 76))\n            );\n          })([\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(79);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a) {\n                if (null != a) return a;\n                throw new Error('Got unexpected null or undefined');\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(133);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(53);\n              var d = b.all;\n              a.exports = b.IS_HTMLDDA\n                ? function (a) {\n                    return 'function' == typeof a || a === d;\n                  }\n                : function (a) {\n                    return 'function' == typeof a;\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(98);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a) {\n                try {\n                  return !!a();\n                } catch (a) {\n                  return !0;\n                }\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(8);\n              var d = c(59),\n                e = c(14),\n                f = c(60),\n                g = c(57);\n              c = c(56);\n              var h = b.Symbol,\n                i = d('wks'),\n                j = c ? h['for'] || h : (h && h.withoutSetter) || f;\n              a.exports = function (a) {\n                return (\n                  e(i, a) || (i[a] = g && e(h, a) ? h[a] : j('Symbol.' + a)),\n                  i[a]\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(25);\n              c = Function.prototype;\n              var d = c.call;\n              c = b && c.bind.bind(d, d);\n              a.exports = b\n                ? c\n                : function (a) {\n                    return function () {\n                      return d.apply(a, arguments);\n                    };\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              (function (b) {\n                var c = function (a) {\n                  return a && a.Math === Math && a;\n                };\n                a.exports =\n                  c(\n                    'object' ==\n                      (typeof globalThis === 'undefined'\n                        ? 'undefined'\n                        : h(globalThis)) && globalThis\n                  ) ||\n                  c(\n                    'object' ==\n                      (typeof f === 'undefined' ? 'undefined' : h(f)) && f\n                  ) ||\n                  c(\n                    'object' ==\n                      (typeof self === 'undefined' ? 'undefined' : h(self)) &&\n                      self\n                  ) ||\n                  c(\n                    'object' ==\n                      (typeof b === 'undefined' ? 'undefined' : h(b)) && b\n                  ) ||\n                  (function () {\n                    return this;\n                  })() ||\n                  this ||\n                  Function('return this')();\n              }).call(this, c(84));\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(138);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(8),\n                e = c(85),\n                f = c(26),\n                g = c(3),\n                i = c(54).f,\n                j = c(92),\n                k = c(40),\n                l = c(44),\n                m = c(23),\n                n = c(14),\n                o = function (a) {\n                  var b = function b(c, d, f) {\n                    if (this instanceof b) {\n                      switch (arguments.length) {\n                        case 0:\n                          return new a();\n                        case 1:\n                          return new a(c);\n                        case 2:\n                          return new a(c, d);\n                      }\n                      return new a(c, d, f);\n                    }\n                    return e(a, this, arguments);\n                  };\n                  return (b.prototype = a.prototype), b;\n                };\n              a.exports = function (a, b) {\n                var c,\n                  e,\n                  p,\n                  q,\n                  r,\n                  s,\n                  t = a.target,\n                  u = a.global,\n                  v = a.stat,\n                  w = a.proto,\n                  x = u ? d : v ? d[t] : (d[t] || {}).prototype,\n                  y = u ? k : k[t] || m(k, t, {})[t],\n                  z = y.prototype;\n                for (p in b)\n                  (e =\n                    !(c = j(u ? p : t + (v ? '.' : '#') + p, a.forced)) &&\n                    x &&\n                    n(x, p)),\n                    (q = y[p]),\n                    e &&\n                      (r = a.dontCallGetSet ? (s = i(x, p)) && s.value : x[p]),\n                    (s = e && r ? r : b[p]),\n                    (e &&\n                      (typeof q === 'undefined' ? 'undefined' : h(q)) ==\n                        (typeof s === 'undefined' ? 'undefined' : h(s))) ||\n                      ((e =\n                        a.bind && e\n                          ? l(s, d)\n                          : a.wrap && e\n                          ? o(s)\n                          : w && g(s)\n                          ? f(s)\n                          : s),\n                      (a.sham || (s && s.sham) || (q && q.sham)) &&\n                        m(e, 'sham', !0),\n                      m(y, p, e),\n                      w &&\n                        (n(k, (q = t + 'Prototype')) || m(k, q, {}),\n                        m(k[q], p, s),\n                        a.real && z && (c || !z[p]) && m(z, p, s)));\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(77);\n              a.exports = function a(b, c) {\n                return (\n                  !(!b || !c) &&\n                  (b === c ||\n                    (!d(b) &&\n                      (d(c)\n                        ? a(b, c.parentNode)\n                        : 'contains' in b\n                        ? b.contains(c)\n                        : !!b.compareDocumentPosition &&\n                          !!(16 & b.compareDocumentPosition(c)))))\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(128);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(3);\n              b = c(53);\n              var e = b.all;\n              a.exports = b.IS_HTMLDDA\n                ? function (a) {\n                    return 'object' ==\n                      (typeof a === 'undefined' ? 'undefined' : h(a))\n                      ? null !== a\n                      : d(a) || a === e;\n                  }\n                : function (a) {\n                    return 'object' ==\n                      (typeof a === 'undefined' ? 'undefined' : h(a))\n                      ? null !== a\n                      : d(a);\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(22),\n                e = b({}.hasOwnProperty);\n              a.exports =\n                Object.hasOwn ||\n                function (a, b) {\n                  return e(d(a), b);\n                };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(5);\n              a.exports = !b(function () {\n                return (\n                  7 !==\n                  Object.defineProperty({}, 1, {\n                    get: function () {\n                      return 7;\n                    },\n                  })[1]\n                );\n              });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(25);\n              var d = Function.prototype.call;\n              a.exports = b\n                ? d.bind(d)\n                : function () {\n                    return d.apply(d, arguments);\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(13),\n                e = String,\n                f = TypeError;\n              a.exports = function (a) {\n                if (d(a)) return a;\n                throw f(e(a) + ' is not an object');\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(30);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(158);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = b({}.toString),\n                e = b(''.slice);\n              a.exports = function (a) {\n                return e(d(a), 8, -1);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(3),\n                e = c(58),\n                f = TypeError;\n              a.exports = function (a) {\n                if (d(a)) return a;\n                throw f(e(a) + ' is not a function');\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(29),\n                e = Object;\n              a.exports = function (a) {\n                return e(d(a));\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(15);\n              var d = c(32),\n                e = c(27);\n              a.exports = b\n                ? function (a, b, c) {\n                    return d.f(a, b, e(1, c));\n                  }\n                : function (a, b, c) {\n                    return (a[b] = c), a;\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(145);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(5);\n              a.exports = !b(function () {\n                var a = function () {}.bind();\n                return (\n                  'function' != typeof a ||\n                  Object.prototype.hasOwnProperty.call(a, 'prototype')\n                );\n              });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(20),\n                e = c(7);\n              a.exports = function (a) {\n                if ('Function' === d(a)) return e(a);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a, b) {\n                return {\n                  enumerable: !(1 & a),\n                  configurable: !(2 & a),\n                  writable: !(4 & a),\n                  value: b,\n                };\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(37),\n                e = c(29);\n              a.exports = function (a) {\n                return d(e(a));\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(38),\n                e = TypeError;\n              a.exports = function (a) {\n                if (d(a)) throw e(\"Can't call method on \" + a);\n                return a;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(40),\n                e = c(8),\n                f = c(3),\n                g = function (a) {\n                  return f(a) ? a : void 0;\n                };\n              a.exports = function (a, b) {\n                return arguments.length < 2\n                  ? g(d[a]) || g(e[a])\n                  : (d[a] && d[a][b]) || (e[a] && e[a][b]);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = !0;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(15);\n              var d = c(61),\n                e = c(63),\n                f = c(17),\n                g = c(39),\n                h = TypeError,\n                i = Object.defineProperty,\n                j = Object.getOwnPropertyDescriptor;\n              b.f = a\n                ? e\n                  ? function (a, b, c) {\n                      if (\n                        (f(a),\n                        (b = g(b)),\n                        f(c),\n                        'function' == typeof a &&\n                          'prototype' === b &&\n                          'value' in c &&\n                          'writable' in c &&\n                          !c.writable)\n                      ) {\n                        var d = j(a, b);\n                        d &&\n                          d.writable &&\n                          ((a[b] = c.value),\n                          (c = {\n                            configurable:\n                              'configurable' in c\n                                ? c.configurable\n                                : d.configurable,\n                            enumerable:\n                              'enumerable' in c ? c.enumerable : d.enumerable,\n                            writable: !1,\n                          }));\n                      }\n                      return i(a, b, c);\n                    }\n                  : i\n                : function (a, b, c) {\n                    if ((f(a), (b = g(b)), f(c), d))\n                      try {\n                        return i(a, b, c);\n                      } catch (a) {}\n                    if ('get' in c || 'set' in c)\n                      throw h('Accessors not supported');\n                    return 'value' in c && (a[b] = c.value), a;\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(64);\n              a.exports = function (a) {\n                return d(a.length);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(47);\n              var d = c(3),\n                e = c(20),\n                f = c(6)('toStringTag'),\n                g = Object,\n                h =\n                  'Arguments' ===\n                  e(\n                    (function () {\n                      return arguments;\n                    })()\n                  );\n              a.exports = b\n                ? e\n                : function (a) {\n                    var b;\n                    return void 0 === a\n                      ? 'Undefined'\n                      : null === a\n                      ? 'Null'\n                      : 'string' ==\n                        typeof (b = (function (a, b) {\n                          try {\n                            return a[b];\n                          } catch (a) {}\n                        })((a = g(a)), f))\n                      ? b\n                      : h\n                      ? e(a)\n                      : 'Object' === (b = e(a)) && d(a.callee)\n                      ? 'Arguments'\n                      : b;\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = {};\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a) {\n                var b = [];\n                return (\n                  (function a(b, c) {\n                    var d = b.length,\n                      e = 0;\n                    for (; d--; ) {\n                      var f = b[e++];\n                      Array.isArray(f) ? a(f, c) : c.push(f);\n                    }\n                  })(a, b),\n                  b\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(5),\n                e = c(20),\n                f = Object,\n                g = b(''.split);\n              a.exports = d(function () {\n                return !f('z').propertyIsEnumerable(0);\n              })\n                ? function (a) {\n                    return 'String' === e(a) ? g(a, '') : f(a);\n                  }\n                : f;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a) {\n                return null == a;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(87),\n                e = c(55);\n              a.exports = function (a) {\n                a = d(a, 'string');\n                return e(a) ? a : a + '';\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = {};\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d, e;\n              b = c(8);\n              c = c(89);\n              var f = b.process;\n              b = b.Deno;\n              f = (f && f.versions) || (b && b.version);\n              b = f && f.v8;\n              b &&\n                (e =\n                  (d = b.split('.'))[0] > 0 && d[0] < 4 ? 1 : +(d[0] + d[1])),\n                !e &&\n                  c &&\n                  (!(d = c.match(/Edge\\/(\\d+)/)) || d[1] >= 74) &&\n                  (d = c.match(/Chrome\\/(\\d+)/)) &&\n                  (e = +d[1]),\n                (a.exports = e);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(21),\n                e = c(38);\n              a.exports = function (a, b) {\n                a = a[b];\n                return e(a) ? void 0 : d(a);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(8);\n              c = c(91);\n              b = b['__core-js_shared__'] || c('__core-js_shared__', {});\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(26);\n              var d = c(21),\n                e = c(25),\n                f = b(b.bind);\n              a.exports = function (a, b) {\n                return (\n                  d(a),\n                  void 0 === b\n                    ? a\n                    : e\n                    ? f(a, b)\n                    : function () {\n                        return a.apply(b, arguments);\n                      }\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(44);\n              b = c(7);\n              var e = c(37),\n                f = c(22),\n                g = c(33),\n                h = c(94),\n                i = b([].push);\n              c = function (a) {\n                var b = 1 === a,\n                  c = 2 === a,\n                  j = 3 === a,\n                  k = 4 === a,\n                  l = 6 === a,\n                  m = 7 === a,\n                  n = 5 === a || l;\n                return function (o, p, q, r) {\n                  for (\n                    var s,\n                      t,\n                      u = f(o),\n                      v = e(u),\n                      p = d(p, q),\n                      q = g(v),\n                      w = 0,\n                      r = r || h,\n                      r = b ? r(o, q) : c || m ? r(o, 0) : void 0;\n                    q > w;\n                    w++\n                  )\n                    if ((n || w in v) && ((t = p((s = v[w]), w, u)), a))\n                      if (b) r[w] = t;\n                      else if (t)\n                        switch (a) {\n                          case 3:\n                            return !0;\n                          case 5:\n                            return s;\n                          case 6:\n                            return w;\n                          case 2:\n                            i(r, s);\n                        }\n                      else\n                        switch (a) {\n                          case 4:\n                            return !1;\n                          case 7:\n                            i(r, s);\n                        }\n                  return l ? -1 : j || k ? k : r;\n                };\n              };\n              a.exports = {\n                forEach: c(0),\n                map: c(1),\n                filter: c(2),\n                some: c(3),\n                every: c(4),\n                find: c(5),\n                findIndex: c(6),\n                filterReject: c(7),\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(93);\n              a.exports = function (a) {\n                a = +a;\n                return a != a || 0 === a ? 0 : d(a);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = {};\n              (b[c(6)('toStringTag')] = 'z'),\n                (a.exports = '[object z]' === String(b));\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(34),\n                e = String;\n              a.exports = function (a) {\n                if ('Symbol' === d(a))\n                  throw TypeError('Cannot convert a Symbol value to a string');\n                return e(a);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(59);\n              var d = c(60),\n                e = b('keys');\n              a.exports = function (a) {\n                return e[a] || (e[a] = d(a));\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = {};\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(28),\n                e = c(112),\n                f = c(33);\n              b = function (a) {\n                return function (b, c, g) {\n                  var h;\n                  b = d(b);\n                  var i = f(b);\n                  g = e(g, i);\n                  if (a && c != c) {\n                    for (; i > g; ) if ((h = b[g++]) != h) return !0;\n                  } else\n                    for (; i > g; g++)\n                      if ((a || g in b) && b[g] === c) return a || g || 0;\n                  return !a && -1;\n                };\n              };\n              a.exports = {\n                includes: b(!0),\n                indexOf: b(!1),\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = c(153);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b =\n                'object' == (typeof g === 'undefined' ? 'undefined' : h(g)) &&\n                g.all;\n              c = void 0 === b && void 0 !== b;\n              a.exports = {\n                all: b,\n                IS_HTMLDDA: c,\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(15);\n              var d = c(16),\n                e = c(86),\n                f = c(27),\n                g = c(28),\n                h = c(39),\n                i = c(14),\n                j = c(61),\n                k = Object.getOwnPropertyDescriptor;\n              b.f = a\n                ? k\n                : function (a, b) {\n                    if (((a = g(a)), (b = h(b)), j))\n                      try {\n                        return k(a, b);\n                      } catch (a) {}\n                    if (i(a, b)) return f(!d(e.f, a, b), a[b]);\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(30),\n                e = c(3),\n                f = c(88);\n              b = c(56);\n              var g = Object;\n              a.exports = b\n                ? function (a) {\n                    return (\n                      'symbol' ==\n                      (typeof a === 'undefined' ? 'undefined' : h(a))\n                    );\n                  }\n                : function (a) {\n                    var b = d('Symbol');\n                    return e(b) && f(b.prototype, g(a));\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(57);\n              a.exports =\n                b &&\n                !(typeof Symbol === 'function' ? Symbol.sham : '@@sham') &&\n                'symbol' ==\n                  h(\n                    typeof Symbol === 'function'\n                      ? Symbol.iterator\n                      : '@@iterator'\n                  );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(41);\n              b = c(5);\n              var e = c(8).String;\n              a.exports =\n                !!Object.getOwnPropertySymbols &&\n                !b(function () {\n                  var a = Symbol('symbol detection');\n                  return (\n                    !e(a) ||\n                    !(Object(a) instanceof Symbol) ||\n                    (!(typeof Symbol === 'function' ? Symbol.sham : '@@sham') &&\n                      d &&\n                      d < 41)\n                  );\n                });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = String;\n              a.exports = function (a) {\n                try {\n                  return d(a);\n                } catch (a) {\n                  return 'Object';\n                }\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(31);\n              var d = c(43);\n              (a.exports = function (a, b) {\n                return d[a] || (d[a] = void 0 !== b ? b : {});\n              })('versions', []).push({\n                version: '3.32.2',\n                mode: b ? 'pure' : 'global',\n                copyright: '\\xa9 2014-2023 Denis Pushkarev (zloirock.ru)',\n                license:\n                  'https://github.com/zloirock/core-js/blob/v3.32.2/LICENSE',\n                source: 'https://github.com/zloirock/core-js',\n              });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = 0,\n                e = Math.random(),\n                f = b((1).toString);\n              a.exports = function (a) {\n                return (\n                  'Symbol(' + (void 0 === a ? '' : a) + ')_' + f(++d + e, 36)\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(15);\n              var d = c(5),\n                e = c(62);\n              a.exports =\n                !b &&\n                !d(function () {\n                  return (\n                    7 !==\n                    Object.defineProperty(e('div'), 'a', {\n                      get: function () {\n                        return 7;\n                      },\n                    }).a\n                  );\n                });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(8);\n              c = c(13);\n              var d = b.document,\n                e = c(d) && c(d.createElement);\n              a.exports = function (a) {\n                return e ? d.createElement(a) : {};\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(15);\n              c = c(5);\n              a.exports =\n                b &&\n                c(function () {\n                  return (\n                    42 !==\n                    Object.defineProperty(function () {}, 'prototype', {\n                      value: 42,\n                      writable: !1,\n                    }).prototype\n                  );\n                });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(46),\n                e = Math.min;\n              a.exports = function (a) {\n                return a > 0 ? e(d(a), 9007199254740991) : 0;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(5),\n                e = c(3),\n                f = c(34),\n                g = c(30),\n                h = c(97),\n                i = function () {},\n                j = [],\n                k = g('Reflect', 'construct'),\n                l = /^\\s*(?:class|function)\\b/,\n                m = b(l.exec),\n                n = !l.exec(i),\n                o = function (a) {\n                  if (!e(a)) return !1;\n                  try {\n                    return k(i, j, a), !0;\n                  } catch (a) {\n                    return !1;\n                  }\n                };\n              c = function (a) {\n                if (!e(a)) return !1;\n                switch (f(a)) {\n                  case 'AsyncFunction':\n                  case 'GeneratorFunction':\n                  case 'AsyncGeneratorFunction':\n                    return !1;\n                }\n                try {\n                  return n || !!m(l, h(a));\n                } catch (a) {\n                  return !0;\n                }\n              };\n              (c.sham = !0),\n                (a.exports =\n                  !k ||\n                  d(function () {\n                    var a;\n                    return (\n                      o(o.call) ||\n                      !o(Object) ||\n                      !o(function () {\n                        a = !0;\n                      }) ||\n                      a\n                    );\n                  })\n                    ? c\n                    : o);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(5);\n              b = c(6);\n              var e = c(41),\n                f = b('species');\n              a.exports = function (a) {\n                return (\n                  e >= 51 ||\n                  !d(function () {\n                    var b = [];\n                    return (\n                      ((b.constructor = {})[f] = function () {\n                        return {\n                          foo: 1,\n                        };\n                      }),\n                      1 !== b[a](Boolean).foo\n                    );\n                  })\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d, e;\n              b = c(5);\n              var f = c(3),\n                g = c(13),\n                h = c(68),\n                i = c(70),\n                j = c(71),\n                k = c(6);\n              c = c(31);\n              var l = k('iterator');\n              k = !1;\n              [].keys &&\n                ('next' in (e = [].keys())\n                  ? (i = i(i(e))) !== Object.prototype && (d = i)\n                  : (k = !0)),\n                !g(d) ||\n                b(function () {\n                  var a = {};\n                  return d[l].call(a) !== a;\n                })\n                  ? (d = {})\n                  : c && (d = h(d)),\n                f(d[l]) ||\n                  j(d, l, function () {\n                    return this;\n                  }),\n                (a.exports = {\n                  IteratorPrototype: d,\n                  BUGGY_SAFARI_ITERATORS: k,\n                });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d,\n                e = c(17),\n                f = c(109),\n                h = c(69);\n              b = c(50);\n              var i = c(113),\n                j = c(62);\n              c = c(49);\n              var k = c('IE_PROTO'),\n                l = function () {},\n                m = function (a) {\n                  return '<script>' + a + '</script>';\n                },\n                n = function (a) {\n                  a.write(m('')), a.close();\n                  var b = a.parentWindow.Object;\n                  return (a = null), b;\n                },\n                o = function () {\n                  try {\n                    d = new ActiveXObject('htmlfile');\n                  } catch (a) {}\n                  var a;\n                  o =\n                    'undefined' != typeof g\n                      ? g.domain && d\n                        ? n(d)\n                        : (((a = j('iframe')).style.display = 'none'),\n                          i.appendChild(a),\n                          (a.src = String('javascript:')),\n                          (a = a.contentWindow.document).open(),\n                          a.write(m('document.F=Object')),\n                          a.close(),\n                          a.F)\n                      : n(d);\n                  for (a = h.length; a--; ) delete o.prototype[h[a]];\n                  return o();\n                };\n              (b[k] = !0),\n                (a.exports =\n                  Object.create ||\n                  function (a, b) {\n                    var c;\n                    return (\n                      null !== a\n                        ? ((l.prototype = e(a)),\n                          (c = new l()),\n                          (l.prototype = null),\n                          (c[k] = a))\n                        : (c = o()),\n                      void 0 === b ? c : f.f(c, b)\n                    );\n                  });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = [\n                'constructor',\n                'hasOwnProperty',\n                'isPrototypeOf',\n                'propertyIsEnumerable',\n                'toLocaleString',\n                'toString',\n                'valueOf',\n              ];\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(14),\n                e = c(3),\n                f = c(22);\n              b = c(49);\n              c = c(114);\n              var g = b('IE_PROTO'),\n                h = Object,\n                i = h.prototype;\n              a.exports = c\n                ? h.getPrototypeOf\n                : function (a) {\n                    a = f(a);\n                    if (d(a, g)) return a[g];\n                    var b = a.constructor;\n                    return e(b) && a instanceof b\n                      ? b.prototype\n                      : a instanceof h\n                      ? i\n                      : null;\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(23);\n              a.exports = function (a, b, c, e) {\n                return e && e.enumerable ? (a[b] = c) : d(a, b, c), a;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(47),\n                e = c(32).f,\n                f = c(23),\n                g = c(14),\n                h = c(115),\n                i = c(6)('toStringTag');\n              a.exports = function (a, b, c, j) {\n                if (a) {\n                  c = c ? a : a.prototype;\n                  g(c, i) ||\n                    e(c, i, {\n                      configurable: !0,\n                      value: b,\n                    }),\n                    j && !d && f(c, 'toString', h);\n                }\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(34),\n                e = c(42),\n                f = c(38),\n                g = c(35),\n                h = c(6)('iterator');\n              a.exports = function (a) {\n                if (!f(a)) return e(a, h) || e(a, '@@iterator') || g[d(a)];\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function () {};\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(5);\n              a.exports = function (a, b) {\n                var c = [][a];\n                return (\n                  !!c &&\n                  d(function () {\n                    c.call(\n                      null,\n                      b ||\n                        function () {\n                          return 1;\n                        },\n                      1\n                    );\n                  })\n                );\n              };\n            },\n            function (a, b, c) {\n              a.exports = c(163);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(78);\n              a.exports = function (a) {\n                return d(a) && 3 == a.nodeType;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a) {\n                var b = (a ? a.ownerDocument || a : g).defaultView || f;\n                return !(\n                  !a ||\n                  !('function' == typeof b.Node\n                    ? a instanceof b.Node\n                    : 'object' ==\n                        (typeof a === 'undefined' ? 'undefined' : h(a)) &&\n                      'number' == typeof a.nodeType &&\n                      'string' == typeof a.nodeName)\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(80);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(81);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(82);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(83);\n              b = c(18);\n              a.exports = b('Array', 'map');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              var d = c(45).map;\n              a(\n                {\n                  target: 'Array',\n                  proto: !0,\n                  forced: !c(66)('map'),\n                },\n                {\n                  map: function (a) {\n                    return d(\n                      this,\n                      a,\n                      arguments.length > 1 ? arguments[1] : void 0\n                    );\n                  },\n                }\n              );\n            },\n            function (a, b) {\n              b = (function () {\n                return this;\n              })();\n              try {\n                b = b || new Function('return this')();\n              } catch (a) {\n                'object' == (typeof f === 'undefined' ? 'undefined' : h(f)) &&\n                  (b = f);\n              }\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(25);\n              c = Function.prototype;\n              var d = c.apply,\n                e = c.call;\n              a.exports =\n                ('object' ==\n                  (typeof Reflect === 'undefined' ? 'undefined' : h(Reflect)) &&\n                  Reflect.apply) ||\n                (b\n                  ? e.bind(d)\n                  : function () {\n                      return e.apply(d, arguments);\n                    });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = {}.propertyIsEnumerable;\n              var d = Object.getOwnPropertyDescriptor;\n              c =\n                d &&\n                !a.call(\n                  {\n                    1: 2,\n                  },\n                  1\n                );\n              b.f = c\n                ? function (a) {\n                    a = d(this, a);\n                    return !!a && a.enumerable;\n                  }\n                : a;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(16),\n                e = c(13),\n                f = c(55),\n                g = c(42),\n                h = c(90);\n              b = c(6);\n              var i = TypeError,\n                j = b('toPrimitive');\n              a.exports = function (a, b) {\n                if (!e(a) || f(a)) return a;\n                var c = g(a, j);\n                if (c) {\n                  if (\n                    (void 0 === b && (b = 'default'),\n                    (c = d(c, a, b)),\n                    !e(c) || f(c))\n                  )\n                    return c;\n                  throw i(\"Can't convert object to primitive value\");\n                }\n                return void 0 === b && (b = 'number'), h(a, b);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              a.exports = b({}.isPrototypeOf);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports =\n                ('undefined' != typeof navigator &&\n                  String(navigator.userAgent)) ||\n                '';\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(16),\n                e = c(3),\n                f = c(13),\n                g = TypeError;\n              a.exports = function (a, b) {\n                var c, h;\n                if ('string' === b && e((c = a.toString)) && !f((h = d(c, a))))\n                  return h;\n                if (e((c = a.valueOf)) && !f((h = d(c, a)))) return h;\n                if ('string' !== b && e((c = a.toString)) && !f((h = d(c, a))))\n                  return h;\n                throw g(\"Can't convert object to primitive value\");\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(8),\n                e = Object.defineProperty;\n              a.exports = function (a, b) {\n                try {\n                  e(d, a, {\n                    value: b,\n                    configurable: !0,\n                    writable: !0,\n                  });\n                } catch (c) {\n                  d[a] = b;\n                }\n                return b;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(5),\n                e = c(3),\n                f = /#|\\.prototype\\./;\n              b = function (a, b) {\n                a = h[g(a)];\n                return a === j || (a !== i && (e(b) ? d(b) : !!b));\n              };\n              var g = (b.normalize = function (a) {\n                  return String(a).replace(f, '.').toLowerCase();\n                }),\n                h = (b.data = {}),\n                i = (b.NATIVE = 'N'),\n                j = (b.POLYFILL = 'P');\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = Math.ceil,\n                e = Math.floor;\n              a.exports =\n                Math.trunc ||\n                function (a) {\n                  a = +a;\n                  return (a > 0 ? e : d)(a);\n                };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(95);\n              a.exports = function (a, b) {\n                return new (d(a))(0 === b ? 0 : b);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(96),\n                e = c(65),\n                f = c(13),\n                g = c(6)('species'),\n                h = Array;\n              a.exports = function (a) {\n                var b;\n                return (\n                  d(a) &&\n                    ((b = a.constructor),\n                    ((e(b) && (b === h || d(b.prototype))) ||\n                      (f(b) && null === (b = b[g]))) &&\n                      (b = void 0)),\n                  void 0 === b ? h : b\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(20);\n              a.exports =\n                Array.isArray ||\n                function (a) {\n                  return 'Array' === d(a);\n                };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(3);\n              c = c(43);\n              var e = b(Function.toString);\n              d(c.inspectSource) ||\n                (c.inspectSource = function (a) {\n                  return e(a);\n                }),\n                (a.exports = c.inspectSource);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(99);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(100);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(101);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(102), c(120);\n              b = c(40);\n              a.exports = b.Array.from;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(103).charAt,\n                e = c(48);\n              a = c(104);\n              b = c(106);\n              var f = c(119),\n                g = a.set,\n                h = a.getterFor('String Iterator');\n              b(\n                String,\n                'String',\n                function (a) {\n                  g(this, {\n                    type: 'String Iterator',\n                    string: e(a),\n                    index: 0,\n                  });\n                },\n                function () {\n                  var a = h(this),\n                    b = a.string,\n                    c = a.index;\n                  return c >= b.length\n                    ? f(void 0, !0)\n                    : ((b = d(b, c)), (a.index += b.length), f(b, !1));\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(46),\n                e = c(48),\n                f = c(29),\n                g = b(''.charAt),\n                h = b(''.charCodeAt),\n                i = b(''.slice);\n              c = function (a) {\n                return function (b, c) {\n                  var j, k;\n                  b = e(f(b));\n                  c = d(c);\n                  var l = b.length;\n                  return c < 0 || c >= l\n                    ? a\n                      ? ''\n                      : void 0\n                    : (j = h(b, c)) < 55296 ||\n                      j > 56319 ||\n                      c + 1 === l ||\n                      (k = h(b, c + 1)) < 56320 ||\n                      k > 57343\n                    ? a\n                      ? g(b, c)\n                      : j\n                    : a\n                    ? i(b, c, c + 2)\n                    : k - 56320 + ((j - 55296) << 10) + 65536;\n                };\n              };\n              a.exports = {\n                codeAt: c(!1),\n                charAt: c(!0),\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d, e, f;\n              b = c(105);\n              var g = c(8),\n                h = c(13),\n                i = c(23),\n                j = c(14),\n                k = c(43),\n                l = c(49);\n              c = c(50);\n              var m = g.TypeError;\n              g = g.WeakMap;\n              if (b || k.state) {\n                var n = k.state || (k.state = new g());\n                (n.get = n.get),\n                  (n.has = n.has),\n                  (n.set = n.set),\n                  (d = function (a, b) {\n                    if (n.has(a)) throw m('Object already initialized');\n                    return (b.facade = a), n.set(a, b), b;\n                  }),\n                  (e = function (a) {\n                    return n.get(a) || {};\n                  }),\n                  (f = function (a) {\n                    return n.has(a);\n                  });\n              } else {\n                var o = l('state');\n                (c[o] = !0),\n                  (d = function (a, b) {\n                    if (j(a, o)) throw m('Object already initialized');\n                    return (b.facade = a), i(a, o, b), b;\n                  }),\n                  (e = function (a) {\n                    return j(a, o) ? a[o] : {};\n                  }),\n                  (f = function (a) {\n                    return j(a, o);\n                  });\n              }\n              a.exports = {\n                set: d,\n                get: e,\n                has: f,\n                enforce: function (a) {\n                  return f(a) ? e(a) : d(a, {});\n                },\n                getterFor: function (a) {\n                  return function (b) {\n                    var c;\n                    if (!h(b) || (c = e(b)).type !== a)\n                      throw m('Incompatible receiver, ' + a + ' required');\n                    return c;\n                  };\n                },\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(8);\n              c = c(3);\n              b = b.WeakMap;\n              a.exports = c(b) && /native code/.test(String(b));\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(10),\n                e = c(16),\n                f = c(31);\n              b = c(107);\n              var g = c(3),\n                h = c(108),\n                i = c(70),\n                j = c(116),\n                k = c(72),\n                l = c(23),\n                m = c(71),\n                n = c(6),\n                o = c(35);\n              c = c(67);\n              var p = b.PROPER,\n                q = b.CONFIGURABLE,\n                r = c.IteratorPrototype,\n                s = c.BUGGY_SAFARI_ITERATORS,\n                t = n('iterator'),\n                u = function () {\n                  return this;\n                };\n              a.exports = function (a, b, c, v, n, w, x) {\n                h(c, b, v);\n                var y, z;\n                v = function (a) {\n                  if (a === n && E) return E;\n                  if (!s && a && a in C) return C[a];\n                  switch (a) {\n                    case 'keys':\n                    case 'values':\n                    case 'entries':\n                      return function () {\n                        return new c(this, a);\n                      };\n                  }\n                  return function () {\n                    return new c(this);\n                  };\n                };\n                var A = b + ' Iterator',\n                  B = !1,\n                  C = a.prototype,\n                  D = C[t] || C['@@iterator'] || (n && C[n]),\n                  E = (!s && D) || v(n),\n                  F = ('Array' === b && C.entries) || D;\n                if (\n                  (F &&\n                    (y = i(F.call(new a()))) !== Object.prototype &&\n                    y.next &&\n                    (f || i(y) === r || (j ? j(y, r) : g(y[t]) || m(y, t, u)),\n                    k(y, A, !0, !0),\n                    f && (o[A] = u)),\n                  p &&\n                    'values' === n &&\n                    D &&\n                    'values' !== D.name &&\n                    (!f && q\n                      ? l(C, 'name', 'values')\n                      : ((B = !0),\n                        (E = function () {\n                          return e(D, this);\n                        }))),\n                  n)\n                )\n                  if (\n                    ((z = {\n                      values: v('values'),\n                      keys: w ? E : v('keys'),\n                      entries: v('entries'),\n                    }),\n                    x)\n                  )\n                    for (F in z) (s || B || !(F in C)) && m(C, F, z[F]);\n                  else\n                    d(\n                      {\n                        target: b,\n                        proto: !0,\n                        forced: s || B,\n                      },\n                      z\n                    );\n                return (\n                  (f && !x) ||\n                    C[t] === E ||\n                    m(C, t, E, {\n                      name: n,\n                    }),\n                  (o[b] = E),\n                  z\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(15);\n              c = c(14);\n              var d = Function.prototype,\n                e = b && Object.getOwnPropertyDescriptor;\n              c = c(d, 'name');\n              var f = c && 'something' === function () {}.name;\n              b = c && (!b || (b && e(d, 'name').configurable));\n              a.exports = {\n                EXISTS: c,\n                PROPER: f,\n                CONFIGURABLE: b,\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(67).IteratorPrototype,\n                e = c(68),\n                f = c(27),\n                g = c(72),\n                h = c(35),\n                i = function () {\n                  return this;\n                };\n              a.exports = function (a, b, c, j) {\n                b = b + ' Iterator';\n                return (\n                  (a.prototype = e(d, {\n                    next: f(+!j, c),\n                  })),\n                  g(a, b, !1, !0),\n                  (h[b] = i),\n                  a\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(15);\n              var d = c(63),\n                e = c(32),\n                f = c(17),\n                g = c(28),\n                h = c(110);\n              b.f =\n                a && !d\n                  ? Object.defineProperties\n                  : function (a, b) {\n                      f(a);\n                      for (\n                        var c, d = g(b), b = h(b), i = b.length, j = 0;\n                        i > j;\n\n                      )\n                        e.f(a, (c = b[j++]), d[c]);\n                      return a;\n                    };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(111),\n                e = c(69);\n              a.exports =\n                Object.keys ||\n                function (a) {\n                  return d(a, e);\n                };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(7);\n              var d = c(14),\n                e = c(28),\n                f = c(51).indexOf,\n                g = c(50),\n                h = b([].push);\n              a.exports = function (a, b) {\n                var c;\n                a = e(a);\n                var i = 0,\n                  j = [];\n                for (c in a) !d(g, c) && d(a, c) && h(j, c);\n                for (; b.length > i; )\n                  d(a, (c = b[i++])) && (~f(j, c) || h(j, c));\n                return j;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(46),\n                e = Math.max,\n                f = Math.min;\n              a.exports = function (a, b) {\n                a = d(a);\n                return a < 0 ? e(a + b, 0) : f(a, b);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(30);\n              a.exports = b('document', 'documentElement');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(5);\n              a.exports = !b(function () {\n                function a() {}\n                return (\n                  (a.prototype.constructor = null),\n                  Object.getPrototypeOf(new a()) !== a.prototype\n                );\n              });\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(47);\n              var d = c(34);\n              a.exports = b\n                ? {}.toString\n                : function () {\n                    return '[object ' + d(this) + ']';\n                  };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(117),\n                e = c(17),\n                f = c(118);\n              a.exports =\n                Object.setPrototypeOf ||\n                ('__proto__' in {}\n                  ? (function () {\n                      var a,\n                        b = !1,\n                        c = {};\n                      try {\n                        (a = d(Object.prototype, '__proto__', 'set'))(c, []),\n                          (b = c instanceof Array);\n                      } catch (a) {}\n                      return function (c, d) {\n                        return e(c), f(d), b ? a(c, d) : (c.__proto__ = d), c;\n                      };\n                    })()\n                  : void 0);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(7),\n                e = c(21);\n              a.exports = function (a, b, c) {\n                try {\n                  return d(e(Object.getOwnPropertyDescriptor(a, b)[c]));\n                } catch (a) {}\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(3),\n                e = String,\n                f = TypeError;\n              a.exports = function (a) {\n                if (\n                  'object' == (typeof a === 'undefined' ? 'undefined' : h(a)) ||\n                  d(a)\n                )\n                  return a;\n                throw f(\"Can't set \" + e(a) + ' as a prototype');\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a.exports = function (a, b) {\n                return {\n                  value: a,\n                  done: b,\n                };\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              b = c(121);\n              a(\n                {\n                  target: 'Array',\n                  stat: !0,\n                  forced: !c(127)(function (a) {\n                    Array.from(a);\n                  }),\n                },\n                {\n                  from: b,\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(44),\n                e = c(16),\n                f = c(22),\n                g = c(122),\n                h = c(124),\n                i = c(65),\n                j = c(33),\n                k = c(125),\n                l = c(126),\n                m = c(73),\n                n = Array;\n              a.exports = function (a) {\n                var b = f(a),\n                  c = i(this),\n                  o = arguments.length,\n                  p = o > 1 ? arguments[1] : void 0,\n                  q = void 0 !== p;\n                q && (p = d(p, o > 2 ? arguments[2] : void 0));\n                var r,\n                  s,\n                  t,\n                  u,\n                  v,\n                  w,\n                  x = m(b),\n                  y = 0;\n                if (!x || (this === n && h(x)))\n                  for (r = j(b), s = c ? new this(r) : n(r); r > y; y++)\n                    (w = q ? p(b[y], y) : b[y]), k(s, y, w);\n                else\n                  for (\n                    v = (u = l(b, x)).next, s = c ? new this() : [];\n                    !(t = e(v, u)).done;\n                    y++\n                  )\n                    (w = q ? g(u, p, [t.value, y], !0) : t.value), k(s, y, w);\n                return (s.length = y), s;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(17),\n                e = c(123);\n              a.exports = function (a, b, c, f) {\n                try {\n                  return f ? b(d(c)[0], c[1]) : b(c);\n                } catch (b) {\n                  e(a, 'throw', b);\n                }\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(16),\n                e = c(17),\n                f = c(42);\n              a.exports = function (a, b, c) {\n                var g, h;\n                e(a);\n                try {\n                  if (!(g = f(a, 'return'))) {\n                    if ('throw' === b) throw c;\n                    return c;\n                  }\n                  g = d(g, a);\n                } catch (a) {\n                  (h = !0), (g = a);\n                }\n                if ('throw' === b) throw c;\n                if (h) throw g;\n                return e(g), c;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(6);\n              var d = c(35),\n                e = b('iterator'),\n                f = Array.prototype;\n              a.exports = function (a) {\n                return void 0 !== a && (d.Array === a || f[e] === a);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(39),\n                e = c(32),\n                f = c(27);\n              a.exports = function (a, b, c) {\n                b = d(b);\n                b in a ? e.f(a, b, f(0, c)) : (a[b] = c);\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(16),\n                e = c(21),\n                f = c(17),\n                g = c(58),\n                h = c(73),\n                i = TypeError;\n              a.exports = function (a, b) {\n                var c = arguments.length < 2 ? h(a) : b;\n                if (e(c)) return f(d(c, a));\n                throw i(g(a) + ' is not iterable');\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(6)('iterator'),\n                e = !1;\n              try {\n                var f = 0;\n                b = {\n                  next: function () {\n                    return {\n                      done: !!f++,\n                    };\n                  },\n                  return: function () {\n                    e = !0;\n                  },\n                };\n                (b[d] = function () {\n                  return this;\n                }),\n                  Array.from(b, function () {\n                    throw 2;\n                  });\n              } catch (a) {}\n              a.exports = function (a, b) {\n                try {\n                  if (!b && !e) return !1;\n                } catch (a) {\n                  return !1;\n                }\n                b = !1;\n                try {\n                  var c = {};\n                  (c[d] = function () {\n                    return {\n                      next: function () {\n                        return {\n                          done: (b = !0),\n                        };\n                      },\n                    };\n                  }),\n                    a(c);\n                } catch (a) {}\n                return b;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(129);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(130);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(131);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(132);\n              b = c(18);\n              a.exports = b('Array', 'includes');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              var d = c(51).includes;\n              b = c(5);\n              c = c(74);\n              a(\n                {\n                  target: 'Array',\n                  proto: !0,\n                  forced: b(function () {\n                    return !Array(1).includes();\n                  }),\n                },\n                {\n                  includes: function (a) {\n                    return d(\n                      this,\n                      a,\n                      arguments.length > 1 ? arguments[1] : void 0\n                    );\n                  },\n                }\n              ),\n                c('includes');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(134);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(135);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(136);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(137);\n              b = c(18);\n              a.exports = b('Array', 'filter');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              var d = c(45).filter;\n              a(\n                {\n                  target: 'Array',\n                  proto: !0,\n                  forced: !c(66)('filter'),\n                },\n                {\n                  filter: function (a) {\n                    return d(\n                      this,\n                      a,\n                      arguments.length > 1 ? arguments[1] : void 0\n                    );\n                  },\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(139);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(140);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(141);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(142);\n              b = c(18);\n              a.exports = b('Array', 'reduce');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              var d = c(143).left;\n              b = c(75);\n              var e = c(41);\n              a(\n                {\n                  target: 'Array',\n                  proto: !0,\n                  forced: (!c(144) && e > 79 && e < 83) || !b('reduce'),\n                },\n                {\n                  reduce: function (a) {\n                    var b = arguments.length;\n                    return d(this, a, b, b > 1 ? arguments[1] : void 0);\n                  },\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(21),\n                e = c(22),\n                f = c(37),\n                g = c(33),\n                h = TypeError;\n              b = function (a) {\n                return function (b, c, i, j) {\n                  d(c);\n                  b = e(b);\n                  var k = f(b),\n                    l = g(b),\n                    m = a ? l - 1 : 0,\n                    n = a ? -1 : 1;\n                  if (i < 2)\n                    for (;;) {\n                      if (m in k) {\n                        (j = k[m]), (m += n);\n                        break;\n                      }\n                      if (((m += n), a ? m < 0 : l <= m))\n                        throw h('Reduce of empty array with no initial value');\n                    }\n                  for (; a ? m >= 0 : l > m; m += n)\n                    m in k && (j = c(j, k[m], m, b));\n                  return j;\n                };\n              };\n              a.exports = {\n                left: b(!1),\n                right: b(!0),\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(8);\n              c = c(20);\n              a.exports = 'process' === c(b.process);\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(146);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(147);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(148);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(149);\n              b = c(18);\n              a.exports = b('String', 'startsWith');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              b = c(26);\n              var d = c(54).f,\n                e = c(64),\n                f = c(48),\n                g = c(150),\n                h = c(29),\n                i = c(152);\n              c = c(31);\n              var j = b(''.startsWith),\n                k = b(''.slice),\n                l = Math.min;\n              b = i('startsWith');\n              a(\n                {\n                  target: 'String',\n                  proto: !0,\n                  forced:\n                    !!(\n                      c ||\n                      b ||\n                      ((i = d(String.prototype, 'startsWith')),\n                      !i || i.writable)\n                    ) && !b,\n                },\n                {\n                  startsWith: function (a) {\n                    var b = f(h(this));\n                    g(a);\n                    var c = e(\n                        l(\n                          arguments.length > 1 ? arguments[1] : void 0,\n                          b.length\n                        )\n                      ),\n                      d = f(a);\n                    return j ? j(b, d, c) : k(b, c, c + d.length) === d;\n                  },\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(151),\n                e = TypeError;\n              a.exports = function (a) {\n                if (d(a))\n                  throw e(\"The method doesn't accept regular expressions\");\n                return a;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(13),\n                e = c(20),\n                f = c(6)('match');\n              a.exports = function (a) {\n                var b;\n                return (\n                  d(a) && (void 0 !== (b = a[f]) ? !!b : 'RegExp' === e(a))\n                );\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              var d = c(6)('match');\n              a.exports = function (a) {\n                var b = /./;\n                try {\n                  '/./'[a](b);\n                } catch (c) {\n                  try {\n                    return (b[d] = !1), '/./'[a](b);\n                  } catch (a) {}\n                }\n                return !1;\n              };\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(154);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(155);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(156);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(157);\n              b = c(18);\n              a.exports = b('Array', 'indexOf');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              b = c(26);\n              var d = c(51).indexOf;\n              c = c(75);\n              var e = b([].indexOf),\n                f = !!e && 1 / e([1], 1, -0) < 0;\n              a(\n                {\n                  target: 'Array',\n                  proto: !0,\n                  forced: f || !c('indexOf'),\n                },\n                {\n                  indexOf: function (a) {\n                    var b = arguments.length > 1 ? arguments[1] : void 0;\n                    return f ? e(this, a, b) || 0 : d(this, a, b);\n                  },\n                }\n              );\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(159);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(160);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              b = c(161);\n              a.exports = b;\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c(162);\n              b = c(18);\n              a.exports = b('Array', 'find');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              a = c(10);\n              var d = c(45).find;\n              b = c(74);\n              c = !0;\n              'find' in [] &&\n                Array(1).find(function () {\n                  c = !1;\n                }),\n                a(\n                  {\n                    target: 'Array',\n                    proto: !0,\n                    forced: c,\n                  },\n                  {\n                    find: function (a) {\n                      return d(\n                        this,\n                        a,\n                        arguments.length > 1 ? arguments[1] : void 0\n                      );\n                    },\n                  }\n                ),\n                b('find');\n            },\n            function (a, b, c) {\n              'use strict';\n\n              c.r(b);\n              var d = {};\n              function e(a) {\n                if (null == a) return null;\n                if (null != a.innerText && 0 !== a.innerText.length)\n                  return a.innerText;\n                var b = a.text;\n                return null != b && 'string' == typeof b && 0 !== b.length\n                  ? b\n                  : null != a.textContent && a.textContent.length > 0\n                  ? a.textContent\n                  : null;\n              }\n              c.r(d),\n                c.d(d, 'BUTTON_SELECTOR_SEPARATOR', function () {\n                  return R;\n                }),\n                c.d(d, 'BUTTON_SELECTORS', function () {\n                  return S;\n                }),\n                c.d(d, 'BUTTON_SELECTOR_FORM_BLACKLIST', function () {\n                  return Ka;\n                }),\n                c.d(d, 'EXTENDED_BUTTON_SELECTORS', function () {\n                  return La;\n                }),\n                c.d(d, 'EXPLICIT_BUTTON_SELECTORS', function () {\n                  return Ma;\n                });\n              function i(a) {\n                var b = void 0;\n                switch (a.tagName.toLowerCase()) {\n                  case 'meta':\n                    b = a.getAttribute('content');\n                    break;\n                  case 'audio':\n                  case 'embed':\n                  case 'iframe':\n                  case 'img':\n                  case 'source':\n                  case 'track':\n                  case 'video':\n                    b = a.getAttribute('src');\n                    break;\n                  case 'a':\n                  case 'area':\n                  case 'link':\n                    b = a.getAttribute('href');\n                    break;\n                  case 'object':\n                    b = a.getAttribute('data');\n                    break;\n                  case 'data':\n                  case 'meter':\n                    b = a.getAttribute('value');\n                    break;\n                  case 'time':\n                    b = a.getAttribute('datetime');\n                    break;\n                  default:\n                    b = e(a) || '';\n                }\n                return 'string' == typeof b ? b.substr(0, 500) : '';\n              }\n              var j = [\n                  'Order',\n                  'AggregateOffer',\n                  'CreativeWork',\n                  'Event',\n                  'MenuItem',\n                  'Product',\n                  'Service',\n                  'Trip',\n                  'ActionAccessSpecification',\n                  'ConsumeAction',\n                  'MediaSubscription',\n                  'Organization',\n                  'Person',\n                ],\n                k = c(11),\n                l = c.n(k);\n              k = c(1);\n              var m = c.n(k);\n              k = c(2);\n              var n = c.n(k);\n              k = c(4);\n              var o = c.n(k);\n              k = c(12);\n              var p = c.n(k);\n              k = c(0);\n              var q = c.n(k),\n                r = function (a) {\n                  for (\n                    var b = q()(j, function (a) {\n                        return '[vocab$=\"'\n                          .concat('http://schema.org/', '\"][typeof$=\"')\n                          .concat(a, '\"]');\n                      }).join(', '),\n                      c = [],\n                      b = o()(g.querySelectorAll(b)),\n                      d = [];\n                    b.length > 0;\n\n                  ) {\n                    var e = b.pop();\n                    if (!p()(c, e)) {\n                      var s = {\n                        '@context': 'http://schema.org',\n                      };\n                      d.push({\n                        htmlElement: e,\n                        jsonLD: s,\n                      });\n                      for (\n                        e = [\n                          {\n                            element: e,\n                            workingNode: s,\n                          },\n                        ];\n                        e.length;\n\n                      ) {\n                        s = e.pop();\n                        var v = s.element;\n                        s = s.workingNode;\n                        var f = m()(v.getAttribute('typeof'));\n                        s['@type'] = f;\n                        for (\n                          f = o()(v.querySelectorAll('[property]')).reverse();\n                          f.length;\n\n                        ) {\n                          var h = f.pop();\n                          if (!p()(c, h)) {\n                            c.push(h);\n                            var w = m()(h.getAttribute('property'));\n                            if (h.hasAttribute('typeof')) {\n                              var k = {};\n                              (s[w] = k),\n                                e.push({\n                                  element: v,\n                                  workingNode: s,\n                                }),\n                                e.push({\n                                  element: h,\n                                  workingNode: k,\n                                });\n                              break;\n                            }\n                            s[w] = i(h);\n                          }\n                        }\n                      }\n                    }\n                  }\n                  return n()(d, function (b) {\n                    return l()(b.htmlElement, a);\n                  });\n                };\n              function s(a) {\n                return (s =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              function t(a) {\n                return (\n                  'object' ===\n                  ('undefined' == typeof HTMLElement\n                    ? 'undefined'\n                    : s(HTMLElement))\n                    ? a instanceof HTMLElement\n                    : null != a &&\n                      'object' === s(a) &&\n                      null !== a &&\n                      1 === a.nodeType &&\n                      'string' == typeof a.nodeName\n                )\n                  ? a\n                  : null;\n              }\n              k = c(9);\n              var u = c.n(k);\n              function v(a) {\n                return (v =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              function w(a, b) {\n                var c = Object.keys(a);\n                if (Object.getOwnPropertySymbols) {\n                  var d = Object.getOwnPropertySymbols(a);\n                  b &&\n                    (d = d.filter(function (b) {\n                      return Object.getOwnPropertyDescriptor(a, b).enumerable;\n                    })),\n                    c.push.apply(c, d);\n                }\n                return c;\n              }\n              function x(a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = null != arguments[b] ? arguments[b] : {};\n                  b % 2\n                    ? w(Object(c), !0).forEach(function (b) {\n                        z(a, b, c[b]);\n                      })\n                    : Object.getOwnPropertyDescriptors\n                    ? Object.defineProperties(\n                        a,\n                        Object.getOwnPropertyDescriptors(c)\n                      )\n                    : w(Object(c)).forEach(function (b) {\n                        Object.defineProperty(\n                          a,\n                          b,\n                          Object.getOwnPropertyDescriptor(c, b)\n                        );\n                      });\n                }\n                return a;\n              }\n              function y(a, b) {\n                for (var c = 0; c < b.length; c++) {\n                  var d = b[c];\n                  (d.enumerable = d.enumerable || !1),\n                    (d.configurable = !0),\n                    'value' in d && (d.writable = !0),\n                    Object.defineProperty(a, A(d.key), d);\n                }\n              }\n              function z(a, b, c) {\n                return (\n                  (b = A(b)) in a\n                    ? Object.defineProperty(a, b, {\n                        value: c,\n                        enumerable: !0,\n                        configurable: !0,\n                        writable: !0,\n                      })\n                    : (a[b] = c),\n                  a\n                );\n              }\n              function A(a) {\n                a = (function (a, b) {\n                  if ('object' !== v(a) || null === a) return a;\n                  var c =\n                    a[\n                      typeof Symbol === 'function'\n                        ? Symbol.toPrimitive\n                        : '@@toPrimitive'\n                    ];\n                  if (void 0 !== c) {\n                    c = c.call(a, b || 'default');\n                    if ('object' !== v(c)) return c;\n                    throw new TypeError(\n                      '@@toPrimitive must return a primitive value.'\n                    );\n                  }\n                  return ('string' === b ? String : Number)(a);\n                })(a, 'string');\n                return 'symbol' === v(a) ? a : String(a);\n              }\n              var B = (function () {\n                  function a(b) {\n                    !(function (a, b) {\n                      if (!(a instanceof b))\n                        throw new TypeError(\n                          'Cannot call a class as a function'\n                        );\n                    })(this, a),\n                      z(this, '_anchorElement', void 0),\n                      z(this, '_parsedQuery', void 0),\n                      (this._anchorElement = g.createElement('a')),\n                      (this._anchorElement.href = b);\n                  }\n                  var b, c, d;\n                  return (\n                    (b = a),\n                    (c = [\n                      {\n                        key: 'hash',\n                        get: function () {\n                          return this._anchorElement.hash;\n                        },\n                      },\n                      {\n                        key: 'host',\n                        get: function () {\n                          return this._anchorElement.host;\n                        },\n                      },\n                      {\n                        key: 'hostname',\n                        get: function () {\n                          return this._anchorElement.hostname;\n                        },\n                      },\n                      {\n                        key: 'pathname',\n                        get: function () {\n                          return this._anchorElement.pathname.replace(\n                            /(^\\/?)/,\n                            '/'\n                          );\n                        },\n                      },\n                      {\n                        key: 'port',\n                        get: function () {\n                          return this._anchorElement.port;\n                        },\n                      },\n                      {\n                        key: 'protocol',\n                        get: function () {\n                          return this._anchorElement.protocol;\n                        },\n                      },\n                      {\n                        key: 'searchParams',\n                        get: function () {\n                          var a = this;\n                          return {\n                            get: function (b) {\n                              if (null != a._parsedQuery)\n                                return a._parsedQuery[b] || null;\n                              var c = a._anchorElement.search;\n                              if ('' === c || null == c)\n                                return (a._parsedQuery = {}), null;\n                              c = '?' === c[0] ? c.substring(1) : c;\n                              return (\n                                (a._parsedQuery = u()(\n                                  c.split('&'),\n                                  function (a, b) {\n                                    b = b.split('=');\n                                    return null == b || 2 !== b.length\n                                      ? a\n                                      : x(\n                                          x({}, a),\n                                          {},\n                                          z(\n                                            {},\n                                            decodeURIComponent(b[0]),\n                                            decodeURIComponent(b[1])\n                                          )\n                                        );\n                                  },\n                                  {}\n                                )),\n                                a._parsedQuery[b] || null\n                              );\n                            },\n                          };\n                        },\n                      },\n                      {\n                        key: 'toString',\n                        value: function () {\n                          return this._anchorElement.href;\n                        },\n                      },\n                      {\n                        key: 'toJSON',\n                        value: function () {\n                          return this._anchorElement.href;\n                        },\n                      },\n                    ]) && y(b.prototype, c),\n                    d && y(b, d),\n                    Object.defineProperty(b, 'prototype', {\n                      writable: !1,\n                    }),\n                    a\n                  );\n                })(),\n                C = /^\\s*:scope/gi;\n              k = function a(b, c) {\n                if ('>' === c[c.length - 1]) return [];\n                var d = '>' === c[0];\n                if ((a.CAN_USE_SCOPE || !c.match(C)) && !d)\n                  return b.querySelectorAll(c);\n                var e = c;\n                d && (e = ':scope '.concat(c));\n                d = !1;\n                b.id ||\n                  ((b.id = '__fb_scoped_query_selector_' + Date.now()),\n                  (d = !0));\n                c = b.querySelectorAll(e.replace(C, '#' + b.id));\n                return d && (b.id = ''), c;\n              };\n              k.CAN_USE_SCOPE = !0;\n              var D = g.createElement('div');\n              try {\n                D.querySelectorAll(':scope *');\n              } catch (a) {\n                k.CAN_USE_SCOPE = !1;\n              }\n              var E = k;\n              D = c(36);\n              var F = c.n(D);\n              k = c(19);\n              var G = c.n(k);\n              D = (c(52), c(24));\n              var H = c.n(D);\n              function I(a) {\n                return (\n                  (function (a) {\n                    if (Array.isArray(a)) return L(a);\n                  })(a) ||\n                  (function (a) {\n                    if (\n                      ('undefined' != typeof Symbol &&\n                        null !=\n                          a[\n                            typeof Symbol === 'function'\n                              ? Symbol.iterator\n                              : '@@iterator'\n                          ]) ||\n                      null != a['@@iterator']\n                    )\n                      return Array.from(a);\n                  })(a) ||\n                  K(a) ||\n                  (function () {\n                    throw new TypeError(\n                      'Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'\n                    );\n                  })()\n                );\n              }\n              function J(a, b) {\n                return (\n                  (function (a) {\n                    if (Array.isArray(a)) return a;\n                  })(a) ||\n                  (function (a, b) {\n                    var c =\n                      null == a\n                        ? null\n                        : ('undefined' != typeof Symbol &&\n                            a[\n                              typeof Symbol === 'function'\n                                ? Symbol.iterator\n                                : '@@iterator'\n                            ]) ||\n                          a['@@iterator'];\n                    if (null != c) {\n                      var d,\n                        e,\n                        f = [],\n                        g = !0,\n                        h = !1;\n                      try {\n                        if (((a = (c = c.call(a)).next), 0 === b)) {\n                          if (Object(c) !== c) return;\n                          g = !1;\n                        } else\n                          for (\n                            ;\n                            !(g = (d = a.call(c)).done) &&\n                            (f.push(d.value), f.length !== b);\n                            g = !0\n                          );\n                      } catch (a) {\n                        (h = !0), (e = a);\n                      } finally {\n                        try {\n                          if (\n                            !g &&\n                            null != c['return'] &&\n                            ((d = c['return']()), Object(d) !== d)\n                          )\n                            return;\n                        } finally {\n                          if (h) throw e;\n                        }\n                      }\n                      return f;\n                    }\n                  })(a, b) ||\n                  K(a, b) ||\n                  (function () {\n                    throw new TypeError(\n                      'Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'\n                    );\n                  })()\n                );\n              }\n              function K(a, b) {\n                if (a) {\n                  if ('string' == typeof a) return L(a, b);\n                  var c = Object.prototype.toString.call(a).slice(8, -1);\n                  return (\n                    'Object' === c && a.constructor && (c = a.constructor.name),\n                    'Map' === c || 'Set' === c\n                      ? Array.from(a)\n                      : 'Arguments' === c ||\n                        /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c)\n                      ? L(a, b)\n                      : void 0\n                  );\n                }\n              }\n              function L(a, b) {\n                (null == b || b > a.length) && (b = a.length);\n                for (var c = 0, d = new Array(b); c < b; c++) d[c] = a[c];\n                return d;\n              }\n              function aa(a, b) {\n                return ba(\n                  a,\n                  n()(\n                    q()(\n                      b.split(/((?:closest|children)\\([^)]+\\))/),\n                      function (a) {\n                        return a.trim();\n                      }\n                    ),\n                    Boolean\n                  )\n                );\n              }\n              function ba(a, b) {\n                var c = function (a, b) {\n                  return b.substring(a.length, b.length - 1).trim();\n                };\n                b = q()(b, function (a) {\n                  return H()(a, 'closest(')\n                    ? {\n                        selector: c('closest(', a),\n                        type: 'closest',\n                      }\n                    : H()(a, 'children(')\n                    ? {\n                        selector: c('children(', a),\n                        type: 'children',\n                      }\n                    : {\n                        selector: a,\n                        type: 'standard',\n                      };\n                });\n                b = u()(\n                  b,\n                  function (a, b) {\n                    if ('standard' !== b.type) return [].concat(I(a), [b]);\n                    var c = a[a.length - 1];\n                    return c && 'standard' === c.type\n                      ? ((c.selector += ' ' + b.selector), a)\n                      : [].concat(I(a), [b]);\n                  },\n                  []\n                );\n                return u()(\n                  b,\n                  function (a, b) {\n                    return n()(\n                      F()(\n                        q()(a, function (a) {\n                          return ca(a, b);\n                        })\n                      ),\n                      Boolean\n                    );\n                  },\n                  [a]\n                );\n              }\n              var ca = function (a, b) {\n                var c = b.selector;\n                switch (b.type) {\n                  case 'children':\n                    if (null == a) return [];\n                    b = J(c.split(','), 2);\n                    var d = b[0],\n                      e = b[1];\n                    return [\n                      o()(\n                        n()(o()(a.childNodes), function (a) {\n                          return null != t(a) && a.matches(e);\n                        })\n                      )[parseInt(d, 0)],\n                    ];\n                  case 'closest':\n                    return a.parentNode ? [a.parentNode.closest(c)] : [];\n                  default:\n                    return o()(E(a, c));\n                }\n              };\n              if (\n                (Element.prototype.matches ||\n                  (Element.prototype.matches =\n                    Element.prototype.msMatchesSelector ||\n                    Element.prototype.webkitMatchesSelector),\n                !Element.prototype.closest)\n              ) {\n                var da = g.documentElement;\n                Element.prototype.closest = function (a) {\n                  var b = this;\n                  if (!da.contains(b)) return null;\n                  do {\n                    if (b.matches(a)) return b;\n                    b = b.parentElement || b.parentNode;\n                  } while (null !== b && 1 === b.nodeType);\n                  return null;\n                };\n              }\n              var ea = [\n                'og',\n                'product',\n                'music',\n                'video',\n                'article',\n                'book',\n                'profile',\n                'website',\n                'twitter',\n              ];\n              function M(a) {\n                return (M =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              function fa(a, b) {\n                var c = Object.keys(a);\n                if (Object.getOwnPropertySymbols) {\n                  var d = Object.getOwnPropertySymbols(a);\n                  b &&\n                    (d = d.filter(function (b) {\n                      return Object.getOwnPropertyDescriptor(a, b).enumerable;\n                    })),\n                    c.push.apply(c, d);\n                }\n                return c;\n              }\n              function ga(a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = null != arguments[b] ? arguments[b] : {};\n                  b % 2\n                    ? fa(Object(c), !0).forEach(function (b) {\n                        ha(a, b, c[b]);\n                      })\n                    : Object.getOwnPropertyDescriptors\n                    ? Object.defineProperties(\n                        a,\n                        Object.getOwnPropertyDescriptors(c)\n                      )\n                    : fa(Object(c)).forEach(function (b) {\n                        Object.defineProperty(\n                          a,\n                          b,\n                          Object.getOwnPropertyDescriptor(c, b)\n                        );\n                      });\n                }\n                return a;\n              }\n              function ha(a, b, c) {\n                return (\n                  (b = (function (a) {\n                    a = (function (a, b) {\n                      if ('object' !== M(a) || null === a) return a;\n                      var c =\n                        a[\n                          typeof Symbol === 'function'\n                            ? Symbol.toPrimitive\n                            : '@@toPrimitive'\n                        ];\n                      if (void 0 !== c) {\n                        c = c.call(a, b || 'default');\n                        if ('object' !== M(c)) return c;\n                        throw new TypeError(\n                          '@@toPrimitive must return a primitive value.'\n                        );\n                      }\n                      return ('string' === b ? String : Number)(a);\n                    })(a, 'string');\n                    return 'symbol' === M(a) ? a : String(a);\n                  })(b)) in a\n                    ? Object.defineProperty(a, b, {\n                        value: c,\n                        enumerable: !0,\n                        configurable: !0,\n                        writable: !0,\n                      })\n                    : (a[b] = c),\n                  a\n                );\n              }\n              var ia = function () {\n                  var a = u()(\n                    n()(\n                      q()(\n                        o()(g.querySelectorAll('meta[property]')),\n                        function (a) {\n                          var b = a.getAttribute('property');\n                          a = a.getAttribute('content');\n                          return 'string' == typeof b &&\n                            -1 !== b.indexOf(':') &&\n                            'string' == typeof a &&\n                            p()(ea, b.split(':')[0])\n                            ? {\n                                key: b,\n                                value: a.substr(0, 500),\n                              }\n                            : null;\n                        }\n                      ),\n                      Boolean\n                    ),\n                    function (a, b) {\n                      return ga(\n                        ga({}, a),\n                        {},\n                        ha({}, b.key, a[b.key] || b.value)\n                      );\n                    },\n                    {}\n                  );\n                  return 'product.item' !== a['og:type']\n                    ? null\n                    : {\n                        '@context': 'http://schema.org',\n                        '@type': 'Product',\n                        offers: {\n                          price: a['product:price:amount'],\n                          priceCurrency: a['product:price:currency'],\n                        },\n                        productID: a['product:retailer_item_id'],\n                      };\n                },\n                ja = 'PATH',\n                ka = 'QUERY_STRING';\n              function la(a) {\n                return (\n                  (function (a) {\n                    if (Array.isArray(a)) return na(a);\n                  })(a) ||\n                  (function (a) {\n                    if (\n                      ('undefined' != typeof Symbol &&\n                        null !=\n                          a[\n                            typeof Symbol === 'function'\n                              ? Symbol.iterator\n                              : '@@iterator'\n                          ]) ||\n                      null != a['@@iterator']\n                    )\n                      return Array.from(a);\n                  })(a) ||\n                  ma(a) ||\n                  (function () {\n                    throw new TypeError(\n                      'Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'\n                    );\n                  })()\n                );\n              }\n              function ma(a, b) {\n                if (a) {\n                  if ('string' == typeof a) return na(a, b);\n                  var c = Object.prototype.toString.call(a).slice(8, -1);\n                  return (\n                    'Object' === c && a.constructor && (c = a.constructor.name),\n                    'Map' === c || 'Set' === c\n                      ? Array.from(a)\n                      : 'Arguments' === c ||\n                        /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c)\n                      ? na(a, b)\n                      : void 0\n                  );\n                }\n              }\n              function na(a, b) {\n                (null == b || b > a.length) && (b = a.length);\n                for (var c = 0, d = new Array(b); c < b; c++) d[c] = a[c];\n                return d;\n              }\n              function oa(a, b) {\n                a = m()(t(a)).className;\n                b = m()(t(b)).className;\n                a = a.split(' ');\n                var c = b.split(' ');\n                return a\n                  .filter(function (a) {\n                    return c.includes(a);\n                  })\n                  .toString();\n              }\n              var N = 0,\n                pa = 1,\n                qa = 2;\n              function ra(a, b) {\n                if (\n                  (a && !b) ||\n                  (!a && b) ||\n                  void 0 === a ||\n                  void 0 === b ||\n                  a.nodeType !== b.nodeType ||\n                  a.nodeName !== b.nodeName\n                )\n                  return N;\n                a = t(a);\n                b = t(b);\n                if ((a && !b) || (!a && b)) return N;\n                if (a && b) {\n                  if (a.tagName !== b.tagName) return N;\n                  if (a.className === b.className) return pa;\n                }\n                return qa;\n              }\n              function sa(a, b, c, d) {\n                var e = ra(a, d.node);\n                return e === N\n                  ? e\n                  : c > 0 && b !== d.index\n                  ? N\n                  : 1 === e\n                  ? pa\n                  : 0 === d.relativeClass.length\n                  ? N\n                  : (oa(a, d.node), d.relativeClass, pa);\n              }\n              function ta(a, b, c, d) {\n                if (d === c.length - 1) {\n                  if (!sa(a, b, d, c[d])) return null;\n                  var e = t(a);\n                  if (e) return [e];\n                }\n                if (!a || !sa(a, b, d, c[d])) return null;\n                for (e = [], b = a.firstChild, a = 0; b; ) {\n                  var f = ta(b, a, c, d + 1);\n                  f && e.push.apply(e, la(f)), (b = b.nextSibling), (a += 1);\n                }\n                return e;\n              }\n              function ua(a, b) {\n                var c = [],\n                  d = (function (a, b) {\n                    var c =\n                      ('undefined' != typeof Symbol &&\n                        a[\n                          typeof Symbol === 'function'\n                            ? Symbol.iterator\n                            : '@@iterator'\n                        ]) ||\n                      a['@@iterator'];\n                    if (!c) {\n                      if (\n                        Array.isArray(a) ||\n                        (c = ma(a)) ||\n                        (b && a && 'number' == typeof a.length)\n                      ) {\n                        c && (a = c);\n                        var g = 0;\n                        b = function () {};\n                        return {\n                          s: b,\n                          n: function () {\n                            return g >= a.length\n                              ? {\n                                  done: !0,\n                                }\n                              : {\n                                  done: !1,\n                                  value: a[g++],\n                                };\n                          },\n                          e: function (a) {\n                            throw a;\n                          },\n                          f: b,\n                        };\n                      }\n                      throw new TypeError(\n                        'Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'\n                      );\n                    }\n                    var d,\n                      e = !0,\n                      f = !1;\n                    return {\n                      s: function () {\n                        c = c.call(a);\n                      },\n                      n: function () {\n                        var a = c.next();\n                        return (e = a.done), a;\n                      },\n                      e: function (a) {\n                        (f = !0), (d = a);\n                      },\n                      f: function () {\n                        try {\n                          e || null == c['return'] || c['return']();\n                        } finally {\n                          if (f) throw d;\n                        }\n                      },\n                    };\n                  })(a);\n                try {\n                  for (d.s(); !(a = d.n()).done; ) {\n                    a = ta(a.value, 0, b, 0);\n                    a && c.push.apply(c, la(a));\n                  }\n                } catch (a) {\n                  d.e(a);\n                } finally {\n                  d.f();\n                }\n                return c;\n              }\n              function va(a, b) {\n                a = (function (a, b) {\n                  for (\n                    var c = function (a) {\n                        var b = a.parentNode;\n                        if (!b) return -1;\n                        for (var b = b.firstChild, c = 0; b && b !== a; )\n                          (b = b.nextSibling), (c += 1);\n                        return b === a ? c : -1;\n                      },\n                      a = a,\n                      b = b,\n                      d = [],\n                      e = [];\n                    !a.isSameNode(b);\n\n                  ) {\n                    var f = ra(a, b);\n                    if (f === N) return null;\n                    var g = '';\n                    if (f === qa && 0 === (g = oa(a, b)).length) return null;\n                    if (\n                      (d.push({\n                        node: a,\n                        relativeClass: g,\n                        index: c(a),\n                      }),\n                      e.push(b),\n                      (a = a.parentNode),\n                      (b = b.parentNode),\n                      !a || !b)\n                    )\n                      return null;\n                  }\n                  return a && b && a.isSameNode(b) && d.length > 0\n                    ? {\n                        parentNode: a,\n                        node1Tree: d.reverse(),\n                        node2Tree: e.reverse(),\n                      }\n                    : null;\n                })(a, b);\n                if (!a) return null;\n                b = (function (a, b, c) {\n                  for (var d = [], a = a.firstChild; a; )\n                    a.isSameNode(b.node) ||\n                      a.isSameNode(c) ||\n                      !ra(b.node, a) ||\n                      d.push(a),\n                      (a = a.nextSibling);\n                  return d;\n                })(a.parentNode, a.node1Tree[0], a.node2Tree[0]);\n                return b && 0 !== b.length ? ua(b, a.node1Tree) : null;\n              }\n              function O(a) {\n                return (O =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              function wa(a, b) {\n                return (\n                  (function (a) {\n                    if (Array.isArray(a)) return a;\n                  })(a) ||\n                  (function (a, b) {\n                    var c =\n                      null == a\n                        ? null\n                        : ('undefined' != typeof Symbol &&\n                            a[\n                              typeof Symbol === 'function'\n                                ? Symbol.iterator\n                                : '@@iterator'\n                            ]) ||\n                          a['@@iterator'];\n                    if (null != c) {\n                      var d,\n                        e,\n                        f = [],\n                        g = !0,\n                        h = !1;\n                      try {\n                        if (((a = (c = c.call(a)).next), 0 === b)) {\n                          if (Object(c) !== c) return;\n                          g = !1;\n                        } else\n                          for (\n                            ;\n                            !(g = (d = a.call(c)).done) &&\n                            (f.push(d.value), f.length !== b);\n                            g = !0\n                          );\n                      } catch (a) {\n                        (h = !0), (e = a);\n                      } finally {\n                        try {\n                          if (\n                            !g &&\n                            null != c['return'] &&\n                            ((d = c['return']()), Object(d) !== d)\n                          )\n                            return;\n                        } finally {\n                          if (h) throw e;\n                        }\n                      }\n                      return f;\n                    }\n                  })(a, b) ||\n                  (function (a, b) {\n                    if (!a) return;\n                    if ('string' == typeof a) return xa(a, b);\n                    var c = Object.prototype.toString.call(a).slice(8, -1);\n                    'Object' === c && a.constructor && (c = a.constructor.name);\n                    if ('Map' === c || 'Set' === c) return Array.from(a);\n                    if (\n                      'Arguments' === c ||\n                      /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c)\n                    )\n                      return xa(a, b);\n                  })(a, b) ||\n                  (function () {\n                    throw new TypeError(\n                      'Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'\n                    );\n                  })()\n                );\n              }\n              function xa(a, b) {\n                (null == b || b > a.length) && (b = a.length);\n                for (var c = 0, d = new Array(b); c < b; c++) d[c] = a[c];\n                return d;\n              }\n              function ya(a, b) {\n                var c = Object.keys(a);\n                if (Object.getOwnPropertySymbols) {\n                  var d = Object.getOwnPropertySymbols(a);\n                  b &&\n                    (d = d.filter(function (b) {\n                      return Object.getOwnPropertyDescriptor(a, b).enumerable;\n                    })),\n                    c.push.apply(c, d);\n                }\n                return c;\n              }\n              function za(a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = null != arguments[b] ? arguments[b] : {};\n                  b % 2\n                    ? ya(Object(c), !0).forEach(function (b) {\n                        Aa(a, b, c[b]);\n                      })\n                    : Object.getOwnPropertyDescriptors\n                    ? Object.defineProperties(\n                        a,\n                        Object.getOwnPropertyDescriptors(c)\n                      )\n                    : ya(Object(c)).forEach(function (b) {\n                        Object.defineProperty(\n                          a,\n                          b,\n                          Object.getOwnPropertyDescriptor(c, b)\n                        );\n                      });\n                }\n                return a;\n              }\n              function Aa(a, b, c) {\n                return (\n                  (b = (function (a) {\n                    a = (function (a, b) {\n                      if ('object' !== O(a) || null === a) return a;\n                      var c =\n                        a[\n                          typeof Symbol === 'function'\n                            ? Symbol.toPrimitive\n                            : '@@toPrimitive'\n                        ];\n                      if (void 0 !== c) {\n                        c = c.call(a, b || 'default');\n                        if ('object' !== O(c)) return c;\n                        throw new TypeError(\n                          '@@toPrimitive must return a primitive value.'\n                        );\n                      }\n                      return ('string' === b ? String : Number)(a);\n                    })(a, 'string');\n                    return 'symbol' === O(a) ? a : String(a);\n                  })(b)) in a\n                    ? Object.defineProperty(a, b, {\n                        value: c,\n                        enumerable: !0,\n                        configurable: !0,\n                        writable: !0,\n                      })\n                    : (a[b] = c),\n                  a\n                );\n              }\n              var P = u()(\n                  [\n                    'CONSTANT_VALUE',\n                    'CSS',\n                    'URI',\n                    'SCHEMA_DOT_ORG',\n                    'JSON_LD',\n                    'RDFA',\n                    'OPEN_GRAPH',\n                    'GTM',\n                    'META_TAG',\n                    'GLOBAL_VARIABLE',\n                  ],\n                  function (a, b, c) {\n                    return za(za({}, a), {}, Aa({}, b, c));\n                  },\n                  {}\n                ),\n                Ba = {\n                  '@context': 'http://schema.org',\n                  '@type': 'Product',\n                  additionalType: void 0,\n                  offers: {\n                    price: void 0,\n                    priceCurrency: void 0,\n                  },\n                  productID: void 0,\n                },\n                Ca = function (a, b, c) {\n                  if (null == c) return a;\n                  var d = m()(a.offers);\n                  return {\n                    '@context': 'http://schema.org',\n                    '@type': 'Product',\n                    additionalType:\n                      null != a.additionalType\n                        ? a.additionalType\n                        : 'content_type' === b\n                        ? c\n                        : void 0,\n                    offers: {\n                      price:\n                        null != d.price ? d.price : 'value' === b ? c : void 0,\n                      priceCurrency:\n                        null != d.priceCurrency\n                          ? d.priceCurrency\n                          : 'currency' === b\n                          ? c\n                          : void 0,\n                    },\n                    productID:\n                      null != a.productID\n                        ? a.productID\n                        : 'content_ids' === b\n                        ? c\n                        : void 0,\n                  };\n                };\n              function a(a, b) {\n                b = b.sort(function (a, b) {\n                  return P[a.extractorType] > P[b.extractorType] ? 1 : -1;\n                });\n                return n()(\n                  F()(\n                    q()(b, function (b) {\n                      switch (b.extractorType) {\n                        case 'SCHEMA_DOT_ORG':\n                          return q()(\n                            (function (a) {\n                              for (\n                                var b = q()(j, function (a) {\n                                    return '[itemtype$=\"'\n                                      .concat('schema.org/')\n                                      .concat(a, '\"]');\n                                  }).join(', '),\n                                  c = [],\n                                  b = o()(g.querySelectorAll(b)),\n                                  d = [];\n                                b.length > 0;\n\n                              ) {\n                                var e = b.pop();\n                                if (!p()(c, e)) {\n                                  var s = {\n                                    '@context': 'http://schema.org',\n                                  };\n                                  d.push({\n                                    htmlElement: e,\n                                    jsonLD: s,\n                                  });\n                                  for (\n                                    e = [\n                                      {\n                                        element: e,\n                                        workingNode: s,\n                                      },\n                                    ];\n                                    e.length;\n\n                                  ) {\n                                    s = e.pop();\n                                    var v = s.element;\n                                    s = s.workingNode;\n                                    var f = m()(v.getAttribute('itemtype'));\n                                    s['@type'] = f.substr(\n                                      f.indexOf('schema.org/') +\n                                        'schema.org/'.length\n                                    );\n                                    for (\n                                      f = o()(\n                                        v.querySelectorAll('[itemprop]')\n                                      ).reverse();\n                                      f.length;\n\n                                    ) {\n                                      var h = f.pop();\n                                      if (!p()(c, h)) {\n                                        c.push(h);\n                                        var w = m()(h.getAttribute('itemprop'));\n                                        if (h.hasAttribute('itemscope')) {\n                                          var k = {};\n                                          (s[w] = k),\n                                            e.push({\n                                              element: v,\n                                              workingNode: s,\n                                            }),\n                                            e.push({\n                                              element: h,\n                                              workingNode: k,\n                                            });\n                                          break;\n                                        }\n                                        s[w] = i(h);\n                                      }\n                                    }\n                                  }\n                                }\n                              }\n                              return n()(d, function (b) {\n                                return l()(b.htmlElement, a);\n                              });\n                            })(a),\n                            function (a) {\n                              return {\n                                extractorID: b.id,\n                                jsonLD: a.jsonLD,\n                              };\n                            }\n                          );\n                        case 'RDFA':\n                          return q()(r(a), function (a) {\n                            return {\n                              extractorID: b.id,\n                              jsonLD: a.jsonLD,\n                            };\n                          });\n                        case 'OPEN_GRAPH':\n                          return {\n                            extractorID: b.id,\n                            jsonLD: ia(),\n                          };\n                        case 'CSS':\n                          var c = q()(\n                            b.extractorConfig.parameterSelectors,\n                            function (b) {\n                              return null === (b = aa(a, b.selector)) ||\n                                void 0 === b\n                                ? void 0\n                                : b[0];\n                            }\n                          );\n                          if (null == c) return null;\n                          if (2 === c.length) {\n                            var d = c[0],\n                              e = c[1];\n                            if (null != d && null != e) {\n                              d = va(d, e);\n                              d && c.push.apply(c, d);\n                            }\n                          }\n                          var h =\n                            b.extractorConfig.parameterSelectors[0]\n                              .parameterType;\n                          e = q()(c, function (a) {\n                            a =\n                              (null == a ? void 0 : a.innerText) ||\n                              (null == a ? void 0 : a.textContent);\n                            return [h, a];\n                          });\n                          d = q()(\n                            n()(e, function (a) {\n                              return 'totalPrice' !== wa(a, 1)[0];\n                            }),\n                            function (a) {\n                              a = wa(a, 2);\n                              var b = a[0];\n                              a = a[1];\n                              return Ca(Ba, b, a);\n                            }\n                          );\n                          if (\n                            'InitiateCheckout' === b.eventType ||\n                            'Purchase' === b.eventType\n                          ) {\n                            c = G()(e, function (a) {\n                              return 'totalPrice' === wa(a, 1)[0];\n                            });\n                            c &&\n                              (d = [\n                                {\n                                  '@context': 'http://schema.org',\n                                  '@type': 'ItemList',\n                                  itemListElement: q()(d, function (a, b) {\n                                    return {\n                                      '@type': 'ListItem',\n                                      item: a,\n                                      position: b + 1,\n                                    };\n                                  }),\n                                  totalPrice: null != c[1] ? c[1] : void 0,\n                                },\n                              ]);\n                          }\n                          return q()(d, function (a) {\n                            return {\n                              extractorID: b.id,\n                              jsonLD: a,\n                            };\n                          });\n                        case 'CONSTANT_VALUE':\n                          e = b.extractorConfig;\n                          c = e.parameterType;\n                          d = e.value;\n                          return {\n                            extractorID: b.id,\n                            jsonLD: Ca(Ba, c, d),\n                          };\n                        case 'URI':\n                          e = b.extractorConfig.parameterType;\n                          c = (function (a, b, c) {\n                            a = new B(a);\n                            switch (b) {\n                              case ja:\n                                b = n()(\n                                  q()(a.pathname.split('/'), function (a) {\n                                    return a.trim();\n                                  }),\n                                  Boolean\n                                );\n                                var d = parseInt(c, 10);\n                                return d < b.length ? b[d] : null;\n                              case ka:\n                                return a.searchParams.get(c);\n                            }\n                            return null;\n                          })(\n                            f.location.href,\n                            b.extractorConfig.context,\n                            b.extractorConfig.value\n                          );\n                          return {\n                            extractorID: b.id,\n                            jsonLD: Ca(Ba, e, c),\n                          };\n                        default:\n                          throw new Error(\n                            'Extractor '.concat(b.extractorType, ' not mapped')\n                          );\n                      }\n                    })\n                  ),\n                  function (a) {\n                    a = a.jsonLD;\n                    return Boolean(a);\n                  }\n                );\n              }\n              a.EXTRACTOR_PRECEDENCE = P;\n              var Da = a;\n              function Ea(a) {\n                switch (a.extractor_type) {\n                  case 'CSS':\n                    if (null == a.extractor_config)\n                      throw new Error('extractor_config must be set');\n                    var b = a.extractor_config;\n                    if (b.parameter_type)\n                      throw new Error('extractor_config must be set');\n                    return {\n                      domainURI: new B(a.domain_uri),\n                      eventType: a.event_type,\n                      extractorConfig:\n                        ((b = b),\n                        {\n                          parameterSelectors: q()(\n                            b.parameter_selectors,\n                            function (a) {\n                              return {\n                                parameterType: a.parameter_type,\n                                selector: a.selector,\n                              };\n                            }\n                          ),\n                        }),\n                      extractorType: 'CSS',\n                      id: m()(a.id),\n                      ruleId:\n                        null === (b = a.event_rule) || void 0 === b\n                          ? void 0\n                          : b.id,\n                    };\n                  case 'CONSTANT_VALUE':\n                    if (null == a.extractor_config)\n                      throw new Error('extractor_config must be set');\n                    b = a.extractor_config;\n                    if (b.parameter_selectors)\n                      throw new Error('extractor_config must be set');\n                    return {\n                      domainURI: new B(a.domain_uri),\n                      eventType: a.event_type,\n                      extractorConfig: Fa(b),\n                      extractorType: 'CONSTANT_VALUE',\n                      id: m()(a.id),\n                      ruleId:\n                        null === (b = a.event_rule) || void 0 === b\n                          ? void 0\n                          : b.id,\n                    };\n                  case 'URI':\n                    if (null == a.extractor_config)\n                      throw new Error('extractor_config must be set');\n                    b = a.extractor_config;\n                    if (b.parameter_selectors)\n                      throw new Error('extractor_config must be set');\n                    return {\n                      domainURI: new B(a.domain_uri),\n                      eventType: a.event_type,\n                      extractorConfig: Ga(b),\n                      extractorType: 'URI',\n                      id: m()(a.id),\n                      ruleId:\n                        null === (b = a.event_rule) || void 0 === b\n                          ? void 0\n                          : b.id,\n                    };\n                  default:\n                    return {\n                      domainURI: new B(a.domain_uri),\n                      eventType: a.event_type,\n                      extractorType: a.extractor_type,\n                      id: m()(a.id),\n                      ruleId:\n                        null === (b = a.event_rule) || void 0 === b\n                          ? void 0\n                          : b.id,\n                    };\n                }\n              }\n              function Fa(a) {\n                return {\n                  parameterType: a.parameter_type,\n                  value: a.value,\n                };\n              }\n              function Ga(a) {\n                return {\n                  context: a.context,\n                  parameterType: a.parameter_type,\n                  value: a.value,\n                };\n              }\n              a.EXTRACTOR_PRECEDENCE = P;\n              var Ha = function (a, b, c) {\n                  return 'string' != typeof a\n                    ? ''\n                    : a.length < c && 0 === b\n                    ? a\n                    : []\n                        .concat(o()(a))\n                        .slice(b, b + c)\n                        .join('');\n                },\n                Q = function (a, b) {\n                  return Ha(a, 0, b);\n                },\n                Ia = [\n                  'button',\n                  'submit',\n                  'input',\n                  'li',\n                  'option',\n                  'progress',\n                  'param',\n                ];\n              function Ja(a) {\n                var b = e(a);\n                if (null != b && '' !== b) return Q(b, 120);\n                b = a.type;\n                a = a.value;\n                return null != b && p()(Ia, b) && null != a && '' !== a\n                  ? Q(a, 120)\n                  : Q('', 120);\n              }\n              var R = ', ',\n                S = [\n                  \"input[type='button']\",\n                  \"input[type='image']\",\n                  \"input[type='submit']\",\n                  'button',\n                  '[class*=btn]',\n                  '[class*=Btn]',\n                  '[class*=submit]',\n                  '[class*=Submit]',\n                  '[class*=button]',\n                  '[class*=Button]',\n                  '[role*=button]',\n                  \"[href^='tel:']\",\n                  \"[href^='callto:']\",\n                  \"[href^='mailto:']\",\n                  \"[href^='sms:']\",\n                  \"[href^='skype:']\",\n                  \"[href^='whatsapp:']\",\n                  '[id*=btn]',\n                  '[id*=Btn]',\n                  '[id*=button]',\n                  '[id*=Button]',\n                  'a',\n                ].join(R),\n                Ka = [\n                  \"[href^='tel:']\",\n                  \"[href^='callto:']\",\n                  \"[href^='sms:']\",\n                  \"[href^='skype:']\",\n                  \"[href^='whatsapp:']\",\n                ].join(R),\n                La = S,\n                Ma = [\n                  \"input[type='button']\",\n                  \"input[type='submit']\",\n                  'button',\n                  'a',\n                ].join(R);\n              function Na(a) {\n                var b = '';\n                if ('IMG' === a.tagName) return a.getAttribute('src') || '';\n                if (f.getComputedStyle) {\n                  var c = f\n                    .getComputedStyle(a)\n                    .getPropertyValue('background-image');\n                  if (null != c && 'none' !== c && c.length > 0) return c;\n                }\n                if (\n                  'INPUT' === a.tagName &&\n                  'image' === a.getAttribute('type')\n                ) {\n                  c = a.getAttribute('src');\n                  if (null != c) return c;\n                }\n                c = a.getElementsByTagName('img');\n                if (0 !== c.length) {\n                  a = c.item(0);\n                  b = (a ? a.getAttribute('src') : null) || '';\n                }\n                return b;\n              }\n              var Oa = [\n                  'sms:',\n                  'mailto:',\n                  'tel:',\n                  'whatsapp:',\n                  'https://wa.me/',\n                  'skype:',\n                  'callto:',\n                ],\n                Pa = /[\\-!$><-==&_\\/\\?\\.,0-9:; \\]\\[%~\\\"\\{\\}\\)\\(\\+\\@\\^\\`]/g,\n                Qa = /((([a-z])(?=[A-Z]))|(([A-Z])(?=[A-Z][a-z])))/g,\n                Ra = /(^\\S{1}(?!\\S))|((\\s)\\S{1}(?!\\S))/g,\n                Sa = /\\s+/g;\n              function Ta(a) {\n                return (\n                  !!(function (a) {\n                    var b = Oa;\n                    if (!a.hasAttribute('href')) return !1;\n                    var c = a.getAttribute('href');\n                    return (\n                      null != c &&\n                      !!G()(b, function (a) {\n                        return H()(c, a);\n                      })\n                    );\n                  })(a) ||\n                  !!Ja(a)\n                    .replace(Pa, ' ')\n                    .replace(Qa, function (a) {\n                      return a + ' ';\n                    })\n                    .replace(Ra, function (a) {\n                      return Q(a, a.length - 1) + ' ';\n                    })\n                    .replace(Sa, ' ')\n                    .trim()\n                    .toLowerCase() ||\n                  !!Na(a)\n                );\n              }\n              function Ua(a) {\n                if (null == a || a === g.body || !Ta(a)) return !1;\n                a =\n                  ('function' == typeof a.getBoundingClientRect &&\n                    a.getBoundingClientRect().height) ||\n                  a.offsetHeight;\n                return !isNaN(a) && a < 600 && a > 10;\n              }\n              function Va(a, b) {\n                for (var c = 0; c < b.length; c++) {\n                  var d = b[c];\n                  (d.enumerable = d.enumerable || !1),\n                    (d.configurable = !0),\n                    'value' in d && (d.writable = !0),\n                    Object.defineProperty(a, Wa(d.key), d);\n                }\n              }\n              function Wa(a) {\n                a = (function (a, b) {\n                  if ('object' !== T(a) || null === a) return a;\n                  var c =\n                    a[\n                      typeof Symbol === 'function'\n                        ? Symbol.toPrimitive\n                        : '@@toPrimitive'\n                    ];\n                  if (void 0 !== c) {\n                    c = c.call(a, b || 'default');\n                    if ('object' !== T(c)) return c;\n                    throw new TypeError(\n                      '@@toPrimitive must return a primitive value.'\n                    );\n                  }\n                  return ('string' === b ? String : Number)(a);\n                })(a, 'string');\n                return 'symbol' === T(a) ? a : String(a);\n              }\n              function T(a) {\n                return (T =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              var Xa = Object.prototype.toString,\n                Ya = !('addEventListener' in g);\n              function Za(a) {\n                return Array.isArray\n                  ? Array.isArray(a)\n                  : '[object Array]' === Xa.call(a);\n              }\n              function $a(a) {\n                return null != a && 'object' === T(a) && !1 === Za(a);\n              }\n              function ab(a) {\n                return (\n                  !0 === $a(a) &&\n                  '[object Object]' === Object.prototype.toString.call(a)\n                );\n              }\n              var bb =\n                  Number.isInteger ||\n                  function (a) {\n                    return (\n                      'number' == typeof a && isFinite(a) && Math.floor(a) === a\n                    );\n                  },\n                cb = Object.prototype.hasOwnProperty,\n                db = !{\n                  toString: null,\n                }.propertyIsEnumerable('toString'),\n                eb = [\n                  'toString',\n                  'toLocaleString',\n                  'valueOf',\n                  'hasOwnProperty',\n                  'isPrototypeOf',\n                  'propertyIsEnumerable',\n                  'constructor',\n                ],\n                fb = eb.length;\n              function gb(a) {\n                if ('object' !== T(a) && ('function' != typeof a || null === a))\n                  throw new TypeError('Object.keys called on non-object');\n                var b = [];\n                for (var c in a) cb.call(a, c) && b.push(c);\n                if (db)\n                  for (c = 0; c < fb; c++) cb.call(a, eb[c]) && b.push(eb[c]);\n                return b;\n              }\n              function hb(a, b) {\n                if (null == a)\n                  throw new TypeError(' array is null or not defined');\n                a = Object(a);\n                var c = a.length >>> 0;\n                if ('function' != typeof b)\n                  throw new TypeError(b + ' is not a function');\n                for (var d = new Array(c), e = 0; e < c; ) {\n                  var f;\n                  e in a && ((f = b(a[e], e, a)), (d[e] = f)), e++;\n                }\n                return d;\n              }\n              function ib(a) {\n                if ('function' != typeof a) throw new TypeError();\n                for (\n                  var b = Object(this),\n                    c = b.length >>> 0,\n                    d = arguments.length >= 2 ? arguments[1] : void 0,\n                    e = 0;\n                  e < c;\n                  e++\n                )\n                  if (e in b && a.call(d, b[e], e, b)) return !0;\n                return !1;\n              }\n              function jb(a) {\n                if (null == this) throw new TypeError();\n                var b = Object(this),\n                  c = b.length >>> 0;\n                if ('function' != typeof a) throw new TypeError();\n                for (\n                  var d = [],\n                    e = arguments.length >= 2 ? arguments[1] : void 0,\n                    f = 0;\n                  f < c;\n                  f++\n                )\n                  if (f in b) {\n                    var g = b[f];\n                    a.call(e, g, f, b) && d.push(g);\n                  }\n                return d;\n              }\n              function U(a, b) {\n                try {\n                  return b(a);\n                } catch (a) {\n                  if (a instanceof TypeError) {\n                    if (kb.test(a)) return null;\n                    if (lb.test(a)) return;\n                  }\n                  throw a;\n                }\n              }\n              var kb = /^null | null$|^[^(]* null /i,\n                lb = /^undefined | undefined$|^[^(]* undefined /i;\n              U['default'] = U;\n              k = {\n                FBSet: (function () {\n                  function a(b) {\n                    var c, d, e;\n                    !(function (a, b) {\n                      if (!(a instanceof b))\n                        throw new TypeError(\n                          'Cannot call a class as a function'\n                        );\n                    })(this, a),\n                      (c = this),\n                      (e = void 0),\n                      (d = Wa('items')) in c\n                        ? Object.defineProperty(c, d, {\n                            value: e,\n                            enumerable: !0,\n                            configurable: !0,\n                            writable: !0,\n                          })\n                        : (c[d] = e),\n                      (this.items = b || []);\n                  }\n                  var b, c, d;\n                  return (\n                    (b = a),\n                    (c = [\n                      {\n                        key: 'has',\n                        value: function (a) {\n                          return ib.call(this.items, function (b) {\n                            return b === a;\n                          });\n                        },\n                      },\n                      {\n                        key: 'add',\n                        value: function (a) {\n                          this.items.push(a);\n                        },\n                      },\n                    ]) && Va(b.prototype, c),\n                    d && Va(b, d),\n                    Object.defineProperty(b, 'prototype', {\n                      writable: !1,\n                    }),\n                    a\n                  );\n                })(),\n                castTo: function (a) {\n                  return a;\n                },\n                each: function (a, b) {\n                  hb.call(this, a, b);\n                },\n                filter: function (a, b) {\n                  return jb.call(a, b);\n                },\n                idx: U,\n                isArray: Za,\n                isEmptyObject: function (a) {\n                  return 0 === gb(a).length;\n                },\n                isInstanceOf: function (a, b) {\n                  return null != b && a instanceof b;\n                },\n                isInteger: bb,\n                isNumber: function (a) {\n                  return (\n                    'number' == typeof a ||\n                    ('string' == typeof a && /^\\d+$/.test(a))\n                  );\n                },\n                isObject: $a,\n                isPlainObject: function (a) {\n                  if (!1 === ab(a)) return !1;\n                  a = a.constructor;\n                  if ('function' != typeof a) return !1;\n                  a = a.prototype;\n                  return (\n                    !1 !== ab(a) &&\n                    !1 !==\n                      Object.prototype.hasOwnProperty.call(a, 'isPrototypeOf')\n                  );\n                },\n                isSafeInteger: function (a) {\n                  return bb(a) && a >= 0 && a <= Number.MAX_SAFE_INTEGER;\n                },\n                keys: gb,\n                listenOnce: function (a, b, c) {\n                  var d = Ya ? 'on' + b : b;\n                  b = Ya ? a.attachEvent : a.addEventListener;\n                  var e = Ya ? a.detachEvent : a.removeEventListener;\n                  b &&\n                    b.call(\n                      a,\n                      d,\n                      function b() {\n                        e && e.call(a, d, b, !1), c();\n                      },\n                      !1\n                    );\n                },\n                map: hb,\n                reduce: function (a, b, c, d) {\n                  if (null == a)\n                    throw new TypeError(' array is null or not defined');\n                  if ('function' != typeof b)\n                    throw new TypeError(b + ' is not a function');\n                  var e = Object(a),\n                    f = e.length >>> 0,\n                    g = 0;\n                  if (null != c || !0 === d) d = c;\n                  else {\n                    for (; g < f && !(g in e); ) g++;\n                    if (g >= f)\n                      throw new TypeError(\n                        'Reduce of empty array with no initial value'\n                      );\n                    d = e[g++];\n                  }\n                  for (; g < f; ) g in e && (d = b(d, e[g], g, a)), g++;\n                  return d;\n                },\n                some: function (a, b) {\n                  return ib.call(a, b);\n                },\n                stringIncludes: function (a, b) {\n                  return null != a && null != b && a.indexOf(b) >= 0;\n                },\n                stringStartsWith: function (a, b) {\n                  return null != a && null != b && 0 === a.indexOf(b);\n                },\n              };\n              function mb(a, b) {\n                var c = Object.keys(a);\n                if (Object.getOwnPropertySymbols) {\n                  var d = Object.getOwnPropertySymbols(a);\n                  b &&\n                    (d = d.filter(function (b) {\n                      return Object.getOwnPropertyDescriptor(a, b).enumerable;\n                    })),\n                    c.push.apply(c, d);\n                }\n                return c;\n              }\n              function nb(a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = null != arguments[b] ? arguments[b] : {};\n                  b % 2\n                    ? mb(Object(c), !0).forEach(function (b) {\n                        ob(a, b, c[b]);\n                      })\n                    : Object.getOwnPropertyDescriptors\n                    ? Object.defineProperties(\n                        a,\n                        Object.getOwnPropertyDescriptors(c)\n                      )\n                    : mb(Object(c)).forEach(function (b) {\n                        Object.defineProperty(\n                          a,\n                          b,\n                          Object.getOwnPropertyDescriptor(c, b)\n                        );\n                      });\n                }\n                return a;\n              }\n              function ob(a, b, c) {\n                return (\n                  (b = qb(b)) in a\n                    ? Object.defineProperty(a, b, {\n                        value: c,\n                        enumerable: !0,\n                        configurable: !0,\n                        writable: !0,\n                      })\n                    : (a[b] = c),\n                  a\n                );\n              }\n              function V(a) {\n                return (V =\n                  'function' == typeof Symbol &&\n                  'symbol' ==\n                    h(\n                      typeof Symbol === 'function'\n                        ? Symbol.iterator\n                        : '@@iterator'\n                    )\n                    ? function (a) {\n                        return typeof a === 'undefined' ? 'undefined' : h(a);\n                      }\n                    : function (a) {\n                        return a &&\n                          'function' == typeof Symbol &&\n                          a.constructor === Symbol &&\n                          a !==\n                            (typeof Symbol === 'function'\n                              ? Symbol.prototype\n                              : '@@prototype')\n                          ? 'symbol'\n                          : typeof a === 'undefined'\n                          ? 'undefined'\n                          : h(a);\n                      })(a);\n              }\n              function pb(a, b) {\n                for (var c = 0; c < b.length; c++) {\n                  var d = b[c];\n                  (d.enumerable = d.enumerable || !1),\n                    (d.configurable = !0),\n                    'value' in d && (d.writable = !0),\n                    Object.defineProperty(a, qb(d.key), d);\n                }\n              }\n              function qb(a) {\n                a = (function (a, b) {\n                  if ('object' !== V(a) || null === a) return a;\n                  var c =\n                    a[\n                      typeof Symbol === 'function'\n                        ? Symbol.toPrimitive\n                        : '@@toPrimitive'\n                    ];\n                  if (void 0 !== c) {\n                    c = c.call(a, b || 'default');\n                    if ('object' !== V(c)) return c;\n                    throw new TypeError(\n                      '@@toPrimitive must return a primitive value.'\n                    );\n                  }\n                  return ('string' === b ? String : Number)(a);\n                })(a, 'string');\n                return 'symbol' === V(a) ? a : String(a);\n              }\n              function rb(a, b) {\n                if (!(a instanceof b))\n                  throw new TypeError('Cannot call a class as a function');\n              }\n              function sb(a, b) {\n                if (b && ('object' === V(b) || 'function' == typeof b))\n                  return b;\n                if (void 0 !== b)\n                  throw new TypeError(\n                    'Derived constructors may only return object or undefined'\n                  );\n                return (function (a) {\n                  if (void 0 === a)\n                    throw new ReferenceError(\n                      \"this hasn't been initialised - super() hasn't been called\"\n                    );\n                  return a;\n                })(a);\n              }\n              function tb(a) {\n                var b = 'function' == typeof Map ? new Map() : void 0;\n                return (tb = function (a) {\n                  if (\n                    null === a ||\n                    ((c = a),\n                    -1 === Function.toString.call(c).indexOf('[native code]'))\n                  )\n                    return a;\n                  var c;\n                  if ('function' != typeof a)\n                    throw new TypeError(\n                      'Super expression must either be null or a function'\n                    );\n                  if (void 0 !== b) {\n                    if (b.has(a)) return b.get(a);\n                    b.set(a, d);\n                  }\n                  function d() {\n                    return ub(a, arguments, xb(this).constructor);\n                  }\n                  return (\n                    (d.prototype = Object.create(a.prototype, {\n                      constructor: {\n                        value: d,\n                        enumerable: !1,\n                        writable: !0,\n                        configurable: !0,\n                      },\n                    })),\n                    wb(d, a)\n                  );\n                })(a);\n              }\n              function ub(a, b, c) {\n                return (ub = vb()\n                  ? Reflect.construct.bind()\n                  : function (a, b, c) {\n                      var d = [null];\n                      d.push.apply(d, b);\n                      b = new (Function.bind.apply(a, d))();\n                      return c && wb(b, c.prototype), b;\n                    }).apply(null, arguments);\n              }\n              function vb() {\n                if ('undefined' == typeof Reflect || !Reflect.construct)\n                  return !1;\n                if (Reflect.construct.sham) return !1;\n                if ('function' == typeof Proxy) return !0;\n                try {\n                  return (\n                    Boolean.prototype.valueOf.call(\n                      Reflect.construct(Boolean, [], function () {})\n                    ),\n                    !0\n                  );\n                } catch (a) {\n                  return !1;\n                }\n              }\n              function wb(a, b) {\n                return (wb = Object.setPrototypeOf\n                  ? Object.setPrototypeOf.bind()\n                  : function (a, b) {\n                      return (a.__proto__ = b), a;\n                    })(a, b);\n              }\n              function xb(a) {\n                return (xb = Object.setPrototypeOf\n                  ? Object.getPrototypeOf.bind()\n                  : function (a) {\n                      return a.__proto__ || Object.getPrototypeOf(a);\n                    })(a);\n              }\n              var yb = k.isSafeInteger,\n                zb = k.reduce,\n                W = (function (a) {\n                  !(function (a, b) {\n                    if ('function' != typeof b && null !== b)\n                      throw new TypeError(\n                        'Super expression must either be null or a function'\n                      );\n                    (a.prototype = Object.create(b && b.prototype, {\n                      constructor: {\n                        value: a,\n                        writable: !0,\n                        configurable: !0,\n                      },\n                    })),\n                      Object.defineProperty(a, 'prototype', {\n                        writable: !1,\n                      }),\n                      b && wb(a, b);\n                  })(g, a);\n                  var b,\n                    c,\n                    d,\n                    e,\n                    f =\n                      ((b = g),\n                      (c = vb()),\n                      function () {\n                        var a,\n                          d = xb(b);\n                        if (c) {\n                          var e = xb(this).constructor;\n                          a = Reflect.construct(d, arguments, e);\n                        } else a = d.apply(this, arguments);\n                        return sb(this, a);\n                      });\n                  function g() {\n                    var a,\n                      b =\n                        arguments.length > 0 && void 0 !== arguments[0]\n                          ? arguments[0]\n                          : '';\n                    return (\n                      rb(this, g),\n                      ((a = f.call(this, b)).name = 'PixelCoercionError'),\n                      a\n                    );\n                  }\n                  return (\n                    (a = g),\n                    d && pb(a.prototype, d),\n                    e && pb(a, e),\n                    Object.defineProperty(a, 'prototype', {\n                      writable: !1,\n                    }),\n                    a\n                  );\n                })(tb(Error));\n              function Ab() {\n                return function (a) {\n                  if (null == a || !Array.isArray(a)) throw new W();\n                  return a;\n                };\n              }\n              function Bb(a, b) {\n                try {\n                  return b(a);\n                } catch (a) {\n                  if ('PixelCoercionError' === a.name) return null;\n                  throw a;\n                }\n              }\n              function X(a, b) {\n                return b(a);\n              }\n              function Cb(a) {\n                if (!a) throw new W();\n              }\n              function Db(a) {\n                var b = a.def,\n                  c = a.validators;\n                return function (a) {\n                  var d = X(a, b);\n                  return (\n                    c.forEach(function (a) {\n                      if (!a(d)) throw new W();\n                    }),\n                    d\n                  );\n                };\n              }\n              var Eb = /^[1-9][0-9]{0,25}$/,\n                Y = {\n                  allowNull: function (a) {\n                    return function (b) {\n                      return null == b ? null : a(b);\n                    };\n                  },\n                  array: Ab,\n                  arrayOf: function (a) {\n                    return function (b) {\n                      return X(b, Y.array()).map(a);\n                    };\n                  },\n                  assert: Cb,\n                  boolean: function () {\n                    return function (a) {\n                      if ('boolean' != typeof a) throw new W();\n                      return a;\n                    };\n                  },\n                  enumeration: function (a) {\n                    return function (b) {\n                      if (((c = a), Object.values(c)).includes(b)) return b;\n                      var c;\n                      throw new W();\n                    };\n                  },\n                  fbid: function () {\n                    return Db({\n                      def: function (a) {\n                        var b = Bb(a, Y.number());\n                        return null != b\n                          ? (Y.assert(yb(b)), ''.concat(b))\n                          : X(a, Y.string());\n                      },\n                      validators: [\n                        function (a) {\n                          return Eb.test(a);\n                        },\n                      ],\n                    });\n                  },\n                  mapOf: function (a) {\n                    return function (b) {\n                      var c = X(b, Y.object());\n                      return zb(\n                        Object.keys(c),\n                        function (b, d) {\n                          return nb(nb({}, b), {}, ob({}, d, a(c[d])));\n                        },\n                        {}\n                      );\n                    };\n                  },\n                  matches: function (a) {\n                    return function (b) {\n                      b = X(b, Y.string());\n                      if (a.test(b)) return b;\n                      throw new W();\n                    };\n                  },\n                  number: function () {\n                    return function (a) {\n                      if ('number' != typeof a) throw new W();\n                      return a;\n                    };\n                  },\n                  object: function () {\n                    return function (a) {\n                      if ('object' !== V(a) || Array.isArray(a) || null == a)\n                        throw new W();\n                      return a;\n                    };\n                  },\n                  objectOrString: function () {\n                    return function (a) {\n                      if (\n                        ('object' !== V(a) && 'string' != typeof a) ||\n                        Array.isArray(a) ||\n                        null == a\n                      )\n                        throw new W();\n                      return a;\n                    };\n                  },\n                  objectWithFields: function (a) {\n                    return function (b) {\n                      var c = X(b, Y.object());\n                      return zb(\n                        Object.keys(a),\n                        function (b, d) {\n                          if (null == b) return null;\n                          var e = a[d](c[d]);\n                          return nb(nb({}, b), {}, ob({}, d, e));\n                        },\n                        {}\n                      );\n                    };\n                  },\n                  string: function () {\n                    return function (a) {\n                      if ('string' != typeof a) throw new W();\n                      return a;\n                    };\n                  },\n                  stringOrNumber: function () {\n                    return function (a) {\n                      if ('string' != typeof a && 'number' != typeof a)\n                        throw new W();\n                      return a;\n                    };\n                  },\n                  tuple: function (a) {\n                    return function (b) {\n                      b = X(b, Ab());\n                      return (\n                        Cb(b.length === a.length),\n                        b.map(function (b, c) {\n                          return X(b, a[c]);\n                        })\n                      );\n                    };\n                  },\n                  withValidation: Db,\n                  func: function () {\n                    return function (a) {\n                      if ('function' != typeof a || null == a) throw new W();\n                      return a;\n                    };\n                  },\n                };\n              D = {\n                Typed: Y,\n                coerce: Bb,\n                enforce: X,\n                PixelCoercionError: W,\n              };\n              a = D.Typed;\n              var Fb = a.objectWithFields({\n                type: a.withValidation({\n                  def: a.number(),\n                  validators: [\n                    function (a) {\n                      return a >= 1 && a <= 3;\n                    },\n                  ],\n                }),\n                conditions: a.arrayOf(\n                  a.objectWithFields({\n                    targetType: a.withValidation({\n                      def: a.number(),\n                      validators: [\n                        function (a) {\n                          return a >= 1 && a <= 6;\n                        },\n                      ],\n                    }),\n                    extractor: a.allowNull(\n                      a.withValidation({\n                        def: a.number(),\n                        validators: [\n                          function (a) {\n                            return a >= 1 && a <= 11;\n                          },\n                        ],\n                      })\n                    ),\n                    operator: a.withValidation({\n                      def: a.number(),\n                      validators: [\n                        function (a) {\n                          return a >= 1 && a <= 4;\n                        },\n                      ],\n                    }),\n                    action: a.withValidation({\n                      def: a.number(),\n                      validators: [\n                        function (a) {\n                          return a >= 1 && a <= 4;\n                        },\n                      ],\n                    }),\n                    value: a.allowNull(a.string()),\n                  })\n                ),\n              });\n              function Gb(a) {\n                var b = [];\n                a = a;\n                do {\n                  var c = a.indexOf('*');\n                  c < 0\n                    ? (b.push(a), (a = ''))\n                    : 0 === c\n                    ? (b.push('*'), (a = a.slice(1)))\n                    : (b.push(a.slice(0, c)), (a = a.slice(c)));\n                } while (a.length > 0);\n                return b;\n              }\n              U = function (a, b) {\n                for (var a = Gb(a), b = b, c = 0; c < a.length; c++) {\n                  var d = a[c];\n                  if ('*' !== d) {\n                    if (0 !== b.indexOf(d)) return !1;\n                    b = b.slice(d.length);\n                  } else {\n                    if (c === a.length - 1) return !0;\n                    d = a[c + 1];\n                    if ('*' === d) continue;\n                    d = b.indexOf(d);\n                    if (d < 0) return !1;\n                    b = b.slice(d);\n                  }\n                }\n                return '' === b;\n              };\n              var Hb = D.enforce,\n                Ib = U,\n                Jb = Object.freeze({\n                  CLICK: 1,\n                  LOAD: 2,\n                  BECOME_VISIBLE: 3,\n                  TRACK: 4,\n                }),\n                Kb = Object.freeze({\n                  BUTTON: 1,\n                  PAGE: 2,\n                  JS_VARIABLE: 3,\n                  EVENT: 4,\n                  ELEMENT: 6,\n                }),\n                Lb = Object.freeze({\n                  CONTAINS: 1,\n                  EQUALS: 2,\n                  DOMAIN_MATCHES: 3,\n                  STRING_MATCHES: 4,\n                }),\n                Z = Object.freeze({\n                  URL: 1,\n                  TOKENIZED_TEXT_V1: 2,\n                  TOKENIZED_TEXT_V2: 3,\n                  TEXT: 4,\n                  CLASS_NAME: 5,\n                  ELEMENT_ID: 6,\n                  EVENT_NAME: 7,\n                  DESTINATION_URL: 8,\n                  DOMAIN: 9,\n                  PAGE_TITLE: 10,\n                  IMAGE_URL: 11,\n                }),\n                Mb = Object.freeze({\n                  ALL: 1,\n                  ANY: 2,\n                  NONE: 3,\n                });\n              function Nb(a, b, c) {\n                if (null == b) return null;\n                switch (a) {\n                  case Kb.PAGE:\n                    return (function (a, b) {\n                      switch (a) {\n                        case Z.URL:\n                          return b.resolvedLink;\n                        case Z.DOMAIN:\n                          return new URL(b.resolvedLink).hostname;\n                        case Z.PAGE_TITLE:\n                          if (null != b.pageFeatures)\n                            return JSON.parse(\n                              b.pageFeatures\n                            ).title.toLowerCase();\n                        default:\n                          return null;\n                      }\n                    })(b, c);\n                  case Kb.BUTTON:\n                    return (function (a, b) {\n                      var c;\n                      null != b.buttonText && (c = b.buttonText.toLowerCase());\n                      var d = {};\n                      switch (\n                        (null != b.buttonFeatures &&\n                          (d = JSON.parse(b.buttonFeatures)),\n                        a)\n                      ) {\n                        case Z.DESTINATION_URL:\n                          return d.destination;\n                        case Z.TEXT:\n                          return c;\n                        case Z.TOKENIZED_TEXT_V1:\n                          return null == c ? null : Qb(c);\n                        case Z.TOKENIZED_TEXT_V2:\n                          return null == c ? null : Rb(c);\n                        case Z.ELEMENT_ID:\n                          return d.id;\n                        case Z.CLASS_NAME:\n                          return d.classList;\n                        case Z.IMAGE_URL:\n                          return d.imageUrl;\n                        default:\n                          return null;\n                      }\n                    })(b, c);\n                  case Kb.EVENT:\n                    return (function (a, b) {\n                      switch (a) {\n                        case Z.EVENT_NAME:\n                          return b.event;\n                        default:\n                          return null;\n                      }\n                    })(b, c);\n                  default:\n                    return null;\n                }\n              }\n              function Ob(a) {\n                return null != a ? a.split('#')[0] : a;\n              }\n              function Pb(a, b) {\n                var c;\n                a = a.replace(\n                  /[\\-!$><-==&_\\/\\?\\.,0-9:; \\]\\[%~\\\"\\{\\}\\)\\(\\+\\@\\^\\`]/g,\n                  ' '\n                );\n                var d = a.replace(/([A-Z])/g, ' $1').split(' ');\n                if (null == d || 0 == d.length) return '';\n                for (a = d[0], c = 1; c < d.length; c++)\n                  null != d[c - 1] &&\n                  null != d[c] &&\n                  1 === d[c - 1].length &&\n                  1 === d[c].length &&\n                  d[c - 1] === d[c - 1].toUpperCase() &&\n                  d[c] === d[c].toUpperCase()\n                    ? (a += d[c])\n                    : (a += ' ' + d[c]);\n                d = a.split(' ');\n                if (null == d || 0 == d.length) return a;\n                a = '';\n                b = b ? 1 : 2;\n                for (c = 0; c < d.length; c++)\n                  null != d[c] && d[c].length > b && (a += d[c] + ' ');\n                return a.replace(/\\s+/g, ' ');\n              }\n              function Qb(a) {\n                var b = Pb(a, !0).toLowerCase().split(' ');\n                return b\n                  .filter(function (a, c) {\n                    return b.indexOf(a) === c;\n                  })\n                  .join(' ')\n                  .trim();\n              }\n              function Rb(a) {\n                return Pb(a, !1).toLowerCase().trim();\n              }\n              function Sb(a, b) {\n                if (b.startsWith('*.')) {\n                  var c = b.slice(2).split('.').reverse(),\n                    d = a.split('.').reverse();\n                  if (c.length !== d.length) return !1;\n                  for (var e = 0; e < c.length; e++)\n                    if (c[e] !== d[e]) return !1;\n                  return !0;\n                }\n                return a === b;\n              }\n              function Tb(a, b) {\n                if (\n                  !(function (a, b) {\n                    switch (a) {\n                      case Jb.LOAD:\n                        return 'PageView' === b.event;\n                      case Jb.CLICK:\n                        return 'SubscribedButtonClick' === b.event;\n                      case Jb.TRACK:\n                        return !0;\n                      case Jb.BECOME_VISIBLE:\n                      default:\n                        return !1;\n                    }\n                  })(a.action, b)\n                )\n                  return !1;\n                b = Nb(a.targetType, a.extractor, b);\n                if (null == b) return !1;\n                var c = a.value;\n                return (\n                  null != c &&\n                  ((a.extractor !== Z.TOKENIZED_TEXT_V1 &&\n                    a.extractor !== Z.TOKENIZED_TEXT_V2) ||\n                    (c = c.toLowerCase()),\n                  (function (a, b, c) {\n                    switch (a) {\n                      case Lb.EQUALS:\n                        return (\n                          b === c ||\n                          b.toLowerCase() ===\n                            unescape(encodeURIComponent(c)).toLowerCase() ||\n                          Qb(b) === c ||\n                          Ob(b) === Ob(c)\n                        );\n                      case Lb.CONTAINS:\n                        return null != c && c.includes(b);\n                      case Lb.DOMAIN_MATCHES:\n                        return Sb(c, b);\n                      case Lb.STRING_MATCHES:\n                        return null != c && Ib(b, c);\n                      default:\n                        return !1;\n                    }\n                  })(a.operator, c, b))\n                );\n              }\n              var Ub = {\n                  isMatchESTRule: function (a, b) {\n                    var c = a;\n                    'string' == typeof a && (c = JSON.parse(a));\n                    for (\n                      var a = Hb(c, Fb), c = [], d = 0;\n                      d < a.conditions.length;\n                      d++\n                    )\n                      c.push(Tb(a.conditions[d], b));\n                    switch (a.type) {\n                      case Mb.ALL:\n                        return !c.includes(!1);\n                      case Mb.ANY:\n                        return c.includes(!0);\n                      case Mb.NONE:\n                        return !c.includes(!0);\n                    }\n                    return !1;\n                  },\n                  getKeywordsStringFromTextV1: Qb,\n                  getKeywordsStringFromTextV2: Rb,\n                  domainMatches: Sb,\n                },\n                Vb = D.coerce;\n              a = D.Typed;\n              var $ = k.each,\n                Wb = k.filter,\n                Xb = k.reduce,\n                Yb = [\n                  'product',\n                  'product_group',\n                  'vehicle',\n                  'automotive_model',\n                ],\n                Zb = a.objectWithFields({\n                  '@context': a.string(),\n                  additionalType: a.allowNull(a.string()),\n                  offers: a.allowNull(\n                    a.objectWithFields({\n                      priceCurrency: a.allowNull(a.string()),\n                      price: a.allowNull(a.string()),\n                    })\n                  ),\n                  productID: a.allowNull(a.string()),\n                  sku: a.allowNull(a.string()),\n                  '@type': a.string(),\n                }),\n                $b = a.objectWithFields({\n                  '@context': a.string(),\n                  '@type': a.string(),\n                  item: Zb,\n                }),\n                ac = a.objectWithFields({\n                  '@context': a.string(),\n                  '@type': a.string(),\n                  itemListElement: a.array(),\n                  totalPrice: a.allowNull(a.string()),\n                });\n              function bc(a) {\n                a = Vb(a, Zb);\n                if (null == a) return null;\n                var b = 'string' == typeof a.productID ? a.productID : null,\n                  c = 'string' == typeof a.sku ? a.sku : null,\n                  d = a.offers,\n                  e = null,\n                  f = null;\n                null != d && ((e = fc(d.price)), (f = d.priceCurrency));\n                d =\n                  'string' == typeof a.additionalType &&\n                  Yb.includes(a.additionalType)\n                    ? a.additionalType\n                    : null;\n                a = [b, c];\n                b = {};\n                return (\n                  (a = Wb(a, function (a) {\n                    return null != a;\n                  })).length && (b.content_ids = a),\n                  null != f && (b.currency = f),\n                  null != e && (b.value = e),\n                  null != d && (b.content_type = d),\n                  [b]\n                );\n              }\n              function cc(a) {\n                a = Vb(a, $b);\n                return null == a ? null : ec([a.item]);\n              }\n              function dc(a) {\n                a = Vb(a, ac);\n                if (null == a) return null;\n                var b = 'string' == typeof a.totalPrice ? a.totalPrice : null;\n                b = fc(b);\n                a = ec(a.itemListElement);\n                var c = null;\n                return (\n                  null != a &&\n                    a.length > 0 &&\n                    (c = Xb(\n                      a,\n                      function (a, b) {\n                        b = b.value;\n                        if (null == b) return a;\n                        try {\n                          b = parseFloat(b);\n                          return null == a ? b : a + b;\n                        } catch (b) {\n                          return a;\n                        }\n                      },\n                      null,\n                      !0\n                    )),\n                  (a = [\n                    {\n                      value: b,\n                    },\n                    {\n                      value: null != c ? c.toString() : null,\n                    },\n                  ].concat(a))\n                );\n              }\n              function ec(a) {\n                var b = [];\n                return (\n                  $(a, function (c) {\n                    if (null != a) {\n                      var d = 'string' == typeof c['@type'] ? c['@type'] : null;\n                      if (null !== d) {\n                        var e = null;\n                        switch (d) {\n                          case 'Product':\n                            e = bc(c);\n                            break;\n                          case 'ItemList':\n                            e = dc(c);\n                            break;\n                          case 'ListItem':\n                            e = cc(c);\n                        }\n                        null != e && (b = b.concat(e));\n                      }\n                    }\n                  }),\n                  (b = Wb(b, function (a) {\n                    return null != a;\n                  })),\n                  $(b, function (a) {\n                    $(Object.keys(a), function (b) {\n                      var c = a[b];\n                      (Array.isArray(c) && c.length > 0) ||\n                        ('string' == typeof c && '' !== c) ||\n                        delete a[b];\n                    });\n                  }),\n                  (b = Wb(b, function (a) {\n                    return Object.keys(a).length > 0;\n                  }))\n                );\n              }\n              function fc(a) {\n                if (null == a) return null;\n                a = a.replace(/\\\\u[\\dA-F]{4}/gi, function (a) {\n                  a = a.replace(/\\\\u/g, '');\n                  a = parseInt(a, 16);\n                  return String.fromCharCode(a);\n                });\n                if (\n                  !gc(\n                    (a = (function (a) {\n                      a = a;\n                      if (a.length >= 3) {\n                        var b = a.substring(a.length - 3);\n                        if (/((\\.)(\\d)(0)|(\\,)(0)(0))/.test(b)) {\n                          var c = b.charAt(0),\n                            d = b.charAt(1);\n                          b = b.charAt(2);\n                          '0' !== d && (c += d),\n                            '0' !== b && (c += b),\n                            1 === c.length && (c = ''),\n                            (a = a.substring(0, a.length - 3) + c);\n                        }\n                      }\n                      return a;\n                    })(\n                      (a = (a = (a = a.replace(/[^\\d,\\.]/g, '')).replace(\n                        /(\\.){2,}/g,\n                        ''\n                      )).replace(/(\\,){2,}/g, ''))\n                    ))\n                  )\n                )\n                  return null;\n                var b = (function (a) {\n                  a = a;\n                  if (null == a) return null;\n                  var b = (function (a) {\n                    a = a.replace(/\\,/g, '');\n                    return ic(hc(a), !1);\n                  })(a);\n                  a = (function (a) {\n                    a = a.replace(/\\./g, '');\n                    return ic(hc(a.replace(/\\,/g, '.')), !0);\n                  })(a);\n                  if (null == b || null == a)\n                    return null != b ? b : null != a ? a : null;\n                  var c = a.length;\n                  c > 0 && '0' !== a.charAt(c - 1) && (c -= 1);\n                  return b.length >= c ? b : a;\n                })(a);\n                return null == b ? null : gc((a = b)) ? a : null;\n              }\n              function gc(a) {\n                return /\\d/.test(a);\n              }\n              function hc(a) {\n                a = a;\n                var b = a.indexOf('.');\n                return b < 0\n                  ? a\n                  : (a =\n                      a.substring(0, b + 1) +\n                      a.substring(b + 1).replace(/\\./g, ''));\n              }\n              function ic(a, b) {\n                try {\n                  a = parseFloat(a);\n                  if ('number' != typeof (c = a) || Number.isNaN(c))\n                    return null;\n                  c = b ? 3 : 2;\n                  return parseFloat(a.toFixed(c)).toString();\n                } catch (a) {\n                  return null;\n                }\n                var c;\n              }\n              var jc = {\n                  genCustomData: ec,\n                  reduceCustomData: function (a) {\n                    if (0 === a.length) return {};\n                    var b = Xb(\n                      a,\n                      function (a, b) {\n                        return (\n                          $(Object.keys(b), function (c) {\n                            var d = b[c],\n                              e = a[c];\n                            if (null == e) a[c] = d;\n                            else if (Array.isArray(e)) {\n                              d = Array.isArray(d) ? d : [d];\n                              a[c] = e.concat(d);\n                            }\n                          }),\n                          a\n                        );\n                      },\n                      {}\n                    );\n                    return (\n                      $(Object.keys(b), function (a) {\n                        b[a], null == b[a] && delete b[a];\n                      }),\n                      b\n                    );\n                  },\n                  getProductData: bc,\n                  getItemListData: dc,\n                  getListItemData: cc,\n                  genNormalizePrice: fc,\n                },\n                kc = function (a, b) {\n                  var c = a.id,\n                    d = a.tagName,\n                    f = e(a);\n                  d = d.toLowerCase();\n                  var g = a.className,\n                    h = a.querySelectorAll(S).length,\n                    i = null;\n                  'A' === a.tagName && a instanceof HTMLAnchorElement && a.href\n                    ? (i = a.href)\n                    : null != b &&\n                      b instanceof HTMLFormElement &&\n                      b.action &&\n                      (i = b.action),\n                    'string' != typeof i && (i = '');\n                  b = {\n                    classList: g,\n                    destination: i,\n                    id: c,\n                    imageUrl: Na(a),\n                    innerText: f || '',\n                    numChildButtons: h,\n                    tag: d,\n                    type: a.getAttribute('type'),\n                  };\n                  return (\n                    (a instanceof HTMLInputElement ||\n                      a instanceof HTMLSelectElement ||\n                      a instanceof HTMLTextAreaElement ||\n                      a instanceof HTMLButtonElement) &&\n                      ((b.name = a.name), (b.value = a.value)),\n                    a instanceof HTMLAnchorElement && (b.name = a.name),\n                    b\n                  );\n                },\n                lc = function () {\n                  var a = g.querySelector('title');\n                  return {\n                    title: Q(a && a.text, 500),\n                  };\n                },\n                mc = function (a, b) {\n                  var c = a;\n                  c =\n                    a.matches ||\n                    c.matchesSelector ||\n                    c.mozMatchesSelector ||\n                    c.msMatchesSelector ||\n                    c.oMatchesSelector ||\n                    c.webkitMatchesSelector ||\n                    null;\n                  return null !== c && c.bind(a)(b);\n                },\n                nc = function (a) {\n                  if (a instanceof HTMLInputElement) return a.form;\n                  if (mc(a, Ka)) return null;\n                  for (a = t(a); 'FORM' !== a.nodeName; ) {\n                    var b = t(a.parentElement);\n                    if (null == b) return null;\n                    a = b;\n                  }\n                  return a;\n                },\n                oc = function (a) {\n                  return Ja(a).substring(0, 200);\n                },\n                pc = function (a) {\n                  if (\n                    null != f.FacebookIWL &&\n                    null != f.FacebookIWL.getIWLRoot &&\n                    'function' == typeof f.FacebookIWL.getIWLRoot\n                  ) {\n                    var b = f.FacebookIWL.getIWLRoot();\n                    return b && b.contains(a);\n                  }\n                  return !1;\n                },\n                qc = k\n                  .filter(S.split(R), function (a) {\n                    return 'a' !== a;\n                  })\n                  .join(R),\n                rc = function a(b, c) {\n                  if (null == b || !Ua(b)) return null;\n                  if (mc(b, c ? S : qc)) return b;\n                  b = t(b.parentNode);\n                  return null != b ? a(b, c) : null;\n                };\n              c.d(b, 'inferredEventsSharedUtils', function () {\n                return sc;\n              }),\n                c.d(b, 'getJsonLDForExtractors', function () {\n                  return Da;\n                }),\n                c.d(b, 'getParameterExtractorFromGraphPayload', function () {\n                  return Ea;\n                }),\n                c.d(b, 'unicodeSafeTruncate', function () {\n                  return Q;\n                }),\n                c.d(b, 'signalsGetTextFromElement', function () {\n                  return e;\n                }),\n                c.d(b, 'signalsGetTextOrValueFromElement', function () {\n                  return Ja;\n                }),\n                c.d(b, 'signalsGetValueFromHTMLElement', function () {\n                  return i;\n                }),\n                c.d(b, 'signalsGetButtonImageUrl', function () {\n                  return Na;\n                }),\n                c.d(b, 'signalsIsSaneButton', function () {\n                  return Ua;\n                }),\n                c.d(b, 'signalsConvertNodeToHTMLElement', function () {\n                  return t;\n                }),\n                c.d(b, 'SignalsESTRuleEngine', function () {\n                  return Ub;\n                }),\n                c.d(b, 'SignalsESTCustomData', function () {\n                  return jc;\n                }),\n                c.d(b, 'signalsExtractButtonFeatures', function () {\n                  return kc;\n                }),\n                c.d(b, 'signalsExtractPageFeatures', function () {\n                  return lc;\n                }),\n                c.d(b, 'signalsExtractForm', function () {\n                  return nc;\n                }),\n                c.d(b, 'signalsGetTruncatedButtonText', function () {\n                  return oc;\n                }),\n                c.d(b, 'signalsIsIWLElement', function () {\n                  return pc;\n                }),\n                c.d(b, 'signalsGetWrappingButton', function () {\n                  return rc;\n                });\n              var sc = d;\n            },\n          ]);\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsValidationUtils', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.stringStartsWith,\n            c = /^[a-f0-9]{64}$/i,\n            d = /^\\s+|\\s+$/g,\n            e = /\\s+/g,\n            g = /[!\\\"#\\$%&\\'\\(\\)\\*\\+,\\-\\.\\/:;<=>\\?@ \\[\\\\\\]\\^_`\\{\\|\\}~\\s]+/g,\n            h = /\\W+/g,\n            i = /^1\\(?\\d{3}\\)?\\d{7}$/,\n            j = /^47\\d{8}$/,\n            l = /^\\d{1,4}\\(?\\d{2,3}\\)?\\d{4,}$/;\n          function m(a) {\n            return typeof a === 'string' ? a.replace(d, '') : '';\n          }\n          function n(a) {\n            var b =\n                arguments.length > 1 && arguments[1] !== void 0\n                  ? arguments[1]\n                  : 'whitespace_only',\n              c = '';\n            if (typeof a === 'string')\n              switch (b) {\n                case 'whitespace_only':\n                  c = a.replace(e, '');\n                  break;\n                case 'whitespace_and_punctuation':\n                  c = a.replace(g, '');\n                  break;\n                case 'all_non_latin_alpha_numeric':\n                  c = a.replace(h, '');\n                  break;\n              }\n            return c;\n          }\n          function o(a) {\n            return typeof a === 'string' && c.test(a);\n          }\n          function p(a) {\n            a = String(a)\n              .replace(/[\\-\\s]+/g, '')\n              .replace(/^\\+?0{0,2}/, '');\n            if (b(a, '0')) return !1;\n            if (b(a, '1')) return i.test(a);\n            return b(a, '47') ? j.test(a) : l.test(a);\n          }\n          k.exports = {\n            isInternationalPhoneNumber: p,\n            looksLikeHashed: o,\n            strip: n,\n            trim: m,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsPixelPIIConstants', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsUtils'),\n            b = a.keys;\n          a = a.map;\n          var c = {\n              ct: 'ct',\n              city: 'ct',\n              dob: 'db',\n              dobd: 'dobd',\n              dobm: 'dobm',\n              doby: 'doby',\n              email: 'em',\n              fn: 'fn',\n              f_name: 'fn',\n              gen: 'ge',\n              ln: 'ln',\n              l_name: 'ln',\n              phone: 'ph',\n              st: 'st',\n              state: 'st',\n              zip: 'zp',\n              zip_code: 'zp',\n            },\n            d = {\n              CITY: ['city'],\n              DATE: ['date', 'dt', 'day', 'dobd'],\n              DOB: ['birth', 'bday', 'bdate', 'bmonth', 'byear', 'dob'],\n              FEMALE: ['female', 'girl', 'woman'],\n              FIRST_NAME: ['firstname', 'fn', 'fname', 'givenname', 'forename'],\n              GENDER_FIELDS: ['gender', 'gen', 'sex'],\n              GENDER_VALUES: ['male', 'boy', 'man', 'female', 'girl', 'woman'],\n              LAST_NAME: [\n                'lastname',\n                'ln',\n                'lname',\n                'surname',\n                'sname',\n                'familyname',\n              ],\n              MALE: ['male', 'boy', 'man'],\n              MONTH: ['month', 'mo', 'mnth', 'dobm'],\n              NAME: ['name', 'fullname'],\n              PHONE_NUMBER: ['phone', 'mobile', 'contact'],\n              RESTRICTED: [\n                'ssn',\n                'unique',\n                'cc',\n                'card',\n                'cvv',\n                'cvc',\n                'cvn',\n                'creditcard',\n                'billing',\n                'security',\n                'social',\n                'pass',\n              ],\n              STATE: ['state', 'province'],\n              USERNAME: ['username'],\n              YEAR: ['year', 'yr', 'doby'],\n              ZIP_CODE: [\n                'zip',\n                'zcode',\n                'pincode',\n                'pcode',\n                'postalcode',\n                'postcode',\n              ],\n            },\n            e =\n              /^[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+(:?\\.[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+)*@(?:[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?$/i,\n            g = Object.freeze({\n              US: '^\\\\d{5}$',\n            });\n          a = a(b(g), function (a) {\n            return g[a];\n          });\n          b = {};\n          b['^\\\\d{1,2}/\\\\d{1,2}/\\\\d{4}$'] = ['DD/MM/YYYY', 'MM/DD/YYYY'];\n          b['^\\\\d{1,2}-\\\\d{1,2}-\\\\d{4}$'] = ['DD-MM-YYYY', 'MM-DD-YYYY'];\n          b['^\\\\d{4}/\\\\d{1,2}/\\\\d{1,2}$'] = ['YYYY/MM/DD'];\n          b['^\\\\d{4}-\\\\d{1,2}-\\\\d{1,2}$'] = ['YYYY-MM-DD'];\n          b['^\\\\d{1,2}/\\\\d{1,2}/\\\\d{2}$'] = ['DD/MM/YY', 'MM/DD/YY'];\n          b['^\\\\d{1,2}-\\\\d{1,2}-\\\\d{2}$'] = ['DD-MM-YY', 'MM-DD-YY'];\n          b['^\\\\d{2}/\\\\d{1,2}/\\\\d{1,2}$'] = ['YY/MM/DD'];\n          b['^\\\\d{2}-\\\\d{1,2}-\\\\d{1,2}$'] = ['YY-MM-DD'];\n          var h = [\n            'MM-DD-YYYY',\n            'MM/DD/YYYY',\n            'DD-MM-YYYY',\n            'DD/MM/YYYY',\n            'YYYY-MM-DD',\n            'YYYY/MM/DD',\n            'MM-DD-YY',\n            'MM/DD/YY',\n            'DD-MM-YY',\n            'DD/MM/YY',\n            'YY-MM-DD',\n            'YY/MM/DD',\n          ];\n          k.exports = {\n            EMAIL_REGEX: e,\n            POSSIBLE_FEATURE_FIELDS: d,\n            PII_KEY_ALIAS_TO_SHORT_CODE: c,\n            SIGNALS_FBEVENTS_DATE_FORMATS: h,\n            VALID_DATE_REGEX_FORMATS: b,\n            ZIP_REGEX_VALUES: a,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsPixelPIIUtils', function () {\n      return (function (g, h, i, j) {\n        var k = {\n          exports: {},\n        };\n        k.exports;\n        (function () {\n          'use strict';\n\n          var a =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            b = f.getFbeventsModules('SignalsFBEventsNormalizers'),\n            c = f.getFbeventsModules('SignalsFBEventsPixelPIISchema'),\n            d = f.getFbeventsModules('SignalsFBEventsUtils'),\n            e = f.getFbeventsModules('normalizeSignalsFBEventsEmailType'),\n            g = f.getFbeventsModules('normalizeSignalsFBEventsPostalCodeType'),\n            h = f.getFbeventsModules('normalizeSignalsFBEventsPhoneNumberType'),\n            i = f.getFbeventsModules('normalizeSignalsFBEventsStringType'),\n            j = i.normalizeName,\n            l = i.normalizeCity,\n            m = i.normalizeState;\n          i = f.getFbeventsModules('SignalsPixelPIIConstants');\n          var n = i.EMAIL_REGEX,\n            o = i.POSSIBLE_FEATURE_FIELDS,\n            p = i.PII_KEY_ALIAS_TO_SHORT_CODE,\n            q = i.ZIP_REGEX_VALUES,\n            r = d.some,\n            s = d.stringIncludes;\n          function t(a) {\n            var b = a.id,\n              c = a.keyword,\n              d = a.name,\n              e = a.placeholder;\n            a = a.value;\n            return c.length > 2\n              ? s(d, c) || s(b, c) || s(e, c) || s(a, c)\n              : d === c || b === c || e === c || a === c;\n          }\n          function u(a) {\n            var b = a.id,\n              c = a.keywords,\n              d = a.name,\n              e = a.placeholder,\n              f = a.value;\n            return r(c, function (a) {\n              return t({\n                id: b,\n                keyword: a,\n                name: d,\n                placeholder: e,\n                value: f,\n              });\n            });\n          }\n          function v(a) {\n            return a != null && typeof a === 'string' && n.test(a);\n          }\n          function w(a) {\n            var b = a.value,\n              c = a.parentElement;\n            a = a.previousElementSibling;\n            var d = null;\n            a instanceof HTMLInputElement\n              ? (d = a.value)\n              : a instanceof HTMLTextAreaElement && (d = a.value);\n            if (d == null || typeof d !== 'string') return null;\n            if (c == null) return null;\n            a = c.innerText != null ? c.innerText : c.textContent;\n            if (a == null || a.indexOf('@') < 0) return null;\n            c = d + '@' + b;\n            return !n.test(c) ? null : c;\n          }\n          function x(a, b) {\n            var c = a.name,\n              d = a.id,\n              e = a.placeholder;\n            a = a.value;\n            return (\n              (b === 'tel' && !(a.length <= 6 && o.ZIP_CODE.includes(d))) ||\n              u({\n                id: d,\n                keywords: o.PHONE_NUMBER,\n                name: c,\n                placeholder: e,\n              })\n            );\n          }\n          function y(a) {\n            var b = a.name,\n              c = a.id;\n            a = a.placeholder;\n            return u({\n              id: c,\n              keywords: o.FIRST_NAME,\n              name: b,\n              placeholder: a,\n            });\n          }\n          function z(a) {\n            var b = a.name,\n              c = a.id;\n            a = a.placeholder;\n            return u({\n              id: c,\n              keywords: o.LAST_NAME,\n              name: b,\n              placeholder: a,\n            });\n          }\n          function A(a) {\n            var b = a.name,\n              c = a.id;\n            a = a.placeholder;\n            return (\n              u({\n                id: c,\n                keywords: o.NAME,\n                name: b,\n                placeholder: a,\n              }) &&\n              !u({\n                id: c,\n                keywords: o.USERNAME,\n                name: b,\n                placeholder: a,\n              })\n            );\n          }\n          function B(a) {\n            var b = a.name,\n              c = a.id;\n            a = a.placeholder;\n            return u({\n              id: c,\n              keywords: o.CITY,\n              name: b,\n              placeholder: a,\n            });\n          }\n          function C(a) {\n            var b = a.name,\n              c = a.id;\n            a = a.placeholder;\n            return u({\n              id: c,\n              keywords: o.STATE,\n              name: b,\n              placeholder: a,\n            });\n          }\n          function D(a, b, c) {\n            var d = a.name,\n              e = a.id,\n              f = a.placeholder;\n            a = a.value;\n            if ((b === 'checkbox' || b === 'radio') && c === !0)\n              return u({\n                id: e,\n                keywords: o.GENDER_VALUES,\n                name: d,\n                placeholder: f,\n                value: a,\n              });\n            else if (b === 'text')\n              return u({\n                id: e,\n                keywords: o.GENDER_FIELDS,\n                name: d,\n                placeholder: f,\n              });\n            return !1;\n          }\n          function E(a, b) {\n            var c = a.name;\n            a = a.id;\n            return (\n              (b !== '' &&\n                r(q, function (a) {\n                  a = b.match(String(a));\n                  return a != null && a[0] === b;\n                })) ||\n              u({\n                id: a,\n                keywords: o.ZIP_CODE,\n                name: c,\n              })\n            );\n          }\n          function F(a) {\n            var b = a.name;\n            a = a.id;\n            return u({\n              id: a,\n              keywords: o.RESTRICTED,\n              name: b,\n            });\n          }\n          function G(a) {\n            return a.trim().toLowerCase().replace(/[_-]/g, '');\n          }\n          function H(a) {\n            return a.trim().toLowerCase();\n          }\n          function I(a) {\n            if (\n              r(o.MALE, function (b) {\n                return b === a;\n              })\n            )\n              return 'm';\n            else if (\n              r(o.FEMALE, function (b) {\n                return b === a;\n              })\n            )\n              return 'f';\n            return '';\n          }\n          function J(a) {\n            return p[a] !== void 0 ? p[a] : a;\n          }\n          function K(a, d) {\n            a = J(a);\n            a = c[a];\n            (a == null || a.length === 0) && (a = c['default']);\n            var e = b[a.type];\n            if (e == null) return null;\n            e = e(d, a.typeParams);\n            return e != null && e !== '' ? e : null;\n          }\n          function L(b, c) {\n            var d = c.value,\n              f = c instanceof HTMLInputElement && c.checked === !0,\n              i = b.name,\n              k = b.id,\n              n = b.inputType;\n            b = b.placeholder;\n            i = {\n              id: G(i),\n              name: G(k),\n              placeholder: (b != null && G(b)) || '',\n              value: H(d),\n            };\n            if (F(i) || n === 'password' || d === '' || d == null) return null;\n            else if (v(i.value))\n              return {\n                em: e(i.value),\n              };\n            else if (w(c) != null)\n              return {\n                em: e(w(c)),\n              };\n            else if (y(i))\n              return {\n                fn: j(i.value),\n              };\n            else if (z(i))\n              return {\n                ln: j(i.value),\n              };\n            else if (x(i, n))\n              return {\n                ph: h(i.value),\n              };\n            else if (A(i)) {\n              k = i.value.split(' ');\n              b = {\n                fn: j(k[0]),\n              };\n              k.shift();\n              c = {\n                ln: j(k.join(' ')),\n              };\n              return a({}, b, c);\n            } else if (B(i))\n              return {\n                ct: l(i.value),\n              };\n            else if (C(i))\n              return {\n                st: m(i.value),\n              };\n            else if (n != null && D(i, n, f))\n              return {\n                ge: I(i.value),\n              };\n            else if (E(i, d))\n              return {\n                zp: g(i.value),\n              };\n            return null;\n          }\n          k.exports = {\n            extractPIIFields: L,\n            getNormalizedPIIKey: J,\n            getNormalizedPIIValue: K,\n          };\n        })();\n        return k.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEvents.plugins.identity', function () {\n      return (function (h, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var a = f.getFbeventsModules('SignalsFBEventsLogging'),\n            b = a.logUserError;\n          a = f.getFbeventsModules('SignalsFBEventsPlugin');\n          var c = f.getFbeventsModules('SignalsFBEventsUtils');\n          c = c.FBSet;\n          var d = f.getFbeventsModules('SignalsPixelPIIUtils'),\n            h = d.getNormalizedPIIKey,\n            l = d.getNormalizedPIIValue,\n            m = f.getFbeventsModules('sha256_with_dependencies_new'),\n            n = /^[A-Fa-f0-9]{64}$|^[A-Fa-f0-9]{32}$/,\n            o =\n              /^[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+(:?\\.[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+)*@(?:[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?$/i;\n          d = /^\\s+|\\s+$/g;\n          Object.prototype.hasOwnProperty;\n          var p = new c(['uid']);\n          function q(a) {\n            return !!a && o.test(a);\n          }\n          function r(a, c) {\n            var d = h(a);\n            if (c == null || c === '') return null;\n            var e = l(d, c);\n            if (d === 'em' && !q(e)) {\n              b({\n                key_type: 'email address',\n                key_val: a,\n                type: 'PII_INVALID_TYPE',\n              });\n              throw new Error();\n            }\n            return e != null && e != '' ? e : c;\n          }\n          function s(a, c) {\n            if (c == null) return null;\n            var d = /\\[(.*)\\]/.exec(a);\n            if (d == null) throw new Error();\n            d = g(d, 2);\n            d = d[1];\n            if (p.has(d)) {\n              if (q(c)) {\n                b({\n                  key: a,\n                  type: 'PII_UNHASHED_PII',\n                });\n                throw new Error();\n              }\n              return c;\n            }\n            if (n.test(c)) return c.toLowerCase();\n            a = r(d, c);\n            return a != null && a != '' ? m(a) : null;\n          }\n          d = (function (a) {\n            k(b, a);\n            function b(a) {\n              i(this, b);\n              var c = j(\n                this,\n                (b.__proto__ || Object.getPrototypeOf(b)).call(\n                  this,\n                  function (b) {\n                    b.piiTranslator = a;\n                  }\n                )\n              );\n              c.piiTranslator = a;\n              return c;\n            }\n            return b;\n          })(a);\n          c = new d(s);\n          e.exports = c;\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    e.exports = f.getFbeventsModules('SignalsFBEvents.plugins.identity');\n    f.registerPlugin &&\n      f.registerPlugin('fbevents.plugins.identity', e.exports);\n    f.ensureModuleRegistered('fbevents.plugins.identity', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('signalsFBEventsGetIsAndroid', function () {\n      return (function (f, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var a = f.navigator;\n          a = a.userAgent;\n          var b = a.indexOf('Android') >= 0;\n          function c() {\n            return b;\n          }\n          e.exports = c;\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsGetIsAndroidIAW', function () {\n      return (function (a, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var b = f.getFbeventsModules('signalsFBEventsGetIsAndroid'),\n            c = a.navigator;\n          c = c.userAgent;\n          var d = c.indexOf('FB_IAB') >= 0,\n            g = c.indexOf('Instagram') >= 0,\n            h = 0;\n          c = c.match(/(FBAV|Instagram)[/\\s](\\d+)/);\n          if (c != null) {\n            c = c[0].match(/(\\d+)/);\n            c != null && (h = parseInt(c[0], 10));\n          }\n          function i(a, c) {\n            var e = b() && (d || g);\n            if (!e) return !1;\n            if (d && a != null) return a <= h;\n            return g && c != null ? c <= h : e;\n          }\n          e.exports = i;\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEvents.plugins.privacysandbox',\n      function () {\n        return (function (a, b, c, d) {\n          var e = {\n            exports: {},\n          };\n          e.exports;\n          (function () {\n            'use strict';\n\n            var a = f.getFbeventsModules('signalsFBEventsGetIsChrome'),\n              c = f.getFbeventsModules('signalsFBEventsGetIsAndroidIAW');\n            f.getFbeventsModules('SignalsParamList');\n            var d = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n              g = d.GPS_ENDPOINT,\n              h = f.getFbeventsModules('signalsFBEventsSendGET'),\n              i = f.getFbeventsModules('SignalsFBEventsFiredEvent');\n            d = f.getFbeventsModules('SignalsFBEventsPlugin');\n            e.exports = new d(function (d, e) {\n              if (!a() && !c()) return;\n              if (\n                b.featurePolicy == null ||\n                !b.featurePolicy.allowsFeature('attribution-reporting')\n              )\n                return;\n              i.listen(function (a, b) {\n                a = b.get('id');\n                if (a == null) return;\n                h(b, {\n                  ignoreRequestLengthCheck: !0,\n                  attributionReporting: !0,\n                  url: g,\n                });\n              });\n            });\n          })();\n          return e.exports;\n        })(a, b, c, d);\n      }\n    );\n    e.exports = f.getFbeventsModules('SignalsFBEvents.plugins.privacysandbox');\n    f.registerPlugin &&\n      f.registerPlugin('fbevents.plugins.privacysandbox', e.exports);\n    f.ensureModuleRegistered('fbevents.plugins.privacysandbox', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('signalsFBEventsGetIwlUrl', function () {\n      return (function (a, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var b = f.getFbeventsModules('signalsFBEventsGetTier'),\n            c = d();\n          function d() {\n            try {\n              if (a.trustedTypes && a.trustedTypes.createPolicy) {\n                var b = a.trustedTypes;\n                return b.createPolicy('facebook.com/signals/iwl', {\n                  createScriptURL: function (b) {\n                    var c = typeof a.URL === 'function' ? a.URL : a.webkitURL;\n                    c = new c(b);\n                    c =\n                      c.hostname.endsWith('.facebook.com') &&\n                      c.pathname == '/signals/iwl.js';\n                    if (!c) throw new Error('Disallowed script URL');\n                    return b;\n                  },\n                });\n              }\n            } catch (a) {}\n            return null;\n          }\n          e.exports = function (a, d) {\n            d = b(d);\n            d = d == null ? 'www.facebook.com' : 'www.' + d + '.facebook.com';\n            d = 'https://' + d + '/signals/iwl.js?pixel_id=' + a;\n            if (c != null) return c.createScriptURL(d);\n            else return d;\n          };\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('signalsFBEventsGetTier', function () {\n      return (function (f, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var a = /^https:\\/\\/www\\.([A-Za-z0-9\\.]+)\\.facebook\\.com\\/tr\\/?$/,\n            b = ['https://www.facebook.com/tr', 'https://www.facebook.com/tr/'];\n          e.exports = function (c) {\n            if (b.indexOf(c) !== -1) return null;\n            var d = a.exec(c);\n            if (d == null) throw new Error('Malformed tier: ' + c);\n            return d[1];\n          };\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEvents.plugins.iwlbootstrapper',\n      function () {\n        return (function (a, b, c, d) {\n          var e = {\n            exports: {},\n          };\n          e.exports;\n          (function () {\n            'use strict';\n\n            var c = f.getFbeventsModules('SignalsFBEventsIWLBootStrapEvent'),\n              d = f.getFbeventsModules('SignalsFBEventsLogging'),\n              g = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n              h = f.getFbeventsModules('SignalsFBEventsPlugin'),\n              i = f.getFbeventsModules('signalsFBEventsGetIwlUrl'),\n              j = f.getFbeventsModules('signalsFBEventsGetTier'),\n              k = d.logUserError,\n              l = /^https:\\/\\/.*\\.facebook\\.com$/i,\n              m = 'FACEBOOK_IWL_CONFIG_STORAGE_KEY',\n              n = null;\n            e.exports = new h(function (d, e) {\n              try {\n                n = a.sessionStorage\n                  ? a.sessionStorage\n                  : {\n                      getItem: function (a) {\n                        return null;\n                      },\n                      removeItem: function (a) {},\n                      setItem: function (a, b) {},\n                    };\n              } catch (a) {\n                return;\n              }\n              function h(c, d) {\n                var e = b.createElement('script');\n                e.async = !0;\n                e.onload = function () {\n                  if (!a.FacebookIWL || !a.FacebookIWL.init) return;\n                  var b = j(g.ENDPOINT);\n                  b != null &&\n                    a.FacebookIWL.set &&\n                    a.FacebookIWL.set('tier', b);\n                  d();\n                };\n                a.FacebookIWLSessionEnd = function () {\n                  n.removeItem(m), a.close();\n                };\n                e.src = i(c, g.ENDPOINT);\n                b.body && b.body.appendChild(e);\n              }\n              var o = !1,\n                p = function (a) {\n                  return !!(\n                    e &&\n                    e.pixelsByID &&\n                    Object.prototype.hasOwnProperty.call(e.pixelsByID, a)\n                  );\n                };\n              function q() {\n                if (o) return;\n                var b = n.getItem(m);\n                if (!b) return;\n                b = JSON.parse(b);\n                var c = b.pixelID,\n                  d = b.graphToken,\n                  e = b.sessionStartTime;\n                o = !0;\n                h(c, function () {\n                  var b = p(c) ? c.toString() : null;\n                  a.FacebookIWL.init(b, d, e);\n                });\n              }\n              function r(b) {\n                if (o) return;\n                h(b, function () {\n                  return a.FacebookIWL.showConfirmModal(b);\n                });\n              }\n              function s(a, b, c) {\n                n.setItem(\n                  m,\n                  JSON.stringify({\n                    graphToken: a,\n                    pixelID: b,\n                    sessionStartTime: c,\n                  })\n                ),\n                  q();\n              }\n              c.listen(function (b) {\n                var c = b.graphToken;\n                b = b.pixelID;\n                s(c, b);\n                a.FacebookIWLSessionEnd = function () {\n                  return n.removeItem(m);\n                };\n              });\n              function d(a) {\n                var b = a.data,\n                  c = b.graphToken,\n                  d = b.msg_type,\n                  f = b.pixelID;\n                b = b.sessionStartTime;\n                if (\n                  e &&\n                  e.pixelsByID &&\n                  e.pixelsByID[f] &&\n                  e.pixelsByID[f].codeless === 'false'\n                ) {\n                  k({\n                    pixelID: f,\n                    type: 'SITE_CODELESS_OPT_OUT',\n                  });\n                  return;\n                }\n                if (\n                  n.getItem(m) ||\n                  !l.test(a.origin) ||\n                  !(\n                    a.data &&\n                    (d === 'FACEBOOK_IWL_BOOTSTRAP' ||\n                      d === 'FACEBOOK_IWL_CONFIRM_DOMAIN')\n                  )\n                )\n                  return;\n                if (!Object.prototype.hasOwnProperty.call(e.pixelsByID, f)) {\n                  a.source.postMessage(\n                    'FACEBOOK_IWL_ERROR_PIXEL_DOES_NOT_MATCH',\n                    a.origin\n                  );\n                  return;\n                }\n                switch (d) {\n                  case 'FACEBOOK_IWL_BOOTSTRAP':\n                    a.source.postMessage(\n                      'FACEBOOK_IWL_BOOTSTRAP_ACK',\n                      a.origin\n                    );\n                    s(c, f, b);\n                    break;\n                  case 'FACEBOOK_IWL_CONFIRM_DOMAIN':\n                    a.source.postMessage(\n                      'FACEBOOK_IWL_CONFIRM_DOMAIN_ACK',\n                      a.origin\n                    );\n                    r(f);\n                    break;\n                }\n              }\n              if (n.getItem(m)) {\n                q();\n                return;\n              }\n              a.opener && a.addEventListener('message', d);\n            });\n          })();\n          return e.exports;\n        })(a, b, c, d);\n      }\n    );\n    e.exports = f.getFbeventsModules('SignalsFBEvents.plugins.iwlbootstrapper');\n    f.registerPlugin &&\n      f.registerPlugin('fbevents.plugins.iwlbootstrapper', e.exports);\n    f.ensureModuleRegistered('fbevents.plugins.iwlbootstrapper', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('SignalsFBEventsOptTrackingOptions', function () {\n      return (function (f, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          e.exports = {\n            AUTO_CONFIG_OPT_OUT: 1 << 0,\n            AUTO_CONFIG: 1 << 1,\n            CONFIG_LOADING: 1 << 2,\n            SUPPORTS_DEFINE_PROPERTY: 1 << 3,\n            SUPPORTS_SEND_BEACON: 1 << 4,\n            HAS_INVALIDATED_PII: 1 << 5,\n            SHOULD_PROXY: 1 << 6,\n            IS_HEADLESS: 1 << 7,\n            IS_SELENIUM: 1 << 8,\n            HAS_DETECTION_FAILED: 1 << 9,\n            HAS_CONFLICTING_PII: 1 << 10,\n            HAS_AUTOMATCHED_PII: 1 << 11,\n            FIRST_PARTY_COOKIES: 1 << 12,\n            IS_SHADOW_TEST: 1 << 13,\n          };\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered('SignalsFBEventsProxyState', function () {\n      return (function (f, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var a = !1;\n          e.exports = {\n            getShouldProxy: function () {\n              return a;\n            },\n            setShouldProxy: function (b) {\n              a = b;\n            },\n          };\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    f.ensureModuleRegistered(\n      'SignalsFBEvents.plugins.opttracking',\n      function () {\n        return (function (a, b, c, d) {\n          var e = {\n            exports: {},\n          };\n          e.exports;\n          (function () {\n            'use strict';\n\n            var b = f.getFbeventsModules('SignalsFBEventsEvents'),\n              c = b.getCustomParameters,\n              d = b.piiAutomatched,\n              g = b.piiConflicting,\n              h = b.piiInvalidated,\n              i = f.getFbeventsModules('SignalsFBEventsOptTrackingOptions');\n            b = f.getFbeventsModules('SignalsFBEventsPlugin');\n            var j = f.getFbeventsModules('SignalsFBEventsProxyState'),\n              k = f.getFbeventsModules('SignalsFBEventsUtils'),\n              l = k.some,\n              m = !1;\n            function n() {\n              try {\n                Object.defineProperty({}, 'test', {});\n              } catch (a) {\n                return !1;\n              }\n              return !0;\n            }\n            function o() {\n              return !!(a.navigator && a.navigator.sendBeacon);\n            }\n            function p(a, b) {\n              return a ? b : 0;\n            }\n            var q = ['_selenium', 'callSelenium', '_Selenium_IDE_Recorder'],\n              r = [\n                '__webdriver_evaluate',\n                '__selenium_evaluate',\n                '__webdriver_script_function',\n                '__webdriver_script_func',\n                '__webdriver_script_fn',\n                '__fxdriver_evaluate',\n                '__driver_unwrapped',\n                '__webdriver_unwrapped',\n                '__driver_evaluate',\n                '__selenium_unwrapped',\n                '__fxdriver_unwrapped',\n              ];\n            function s() {\n              if (u(q)) return !0;\n              var b = l(r, function (b) {\n                return a.document[b] ? !0 : !1;\n              });\n              if (b) return !0;\n              b = a.document;\n              for (var c in b)\n                if (c.match(/\\$[a-z]dc_/) && b[c].cache_) return !0;\n              if (\n                a.external &&\n                a.external.toString &&\n                a.external.toString().indexOf('Sequentum') >= 0\n              )\n                return !0;\n              if (b.documentElement && b.documentElement.getAttribute) {\n                c = l(['selenium', 'webdriver', 'driver'], function (b) {\n                  return a.document.documentElement.getAttribute(b) ? !0 : !1;\n                });\n                if (c) return !0;\n              }\n              return !1;\n            }\n            function t() {\n              if (u(['_phantom', '__nightmare', 'callPhantom'])) return !0;\n              return /HeadlessChrome/.test(a.navigator.userAgent) ? !0 : !1;\n            }\n            function u(b) {\n              b = l(b, function (b) {\n                return a[b] ? !0 : !1;\n              });\n              return b;\n            }\n            function v() {\n              var a = 0,\n                b = 0,\n                c = 0;\n              try {\n                (a = p(s(), i.IS_SELENIUM)), (b = p(t(), i.IS_HEADLESS));\n              } catch (a) {\n                c = i.HAS_DETECTION_FAILED;\n              }\n              return {\n                hasDetectionFailed: c,\n                isHeadless: b,\n                isSelenium: a,\n              };\n            }\n            k = new b(function (a, b) {\n              if (m) return;\n              var e = {};\n              h.listen(function (a) {\n                a != null && (e[typeof a === 'string' ? a : a.id] = !0);\n              });\n              var k = {};\n              g.listen(function (a) {\n                a != null && (k[typeof a === 'string' ? a : a.id] = !0);\n              });\n              var l = {};\n              d.listen(function (a) {\n                a != null && (l[typeof a === 'string' ? a : a.id] = !0);\n              });\n              c.listen(function (c) {\n                var d = b.optIns,\n                  f = p(\n                    c != null &&\n                      d.isOptedOut(c.id, 'AutomaticSetup') &&\n                      d.isOptedOut(c.id, 'InferredEvents') &&\n                      d.isOptedOut(c.id, 'Microdata'),\n                    i.AUTO_CONFIG_OPT_OUT\n                  ),\n                  g = p(\n                    c != null &&\n                      (d.isOptedIn(c.id, 'AutomaticSetup') ||\n                        d.isOptedIn(c.id, 'InferredEvents') ||\n                        d.isOptedIn(c.id, 'Microdata')),\n                    i.AUTO_CONFIG\n                  ),\n                  h = p(a.disableConfigLoading !== !0, i.CONFIG_LOADING),\n                  m = p(n(), i.SUPPORTS_DEFINE_PROPERTY),\n                  q = p(o(), i.SUPPORTS_SEND_BEACON),\n                  r = p(c != null && k[c.id], i.HAS_CONFLICTING_PII),\n                  s = p(c != null && e[c.id], i.HAS_INVALIDATED_PII),\n                  t = p(c != null && l[c.id], i.HAS_AUTOMATCHED_PII),\n                  u = p(j.getShouldProxy(), i.SHOULD_PROXY),\n                  w = p(\n                    c != null && d.isOptedIn(c.id, 'FirstPartyCookies'),\n                    i.FIRST_PARTY_COOKIES\n                  );\n                d = p(\n                  c != null && d.isOptedIn(c.id, 'ShadowTest'),\n                  i.IS_SHADOW_TEST\n                );\n                c = v();\n                f =\n                  f |\n                  g |\n                  h |\n                  m |\n                  q |\n                  s |\n                  u |\n                  c.isHeadless |\n                  c.isSelenium |\n                  c.hasDetectionFailed |\n                  r |\n                  t |\n                  w |\n                  d;\n                return {\n                  o: f,\n                };\n              });\n              m = !0;\n            });\n            k.OPTIONS = i;\n            e.exports = k;\n          })();\n          return e.exports;\n        })(a, b, c, d);\n      }\n    );\n    e.exports = f.getFbeventsModules('SignalsFBEvents.plugins.opttracking');\n    f.registerPlugin &&\n      f.registerPlugin('fbevents.plugins.opttracking', e.exports);\n    f.ensureModuleRegistered('fbevents.plugins.opttracking', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\n(function (a, b, c, d) {\n  var e = {\n    exports: {},\n  };\n  e.exports;\n  (function () {\n    var f = a.fbq;\n    f.execStart = a.performance && a.performance.now && a.performance.now();\n    if (\n      !(function () {\n        var b = a.postMessage || function () {};\n        if (!f) {\n          b(\n            {\n              action: 'FB_LOG',\n              logType: 'Facebook Pixel Error',\n              logMessage: 'Pixel code is not installed correctly on this page',\n            },\n            '*'\n          );\n          'error' in console &&\n            console.error(\n              'Facebook Pixel Error: Pixel code is not installed correctly on this page'\n            );\n          return !1;\n        }\n        return !0;\n      })()\n    )\n      return;\n    var g = (function () {\n      function a(a, b) {\n        var c = [],\n          d = !0,\n          e = !1,\n          f = void 0;\n        try {\n          for (\n            var g =\n                a[\n                  typeof Symbol === 'function' ? Symbol.iterator : '@@iterator'\n                ](),\n              a;\n            !(d = (a = g.next()).done);\n            d = !0\n          ) {\n            c.push(a.value);\n            if (b && c.length === b) break;\n          }\n        } catch (a) {\n          (e = !0), (f = a);\n        } finally {\n          try {\n            !d && g['return'] && g['return']();\n          } finally {\n            if (e) throw f;\n          }\n        }\n        return c;\n      }\n      return function (b, c) {\n        if (Array.isArray(b)) return b;\n        else if (\n          (typeof Symbol === 'function' ? Symbol.iterator : '@@iterator') in\n          Object(b)\n        )\n          return a(b, c);\n        else\n          throw new TypeError(\n            'Invalid attempt to destructure non-iterable instance'\n          );\n      };\n    })();\n    function h(a) {\n      return Array.isArray(a) ? a : Array.from(a);\n    }\n    function i(a) {\n      if (Array.isArray(a)) {\n        for (var b = 0, c = Array(a.length); b < a.length; b++) c[b] = a[b];\n        return c;\n      } else return Array.from(a);\n    }\n    f.__fbeventsModules ||\n      ((f.__fbeventsModules = {}),\n      (f.__fbeventsResolvedModules = {}),\n      (f.getFbeventsModules = function (a) {\n        f.__fbeventsResolvedModules[a] ||\n          (f.__fbeventsResolvedModules[a] = f.__fbeventsModules[a]());\n        return f.__fbeventsResolvedModules[a];\n      }),\n      (f.fbIsModuleLoaded = function (a) {\n        return !!f.__fbeventsModules[a];\n      }),\n      (f.ensureModuleRegistered = function (b, a) {\n        f.fbIsModuleLoaded(b) || (f.__fbeventsModules[b] = a);\n      }));\n    f.ensureModuleRegistered('SignalsFBEvents', function () {\n      return (function (a, b, c, d) {\n        var e = {\n          exports: {},\n        };\n        e.exports;\n        (function () {\n          'use strict';\n\n          var j =\n              Object.assign ||\n              function (a) {\n                for (var b = 1; b < arguments.length; b++) {\n                  var c = arguments[b];\n                  for (var d in c)\n                    Object.prototype.hasOwnProperty.call(c, d) && (a[d] = c[d]);\n                }\n                return a;\n              },\n            f = a.fbq;\n          f.execStart =\n            a.performance && typeof a.performance.now === 'function'\n              ? a.performance.now()\n              : null;\n          f.performanceMark = function (b, c) {\n            a.performance != null &&\n              typeof a.performance.mark === 'function' &&\n              (c != null\n                ? a.performance.mark(b + '_' + c)\n                : a.performance.mark(b));\n          };\n          var k = f.getFbeventsModules('SignalsFBEventsNetworkConfig'),\n            l = f.getFbeventsModules('SignalsFBEventsQE'),\n            m = f.getFbeventsModules('SignalsParamList'),\n            n = f.getFbeventsModules('signalsFBEventsSendEvent'),\n            o = f.getFbeventsModules('SignalsFBEventsUtils'),\n            p = f.getFbeventsModules('SignalsFBEventsLogging'),\n            q = f.getFbeventsModules('SignalsEventValidation'),\n            r = f.getFbeventsModules('SignalsFBEventsFBQ'),\n            aa = f.getFbeventsModules('SignalsFBEventsJSLoader'),\n            s = f.getFbeventsModules('SignalsFBEventsFireLock'),\n            t = f.getFbeventsModules('SignalsFBEventsMobileAppBridge'),\n            u = f.getFbeventsModules('signalsFBEventsInjectMethod'),\n            v = f.getFbeventsModules('signalsFBEventsMakeSafe'),\n            ba = f.getFbeventsModules('signalsFBEventsResolveLegacyArguments'),\n            ca = f.getFbeventsModules('SignalsFBEventsPluginManager'),\n            da = f.getFbeventsModules('signalsFBEventsCoercePixelID'),\n            w = f.getFbeventsModules('SignalsFBEventsEvents'),\n            x = f.getFbeventsModules('SignalsFBEventsTyped'),\n            ea = x.coerce,\n            y = x.Typed,\n            fa = f.getFbeventsModules('SignalsFBEventsGuardrail'),\n            ga = f.getFbeventsModules('SignalsFBEventsModuleEncodings'),\n            ha = f.getFbeventsModules('signalsFBEventsDoAutomaticMatching'),\n            z = o.each;\n          x = o.FBSet;\n          var A = o.isEmptyObject,\n            ia = o.isPlainObject,\n            ja = o.isNumber,\n            B = o.keys;\n          o = w.execEnd;\n          var C = w.fired,\n            D = w.getCustomParameters,\n            ka = w.iwlBootstrap,\n            E = w.piiInvalidated,\n            la = w.setIWLExtractors,\n            F = w.validateCustomParameters,\n            G = w.validateUrlParameters,\n            ma = w.setESTRules,\n            na = w.setCCRules,\n            H = p.logError,\n            I = p.logUserError,\n            J = s.global,\n            K = -1,\n            L =\n              'b68919aff001d8366249403a2544fba2d833084f1ad22839b6310aadacb6a138',\n            M = Array.prototype.slice,\n            N = Object.prototype.hasOwnProperty,\n            O = c.href,\n            P = !1,\n            Q = !1,\n            R = [],\n            S = {},\n            T;\n          b.referrer;\n          var U = {\n              PageView: new x(),\n              PixelInitialized: new x(),\n            },\n            V = new r(f, S),\n            W = new ca(V, J),\n            X = new x(['eid']);\n          function Y(a) {\n            for (var b in a) N.call(a, b) && (this[b] = a[b]);\n            return this;\n          }\n          function Z() {\n            try {\n              var a = M.call(arguments);\n              if (J.isLocked() && a[0] !== 'consent') {\n                f.queue.push(arguments);\n                return;\n              }\n              var b = ba(a),\n                c = [].concat(i(b.args)),\n                d = b.isLegacySyntax,\n                e = c.shift();\n              switch (e) {\n                case 'addPixelId':\n                  P = !0;\n                  $.apply(this, c);\n                  break;\n                case 'init':\n                  Q = !0;\n                  $.apply(this, c);\n                  break;\n                case 'set':\n                  oa.apply(this, c);\n                  break;\n                case 'track':\n                  if (ja(c[0])) {\n                    va.apply(this, c);\n                    break;\n                  }\n                  if (d) {\n                    sa.apply(this, c);\n                    break;\n                  }\n                  ra.apply(this, c);\n                  break;\n                case 'trackCustom':\n                  sa.apply(this, c);\n                  break;\n                case 'trackShopify':\n                  ta.apply(this, c);\n                  break;\n                case 'send':\n                  wa.apply(this, c);\n                  break;\n                case 'on':\n                  var j = h(c),\n                    k = j[0],\n                    l = j.slice(1),\n                    m = w[k];\n                  m && m.triggerWeakly(l);\n                  break;\n                case 'loadPlugin':\n                  W.loadPlugin(c[0]);\n                  break;\n                case 'dataProcessingOptions':\n                  switch (c.length) {\n                    case 1:\n                      var n = g(c, 1),\n                        o = n[0];\n                      V.pluginConfig.set(null, 'dataProcessingOptions', {\n                        dataProcessingOptions: o,\n                        dataProcessingCountry: null,\n                        dataProcessingState: null,\n                      });\n                      break;\n                    case 3:\n                      var p = g(c, 3),\n                        q = p[0],\n                        r = p[1],\n                        aa = p[2];\n                      V.pluginConfig.set(null, 'dataProcessingOptions', {\n                        dataProcessingOptions: q,\n                        dataProcessingCountry: r,\n                        dataProcessingState: aa,\n                      });\n                      break;\n                    case 4:\n                      var s = g(c, 3),\n                        t = s[0],\n                        u = s[1],\n                        v = s[2];\n                      V.pluginConfig.set(null, 'dataProcessingOptions', {\n                        dataProcessingOptions: t,\n                        dataProcessingCountry: u,\n                        dataProcessingState: v,\n                      });\n                      break;\n                  }\n                  break;\n                default:\n                  V.callMethod(arguments);\n                  break;\n              }\n            } catch (a) {\n              H(a);\n            }\n          }\n          function oa(a) {\n            for (\n              var b = arguments.length, c = Array(b > 1 ? b - 1 : 0), d = 1;\n              d < b;\n              d++\n            )\n              c[d - 1] = arguments[d];\n            var e = [a].concat(c);\n            switch (a) {\n              case 'endpoint':\n                var g = c[0];\n                if (typeof g !== 'string')\n                  throw new Error('endpoint value must be a string');\n                k.ENDPOINT = g;\n                break;\n              case 'cdn':\n                var h = c[0];\n                if (typeof h !== 'string')\n                  throw new Error('cdn value must be a string');\n                aa.CONFIG.CDN_BASE_URL = h;\n                break;\n              case 'releaseSegment':\n                var i = c[0];\n                if (typeof i !== 'string') {\n                  I({\n                    invalidParamName: 'new_release_segment',\n                    invalidParamValue: i,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                f._releaseSegment = i;\n                break;\n              case 'autoConfig':\n                var j = c[0],\n                  m = c[1],\n                  n = j === !0 || j === 'true' ? 'optIn' : 'optOut';\n                typeof m === 'string'\n                  ? V.callMethod([n, m, 'AutomaticSetup'])\n                  : m === void 0\n                  ? (V.disableAutoConfig = n === 'optOut')\n                  : I({\n                      invalidParamName: 'pixel_id',\n                      invalidParamValue: m,\n                      method: 'set',\n                      params: e,\n                      type: 'INVALID_FBQ_METHOD_PARAMETER',\n                    });\n                break;\n              case 'firstPartyCookies':\n                var o = c[0],\n                  p = c[1],\n                  r = o === !0 || o === 'true' ? 'optIn' : 'optOut';\n                typeof p === 'string'\n                  ? V.callMethod([r, p, 'FirstPartyCookies'])\n                  : p === void 0\n                  ? (V.disableFirstPartyCookies = r === 'optOut')\n                  : I({\n                      invalidParamName: 'pixel_id',\n                      invalidParamValue: p,\n                      method: 'set',\n                      params: e,\n                      type: 'INVALID_FBQ_METHOD_PARAMETER',\n                    });\n                break;\n              case 'experiments':\n                l.setExperiments.apply(l, c);\n                break;\n              case 'guardrails':\n                fa.setGuardrails.apply(fa, c);\n                break;\n              case 'moduleEncodings':\n                ga.setModuleEncodings.apply(ga, c);\n                break;\n              case 'mobileBridge':\n                var s = c[0],\n                  u = c[1];\n                if (typeof s !== 'string') {\n                  I({\n                    invalidParamName: 'pixel_id',\n                    invalidParamValue: s,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                if (typeof u !== 'string') {\n                  I({\n                    invalidParamName: 'app_id',\n                    invalidParamValue: u,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                t.registerBridge([s, u]);\n                break;\n              case 'iwlExtractors':\n                var v = c[0],\n                  ba = c[1];\n                la.triggerWeakly({\n                  extractors: ba,\n                  pixelID: v,\n                });\n                break;\n              case 'estRules':\n                var ca = c[0],\n                  da = c[1];\n                ma.triggerWeakly({\n                  rules: da,\n                  pixelID: ca,\n                });\n                break;\n              case 'ccRules':\n                var w = c[0],\n                  x = c[1];\n                na.triggerWeakly({\n                  rules: x,\n                  pixelID: w,\n                });\n                break;\n              case 'startIWLBootstrap':\n                var z = c[0],\n                  A = c[1];\n                ka.triggerWeakly({\n                  graphToken: z,\n                  pixelID: A,\n                });\n                break;\n              case 'parallelfire':\n                var ja = c[0],\n                  B = c[1];\n                V.pluginConfig.set(ja, 'parallelfire', {\n                  target: B,\n                });\n                break;\n              case 'openbridge':\n                var C = c[0],\n                  D = c[1];\n                C !== null &&\n                  D !== null &&\n                  typeof C === 'string' &&\n                  typeof D === 'string' &&\n                  (V.callMethod(['optIn', C, 'OpenBridge']),\n                  V.pluginConfig.set(C, 'openbridge', {\n                    endpoints: [\n                      {\n                        endpoint: D,\n                      },\n                    ],\n                  }));\n                break;\n              case 'trackSingleOnly':\n                var E = c[0],\n                  F = c[1],\n                  G = ea(E, y['boolean']()),\n                  H = ea(F, y.fbid());\n                if (H == null) {\n                  I({\n                    invalidParamName: 'pixel_id',\n                    invalidParamValue: F,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                if (G == null) {\n                  I({\n                    invalidParamName: 'on_or_off',\n                    invalidParamValue: E,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                var J = q.validateMetadata(a);\n                J.error && I(J.error);\n                J.warnings &&\n                  J.warnings.forEach(function (a) {\n                    I(a);\n                  });\n                N.call(S, H)\n                  ? (S[H].trackSingleOnly = G)\n                  : I({\n                      metadataValue: a,\n                      pixelID: H,\n                      type: 'SET_METADATA_ON_UNINITIALIZED_PIXEL_ID',\n                    });\n                break;\n              case 'userData':\n                var K = c[0],\n                  L = K == null || ia(K);\n                if (!L) {\n                  I({\n                    invalidParamName: 'user_data',\n                    invalidParamValue: K,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  return;\n                }\n                for (var M = 0; M < R.length; M++) {\n                  var O = R[M],\n                    P = V.optIns.isOptedIn(O.id, 'AutomaticMatching'),\n                    Q = V.optIns.isOptedIn(O.id, 'ShopifyAppIntegratedPixel'),\n                    T = l.isInTest('process_pii_from_shopify');\n                  P && Q && T\n                    ? ha(V, O, K)\n                    : I({\n                        invalidParamName: 'pixel_id',\n                        invalidParamValue: O.id,\n                        method: 'set',\n                        params: e,\n                        type: 'INVALID_FBQ_METHOD_PARAMETER',\n                      });\n                }\n                break;\n              default:\n                var U = V.pluginConfig.getWithGlobalFallback(\n                    null,\n                    'dataProcessingOptions'\n                  ),\n                  W = U != null && U.dataProcessingOptions.includes('LDU'),\n                  X = c[0],\n                  Y = c[1];\n                if (typeof a !== 'string')\n                  throw new Error(\n                    \"The metadata setting provided in the 'set' call is invalid.\"\n                  );\n                if (typeof X !== 'string') {\n                  if (W) break;\n                  I({\n                    invalidParamName: 'value',\n                    invalidParamValue: X,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                if (typeof Y !== 'string') {\n                  if (W) break;\n                  I({\n                    invalidParamName: 'pixel_id',\n                    invalidParamValue: Y,\n                    method: 'set',\n                    params: e,\n                    type: 'INVALID_FBQ_METHOD_PARAMETER',\n                  });\n                  break;\n                }\n                qa(a, X, Y);\n                break;\n            }\n          }\n          f._initHandlers = [];\n          f._initsDone = {};\n          function $(a, b, c) {\n            K = K === -1 ? Date.now() : K;\n            var d = da(a);\n            if (d == null) return;\n            var e = b == null || ia(b);\n            e ||\n              I({\n                invalidParamName: 'user_data',\n                invalidParamValue: b,\n                method: 'init',\n                params: [a, b],\n                type: 'INVALID_FBQ_METHOD_PARAMETER',\n              });\n            if (N.call(S, d)) {\n              b != null && A(S[d].userData)\n                ? ((S[d].userData = e ? b || {} : {}), W.loadPlugin('identity'))\n                : I({\n                    pixelID: d,\n                    type: 'DUPLICATE_PIXEL_ID',\n                  });\n              return;\n            }\n            a = {\n              agent: c ? c.agent : null,\n              eventCount: 0,\n              id: d,\n              userData: e ? b || {} : {},\n              userDataFormFields: {},\n            };\n            R.push(a);\n            S[d] = a;\n            b != null && W.loadPlugin('identity');\n            V.optIns.isOptedIn(d, 'OpenBridge') && W.loadPlugin('openbridge3');\n            pa();\n            V.loadConfig(d);\n          }\n          function pa() {\n            for (var a = 0; a < f._initHandlers.length; a++) {\n              var b = f._initHandlers[a];\n              f._initsDone[a] || (f._initsDone[a] = {});\n              for (var c = 0; c < R.length; c++) {\n                var d = R[c];\n                f._initsDone[a][d.id] || ((f._initsDone[a][d.id] = !0), b(d));\n              }\n            }\n          }\n          function qa(a, b, c) {\n            var d = q.validateMetadata(a);\n            d.error && I(d.error);\n            d.warnings &&\n              d.warnings.forEach(function (a) {\n                I(a);\n              });\n            if (N.call(S, c)) {\n              for (var d = 0, e = R.length; d < e; d++)\n                if (R[d].id === c) {\n                  R[d][a] = b;\n                  break;\n                }\n            } else\n              I({\n                metadataValue: b,\n                pixelID: c,\n                type: 'SET_METADATA_ON_UNINITIALIZED_PIXEL_ID',\n              });\n          }\n          function ra(a, b, c) {\n            (b = b || {}),\n              q.validateEventAndLog(a, b),\n              a === 'CustomEvent' &&\n                typeof b.event === 'string' &&\n                (a = b.event),\n              sa.call(this, a, b, c);\n          }\n          function sa(a, b, c) {\n            for (var d = 0, e = R.length; d < e; d++) {\n              var f = R[d];\n              if (\n                !(a === 'PageView' && this.allowDuplicatePageViews) &&\n                Object.prototype.hasOwnProperty.call(U, a) &&\n                U[a].has(f.id)\n              )\n                continue;\n              if (f.trackSingleOnly) continue;\n              za({\n                customData: b,\n                eventData: c,\n                eventName: a,\n                pixel: f,\n              });\n              Object.prototype.hasOwnProperty.call(U, a) && U[a].add(f.id);\n            }\n          }\n          function ta(a, b, c, d, e) {\n            (c = ua(a, c, e)),\n              q.validateEventAndLog(b, c),\n              b === 'CustomEvent' &&\n                typeof c.event === 'string' &&\n                (b = c.event),\n              sa.call(this, b, c, d);\n          }\n          function ua(b, c, d) {\n            c = c || {};\n            try {\n              if (d == null || Object.keys(d).length === 0) return c;\n              var e = V.optIns.isOptedIn(b, 'ShopifyAppIntegratedPixel');\n              if (!e) return c;\n              e = a.fbq.instance.pluginConfig.get(b, 'gating');\n              b = e.gatings.find(function (a) {\n                return a.name === 'content_type_opt';\n              }).passed;\n              if (!b) return c;\n              e = ea(\n                d,\n                y.objectWithFields({\n                  product_variant_ids: y.arrayOf(y.number()),\n                  content_type_favor_variant: y.string(),\n                })\n              );\n              if (e == null) return c;\n              c.content_ids = e.product_variant_ids;\n              c.content_type = e.content_type_favor_variant;\n              return c;\n            } catch (a) {\n              H(a);\n              return c;\n            }\n          }\n          function va(a, b) {\n            za({\n              customData: b,\n              eventName: a,\n              pixel: null,\n            });\n          }\n          function wa(a, b, c) {\n            R.forEach(function (c) {\n              return za({\n                customData: b,\n                eventName: a,\n                pixel: c,\n              });\n            });\n          }\n          function xa(a) {\n            a = a.toLowerCase().trim();\n            var b = a.endsWith('@icloud.com');\n            a = a.endsWith('@privaterelay.appleid.com');\n            if (b) return 2;\n            if (a) return 1;\n          }\n          function ya(a, b, c, d, e) {\n            var g = new m(f.piiTranslator);\n            try {\n              var h = (a && a.userData) || {},\n                i = (a && a.userDataFormFields) || {},\n                k = {},\n                l = {},\n                n = void 0,\n                o = h.em;\n              o != null && xa(o) && ((n = xa(o)), n === 1 && (k.em = L));\n              o = i.em;\n              o != null && xa(o) && ((n = xa(o)), n === 1 && (l.em = L));\n              n != null && g.append('ped', n);\n              g.append('ud', j({}, h, k), !0);\n              g.append('udff', j({}, i, l), !0);\n            } catch (b) {\n              E.trigger(a);\n            }\n            g.append('v', f.version);\n            f._releaseSegment && g.append('r', f._releaseSegment);\n            g.append('a', a && a.agent ? a.agent : f.agent);\n            a && (g.append('ec', a.eventCount), a.eventCount++);\n            o = D.trigger(a, b, c, d, e);\n            z(o, function (a) {\n              return z(B(a), function (b) {\n                if (g.containsKey(b)) {\n                  if (!X.has(b))\n                    throw new Error(\n                      'Custom parameter ' + b + ' has already been specified.'\n                    );\n                } else g.append(b, a[b]);\n              });\n            });\n            g.append('it', K);\n            n = a && a.codeless === 'false';\n            g.append('coo', n);\n            h = V.pluginConfig.getWithGlobalFallback(\n              a ? a.id : null,\n              'dataProcessingOptions'\n            );\n            if (h != null) {\n              k = h.dataProcessingCountry;\n              i = h.dataProcessingOptions;\n              l = h.dataProcessingState;\n              g.append('dpo', i.join(','));\n              g.append('dpoco', k);\n              g.append('dpost', l);\n            }\n            return g;\n          }\n          function za(a) {\n            var d = a.customData,\n              e = a.eventData,\n              f = a.eventName;\n            a = a.pixel;\n            d = d || {};\n            if (a != null && t.pixelHasActiveBridge(a)) {\n              t.sendEvent(a, f, d);\n              return;\n            }\n            var g = ya(a, f, d, void 0, e);\n            if (e != null) {\n              var h = e.eventID;\n              e = e.event_id;\n              h = h != null ? h : e;\n              h == null &&\n                (d.event_id != null || d.eventID != null) &&\n                p.consoleWarn(\n                  'eventID is being sent in the 3rd parameter, it should be in the 4th parameter.'\n                );\n              g.containsKey('eid')\n                ? h == null || h.length == 0\n                  ? p.logError(\n                      new Error('got null or empty eventID from 4th parameter')\n                    )\n                  : g.replaceEntry('eid', h)\n                : g.append('eid', h);\n            }\n            e = F.trigger(a, d, f);\n            z(e, function (a) {\n              a != null &&\n                z(B(a), function (b) {\n                  b != null && g.append(b, a[b]);\n                });\n            });\n            h = c.href;\n            e = b.referrer;\n            var i = {};\n            h != null && (i.dl = h);\n            e != null && (i.rl = e);\n            A(i) || G.trigger(a, i, f, g);\n            n({\n              customData: d,\n              customParams: g,\n              eventName: f,\n              id: a ? a.id : null,\n              piiTranslator: null,\n              documentLink: i.dl ? i.dl : '',\n              referrerLink: i.rl ? i.rl : '',\n            });\n          }\n          function Aa() {\n            while (f.queue && f.queue.length && !J.isLocked()) {\n              var a = f.queue.shift();\n              Z.apply(f, a);\n            }\n          }\n          J.onUnlocked(function () {\n            Aa();\n          });\n          f.pixelId && ((P = !0), $(f.pixelId));\n          ((P && Q) || a.fbq !== a._fbq) &&\n            I({\n              type: 'CONFLICTING_VERSIONS',\n            });\n          R.length > 1 &&\n            I({\n              type: 'MULTIPLE_PIXELS',\n            });\n          function Ba() {\n            if (f.disablePushState === !0) return;\n            if (!d.pushState || !d.replaceState) return;\n            var b = v(function () {\n              T = O;\n              O = c.href;\n              if (O === T) return;\n              var a = new Y({\n                allowDuplicatePageViews: !0,\n              });\n              Z.call(a, 'trackCustom', 'PageView');\n            });\n            u(d, 'pushState', b);\n            u(d, 'replaceState', b);\n            a.addEventListener('popstate', b, !1);\n          }\n          function Ca() {\n            'onpageshow' in a &&\n              a.addEventListener('pageshow', function (a) {\n                if (a.persisted) {\n                  a = new Y({\n                    allowDuplicatePageViews: !0,\n                  });\n                  Z.call(a, 'trackCustom', 'PageView');\n                }\n              });\n          }\n          C.listenOnce(function () {\n            Ba(), Ca();\n          });\n          function Da(a) {\n            f._initHandlers.push(a), pa();\n          }\n          function Ea() {\n            return {\n              pixelInitializationTime: K,\n              pixels: R,\n            };\n          }\n          function Fa(a) {\n            (a.instance = V),\n              (a.callMethod = Z),\n              (a._initHandlers = []),\n              (a._initsDone = {}),\n              (a.send = wa),\n              (a.getEventCustomParameters = ya),\n              (a.addInitHandler = Da),\n              (a.getState = Ea),\n              (a.init = $),\n              (a.set = oa),\n              (a.loadPlugin = function (a) {\n                return W.loadPlugin(a);\n              }),\n              (a.registerPlugin = function (a, b) {\n                W.registerPlugin(a, b);\n              });\n          }\n          Fa(a.fbq);\n          Aa();\n          e.exports = {\n            doExport: Fa,\n          };\n          o.trigger();\n        })();\n        return e.exports;\n      })(a, b, c, d);\n    });\n    e.exports = f.getFbeventsModules('SignalsFBEvents');\n    f.registerPlugin && f.registerPlugin('fbevents', e.exports);\n    f.ensureModuleRegistered('fbevents', function () {\n      return e.exports;\n    });\n  })();\n})(window, document, location, history);\nfbq.registerPlugin('global_config', {\n  __fbEventsPlugin: 1,\n  plugin: function (fbq, instance, config) {\n    fbq.loadPlugin('commonincludes');\n    fbq.loadPlugin('identity');\n    fbq.loadPlugin('privacysandbox');\n    fbq.loadPlugin('opttracking');\n    fbq.set('experiments', [\n      {\n        allocation: 0,\n        code: 'c',\n        name: 'no_op_exp',\n        passRate: 0.5,\n      },\n      {\n        allocation: 0,\n        code: 'd',\n        name: 'config_dedupe',\n        passRate: 1,\n      },\n      {\n        allocation: 0,\n        code: 'e',\n        name: 'send_fbc_when_no_cookie',\n        passRate: 1,\n      },\n      {\n        allocation: 0.02,\n        code: 'f',\n        name: 'send_events_in_batch',\n        passRate: 0.5,\n      },\n      {\n        allocation: 0,\n        code: 'g',\n        name: 'process_pii_from_shopify',\n        passRate: 0,\n      },\n      {\n        allocation: 0,\n        code: 'h',\n        name: 'set_fbc_cookie_after_config_load',\n        passRate: 1,\n      },\n      {\n        allocation: 0,\n        code: 'i',\n        name: 'prioritize_send_beacon_in_url',\n        passRate: 0.5,\n      },\n      {\n        allocation: 0,\n        code: 'j',\n        name: 'fix_fbc_fbp_update',\n        passRate: 0,\n      },\n    ]);\n    fbq.set('guardrails', [\n      {\n        name: 'no_op',\n        code: 'a',\n        passRate: 1,\n        enableForPixels: ['569835061642423'],\n      },\n      {\n        name: 'extract_extra_microdata',\n        code: 'b',\n        passRate: 0,\n        enableForPixels: [],\n      },\n    ]);\n    fbq.set('moduleEncodings', {\n      map: {\n        generateUUID: 0,\n        SignalsConvertNodeToHTMLElement: 1,\n        SignalsEventValidation: 2,\n        SignalsFBEventsActionIDConfigTypedef: 3,\n        SignalsFBEventsBaseEvent: 4,\n        SignalsFBEventsBatcher: 5,\n        SignalsFBEventsBrowserPropertiesConfigTypedef: 6,\n        SignalsFBEventsBufferConfigTypedef: 7,\n        SignalsFBEventsCCRuleEvaluatorConfigTypedef: 8,\n        SignalsFBEventsClientHintConfigTypedef: 9,\n        SignalsFBEventsClientSidePixelForkingConfigTypedef: 10,\n        signalsFBEventsCoerceAutomaticMatchingConfig: 11,\n        signalsFBEventsCoerceBatchingConfig: 12,\n        signalsFBEventsCoerceInferedEventsConfig: 13,\n        signalsFBEventsCoerceParameterExtractors: 14,\n        signalsFBEventsCoercePixelID: 15,\n        SignalsFBEventsCoercePrimitives: 16,\n        signalsFBEventsCoerceStandardParameter: 17,\n        SignalsFBEventsConfigLoadedEvent: 18,\n        SignalsFBEventsConfigStore: 19,\n        SignalsFBEventsCookieConfigTypedef: 20,\n        SignalsFBEventsCookieDeprecationLabelConfigTypedef: 21,\n        SignalsFBEventsDataProcessingOptionsConfigTypedef: 22,\n        SignalsFBEventsDefaultCustomDataConfigTypedef: 23,\n        signalsFBEventsDoAutomaticMatching: 24,\n        SignalsFBEventsESTRuleEngineConfigTypedef: 25,\n        SignalsFBEventsEvents: 26,\n        SignalsFBEventsEventValidationConfigTypedef: 27,\n        SignalsFBEventsExperimentNames: 28,\n        SignalsFBEventsExperimentsTypedef: 29,\n        SignalsFBEventsExtractPII: 30,\n        SignalsFBEventsFBQ: 31,\n        signalsFBEventsFillParamList: 32,\n        SignalsFBEventsFilterProtectedModeEvent: 33,\n        SignalsFBEventsFiredEvent: 34,\n        signalsFBEventsFireEvent: 35,\n        SignalsFBEventsFireLock: 36,\n        SignalsFBEventsForkEvent: 37,\n        SignalsFBEventsGatingConfigTypedef: 38,\n        SignalsFBEventsGetAemResultEvent: 39,\n        SignalsFBEventsGetCustomParametersEvent: 40,\n        signalsFBEventsGetIsChrome: 41,\n        signalsFBEventsGetIsIosInAppBrowser: 42,\n        SignalsFBEventsGetIWLParametersEvent: 43,\n        SignalsFBEventsGetTimingsEvent: 44,\n        SignalsFBEventsGetValidUrl: 45,\n        SignalsFBEventsGuardrail: 46,\n        SignalsFBEventsGuardrailTypedef: 47,\n        SignalsFBEventsIABPCMAEBridgeConfigTypedef: 48,\n        signalsFBEventsInjectMethod: 49,\n        SignalsFBEventsIWLBootStrapEvent: 50,\n        SignalsFBEventsJSLoader: 51,\n        SignalsFBEventsLateValidateCustomParametersEvent: 52,\n        SignalsFBEventsLegacyExperimentGroupsTypedef: 53,\n        SignalsFBEventsLogging: 54,\n        signalsFBEventsMakeSafe: 55,\n        SignalsFBEventsMessageParamsTypedef: 56,\n        SignalsFBEventsMicrodataConfigTypedef: 57,\n        SignalsFBEventsMobileAppBridge: 58,\n        SignalsFBEventsModuleEncodings: 59,\n        SignalsFBEventsModuleEncodingsTypedef: 60,\n        SignalsFBEventsNetworkConfig: 61,\n        SignalsFBEventsOpenBridgeConfigTypedef: 62,\n        SignalsFBEventsOptIn: 63,\n        SignalsFBEventsParallelFireConfigTypedef: 64,\n        SignalsFBEventsPIIAutomatchedEvent: 65,\n        SignalsFBEventsPIIConflictingEvent: 66,\n        SignalsFBEventsPIIInvalidatedEvent: 67,\n        SignalsFBEventsPixelCookie: 68,\n        SignalsFBEventsPixelTypedef: 69,\n        SignalsFBEventsPlugin: 70,\n        SignalsFBEventsPluginLoadedEvent: 71,\n        SignalsFBEventsPluginManager: 72,\n        SignalsFBEventsProcessCCRulesEvent: 73,\n        SignalsFBEventsProhibitedPixelConfigTypedef: 74,\n        SignalsFBEventsProhibitedSourcesTypedef: 75,\n        SignalsFBEventsProtectedDataModeConfigTypedef: 76,\n        SignalsFBEventsQE: 77,\n        signalsFBEventsResolveLegacyArguments: 78,\n        SignalsFBEventsResolveLink: 79,\n        SignalsFBEventsRestrictedDomainsConfigTypedef: 80,\n        signalsFBEventsSendBatch: 81,\n        signalsFBEventsSendBeacon: 82,\n        signalsFBEventsSendBeaconWithParamsInURL: 83,\n        SignalsFBEventsSendCloudbridgeEvent: 84,\n        signalsFBEventsSendEvent: 85,\n        SignalsFBEventsSendEventEvent: 86,\n        signalsFBEventsSendFetch: 87,\n        signalsFBEventsSendFormPOST: 88,\n        signalsFBEventsSendGET: 89,\n        signalsFBEventsSendXHR: 90,\n        SignalsFBEventsSetCCRules: 91,\n        SignalsFBEventsSetESTRules: 92,\n        SignalsFBEventsSetEventIDEvent: 93,\n        SignalsFBEventsSetFBPEvent: 94,\n        SignalsFBEventsSetFilteredEventName: 95,\n        SignalsFBEventsSetIWLExtractorsEvent: 96,\n        SignalsFBEventsShouldRestrictReferrerEvent: 97,\n        SignalsFBEventsStandardParamChecksConfigTypedef: 98,\n        SignalsFBEventsTelemetry: 99,\n        SignalsFBEventsTyped: 100,\n        SignalsFBEventsTypeVersioning: 101,\n        SignalsFBEventsUnwantedDataTypedef: 102,\n        SignalsFBEventsUnwantedEventNamesConfigTypedef: 103,\n        SignalsFBEventsUnwantedEventsConfigTypedef: 104,\n        SignalsFBEventsUnwantedParamsConfigTypedef: 105,\n        SignalsFBEventsURLUtil: 106,\n        SignalsFBEventsUtils: 107,\n        SignalsFBEventsValidateCustomParametersEvent: 108,\n        SignalsFBEventsValidateGetClickIDFromBrowserProperties: 109,\n        SignalsFBEventsValidateUrlParametersEvent: 110,\n        SignalsParamList: 111,\n        SignalsPixelCookieUtils: 112,\n        SignalsFBEvents: 113,\n        'SignalsFBEvents.plugins.actionid': 114,\n        '[object Object]': 115,\n        'SignalsFBEvents.plugins.automaticparameters': 116,\n        'SignalsFBEvents.plugins.browserproperties': 117,\n        'SignalsFBEvents.plugins.buffer': 118,\n        'SignalsFBEvents.plugins.ccruleevaluator': 119,\n        'SignalsFBEvents.plugins.clienthint': 120,\n        'SignalsFBEvents.plugins.clientsidepixelforking': 121,\n        'SignalsFBEvents.plugins.commonincludes': 122,\n        'SignalsFBEvents.plugins.cookie': 123,\n        'SignalsFBEvents.plugins.cookiedeprecationlabel': 124,\n        'SignalsFBEvents.plugins.debug': 125,\n        'SignalsFBEvents.plugins.defaultcustomdata': 126,\n        'SignalsFBEvents.plugins.estruleengine': 127,\n        'SignalsFBEvents.plugins.eventvalidation': 128,\n        'SignalsFBEvents.plugins.gating': 129,\n        'SignalsFBEvents.plugins.iabpcmaebridge': 130,\n        'SignalsFBEvents.plugins.identifyintegration': 131,\n        'SignalsFBEvents.plugins.identity': 132,\n        'SignalsFBEvents.plugins.inferredevents': 133,\n        'SignalsFBEvents.plugins.iwlbootstrapper': 134,\n        'SignalsFBEvents.plugins.iwlparameters': 135,\n        'SignalsFBEvents.plugins.jsonld_microdata': 136,\n        'SignalsFBEvents.plugins.lastexternalreferrer': 137,\n        'SignalsFBEvents.plugins.microdata': 138,\n        'SignalsFBEvents.plugins.openbridge3': 139,\n        'SignalsFBEvents.plugins.openbridgerollout': 140,\n        'SignalsFBEvents.plugins.opttracking': 141,\n        'SignalsFBEvents.plugins.parallelfire': 142,\n        'SignalsFBEvents.plugins.performance': 143,\n        'SignalsFBEvents.plugins.privacysandbox': 144,\n        'SignalsFBEvents.plugins.prohibitedpixels': 145,\n        'SignalsFBEvents.plugins.prohibitedsources': 146,\n        'SignalsFBEvents.plugins.protecteddatamode': 147,\n        'SignalsFBEvents.plugins.shopifyappintegratedpixel': 148,\n        'SignalsFBEvents.plugins.standardparamchecks': 149,\n        'SignalsFBEvents.plugins.timespent': 150,\n        'SignalsFBEvents.plugins.topicsapi': 151,\n        'SignalsFBEvents.plugins.unwanteddata': 152,\n        'SignalsFBEvents.plugins.unwantedeventnames': 153,\n        'SignalsFBEvents.plugins.unwantedevents': 154,\n        'SignalsFBEvents.plugins.unwantedparams': 155,\n        'SignalsFBEventsEvents.plugins.aem': 156,\n        SignalsFBEventsTimespentTracking: 157,\n        'SignalsFBevents.plugins.automaticmatchingforpartnerintegrations': 158,\n        cbsdk_fbevents_embed: 159,\n        SignalsFBEventsCCRuleEngine: 160,\n        SignalsFBEventsESTCustomData: 161,\n        SignalsFBEventsESTRuleEngine: 162,\n        SignalsFBEventsEnums: 163,\n        SignalsFBEventsFbcCombiner: 164,\n        SignalsFBEventsFormFieldFeaturesType: 165,\n        SignalsFBEventsGetIsAndroidChrome: 166,\n        SignalsFBEventsLocalStorageUtils: 167,\n        SignalsFBEventsNormalizers: 168,\n        SignalsFBEventsOptTrackingOptions: 169,\n        SignalsFBEventsPerformanceTiming: 170,\n        SignalsFBEventsPixelPIISchema: 171,\n        SignalsFBEventsProxyState: 172,\n        SignalsFBEventsShared: 173,\n        SignalsFBEventsTransformToCCInput: 174,\n        SignalsFBEventsTypes: 175,\n        SignalsFBEventsValidationUtils: 176,\n        SignalsFBEventsWildcardMatches: 177,\n        SignalsInteractionUtil: 178,\n        SignalsPageVisibilityUtil: 179,\n        SignalsPixelClientSideForkingUtils: 180,\n        SignalsPixelPIIConstants: 181,\n        SignalsPixelPIIUtils: 182,\n        generateEventId: 183,\n        normalizeSignalsFBEventsEmailType: 184,\n        normalizeSignalsFBEventsEnumType: 185,\n        normalizeSignalsFBEventsPhoneNumberType: 186,\n        normalizeSignalsFBEventsPostalCodeType: 187,\n        normalizeSignalsFBEventsStringType: 188,\n        sha256_with_dependencies_new: 189,\n        signalsFBEventsExtractMicrodataSchemas: 190,\n        signalsFBEventsGetIsAndroid: 191,\n        signalsFBEventsGetIsAndroidIAW: 192,\n        signalsFBEventsGetIsChromeInclIOS: 193,\n        signalsFBEventsGetIsMobileSafari: 194,\n        signalsFBEventsGetIsWebview: 195,\n        signalsFBEventsGetIwlUrl: 196,\n        signalsFBEventsGetTier: 197,\n        signalsFBEventsIsHostFacebook: 198,\n        signalsFBEventsMakeSafeString: 199,\n        signalsFBEventsShouldNotDropCookie: 200,\n        SignalsFBEventsAutomaticEventsTypes: 201,\n        SignalsFBEventsFeatureCounter: 202,\n        SignalsFBEventsThrottler: 203,\n        signalsFBEventsCollapseUserData: 204,\n        signalsFBEventsElementDoesMatch: 205,\n        signalsFBEventsExtractButtonFeatures: 206,\n        signalsFBEventsExtractEventPayload: 207,\n        signalsFBEventsExtractForm: 208,\n        signalsFBEventsExtractFormFieldFeatures: 209,\n        signalsFBEventsExtractFromInputs: 210,\n        signalsFBEventsExtractPageFeatures: 211,\n        signalsFBEventsGetTruncatedButtonText: 212,\n        signalsFBEventsGetWrappingButton: 213,\n        signalsFBEventsIsIWLElement: 214,\n        signalsFBEventsIsSaneAndNotDisabledButton: 215,\n        signalsFBEventsValidateButtonEventExtractUserData: 216,\n        'babel.config': 217,\n        signalsFBEventsCoerceUserData: 218,\n        SignalsFBEventsConfigTypes: 219,\n        SignalsFBEventsForkCbsdkEvent: 220,\n        getDeepStackTrace: 221,\n        getIntegrationCandidates: 222,\n        signalsFBEventsSendXHRWithRetry: 223,\n        FeatureGate: 224,\n        OpenBridgeConnection: 225,\n        ResolveLinks: 226,\n        openBridgeDomainFilter: 227,\n        openBridgeGetUserData: 228,\n        topics_api_utility_lib: 229,\n        analytics_debug: 230,\n        analytics_ecommerce: 231,\n        analytics_enhanced_ecommerce: 232,\n        analytics_enhanced_link_attribution: 233,\n        analytics_release: 234,\n        proxy_polyfill: 235,\n        SignalsFBEventsBrowserPropertiesTypedef: 236,\n        SignalsFBEventsClientHintTypedef: 237,\n        SignalsFBEventsESTRuleConditionTypedef: 238,\n        SignalsFBEventsLocalStorageTypedef: 239,\n        fbevents_embed: 240,\n      },\n      hash: 'b8122d5d96cd6f542162ba4f497489972d1ebe228d24c39d34f560e30ae932ce',\n    });\n    config.set(null, 'batching', {\n      batchWaitTimeMs: 10,\n      maxBatchSize: 10,\n    });\n    config.set(null, 'microdata', {\n      waitTimeMs: 500,\n    });\n    instance.configLoaded('global_config');\n  },\n});\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(preview)/p/[id]/layout.tsx",
    "content": "import { ReactNode } from 'react';\nimport { PreviewWrapper } from '@gitroom/frontend/components/preview/preview.wrapper';\n\nexport default async function AppLayout({ children }: { children: ReactNode }) {\n  return (\n    <div className=\"bg-[#000000] min-h-screen\">\n      <PreviewWrapper>{children}</PreviewWrapper>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx",
    "content": "import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';\nexport const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport { CommentsComponents } from '@gitroom/frontend/components/preview/comments.components';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\nimport { CopyClient } from '@gitroom/frontend/components/preview/copy.client';\nimport { getT } from '@gitroom/react/translation/get.translation.service.backend';\nimport dynamicLoad from 'next/dynamic';\n\nconst RenderPreviewDate = dynamicLoad(\n  () =>\n    import('@gitroom/frontend/components/preview/render.preview.date').then(\n      (mod) => mod.RenderPreviewDate\n    ),\n  { ssr: false }\n);\n\ndayjs.extend(utc);\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Preview`,\n  description: '',\n};\nexport default async function Auth({\n  params: { id },\n  searchParams,\n}: {\n  params: {\n    id: string;\n  };\n  searchParams?: {\n    share?: string;\n  };\n}) {\n  const post = await (await internalFetch(`/public/posts/${id}`)).json();\n  const t = await getT();\n  if (!post.length) {\n    return (\n      <div className=\"text-white fixed start-0 top-0 w-full h-full flex justify-center items-center text-[20px]\">\n        {t('post_not_found', 'Post not found')}\n      </div>\n    );\n  }\n  return (\n    <div>\n      <div className=\"mx-auto w-full max-w-[1346px] py-3 text-white\">\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex items-center space-x-4\">\n            <div className=\"flex items-center space-x-2\">\n              <div className=\"min-w-[55px]\">\n                <Link\n                  href=\"/\"\n                  className=\"text-2xl flex items-center justify-center gap-[10px] text-textColor order-1\"\n                >\n                  <div className=\"max-w-[55px]\">\n                    <Image\n                      src={'/postiz.svg'}\n                      width={55}\n                      height={55}\n                      alt=\"Logo\"\n                    />\n                  </div>\n                  <div>\n                    <svg\n                      width=\"80\"\n                      height=\"75\"\n                      viewBox=\"0 0 366 167\"\n                      fill=\"none\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M24.9659 30.4263V43.3825C26.9237 41.3095 29.3998 39.582 32.3941 38.2C35.3885 36.7028 39.0162 35.9543 43.2774 35.9543C47.1931 35.9543 50.8784 36.7028 54.3334 38.2C57.9036 39.6972 61.0131 42.1157 63.6619 45.4555C66.4259 48.6802 68.6141 52.9989 70.2264 58.4118C71.8387 63.8246 72.6449 70.3891 72.6449 78.1053C72.6449 83.6333 72.1266 89.1613 71.0902 94.6893C70.1688 100.217 68.4989 105.169 66.0804 109.546C63.6619 113.922 60.3796 117.492 56.2336 120.256C52.2028 122.905 47.1355 124.23 41.0316 124.23C36.6553 124.23 33.2003 123.654 30.6666 122.502C28.1329 121.235 26.2327 119.796 24.9659 118.183V160.162L0.0898438 166.381V30.4263H24.9659ZM32.7396 109.2C35.734 109.2 38.2676 108.221 40.3406 106.264C42.4136 104.191 44.026 101.542 45.1776 98.3171C46.4445 95.0924 47.3082 91.5222 47.7689 87.6066C48.3447 83.5757 48.6326 79.6025 48.6326 75.6868C48.6326 69.3526 48.0568 64.3429 46.9051 60.6575C45.8686 56.9722 44.6018 54.2658 43.1046 52.5383C41.6075 50.6956 40.1103 49.5439 38.6131 49.0833C37.2311 48.6226 36.137 48.3923 35.3309 48.3923C33.2579 48.3923 31.2425 49.1409 29.2846 50.638C27.3268 52.02 25.8872 54.1506 24.9659 57.0298V105.227C25.5417 106.148 26.463 107.07 27.7299 107.991C28.9967 108.797 30.6666 109.2 32.7396 109.2Z\"\n                        fill=\"currentColor\"\n                      />\n                      <path\n                        d=\"M188.176 31.4627C191.055 42.5188 193.588 51.5593 195.777 58.5845C197.965 65.4945 199.807 71.3105 201.305 76.0323C202.917 80.7541 204.126 84.9001 204.932 88.4703C205.854 92.0405 206.314 96.0137 206.314 100.39C208.272 99.1232 210.172 97.7988 212.015 96.4168C213.858 94.9196 215.413 93.5376 216.679 92.2708H223.935C220.825 96.9926 217.543 100.908 214.088 104.018C210.633 107.012 207.293 109.661 204.069 111.964C201.996 116.456 198.829 119.623 194.567 121.466C190.306 123.308 185.872 124.23 181.266 124.23C176.083 124.23 171.649 123.539 167.964 122.157C164.279 120.659 161.227 118.702 158.808 116.283C156.505 113.749 154.777 110.87 153.626 107.646C152.474 104.421 151.898 101.023 151.898 97.4533C151.898 93.5376 152.819 90.4857 154.662 88.2975C156.62 85.9942 158.866 84.8426 161.399 84.8426C168.424 84.8426 171.937 87.6641 171.937 93.3073C171.937 95.15 171.304 96.7047 170.037 97.9716C168.77 99.2384 167.158 99.8718 165.2 99.8718C164.278 99.8718 163.3 99.7566 162.263 99.5263C161.342 99.1808 160.593 98.5474 160.017 97.6261C160.939 101.657 162.436 104.824 164.509 107.127C166.697 109.431 169.461 110.582 172.801 110.582C175.68 110.582 177.811 109.891 179.193 108.509C180.575 107.012 181.266 104.478 181.266 100.908C181.266 97.1078 180.92 93.7104 180.229 90.7161C179.653 87.6066 178.732 84.2091 177.465 80.5238C176.198 76.8385 174.644 72.4621 172.801 67.3948C170.958 62.2123 168.885 55.5326 166.582 47.3558C160.823 59.6786 153.222 67.5675 143.779 71.0225C143.779 71.9439 143.779 72.8652 143.779 73.7865C143.894 74.5927 143.952 75.4565 143.952 76.3778C143.952 83.0575 143.376 89.334 142.224 95.2076C141.072 100.966 139.115 106.033 136.351 110.41C133.702 114.671 130.247 118.068 125.986 120.602C121.724 123.02 116.484 124.23 110.265 124.23C106.004 124.23 101.916 123.596 98 122.329C94.1995 120.947 90.8021 118.759 87.8078 115.765C84.8134 112.655 82.3949 108.624 80.5523 103.672C78.8248 98.605 77.961 92.4436 77.961 85.188C77.961 80.2359 78.4793 74.9382 79.5158 69.295C80.5523 63.5367 82.4525 58.1814 85.2165 53.2293C87.9805 48.2771 91.7234 44.1887 96.4453 40.964C101.282 37.6242 107.444 35.9543 114.93 35.9543C122.646 35.9543 128.807 38.0273 133.414 42.1733C138.136 46.3193 141.303 52.9989 142.915 62.2123C146.946 61.2909 150.574 58.5269 153.798 53.9203C157.138 49.1984 160.305 42.8643 163.3 34.9177L188.176 31.4627ZM115.102 107.991C117.521 107.991 119.594 107.185 121.321 105.573C123.164 103.845 124.661 101.542 125.813 98.6626C126.964 95.6682 127.771 92.1556 128.231 88.1248C128.807 84.094 129.095 79.7176 129.095 74.9958V72.75C124.488 71.7135 122.185 68.3161 122.185 62.5578C122.185 58.8724 123.682 56.4539 126.677 55.3023C125.41 51.6169 123.855 49.1984 122.012 48.0468C120.285 46.8951 118.788 46.3193 117.521 46.3193C114.987 46.3193 112.799 47.5285 110.956 49.947C109.229 52.2504 107.789 55.2447 106.638 58.93C105.486 62.5002 104.622 66.4734 104.046 70.8498C103.586 75.2261 103.355 79.4297 103.355 83.4605C103.355 88.6431 103.701 92.8466 104.392 96.0713C105.198 99.296 106.177 101.772 107.329 103.5C108.48 105.227 109.747 106.436 111.129 107.127C112.511 107.703 113.835 107.991 115.102 107.991Z\"\n                        fill=\"currentColor\"\n                      />\n                      <path\n                        d=\"M239.554 9.52348V36.818H250.092V43.728H239.554V95.5531C239.554 100.39 240.187 103.615 241.454 105.227C242.836 106.724 245.197 107.473 248.537 107.473C251.877 107.473 254.641 106.033 256.829 103.154C259.132 100.275 260.457 96.6471 260.802 92.2708H268.058C267.136 99.296 265.524 104.939 263.221 109.2C260.917 113.346 258.326 116.571 255.447 118.874C252.568 121.062 249.631 122.502 246.637 123.193C243.642 123.884 240.993 124.23 238.69 124.23C229.822 124.23 223.603 121.811 220.033 116.974C216.463 112.022 214.678 105.515 214.678 97.4533V43.728H209.15V36.818H214.678V12.9785L239.554 9.52348Z\"\n                        fill=\"currentColor\"\n                      />\n                      <path\n                        d=\"M258.833 13.8422C258.833 10.0417 260.158 6.81706 262.806 4.16823C265.455 1.40422 268.68 0.0222168 272.48 0.0222168C276.281 0.0222168 279.506 1.40422 282.154 4.16823C284.918 6.81706 286.3 10.0417 286.3 13.8422C286.3 17.6427 284.918 20.8674 282.154 23.5162C279.506 26.1651 276.281 27.4895 272.48 27.4895C268.68 27.4895 265.455 26.1651 262.806 23.5162C260.158 20.8674 258.833 17.6427 258.833 13.8422ZM285.609 36.818V95.5531C285.609 100.39 286.243 103.615 287.51 105.227C288.892 106.724 291.253 107.473 294.592 107.473C296.09 107.473 297.184 107.358 297.875 107.127C298.681 106.897 299.372 106.667 299.948 106.436C300.063 107.012 300.12 107.588 300.12 108.164C300.12 108.74 300.12 109.315 300.12 109.891C300.12 112.77 299.602 115.131 298.566 116.974C297.644 118.817 296.377 120.314 294.765 121.466C293.268 122.502 291.598 123.193 289.755 123.539C288.028 123.999 286.358 124.23 284.746 124.23C275.878 124.23 269.659 121.811 266.089 116.974C262.518 112.022 260.733 105.515 260.733 97.4533V36.818H285.609ZM351.773 107.473C350.391 107.358 349.354 106.897 348.663 106.091C347.972 105.169 347.627 104.133 347.627 102.981C347.627 101.484 348.26 100.045 349.527 98.6626C350.794 97.1654 352.867 96.4168 355.746 96.4168C358.971 96.4168 361.389 97.5109 363.001 99.6991C364.614 101.772 365.42 104.248 365.42 107.127C365.42 108.97 365.074 110.87 364.383 112.828C363.692 114.671 362.598 116.398 361.101 118.011C359.604 119.508 357.761 120.775 355.573 121.811C353.385 122.732 350.851 123.193 347.972 123.193H300.293L334.152 46.1465H321.369C318.835 46.1465 316.704 46.3193 314.977 46.6648C313.365 46.8951 312.558 47.5285 312.558 48.565C312.558 49.0257 312.674 49.256 312.904 49.256C313.249 49.256 313.595 49.3712 313.94 49.6015C314.401 49.8318 314.747 50.2925 314.977 50.9835C315.322 51.6745 315.495 52.8838 315.495 54.6113C315.495 57.1449 314.689 58.9876 313.077 60.1393C311.579 61.2909 309.852 61.8668 307.894 61.8668C305.591 61.8668 303.345 61.1182 301.157 59.621C299.084 58.0087 298.047 55.5902 298.047 52.3655C298.047 50.638 298.393 48.9105 299.084 47.183C299.775 45.3403 300.811 43.6704 302.193 42.1733C303.575 40.5609 305.303 39.2941 307.376 38.3728C309.449 37.3363 311.867 36.818 314.631 36.818H362.138L329.142 109.891C329.833 109.891 330.812 109.949 332.079 110.064C333.346 110.179 334.67 110.294 336.052 110.41C337.55 110.525 338.989 110.64 340.371 110.755C341.868 110.87 343.193 110.928 344.344 110.928C346.417 110.928 348.145 110.697 349.527 110.237C351.024 109.776 351.773 108.855 351.773 107.473Z\"\n                        fill=\"currentColor\"\n                      />\n                    </svg>\n                  </div>\n                </Link>\n              </div>\n            </div>\n          </div>\n          <div className=\"text-sm text-gray-400 flex items-center gap-[20px]\">\n            {!!searchParams?.share && (\n              <div>\n                <CopyClient />\n              </div>\n            )}\n            <div className=\"flex-1\">\n              {t('publication_date', 'Publication Date:')}{' '}\n              <RenderPreviewDate date={post[0].publishDate} />\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex flex-col lg:flex-row text-white w-full max-w-[1346px] mx-auto\">\n        <div className=\"flex-1\">\n          <div className=\"gap-[20px] flex flex-col\">\n            {post.map((p: any, index: number) => (\n              <div\n                key={String(p.id)}\n                className=\"relative px-4 py-4 bg-third border border-tableBorder\"\n              >\n                <div className=\"flex space-x-3\">\n                  <div>\n                    <div className=\"flex shrink-0 rounded-full h-30 w-30 relative\">\n                      <div className=\"w-[50px] h-[50px] z-[20]\">\n                        <img\n                          className=\"w-full h-full relative z-[20] bg-black aspect-square rounded-full border-tableBorder\"\n                          alt={post[0].integration.name}\n                          src={post[0].integration.picture}\n                        />\n                      </div>\n                      <div className=\"absolute -end-[5px] -bottom-[5px] w-[30px] h-[30px] z-[20]\">\n                        <img\n                          className=\"w-full h-full bg-black aspect-square rounded-full border-tableBorder\"\n                          alt={post[0].integration.providerIdentifier}\n                          src={`/icons/platforms/${post[0].integration.providerIdentifier}.png`}\n                        />\n                      </div>\n                    </div>\n                  </div>\n                  <div className=\"flex-1 space-y-1\">\n                    <div className=\"flex items-center space-x-2\">\n                      <h2 className=\"text-sm font-semibold\">\n                        {post[0].integration.name}\n                      </h2>\n                      <span className=\"text-sm text-gray-500\">\n                        @{post[0].integration.profile}\n                      </span>\n                    </div>\n                    <div className=\"flex flex-col gap-[20px]\">\n                      <div\n                        className=\"text-sm whitespace-pre-wrap\"\n                        dangerouslySetInnerHTML={{\n                          __html: p.content,\n                        }}\n                      />\n                      <div className=\"flex w-full gap-[10px]\">\n                        {JSON.parse(p?.image || '[]').map((p: any) => (\n                          <div\n                            key={p.name}\n                            className=\"flex-1 rounded-[10px] max-h-[500px] overflow-hidden\"\n                          >\n                            <VideoOrImage\n                              isContain={true}\n                              src={p.path}\n                              autoplay={true}\n                            />\n                          </div>\n                        ))}\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"w-full lg:w-96 lg:flex-shrink-0\">\n          <div className=\"p-4 pt-0\">\n            <CommentsComponents postId={id} />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/agents/[id]/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { Agent } from '@gitroom/frontend/components/agents/agent';\nimport { AgentChat } from '@gitroom/frontend/components/agents/agent.chat';\nexport const metadata: Metadata = {\n  title: 'Postiz - Agent',\n  description: '',\n};\nexport default async function Page() {\n  return (\n    <AgentChat />\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/agents/layout.tsx",
    "content": "import { Metadata } from 'next';\nimport { Agent } from '@gitroom/frontend/components/agents/agent';\nexport const metadata: Metadata = {\n  title: 'Postiz - Agent',\n  description: 'agents',\n};\nexport default async function Layout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return <Agent>{children}</Agent>;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/agents/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { redirect } from 'next/navigation';\n\nexport const metadata: Metadata = {\n  title: 'Postiz - Agent',\n  description: '',\n};\n\nexport default async function Page() {\n  return redirect('/agents/new');\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/analytics/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { PlatformAnalytics } from '@gitroom/frontend/components/platform-analytics/platform.analytics';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Analytics`,\n  description: '',\n};\nexport default async function Index() {\n  return <PlatformAnalytics />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/billing/lifetime/page.tsx",
    "content": "import { LifetimeDeal } from '@gitroom/frontend/components/billing/lifetime.deal';\nexport const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Lifetime deal`,\n  description: '',\n};\nexport default async function Page() {\n  return <LifetimeDeal />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/billing/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { BillingComponent } from '@gitroom/frontend/components/billing/billing.component';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Billing`,\n  description: '',\n};\nexport default async function Page() {\n  return (\n    <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n      <BillingComponent />\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/err/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { getT } from '@gitroom/react/translation/get.translation.service.backend';\nexport const metadata: Metadata = {\n  title: 'Error',\n  description: '',\n};\nexport default async function Page() {\n  const t = await getT();\n  return (\n    <div>\n      {t(\n        'we_are_experiencing_some_difficulty_try_to_refresh_the_page',\n        'We are experiencing some difficulty, try to refresh the page'\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/launches/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { LaunchesComponent } from '@gitroom/frontend/components/launches/launches.component';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz Calendar' : 'Gitroom Launches'}`,\n  description: '',\n};\nexport default async function Index() {\n  return <LaunchesComponent />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/layout.tsx",
    "content": "import { LayoutComponent } from '@gitroom/frontend/components/new-layout/layout.component';\n\nexport default async function Layout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return <LayoutComponent>{children}</LayoutComponent>;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/media/page.tsx",
    "content": "import { MediaLayoutComponent } from '@gitroom/frontend/components/new-layout/layout.media.component';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\n\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Media`,\n  description: '',\n};\n\nexport default async function Page() {\n  return <MediaLayoutComponent />\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/plugs/page.tsx",
    "content": "import { Plugs } from '@gitroom/frontend/components/plugs/plugs';\nexport const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Plugs`,\n  description: '',\n};\nexport default async function Index() {\n  return <Plugs />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/settings/page.tsx",
    "content": "import { SettingsPopup } from '@gitroom/frontend/components/layout/settings.component';\nexport const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Settings`,\n  description: '',\n};\nexport default async function Index({\n  searchParams,\n}: {\n  searchParams: {\n    code: string;\n  };\n}) {\n  return <SettingsPopup />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/(site)/third-party/page.tsx",
    "content": "import { ThirdPartyComponent } from '@gitroom/frontend/components/third-parties/third-party.component';\n\nexport const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${\n    isGeneralServerSide() ? 'Postiz Integrations' : 'Gitroom Integrations'\n  }`,\n  description: '',\n};\nexport default async function Index() {\n  return <ThirdPartyComponent />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/api/uploads/[[...path]]/route.ts",
    "content": "import { NextRequest, NextResponse } from 'next/server';\nimport { createReadStream, statSync } from 'fs';\n// @ts-ignore\nimport mime from 'mime';\nasync function* nodeStreamToIterator(stream: any) {\n  for await (const chunk of stream) {\n    yield chunk;\n  }\n}\nfunction iteratorToStream(iterator: any) {\n  return new ReadableStream({\n    async pull(controller) {\n      const { value, done } = await iterator.next();\n      if (done) {\n        controller.close();\n      } else {\n        controller.enqueue(new Uint8Array(value));\n      }\n    },\n  });\n}\nexport const GET = (\n  request: NextRequest,\n  context: {\n    params: {\n      path: string[];\n    };\n  }\n) => {\n  const filePath =\n    process.env.UPLOAD_DIRECTORY + '/' + context.params.path.join('/');\n  const response = createReadStream(filePath);\n  const fileStats = statSync(filePath);\n  const contentType = mime.getType(filePath) || 'application/octet-stream';\n  const iterator = nodeStreamToIterator(response);\n  const webStream = iteratorToStream(iterator);\n  return new Response(webStream, {\n    headers: {\n      'Content-Type': contentType,\n      // Set the appropriate content-type header\n      'Content-Length': fileStats.size.toString(),\n      // Set the content-length header\n      'Last-Modified': fileStats.mtime.toUTCString(),\n      // Set the last-modified header\n      'Cache-Control': 'public, max-age=31536000, immutable', // Example cache-control header\n    },\n  });\n};\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/activate/[code]/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { AfterActivate } from '@gitroom/frontend/components/auth/after.activate';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${\n    isGeneralServerSide() ? 'Postiz' : 'Gitroom'\n  } - Activate your account`,\n  description: '',\n};\nexport default async function Auth() {\n  return <AfterActivate />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/activate/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { Metadata } from 'next';\nimport { Activate } from '@gitroom/frontend/components/auth/activate';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${\n    isGeneralServerSide() ? 'Postiz' : 'Gitroom'\n  } - Activate your account`,\n  description: '',\n};\nexport default async function Auth() {\n  return <Activate />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/forgot/[token]/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { ForgotReturn } from '@gitroom/frontend/components/auth/forgot-return';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,\n  description: '',\n};\nexport default async function Auth(params: {\n  params: {\n    token: string;\n  };\n}) {\n  return <ForgotReturn token={params.params.token} />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/forgot/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { Forgot } from '@gitroom/frontend/components/auth/forgot';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,\n  description: '',\n};\nexport default async function Auth() {\n  return <Forgot />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/layout.tsx",
    "content": "import { getT } from '@gitroom/react/translation/get.translation.service.backend';\n\nexport const dynamic = 'force-dynamic';\nimport { ReactNode } from 'react';\nimport Image from 'next/image';\nimport loadDynamic from 'next/dynamic';\nimport { TestimonialComponent } from '@gitroom/frontend/components/auth/testimonial.component';\nimport { LogoTextComponent } from '@gitroom/frontend/components/ui/logo-text.component';\nconst ReturnUrlComponent = loadDynamic(() => import('./return.url.component'));\nexport default async function AuthLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  const t = await getT();\n\n  return (\n    <div className=\"bg-[#0E0E0E] flex flex-1 p-[12px] gap-[12px] min-h-screen w-screen text-white\">\n      {/*<style>{`html, body {overflow-x: hidden;}`}</style>*/}\n      <ReturnUrlComponent />\n      <div className=\"flex flex-col py-[40px] px-[20px] flex-1 lg:w-[600px] lg:flex-none rounded-[12px] text-white p-[12px] bg-[#1A1919]\">\n        <div className=\"w-full max-w-[440px] mx-auto justify-center gap-[20px] h-full flex flex-col text-white\">\n          <LogoTextComponent />\n          <div className=\"flex\">{children}</div>\n        </div>\n      </div>\n      <div className=\"text-[36px] flex-1 pt-[88px] hidden lg:flex flex-col items-center\">\n        <div className=\"text-center\">\n          Over <span className=\"text-[42px] text-[#FC69FF]\">20,000+</span>{' '}\n          Entrepreneurs use\n          <br />\n          Postiz To Grow Their Social Presence\n        </div>\n        <TestimonialComponent />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/login/page.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport { Login } from '@gitroom/frontend/components/auth/login';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Login`,\n  description: '',\n};\nexport default async function Auth() {\n  return <Login />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/login-required/page.tsx",
    "content": "export default async function LoginRequiredPage() {\n  return (\n    <div className=\"fixed left-0 top-0 w-full h-full bg-[#121212] z-[100] flex justify-center items-center text-4xl\">\n      Login to use the wizard to generate API code\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/page.tsx",
    "content": "import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';\nexport const dynamic = 'force-dynamic';\nimport { Register } from '@gitroom/frontend/components/auth/register';\nimport { Metadata } from 'next';\nimport { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';\nimport Link from 'next/link';\nimport { getT } from '@gitroom/react/translation/get.translation.service.backend';\nimport { LoginWithOidc } from '@gitroom/frontend/components/auth/login.with.oidc';\nexport const metadata: Metadata = {\n  title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Register`,\n  description: '',\n};\nexport default async function Auth(params: {searchParams: {provider: string}}) {\n  const t = await getT();\n  if (process.env.DISABLE_REGISTRATION === 'true') {\n    const canRegister = (\n      await (await internalFetch('/auth/can-register')).json()\n    ).register;\n    if (!canRegister && !params?.searchParams?.provider) {\n      return (\n        <>\n          <LoginWithOidc />\n          <div className=\"text-center\">\n            {t('registration_is_disabled', 'Registration is disabled')}\n            <br />\n            <Link className=\"underline hover:font-bold\" href=\"/auth/login\">\n              {t('login_instead', 'Login instead')}\n            </Link>\n          </div>\n        </>\n      );\n    }\n  }\n  return <Register />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/auth/return.url.component.tsx",
    "content": "'use client';\n\nimport { useSearchParams } from 'next/navigation';\nimport { FC, useCallback, useEffect } from 'react';\nconst ReturnUrlComponent: FC = () => {\n  const params = useSearchParams();\n  const url = params.get('returnUrl');\n  useEffect(() => {\n    if (url?.indexOf?.('http')! > -1) {\n      localStorage.setItem('returnUrl', url!);\n    }\n  }, [url]);\n  return null;\n};\nexport const useReturnUrl = () => {\n  return {\n    getAndClear: useCallback(() => {\n      const data = localStorage.getItem('returnUrl');\n      localStorage.removeItem('returnUrl');\n      return data;\n    }, []),\n  };\n};\nexport default ReturnUrlComponent;\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/integrations/social/[provider]/page.tsx",
    "content": "import { ContinueIntegration } from '@gitroom/frontend/components/launches/continue.integration';\nimport { cookies } from 'next/headers';\n\nexport const dynamic = 'force-dynamic';\n\nexport default async function Page({\n  params: { provider },\n  searchParams,\n}: {\n  params: {\n    provider: string;\n  };\n  searchParams: any;\n}) {\n  const get = cookies().get('auth');\n  return <ContinueIntegration searchParams={searchParams} provider={provider} logged={!!get?.name} />;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/integrations/social/layout.tsx",
    "content": "import { ReactNode } from 'react';\n\nexport default async function IntegrationLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  return (\n    <div className=\"bg-[#0B0A0A] flex flex-1 min-h-screen w-screen\">\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/layout.tsx",
    "content": "import { SentryComponent } from '@gitroom/frontend/components/layout/sentry.component';\n\nexport const dynamic = 'force-dynamic';\nimport '../global.scss';\nimport 'react-tooltip/dist/react-tooltip.css';\nimport '@copilotkit/react-ui/styles.css';\nimport LayoutContext from '@gitroom/frontend/components/layout/layout.context';\nimport { ReactNode } from 'react';\nimport { Plus_Jakarta_Sans } from 'next/font/google';\nimport PlausibleProvider from 'next-plausible';\nimport clsx from 'clsx';\nimport { VariableContextComponent } from '@gitroom/react/helpers/variable.context';\nimport { Fragment } from 'react';\nimport { PHProvider } from '@gitroom/react/helpers/posthog';\nimport UtmSaver from '@gitroom/helpers/utils/utm.saver';\nimport { DubAnalytics } from '@gitroom/frontend/components/layout/dubAnalytics';\nimport { FacebookComponent } from '@gitroom/frontend/components/layout/facebook.component';\nimport { headers } from 'next/headers';\nimport { headerName } from '@gitroom/react/translation/i18n.config';\nimport { HtmlComponent } from '@gitroom/frontend/components/layout/html.component';\nimport Script from 'next/script';\n// import dynamicLoad from 'next/dynamic';\n// const SetTimezone = dynamicLoad(\n//   () => import('@gitroom/frontend/components/layout/set.timezone'),\n//   {\n//     ssr: false,\n//   }\n// );\n\nconst jakartaSans = Plus_Jakarta_Sans({\n  weight: ['600', '500'],\n  style: ['normal', 'italic'],\n  subsets: ['latin'],\n});\n\nexport default async function AppLayout({ children }: { children: ReactNode }) {\n  const allHeaders = headers();\n  const Plausible = !!process.env.STRIPE_PUBLISHABLE_KEY\n    ? PlausibleProvider\n    : Fragment;\n  return (\n    <html>\n      <head>\n        <link rel=\"icon\" href=\"/favicon.ico\" sizes=\"any\" />\n        {!!process.env.DATAFAST_WEBSITE_ID && (\n          <Script\n            data-website-id={process.env.DATAFAST_WEBSITE_ID}\n            data-domain=\"postiz.com\"\n            src=\"https://datafa.st/js/script.js\"\n            strategy=\"afterInteractive\"\n          />\n        )}\n      </head>\n      <body\n        className={clsx(jakartaSans.className, 'dark text-primary !bg-primary')}\n      >\n        <VariableContextComponent\n          storageProvider={\n            process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare'\n          }\n          environment={process.env.NODE_ENV!}\n          backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!}\n          plontoKey={process.env.NEXT_PUBLIC_POLOTNO!}\n          stripeClient={process.env.STRIPE_PUBLISHABLE_KEY!}\n          billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY}\n          discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!}\n          frontEndUrl={process.env.FRONTEND_URL!}\n          isGeneral={!!process.env.IS_GENERAL}\n          genericOauth={!!process.env.POSTIZ_GENERIC_OAUTH}\n          oauthLogoUrl={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL!}\n          oauthDisplayName={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME!}\n          uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!}\n          mcpUrl={process.env.MCP_URL}\n          dub={!!process.env.STRIPE_PUBLISHABLE_KEY}\n          facebookPixel={process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!}\n          telegramBotName={process.env.TELEGRAM_BOT_NAME!}\n          neynarClientId={process.env.NEYNAR_CLIENT_ID!}\n          isSecured={!process.env.NOT_SECURED}\n          disableImageCompression={!!process.env.DISABLE_IMAGE_COMPRESSION}\n          disableXAnalytics={!!process.env.DISABLE_X_ANALYTICS}\n          sentryDsn={process.env.NEXT_PUBLIC_SENTRY_DSN!}\n          extensionId={process.env.EXTENSION_ID || ''}\n          language={allHeaders.get(headerName)}\n          transloadit={\n            process.env.TRANSLOADIT_AUTH && process.env.TRANSLOADIT_TEMPLATE\n              ? [\n                  process.env.TRANSLOADIT_AUTH!,\n                  process.env.TRANSLOADIT_TEMPLATE!,\n                ]\n              : []\n          }\n        >\n          <SentryComponent>\n            {/*<SetTimezone />*/}\n            <HtmlComponent />\n            <DubAnalytics />\n            <FacebookComponent />\n            <Plausible\n              domain={!!process.env.IS_GENERAL ? 'postiz.com' : 'gitroom.com'}\n            >\n              <PHProvider\n                phkey={process.env.NEXT_PUBLIC_POSTHOG_KEY}\n                host={process.env.NEXT_PUBLIC_POSTHOG_HOST}\n              >\n                <LayoutContext>\n                  <UtmSaver />\n                  {children}\n                </LayoutContext>\n              </PHProvider>\n            </Plausible>\n          </SentryComponent>\n        </VariableContextComponent>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/oauth/authorize/layout.tsx",
    "content": "import { Metadata } from 'next';\nimport { ReactNode } from 'react';\n\nexport const metadata: Metadata = {\n  title: 'Authorize Application',\n};\n\nexport default async function OAuthLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  return (\n    <div className=\"bg-[#0B0A0A] flex flex-1 min-h-screen w-screen\">\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(app)/oauth/authorize/page.tsx",
    "content": "'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useSearchParams } from 'next/navigation';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Logo } from '@gitroom/frontend/components/new-layout/logo';\n\nexport default function OAuthAuthorizePage() {\n  const searchParams = useSearchParams();\n  const fetch = useFetch();\n  const [appInfo, setAppInfo] = useState<any>(null);\n  const [error, setError] = useState('');\n  const [loading, setLoading] = useState(true);\n  const [submitting, setSubmitting] = useState(false);\n\n  const clientId = searchParams.get('client_id');\n  const responseType = searchParams.get('response_type');\n  const state = searchParams.get('state');\n\n  useEffect(() => {\n    if (!clientId || !responseType) {\n      setError('Missing required parameters (client_id, response_type)');\n      setLoading(false);\n      return;\n    }\n    if (responseType !== 'code') {\n      setError('Only response_type=code is supported');\n      setLoading(false);\n      return;\n    }\n\n    const params = new URLSearchParams({\n      client_id: clientId,\n      response_type: responseType,\n      ...(state ? { state } : {}),\n    });\n\n    fetch(`/oauth/authorize?${params}`)\n      .then((r) => r.json())\n      .then((data) => {\n        if (data.statusCode && data.statusCode >= 400) {\n          setError(data.message || 'Invalid OAuth request');\n        } else {\n          setAppInfo(data);\n        }\n        setLoading(false);\n      })\n      .catch(() => {\n        setError('Failed to validate OAuth request');\n        setLoading(false);\n      });\n  }, [clientId, responseType, state]);\n\n  const handleAction = useCallback(\n    async (action: 'approve' | 'deny') => {\n      setSubmitting(true);\n      try {\n        const result = await (\n          await fetch('/oauth/authorize', {\n            method: 'POST',\n            body: JSON.stringify({\n              client_id: clientId,\n              state,\n              action,\n            }),\n          })\n        ).json();\n\n        if (result.redirect) {\n          window.location.href = result.redirect;\n        }\n      } catch {\n        setError('Failed to process authorization');\n        setSubmitting(false);\n      }\n    },\n    [clientId, state]\n  );\n\n  if (loading) {\n    return (\n      <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n        <div className=\"absolute inset-0 opacity-30\">\n          <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n          <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n        </div>\n        <div className=\"relative z-10 text-center\">\n          <div className=\"flex justify-center mb-[24px]\">\n            <Logo />\n          </div>\n          <div className=\"text-[16px] text-gray-400\">\n            Please wait...\n          </div>\n          <div className=\"mt-[32px] flex justify-center\">\n            <div className=\"w-[48px] h-[48px] border-[3px] border-[#612BD3] border-t-transparent rounded-full animate-spin\" />\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (error) {\n    return (\n      <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n        <div className=\"absolute inset-0 opacity-30\">\n          <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n          <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n        </div>\n        <div className=\"relative z-10 text-center\">\n          <div className=\"flex justify-center mb-[24px]\">\n            <Logo />\n          </div>\n          <div className=\"w-[80px] h-[80px] mx-auto mb-[24px] rounded-full bg-red-500/20 flex items-center justify-center\">\n            <svg\n              className=\"w-[40px] h-[40px] text-red-500\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 20 20\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n          </div>\n          <div className=\"text-[28px] font-semibold mb-[12px]\">\n            Authorization Error\n          </div>\n          <div className=\"text-[16px] text-gray-400 max-w-[400px]\">\n            {error}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (!appInfo) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n      <div className=\"absolute inset-0 opacity-30\">\n        <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n        <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n      </div>\n\n      <div className=\"relative z-10 w-full max-w-[500px] mx-auto px-[20px]\">\n        <div className=\"flex justify-center mb-[32px]\">\n          <Logo />\n        </div>\n\n        <div className=\"bg-[#1A1919] rounded-[16px] p-[32px] flex flex-col gap-[24px]\">\n          <div className=\"flex flex-col items-center gap-[16px]\">\n            {appInfo.app.picture?.path ? (\n              <img\n                src={appInfo.app.picture.path}\n                alt={appInfo.app.name}\n                className=\"w-[64px] h-[64px] rounded-full object-cover\"\n              />\n            ) : (\n              <div className=\"w-[64px] h-[64px] rounded-full bg-[#2A2929] flex items-center justify-center text-[24px] text-gray-400\">\n                {appInfo.app.name?.[0]?.toUpperCase() || '?'}\n              </div>\n            )}\n            <h2 className=\"text-[24px] font-semibold text-center\">\n              {appInfo.app.name}\n            </h2>\n            {appInfo.app.description && (\n              <div className=\"text-gray-400 text-center text-[14px]\">\n                {appInfo.app.description}\n              </div>\n            )}\n          </div>\n\n          <div className=\"border-t border-[#2A2929] pt-[16px]\">\n            <div className=\"text-[14px] text-gray-400 mb-[12px]\">\n              This application is requesting access to your Postiz account. It\n              will be able to:\n            </div>\n            <ul className=\"text-[14px] list-disc list-inside space-y-[4px]\">\n              <li>Access your integrations and channels</li>\n              <li>Create and schedule posts on your behalf</li>\n              <li>Read your post analytics</li>\n            </ul>\n          </div>\n\n          <div className=\"flex gap-[12px]\">\n            <button\n              onClick={() => handleAction('approve')}\n              disabled={submitting}\n              className=\"flex-1 bg-[#612BD3] hover:bg-[#7B3FF2] disabled:opacity-50 text-white rounded-[8px] py-[10px] px-[16px] text-[14px] font-semibold transition-colors\"\n            >\n              Authorize\n            </button>\n            <button\n              onClick={() => handleAction('deny')}\n              disabled={submitting}\n              className=\"flex-1 bg-[#2A2929] hover:bg-[#3A3939] disabled:opacity-50 text-white rounded-[8px] py-[10px] px-[16px] text-[14px] font-semibold transition-colors\"\n            >\n              Deny\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(extension)/layout.tsx",
    "content": "export const dynamic = 'force-dynamic';\nimport '../global.scss';\nimport 'react-tooltip/dist/react-tooltip.css';\nimport '@copilotkit/react-ui/styles.css';\nimport LayoutContext from '@gitroom/frontend/components/layout/layout.context';\nimport { ReactNode } from 'react';\nimport { Plus_Jakarta_Sans } from 'next/font/google';\nimport clsx from 'clsx';\nimport { VariableContextComponent } from '@gitroom/react/helpers/variable.context';\nimport UtmSaver from '@gitroom/helpers/utils/utm.saver';\n\nconst jakartaSans = Plus_Jakarta_Sans({\n  weight: ['600', '500'],\n  style: ['normal', 'italic'],\n  subsets: ['latin'],\n});\n\nexport default async function AppLayout({ children }: { children: ReactNode }) {\n  return (\n    <html>\n      <head>\n        <link rel=\"icon\" href=\"/favicon.ico\" sizes=\"any\" />\n      </head>\n      <body\n        className={clsx(jakartaSans.className, 'dark text-primary !bg-primary')}\n      >\n        <VariableContextComponent\n          language=\"en\"\n          storageProvider={\n            process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare'\n          }\n          stripeClient=\"\"\n          environment={process.env.NODE_ENV!}\n          backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!}\n          plontoKey={process.env.NEXT_PUBLIC_POLOTNO!}\n          billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY}\n          discordUrl={process.env.NEXT_PUBLIC_DISCORD_SUPPORT!}\n          frontEndUrl={process.env.FRONTEND_URL!}\n          isGeneral={!!process.env.IS_GENERAL}\n          genericOauth={!!process.env.POSTIZ_GENERIC_OAUTH}\n          oauthLogoUrl={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL!}\n          oauthDisplayName={process.env.NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME!}\n          uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!}\n          mcpUrl={process.env.MCP_URL}\n          dub={false}\n          facebookPixel={process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!}\n          telegramBotName={process.env.TELEGRAM_BOT_NAME!}\n          neynarClientId={process.env.NEYNAR_CLIENT_ID!}\n          isSecured={!process.env.NOT_SECURED}\n          disableImageCompression={!!process.env.DISABLE_IMAGE_COMPRESSION}\n          disableXAnalytics={!!process.env.DISABLE_X_ANALYTICS}\n          sentryDsn={process.env.NEXT_PUBLIC_SENTRY_DSN!}\n          extensionId={process.env.EXTENSION_ID || ''}\n          transloadit={\n            process.env.TRANSLOADIT_AUTH && process.env.TRANSLOADIT_TEMPLATE\n              ? [\n                  process.env.TRANSLOADIT_AUTH!,\n                  process.env.TRANSLOADIT_TEMPLATE!,\n                ]\n              : []\n          }\n        >\n          <LayoutContext>\n            <UtmSaver />\n            {children}\n          </LayoutContext>\n        </VariableContextComponent>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(extension)/modal/[style]/[platform]/page.tsx",
    "content": "'use client';\n\nimport { StandaloneModal } from '@gitroom/frontend/components/standalone-modal/standalone.modal';\nexport default async function Modal() {\n  return (\n    <div className=\"w-screen h-screen overflow-hidden bg-black\">\n      <div className=\"text-textColor h-[calc(100vh+80px)] w-[calc(100vw+80px)] -m-[40px]\">\n        <StandaloneModal />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/(extension)/modal/layout.tsx",
    "content": "import { ReactNode } from 'react';\nimport { AppLayout } from '@gitroom/frontend/components/launches/layout.standalone';\nexport default async function AppLayoutIn({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  return <AppLayout>{children}</AppLayout>;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/colors.scss",
    "content": ":root {\n  .dark {\n    --new-back-drop: #000;\n    --new-settings: #242323;\n    --new-border: #252525;\n    --new-bgColor: #0e0e0e;\n    --new-bgColorInner: #1a1919;\n    --new-sep: #454444;\n    --new-bgLineColor: #212121;\n    --new-textItemFocused: #1a1919;\n    --new-textItemBlur: #999999;\n    --new-boxFocused: #fff;\n    --new-textColor: 255 255 255;\n    --new-blockSeparator: #272626;\n    --new-btn-simple: #313030;\n    --new-btn-text: #ffffff;\n    --new-btn-primary: #612bd3;\n    --new-ai-btn: #d82d7e;\n    --new-box-hover: #201f1f;\n    --new-table-border: #2b2b2b;\n    --new-table-header: #1e1d1d;\n    --new-table-text: #9c9c9c;\n    --new-table-text-focused: #fc69ff;\n    --new-small-strips: #191818;\n    --new-big-strips: #161515;\n    --new-col-color: #2c2b2b;\n    --new-menu-dots: #696868;\n    --new-menu-hover: #fff;\n    --menu-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.5);\n    --popup-color: rgba(65, 64, 66, 0.3);\n    --border-preview: transparent;\n    --preview-box-shadow: none;\n    --linkedin-border: #2e3438;\n    --linkedin-bg: #1b1f23;\n    --linkedin-text: #c6c7c8;\n    --facebook-bg: #242526;\n    --facebook-bg-comment: #333334;\n    --instagram-bg: #0b1014;\n    --tiktok-item-bg: #2a2a2a;\n    --tiktok-item-icon-bg: #FFF;\n    --youtube-bg: #0F0F0F;\n    --youtube-button: #F1F1F1;\n    --youtube-action-color: #272727;\n    --youtube-svg-border: #A0A0A0;\n  }\n  .light {\n    --new-back-drop: #2d1b57;\n    --new-settings: #eceef1;\n    --new-sep: #d5d9dd;\n    --new-border: #eaecee;\n    --new-bgColor: #f0f2f4;\n    --new-bgColorInner: #ffffff;\n    --new-bgLineColor: #e7e9eb;\n    --new-textItemFocused: #3900b2;\n    --new-textItemBlur: #777b7f;\n    --new-boxFocused: #ebe8ff;\n    --new-textColor: 14 14 14;\n    --new-blockSeparator: #f2f2f4;\n    --new-btn-simple: #eceef1;\n    --new-btn-text: #0e0e0e;\n    --new-btn-primary: #612bd3;\n    --new-ai-btn: #d82d7e;\n    --new-box-hover: #f4f6f8;\n    --new-table-border: #e7e9eb;\n    --new-table-header: #f5f7f9;\n    --new-table-text: #777b7f;\n    --new-table-text-focused: #fc69ff;\n    --new-small-strips: #191818;\n    --new-big-strips: #f5f7f9;\n    --new-col-color: #eff1f3;\n    --new-menu-dots: #696868;\n    --new-menu-hover: #000;\n    --menu-shadow: -22px 83px 24px 0 rgba(55, 52, 75, 0),\n      -14px 53px 22px 0 rgba(55, 52, 75, 0.01),\n      -8px 30px 19px 0 rgba(55, 52, 75, 0.05),\n      -3px 13px 14px 0 rgba(55, 52, 75, 0.09),\n      -1px 3px 8px 0 rgba(55, 52, 75, 0.1);\n    --popup-color: rgba(55, 37, 97, 0.2);\n    --border-preview: #f1f0f3;\n    --preview-box-shadow: 0 386px 108px 0 rgba(38, 32, 64, 0),\n      0 247px 99px 0 rgba(38, 32, 64, 0.01),\n      0 139px 83px 0 rgba(38, 32, 64, 0.03),\n      0 62px 62px 0 rgba(38, 32, 64, 0.04), 0 15px 34px 0 rgba(38, 32, 64, 0.05);\n    --linkedin-border: #e9e5df;\n    --linkedin-bg: #fff;\n    --linkedin-text: #707070;\n    --facebook-bg: #fff;\n    --facebook-bg-comment: #f6f6f6;\n    --instagram-bg: #fff;\n    --tiktok-item-bg: #EEF1F0;\n    --tiktok-item-icon-bg: #454645;\n    --youtube-bg: #FFF;\n    --youtube-button: #000;\n    --youtube-action-color: #F1F1F1;\n    --youtube-svg-border: #1A1A1A;\n  }\n}\n\n:root {\n  .dark {\n    --color-primary: #0e0e0e;\n    --color-secondary: #090b13;\n    --color-text: #ffffff;\n    --color-third: #080b13;\n    --color-forth: #612ad5;\n    --color-fifth: var(--new-bgLineColor);\n    --color-sixth: var(--new-table-header);\n    --color-seventh: #7236f1;\n    --color-gray: #8c8c8c;\n    --color-input: var(--new-table-header);\n    --color-input-text: #64748b;\n    --color-table-border: var(--new-table-border);\n    --color-custom1: #324264;\n    --color-custom2: #141c2c;\n    --color-custom3: #0b0f1c;\n    --color-custom4: #8155dd;\n    --color-custom5: #e9e9f1;\n    --color-custom6: var(--new-bgLineColor);\n    --color-custom7: #7950f2;\n    --color-custom8: var(--new-btn-simple);\n    --color-custom9: #354258;\n    --color-custom10: #e4b895;\n    --color-custom11: #8b90ff;\n    --color-custom12: #b7c1ff;\n    --color-custom13: #ffac30;\n    --color-custom14: #576a9a;\n    --color-custom15: #0a0a0a;\n    --color-custom16: #121a2d;\n    --color-custom17: #d3d3d3;\n    --color-custom18: #aaaaaa;\n    --color-custom19: #f97066;\n    --color-custom20: #121b2c;\n    --color-custom21: #506490;\n    --color-custom22: #b91c1c;\n    --color-custom23: #000000;\n    --color-custom24: #eaff00;\n    --color-custom25: #2e3336;\n    --color-custom26: #1d9bf0;\n    --color-custom27: #71767b;\n    --color-custom28: #b69dec;\n    --color-custom29: #291259;\n    --color-custom30: #5826c2;\n    --color-custom31: #0f1727;\n    --color-custom32: #181818;\n    --color-custom33: #f2f2f2;\n    --color-custom34: #334155;\n    --color-custom35: #242424;\n    --color-custom36: #6b6b6b;\n    --color-custom37: #0b1416;\n    --color-custom38: #d9d9d9;\n    --color-custom39: #ffffff;\n    --color-custom40: #0e0e0e;\n    --color-custom41: #cebdf2;\n    --color-custom42: #32d583;\n    --color-custom43: #425379;\n    --color-custom44: #283450;\n    --color-custom45: #832ad5;\n    --color-custom46: #658dac;\n    --color-custom47: #182034;\n    --color-custom48: #080b14;\n    --color-custom49: #0a0b14;\n    --color-custom50: #262373;\n    --color-custom51: #4f46e5;\n    --color-custom52: #eaeef2;\n    --color-custom53: #7c7d86;\n    --color-custom54: #afb8c1;\n    --color-custom55: #1b263b;\n    --color-modalCustom: #000000;\n  }\n  .light {\n    --color-primary: #f0f2f4;\n    --color-secondary: #fff;\n    --color-text: #000;\n    --color-third: white;\n    --color-forth: #612ad5;\n    --color-fifth: var(--new-bgLineColor);\n    --color-sixth: var(--new-table-header);\n    --color-seventh: #7236f1;\n    --color-gray: #8c8c8c;\n    --color-input: var(--new-table-header);\n    --color-input-text: #64748b;\n    --color-table-border: #efefef;\n    --color-custom1: #324264;\n    --color-custom2: #f8f8f8;\n    --color-custom3: #fff;\n    --color-custom4: #8155dd;\n    --color-custom5: #e9e9f1;\n    --color-custom6: var(--new-bgLineColor);\n    --color-custom7: #7950f2;\n    --color-custom8: var(--new-btn-simple);\n    --color-custom9: #354258;\n    --color-custom10: #e4b895;\n    --color-custom11: #8b90ff;\n    --color-custom12: #b7c1ff;\n    --color-custom13: #ffac30;\n    --color-custom14: #576a9a;\n    --color-custom15: #0a0a0a;\n    --color-custom16: #121a2d;\n    --color-custom17: #000;\n    --color-custom18: #000;\n    --color-custom19: #f97066;\n    --color-custom20: #f5f5f5;\n    --color-custom21: #506490;\n    --color-custom22: #b91c1c;\n    --color-custom23: #f5f5f5;\n    --color-custom24: #eaff00;\n    --color-custom25: #2e3336;\n    --color-custom26: #1d9bf0;\n    --color-custom27: #71767b;\n    --color-custom28: #b69dec;\n    --color-custom29: #291259;\n    --color-custom30: #efefef;\n    --color-custom31: #e0e0e0;\n    --color-custom32: #181818;\n    --color-custom33: #f2f2f2;\n    --color-custom34: #334155;\n    --color-custom35: #242424;\n    --color-custom36: #6b6b6b;\n    --color-custom37: #0b1416;\n    --color-custom38: #d9d9d9;\n    --color-custom39: #000;\n    --color-custom40: #fff;\n    --color-custom41: #cebdf2;\n    --color-custom42: #32d583;\n    --color-custom43: #425379;\n    --color-custom44: #283450;\n    --color-custom45: #832ad5;\n    --color-custom46: #658dac;\n    --color-custom47: #182034;\n    --color-custom48: #fff;\n    --color-custom49: #0a0b14;\n    --color-custom50: #262373;\n    --color-custom51: #4f46e5;\n    --color-custom52: #eaeef2;\n    --color-custom53: #7c7d86;\n    --color-custom54: #afb8c1;\n    --color-custom55: #d5d7e1;\n    --color-modalCustom: transparent;\n  }\n}\n"
  },
  {
    "path": "apps/frontend/src/app/global-error.tsx",
    "content": "'use client';\nimport * as Sentry from '@sentry/nextjs';\nimport NextError from 'next/error';\nimport { useEffect } from 'react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\n\nexport default function GlobalError({\n  error,\n}: {\n  error: Error & { digest?: string };\n}) {\n  const { sentryDsn } = useVariables();\n\n  useEffect(() => {\n    if (!sentryDsn) {\n      return;\n    }\n    const eventId = Sentry.captureException(error);\n    Sentry.showReportDialog({\n      eventId,\n      title: 'Something broke!',\n      subtitle: 'Please help us fix the issue by providing some details.',\n      labelComments: 'What happened?',\n      labelName: 'Your name',\n      labelEmail: 'Your email',\n      labelSubmit: 'Send Report',\n      lang: 'en',\n    });\n\n  }, [error]);\n  return (\n    <html>\n      <body>\n        <NextError statusCode={0} />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/app/global.scss",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n@import './colors.scss';\n@import './polonto.css';\n@import '@uppy/core/dist/style.css';\n@import '@uppy/dashboard/dist/style.css';\n\nbody {\n  background: var(--new-bgColor) !important;\n  color: var(--new-btn-text);\n}\n\nbody * {\n  outline: none !important;\n}\n\n.box {\n  position: relative;\n}\n.box span {\n  position: relative;\n  z-index: 2;\n}\n#tooltip {\n  z-index: 10000;\n}\n.box:after {\n  border-radius: 50px;\n  width: 100%;\n  height: 100%;\n  left: 0;\n  top: 0;\n  content: '';\n  position: absolute;\n  background: white;\n  opacity: 0;\n  z-index: 1;\n  transition: all 0.3s ease-in-out;\n}\n\n.showbox {\n  @apply text-primary;\n}\n.showbox:after {\n  @apply bg-textColor;\n  opacity: 1;\n  transition: all 0.3s ease-in-out;\n}\n.table1 {\n  width: 100%;\n  border-collapse: collapse;\n}\n\n.table1 thead {\n  @apply bg-customColor8 border-b border-b-fifth;\n  height: 44px;\n  font-size: 12px;\n}\n.table1 thead th,\n.table1 tbody td {\n  text-align: left;\n  padding: 14px 24px;\n}\n\n.table1 tbody td {\n  padding: 16px 24px;\n  font-size: 14px;\n}\n\n.swal2-modal {\n  @apply bg-primary #{!important};\n  @apply border-b-[2px] border-b-sixth;\n}\n\n.swal2-modal * {\n  @apply text-textColor #{!important};\n}\n\n.swal2-icon {\n  @apply text-textColor bg-primary #{!important};\n}\n\n.swal2-cancel {\n  @apply text-white #{!important};\n}\n.swal2-confirm {\n  @apply bg-customColor50 #{!important};\n  @apply text-white #{!important};\n}\n\n.w-md-editor-text {\n  min-height: 100% !important;\n}\n\n.react-tags {\n  @apply border border-newTableBorder placeholder-textColor;\n  position: relative;\n  padding-left: 16px;\n  font-size: 14px;\n  background-color: var(--new-bgColorInner);\n  height: 42px;\n  border-radius: 8px;\n  /* shared font styles */\n  line-height: 1.2;\n  /* clicking anywhere will focus the input */\n  cursor: text;\n  display: flex;\n  align-items: center;\n  margin-right: 0 !important;\n}\n\n.react-tags input {\n  @apply placeholder-textColor;\n  font-size: 14px;\n}\n\n.react-tags.is-active {\n  @apply border-customColor51;\n}\n\n.react-tags.is-disabled {\n  opacity: 0.75;\n  @apply bg-customColor52;\n  /* Prevent any clicking on the component */\n  pointer-events: none;\n  cursor: not-allowed;\n}\n\n.react-tags.is-invalid {\n  border-color: #fd5956;\n  box-shadow: 0 0 0 2px rgba(253, 86, 83, 0.25);\n}\n\n.react-tags__label {\n  position: absolute;\n  left: -10000px;\n  top: auto;\n  width: 1px;\n  height: 1px;\n  overflow: hidden;\n}\n\n.react-tags__list {\n  /* Do not use display: contents, it's too buggy */\n  display: inline;\n  padding: 0;\n}\n\n.react-tags__list-item {\n  display: inline;\n  list-style: none;\n}\n\n.react-tags__tag {\n  margin: 0 0.25rem 0 0;\n  padding: 0.15rem 0.5rem;\n  border: 0;\n  border-radius: 3px;\n  @apply bg-seventh;\n  /* match the font styles */\n  font-size: inherit;\n  line-height: inherit;\n}\n\n.react-tags__tag:hover {\n  @apply text-textColor bg-customColor51;\n}\n\n.react-tags__tag::after {\n  content: '';\n  display: inline-block;\n  width: 0.65rem;\n  height: 0.65rem;\n  clip-path: polygon(\n    10% 0,\n    0 10%,\n    40% 50%,\n    0 90%,\n    10% 100%,\n    50% 60%,\n    90% 100%,\n    100% 90%,\n    60% 50%,\n    100% 10%,\n    90% 0,\n    50% 40%\n  );\n  margin-left: 0.5rem;\n  font-size: 0.875rem;\n  @apply bg-customColor53;\n}\n\n.react-tags__tag:hover::after {\n  @apply bg-primary;\n}\n\n.react-tags__combobox {\n  display: inline-block;\n  /* match tag layout */\n  /* prevents autoresize overflowing the container */\n  max-width: 100%;\n}\n\n.react-tags__combobox-input {\n  /* prevent autoresize overflowing the container */\n  max-width: 100%;\n  width: 100% !important;\n  /* remove styles and layout from this element */\n  margin: 0;\n  padding: 0;\n  border: 0;\n  outline: none;\n  background: none;\n  /* match the font styles */\n  font-size: 16px;\n  line-height: inherit;\n}\n\n.react-tags__combobox-input::placeholder {\n  @apply text-customColor53;\n  opacity: 1;\n}\n\n.react-tags__listbox {\n  position: absolute;\n  z-index: 1;\n  top: calc(100% + 5px);\n  /* Negate the border width on the container */\n  left: -2px;\n  right: -2px;\n  max-height: 12.5rem;\n  overflow-y: auto;\n  @apply bg-input border border-customColor54;\n  border-radius: 6px;\n  box-shadow: rgba(0, 0, 0, 0.1) 0 10px 15px -4px,\n    rgba(0, 0, 0, 0.05) 0 4px 6px -2px;\n}\n\n.react-tags__listbox-option {\n  padding: 0.375rem 0.5rem;\n}\n\n.react-tags__listbox-option:hover {\n  cursor: pointer;\n  @apply bg-third;\n}\n\n.react-tags__listbox-option:not([aria-disabled='true']).is-active {\n  @apply text-textColor bg-customColor51;\n}\n\n.react-tags__listbox-option[aria-disabled='true'] {\n  @apply text-customColor53;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n\n.react-tags__listbox-option[aria-selected='true']::after {\n  content: '✓';\n  margin-left: 0.5rem;\n}\n\n.react-tags__listbox-option[aria-selected='true']:not(.is-active)::after {\n  @apply text-customColor51;\n}\n\n.react-tags__listbox-option-highlight {\n  background-color: #ffdd00;\n}\n\n/*#renderEditor:not(:has(:first-child)) {*/\n/*    display: none;*/\n/*}*/\n.w-md-editor {\n  @apply bg-input #{!important};\n  border: 0 !important;\n  box-shadow: none !important;\n  border-radius: 8px !important;\n  border-bottom-left-radius: 0 !important;\n  border-bottom-right-radius: 0 !important;\n}\n\n.w-md-editor-toolbar {\n  @apply bg-input border-fifth #{!important};\n  height: 40px !important;\n  min-height: 40px !important;\n  padding: 0 8px !important;\n}\n\n.wmde-markdown {\n  background: transparent !important;\n  font-size: 20px !important;\n  font-weight: 400 !important;\n}\n.auto-width {\n  width: auto !important;\n}\n\n.editor :not(.removeEditor *) {\n  @apply text-textColor;\n}\n\n.editor .polonto * {\n  color: black !important;\n}\n\n.bp5-portal {\n  z-index: 9999 !important;\n}\n\n:empty + .existing-empty {\n  display: none;\n}\n\n.mantine-Paper-root {\n  outline: none !important;\n}\n//\n//:root {\n//  --copilot-kit-primary-color: #612ad5 !important;\n//  --copilot-kit-background-color: #0b0f1c !important;\n//  --copilot-kit-separator-color: #1f2941 !important;\n//  --copilot-kit-contrast-color: #ffffff !important;\n//  --copilot-kit-secondary-contrast-color: #ffffff !important;\n//  --copilot-kit-secondary-color: #000 !important;\n//  --copilot-kit-response-button-background-color: #000 !important;\n//  --copilot-kit-response-button-color: #fff !important;\n//}\n\n//.copilotKitWindow {\n//  @apply bg-customColor3 #{!important};\n//}\n\n//.copilotKitButtonIconOpen svg {\n//  display: none !important;\n//}\n//\n//.copilotKitButtonIconOpen:after {\n//  @apply text-textColor;\n//  content: '';\n//  display: block;\n//  position: relative;\n//  z-index: 1;\n//  object-fit: contain;\n//  background: url('/magic.svg') no-repeat center center / contain;\n//  width: 30px;\n//  height: 30px;\n//}\n//\n//.copilotKitPopup {\n//  right: -2rem !important;\n//  bottom: 2rem !important;\n//}\n//\n//.copilotKitWindow {\n//  /*right: -5rem !important;*/\n//}\n\n.trz {\n  transform: translateZ(0);\n}\n\n.uppy-FileInput-container {\n  @apply cursor-pointer font-[500] flex justify-center items-center gap-[4px] text-[12px] rounded-[4px] w-[107px] h-[25px] text-textColor border-[2px];\n  @apply bg-customColor3 border-customColor21;\n}\n\n.uppy-ProgressBar {\n  width: 150px;\n  position: relative;\n}\n\n.uppy-ProgressBar-inner {\n  @apply bg-customColor51;\n  height: 25px;\n  border-radius: 4px;\n}\n\n.uppy-ProgressBar-percentage {\n  position: absolute;\n  color: red;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n\n.uppy-ProgressBar-inner[style='width: 0%;'],\n.uppy-ProgressBar-inner[style='width: 0%;'] + div {\n  opacity: 0;\n}\n.uppy-ProgressBar-inner[style='width: 100%;'],\n.uppy-ProgressBar-inner[style='width: 100%;'] + div {\n  @apply animate-normalFadeOut;\n}\n\n.fill-text-textColor {\n  -webkit-text-fill-color: white !important;\n}\ndiv div .set-font-family {\n  font-family: 'Helvetica Neue', Helvetica !important;\n  font-stretch: 100% !important;\n  font-style: normal !important;\n  font-weight: 400 !important;\n}\n\n.col-calendar:hover:before {\n  content: 'Date passed';\n  color: var(--color-text);\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  white-space: nowrap;\n  opacity: 30%;\n  font-size: 14px;\n}\n\n.loading-shimmer {\n  position: relative;\n  color: rgba(255, 255, 255, 0.5);\n}\n\n.loading-shimmer:before {\n  content: attr(data-text);\n  position: absolute;\n  overflow: hidden;\n  max-width: 100%;\n  white-space: nowrap;\n  color: white;\n  animation: loading 4s linear 0s infinite;\n  filter: blur(0.4px);\n}\n@keyframes loading {\n  0% {\n    max-width: 0;\n  }\n}\n\n.tbaom7c {\n  display: none;\n}\n\n.tags-top > div {\n  flex: 1;\n  margin-right: 20px;\n  border: 0 !important;\n  background: transparent !important;\n  padding-left: 0 !important;\n}\n\n.tags-top .react-tags__combobox {\n  margin-left: 0;\n}\n\n.tags-top .react-tags__combobox {\n  height: 42px;\n  display: flex;\n  background-color: var(--new-bgColorInner);\n  padding-left: 10px;\n  padding-right: 10px;\n  min-width: 150px;\n  text-align: left;\n  border-width: 1px;\n  border-radius: 8px;\n  border-color: var(--new-table-border);\n  font-size: 14px;\n}\n\n.tags-top input {\n  font-size: 14px;\n}\n\n.tags-top input::placeholder {\n  color: var(--new-textColor);\n}\n\n.tags-top .react-tags__list,\n.tags-top .react-tags__list li,\n.tags-top .react-tags__list li > div {\n  height: 35px;\n}\n\n.tags-top .react-tags__listbox {\n  z-index: 1000 !important;\n}\n\n.tags-top .react-tags__list-item > div {\n  display: flex;\n  align-items: center;\n  padding-left: 5px;\n  padding-right: 5px;\n}\n\n.hideCopilot .copilotKitPopup {\n  display: none !important;\n}\n\nhtml[dir='rtl'] .rbox {\n  direction: rtl !important;\n}\n\nhtml[dir='rtl'] .lbox {\n  direction: ltr !important;\n}\n\nhtml[dir='rtl'] [dir='ltr'] {\n  direction: rtl !important;\n}\n//\n.uppy-Dashboard-AddFiles {\n  display: none !important;\n}\n\n.uppy-Dashboard-inner * {\n  @apply text-textColor;\n}\n\n.uppy-Dashboard-inner,\n.uppy-StatusBar {\n  background: transparent;\n  border-radius: 0 !important;\n  border: 0 !important;\n}\n\n.bigWrap .uppy-StatusBar-statusSecondary {\n  display: none !important;\n  opacity: 0 !important;\n}\n.bigWrap .uppy-StatusBar {\n  height: 32px !important;\n}\n.uppy-StatusBar {\n  //@apply bg-customColor55;\n}\n//\n//.uppy-Dashboard-inner {\n//  height: 70px !important;\n//}\n.uppy-Dashboard-files {\n  display: none;\n}\n//\n.uppy-DashboardContent-bar {\n  display: none !important;\n}\n\n.uppy-StatusBar-progress {\n  display: block;\n}\n\n.uppy-StatusBar-content {\n}\n//\n//.uppy-StatusBar-content {\n//  padding: 0 !important;\n//}\n//\n\n.ProseMirror {\n  font-family: Arial;\n}\n\n.ProseMirror:focus {\n  outline: none;\n}\n\n.ProseMirror .mention {\n  font-weight: bold;\n  color: #ae8afc;\n}\n\n.ProseMirror ul,\n.preview ul {\n  list-style: disc;\n  padding-left: 20px;\n}\n\n.preview ul,\n.preview li {\n  white-space: nowrap;\n}\n\n.ProseMirror h1,\n.preview h1 {\n  font-size: 24px;\n  font-weight: bold;\n}\n\n.ProseMirror * {\n  white-space: break-spaces;\n}\n\n.ProseMirror h2,\n.preview h2 {\n  font-size: 20px;\n  font-weight: bold;\n}\n\n.ProseMirror h3,\n.preview h3 {\n  font-size: 18px;\n  font-weight: bold;\n}\n\n.preview p {\n  min-height: 24px;\n}\n\n.repeated-strip {\n  background: repeating-linear-gradient(\n    135deg,\n    var(--new-bgColorInner),\n    var(--new-bgColorInner) 4px,\n    var(--new-big-strips) 4px,\n    var(--new-big-strips) 8px\n  );\n}\n\n.mantine-Modal-inner {\n  backdrop-filter: blur(10px);\n}\n\n.mantine-Modal-modal {\n  padding: 0;\n  @apply bg-newBgColorInner;\n  border-radius: 24px;\n}\n\n.mantine-Overlay-root {\n  background: rgba(65, 64, 66, 0.3) !important;\n}\n\n.dropdown-menu {\n  @apply shadow-menu;\n  background: var(--new-bgColorInner);\n  border: 1px solid var(--new-bgLineColor);\n  border-radius: 18px;\n  display: flex;\n  flex-direction: column;\n  overflow: auto;\n  position: relative;\n\n  button {\n    align-items: center;\n    background-color: transparent;\n    display: flex;\n    text-align: left;\n    width: 100%;\n    padding: 10px;\n\n    &:hover,\n    &:hover.is-selected {\n      background-color: var(--new-bgLineColor);\n    }\n  }\n}\n\n.tiptap {\n  a {\n    @apply underline;\n  }\n}\n.tiptap {\n  :first-child {\n    margin-top: 0;\n  }\n\n  .mention {\n    background-color: var(--purple-light);\n    border-radius: 0.4rem;\n    box-decoration-break: clone;\n    color: #ae8afc;\n    &::after {\n      content: '\\200B';\n    }\n  }\n}\n.blur-xs {\n  filter: blur(4px);\n}\n.blur-s {\n  filter: blur(5px);\n}\n\n.agent {\n  .copilotKitInputContainer {\n    padding: 0 24px !important;\n  }\n\n  .copilotKitInput {\n    width: 100% !important;\n  }\n}\n.rm-bg .b2 {\n  padding-top: 0 !important;\n}\n.rm-bg .b1 {\n  background: transparent !important;\n  gap: 0 !important;\n}\n\n.copilotKitMessage img {\n  width: 200px;\n}\n\n.copilotKitMessage a {\n  color: var(--new-btn-text) !important;\n}\n\n@keyframes marquee-up {\n  0% {\n    transform: translateY(0);\n  }\n  100% {\n    transform: translateY(-50%);\n  }\n}\n\n@keyframes marquee-down {\n  0% {\n    transform: translateY(-50%);\n  }\n  100% {\n    transform: translateY(0%);\n  }\n}\n\n.blackGradBottomBg {\n  background: linear-gradient(180deg, #0e0e0e 0%, rgba(14, 14, 14, 0) 100%);\n  rotate: 180deg;\n}\n\n.blackGradTopBg {\n  background: linear-gradient(180deg, #0e0e0e 0%, rgba(14, 14, 14, 0) 100%);\n}\n\n.billing-form input {\n  background: transparent !important;\n}\n\n.menu-shadow {\n  border-radius: 12px;\n  box-shadow: 0 8px 30px 0 rgba(0, 0, 0, 0.5);\n}\n\n.post-now {\n  box-shadow: -33px 57px 18px 0 rgba(0, 0, 0, 0.01),\n    -21px 36px 17px 0 rgba(0, 0, 0, 0.06), -12px 20px 14px 0 rgba(0, 0, 0, 0.2),\n    -5px 9px 11px 0 rgba(0, 0, 0, 0.34), -1px 2px 6px 0 rgba(0, 0, 0, 0.39);\n}\n\n.uppyChange .uppy-Dashboard-inner {\n  width: 100% !important;\n}\n\n.btnSub:disabled .arrow-change {\n  display: none !important;\n}\n.btnSub:disabled + button {\n  display: none !important;\n}\n\n.tiptap p.is-editor-empty:first-child::before {\n  color: #adb5bd;\n  content: attr(data-placeholder);\n  float: left;\n  height: 0;\n  pointer-events: none;\n}\n\n.w8-max {\n  width: calc(100% / 6);\n  max-width: calc(100% / 6);\n}\n\n.withp3 {\n  width: calc(100% + 9px);\n  height: calc(100% + 6px);\n}\n\n.forceChange .changeColor {\n  background: var(--new-btn-primary) !important;\n  color: #fff !important;\n}\n\n.text-shadow-tags {\n  text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black,\n    1px 1px 0 black;\n}\n"
  },
  {
    "path": "apps/frontend/src/app/polonto.css",
    "content": ".bp5-icon {\n  display: inline-block;\n  flex: 0 0 auto;\n  vertical-align: text-bottom;\n}\n.bp5-icon:not(:empty)::before {\n  content: '' !important;\n  content: unset !important;\n}\n.bp5-icon > svg {\n  display: block;\n}\n.bp5-icon > svg:not([fill]) {\n  fill: currentcolor;\n}\n.bp5-icon.bp5-icon-muted svg {\n  fill-opacity: 15%;\n  overflow: visible;\n}\n.bp5-icon.bp5-icon-muted svg path {\n  stroke: #8f99a8;\n  stroke-opacity: 50%;\n  stroke-width: 0.5px;\n}\n.bp5-dark .bp5-icon .bp5-icon-muted svg {\n  fill-opacity: 20%;\n}\n\nspan.bp5-icon-standard {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  display: inline-block;\n}\n\nspan.bp5-icon-large {\n  font-family: 'blueprint-icons-20', sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 20px;\n  line-height: 1;\n  width: 20px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  display: inline-block;\n}\n\nspan.bp5-icon:empty {\n  font-family: 'blueprint-icons-20';\n  font-size: inherit;\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1;\n}\nspan.bp5-icon:empty::before {\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard {\n  font-size: 16px;\n}\nspan.bp5-icon:empty.bp5-icon-large {\n  font-size: 20px;\n}\n\n.bp5-icon-add::before {\n  content: '\\f109';\n}\n\n.bp5-icon-add-clip::before {\n  content: '\\f101';\n}\n\n.bp5-icon-add-column-left::before {\n  content: '\\f102';\n}\n\n.bp5-icon-add-column-right::before {\n  content: '\\f103';\n}\n\n.bp5-icon-add-location::before {\n  content: '\\f104';\n}\n\n.bp5-icon-add-row-bottom::before {\n  content: '\\f105';\n}\n\n.bp5-icon-add-row-top::before {\n  content: '\\f106';\n}\n\n.bp5-icon-add-to-artifact::before {\n  content: '\\f107';\n}\n\n.bp5-icon-add-to-folder::before {\n  content: '\\f108';\n}\n\n.bp5-icon-aimpoints-target::before {\n  content: '\\f335';\n}\n\n.bp5-icon-airplane::before {\n  content: '\\f10a';\n}\n\n.bp5-icon-align-center::before {\n  content: '\\f10b';\n}\n\n.bp5-icon-align-justify::before {\n  content: '\\f10c';\n}\n\n.bp5-icon-align-left::before {\n  content: '\\f10d';\n}\n\n.bp5-icon-align-right::before {\n  content: '\\f10e';\n}\n\n.bp5-icon-alignment-bottom::before {\n  content: '\\f10f';\n}\n\n.bp5-icon-alignment-horizontal-center::before {\n  content: '\\f110';\n}\n\n.bp5-icon-alignment-left::before {\n  content: '\\f111';\n}\n\n.bp5-icon-alignment-right::before {\n  content: '\\f112';\n}\n\n.bp5-icon-alignment-top::before {\n  content: '\\f113';\n}\n\n.bp5-icon-alignment-vertical-center::before {\n  content: '\\f114';\n}\n\n.bp5-icon-ammunition::before {\n  content: '\\f342';\n}\n\n.bp5-icon-anchor::before {\n  content: '\\f330';\n}\n\n.bp5-icon-annotation::before {\n  content: '\\f115';\n}\n\n.bp5-icon-antenna::before {\n  content: '\\f116';\n}\n\n.bp5-icon-app-header::before {\n  content: '\\f117';\n}\n\n.bp5-icon-application::before {\n  content: '\\f118';\n}\n\n.bp5-icon-applications::before {\n  content: '\\f119';\n}\n\n.bp5-icon-archive::before {\n  content: '\\f11a';\n}\n\n.bp5-icon-area-of-interest::before {\n  content: '\\f11b';\n}\n\n.bp5-icon-array::before {\n  content: '\\f121';\n}\n\n.bp5-icon-array-boolean::before {\n  content: '\\f11c';\n}\n\n.bp5-icon-array-date::before {\n  content: '\\f11d';\n}\n\n.bp5-icon-array-floating-point::before {\n  content: '\\f32d';\n}\n\n.bp5-icon-array-numeric::before {\n  content: '\\f11e';\n}\n\n.bp5-icon-array-string::before {\n  content: '\\f11f';\n}\n\n.bp5-icon-array-timestamp::before {\n  content: '\\f120';\n}\n\n.bp5-icon-arrow-bottom-left::before {\n  content: '\\f122';\n}\n\n.bp5-icon-arrow-bottom-right::before {\n  content: '\\f123';\n}\n\n.bp5-icon-arrow-down::before {\n  content: '\\f124';\n}\n\n.bp5-icon-arrow-left::before {\n  content: '\\f125';\n}\n\n.bp5-icon-arrow-right::before {\n  content: '\\f126';\n}\n\n.bp5-icon-arrow-top-left::before {\n  content: '\\f127';\n}\n\n.bp5-icon-arrow-top-right::before {\n  content: '\\f128';\n}\n\n.bp5-icon-arrow-up::before {\n  content: '\\f129';\n}\n\n.bp5-icon-arrows-horizontal::before {\n  content: '\\f12a';\n}\n\n.bp5-icon-arrows-vertical::before {\n  content: '\\f12b';\n}\n\n.bp5-icon-asterisk::before {\n  content: '\\f12c';\n}\n\n.bp5-icon-at::before {\n  content: '\\f331';\n}\n\n.bp5-icon-automatic-updates::before {\n  content: '\\f12d';\n}\n\n.bp5-icon-axle::before {\n  content: '\\f338';\n}\n\n.bp5-icon-backlink::before {\n  content: '\\f12e';\n}\n\n.bp5-icon-backward-ten::before {\n  content: '\\f35c';\n}\n\n.bp5-icon-badge::before {\n  content: '\\f12f';\n}\n\n.bp5-icon-ban-circle::before {\n  content: '\\f130';\n}\n\n.bp5-icon-bank-account::before {\n  content: '\\f131';\n}\n\n.bp5-icon-barcode::before {\n  content: '\\f132';\n}\n\n.bp5-icon-binary-number::before {\n  content: '\\f357';\n}\n\n.bp5-icon-blank::before {\n  content: '\\f133';\n}\n\n.bp5-icon-blocked-person::before {\n  content: '\\f134';\n}\n\n.bp5-icon-bold::before {\n  content: '\\f135';\n}\n\n.bp5-icon-book::before {\n  content: '\\f136';\n}\n\n.bp5-icon-bookmark::before {\n  content: '\\f137';\n}\n\n.bp5-icon-box::before {\n  content: '\\f138';\n}\n\n.bp5-icon-briefcase::before {\n  content: '\\f139';\n}\n\n.bp5-icon-bring-data::before {\n  content: '\\f13a';\n}\n\n.bp5-icon-bring-forward::before {\n  content: '\\f354';\n}\n\n.bp5-icon-bug::before {\n  content: '\\f32e';\n}\n\n.bp5-icon-buggy::before {\n  content: '\\f13b';\n}\n\n.bp5-icon-build::before {\n  content: '\\f13c';\n}\n\n.bp5-icon-bullseye::before {\n  content: '\\f359';\n}\n\n.bp5-icon-calculator::before {\n  content: '\\f13d';\n}\n\n.bp5-icon-calendar::before {\n  content: '\\f13e';\n}\n\n.bp5-icon-camera::before {\n  content: '\\f13f';\n}\n\n.bp5-icon-caret-down::before {\n  content: '\\f140';\n}\n\n.bp5-icon-caret-left::before {\n  content: '\\f141';\n}\n\n.bp5-icon-caret-right::before {\n  content: '\\f142';\n}\n\n.bp5-icon-caret-up::before {\n  content: '\\f143';\n}\n\n.bp5-icon-cargo-ship::before {\n  content: '\\f144';\n}\n\n.bp5-icon-cell-tower::before {\n  content: '\\f145';\n}\n\n.bp5-icon-changes::before {\n  content: '\\f146';\n}\n\n.bp5-icon-chart::before {\n  content: '\\f147';\n}\n\n.bp5-icon-chat::before {\n  content: '\\f148';\n}\n\n.bp5-icon-chevron-backward::before {\n  content: '\\f149';\n}\n\n.bp5-icon-chevron-down::before {\n  content: '\\f14a';\n}\n\n.bp5-icon-chevron-forward::before {\n  content: '\\f14b';\n}\n\n.bp5-icon-chevron-left::before {\n  content: '\\f14c';\n}\n\n.bp5-icon-chevron-right::before {\n  content: '\\f14d';\n}\n\n.bp5-icon-chevron-up::before {\n  content: '\\f14e';\n}\n\n.bp5-icon-circle::before {\n  content: '\\f153';\n}\n\n.bp5-icon-circle-arrow-down::before {\n  content: '\\f14f';\n}\n\n.bp5-icon-circle-arrow-left::before {\n  content: '\\f150';\n}\n\n.bp5-icon-circle-arrow-right::before {\n  content: '\\f151';\n}\n\n.bp5-icon-circle-arrow-up::before {\n  content: '\\f152';\n}\n\n.bp5-icon-citation::before {\n  content: '\\f154';\n}\n\n.bp5-icon-clean::before {\n  content: '\\f155';\n}\n\n.bp5-icon-clip::before {\n  content: '\\f156';\n}\n\n.bp5-icon-clipboard::before {\n  content: '\\f157';\n}\n\n.bp5-icon-clipboard-file::before {\n  content: '\\f35b';\n}\n\n.bp5-icon-cloud::before {\n  content: '\\f15a';\n}\n\n.bp5-icon-cloud-download::before {\n  content: '\\f158';\n}\n\n.bp5-icon-cloud-server::before {\n  content: '\\f35a';\n}\n\n.bp5-icon-cloud-tick::before {\n  content: '\\f34e';\n}\n\n.bp5-icon-cloud-upload::before {\n  content: '\\f159';\n}\n\n.bp5-icon-code::before {\n  content: '\\f15c';\n}\n\n.bp5-icon-code-block::before {\n  content: '\\f15b';\n}\n\n.bp5-icon-cog::before {\n  content: '\\f15d';\n}\n\n.bp5-icon-collapse-all::before {\n  content: '\\f15e';\n}\n\n.bp5-icon-color-fill::before {\n  content: '\\f328';\n}\n\n.bp5-icon-column-layout::before {\n  content: '\\f15f';\n}\n\n.bp5-icon-comment::before {\n  content: '\\f160';\n}\n\n.bp5-icon-comparison::before {\n  content: '\\f161';\n}\n\n.bp5-icon-compass::before {\n  content: '\\f162';\n}\n\n.bp5-icon-compressed::before {\n  content: '\\f163';\n}\n\n.bp5-icon-confirm::before {\n  content: '\\f164';\n}\n\n.bp5-icon-console::before {\n  content: '\\f165';\n}\n\n.bp5-icon-contrast::before {\n  content: '\\f166';\n}\n\n.bp5-icon-control::before {\n  content: '\\f167';\n}\n\n.bp5-icon-credit-card::before {\n  content: '\\f168';\n}\n\n.bp5-icon-crop::before {\n  content: '\\f353';\n}\n\n.bp5-icon-cross::before {\n  content: '\\f169';\n}\n\n.bp5-icon-cross-circle::before {\n  content: '\\f336';\n}\n\n.bp5-icon-crown::before {\n  content: '\\f16a';\n}\n\n.bp5-icon-css-style::before {\n  content: '\\f36b';\n}\n\n.bp5-icon-cube::before {\n  content: '\\f16d';\n}\n\n.bp5-icon-cube-add::before {\n  content: '\\f16b';\n}\n\n.bp5-icon-cube-remove::before {\n  content: '\\f16c';\n}\n\n.bp5-icon-curly-braces::before {\n  content: '\\f358';\n}\n\n.bp5-icon-curved-range-chart::before {\n  content: '\\f16e';\n}\n\n.bp5-icon-cut::before {\n  content: '\\f16f';\n}\n\n.bp5-icon-cycle::before {\n  content: '\\f170';\n}\n\n.bp5-icon-dashboard::before {\n  content: '\\f171';\n}\n\n.bp5-icon-data-connection::before {\n  content: '\\f172';\n}\n\n.bp5-icon-data-lineage::before {\n  content: '\\f173';\n}\n\n.bp5-icon-data-sync::before {\n  content: '\\f36c';\n}\n\n.bp5-icon-database::before {\n  content: '\\f174';\n}\n\n.bp5-icon-delete::before {\n  content: '\\f175';\n}\n\n.bp5-icon-delta::before {\n  content: '\\f176';\n}\n\n.bp5-icon-derive-column::before {\n  content: '\\f177';\n}\n\n.bp5-icon-desktop::before {\n  content: '\\f178';\n}\n\n.bp5-icon-detection::before {\n  content: '\\f341';\n}\n\n.bp5-icon-diagnosis::before {\n  content: '\\f179';\n}\n\n.bp5-icon-diagram-tree::before {\n  content: '\\f17a';\n}\n\n.bp5-icon-direction-left::before {\n  content: '\\f17b';\n}\n\n.bp5-icon-direction-right::before {\n  content: '\\f17c';\n}\n\n.bp5-icon-disable::before {\n  content: '\\f17d';\n}\n\n.bp5-icon-divide::before {\n  content: '\\f327';\n}\n\n.bp5-icon-document::before {\n  content: '\\f180';\n}\n\n.bp5-icon-document-open::before {\n  content: '\\f17e';\n}\n\n.bp5-icon-document-share::before {\n  content: '\\f17f';\n}\n\n.bp5-icon-dollar::before {\n  content: '\\f181';\n}\n\n.bp5-icon-dot::before {\n  content: '\\f182';\n}\n\n.bp5-icon-double-caret-horizontal::before {\n  content: '\\f183';\n}\n\n.bp5-icon-double-caret-vertical::before {\n  content: '\\f184';\n}\n\n.bp5-icon-double-chevron-down::before {\n  content: '\\f185';\n}\n\n.bp5-icon-double-chevron-left::before {\n  content: '\\f186';\n}\n\n.bp5-icon-double-chevron-right::before {\n  content: '\\f187';\n}\n\n.bp5-icon-double-chevron-up::before {\n  content: '\\f188';\n}\n\n.bp5-icon-doughnut-chart::before {\n  content: '\\f189';\n}\n\n.bp5-icon-download::before {\n  content: '\\f18a';\n}\n\n.bp5-icon-drag-handle-horizontal::before {\n  content: '\\f18b';\n}\n\n.bp5-icon-drag-handle-vertical::before {\n  content: '\\f18c';\n}\n\n.bp5-icon-draw::before {\n  content: '\\f18d';\n}\n\n.bp5-icon-drawer-left::before {\n  content: '\\f18f';\n}\n\n.bp5-icon-drawer-left-filled::before {\n  content: '\\f18e';\n}\n\n.bp5-icon-drawer-right::before {\n  content: '\\f191';\n}\n\n.bp5-icon-drawer-right-filled::before {\n  content: '\\f190';\n}\n\n.bp5-icon-drive-time::before {\n  content: '\\f192';\n}\n\n.bp5-icon-duplicate::before {\n  content: '\\f193';\n}\n\n.bp5-icon-edit::before {\n  content: '\\f194';\n}\n\n.bp5-icon-eject::before {\n  content: '\\f195';\n}\n\n.bp5-icon-emoji::before {\n  content: '\\f196';\n}\n\n.bp5-icon-endnote::before {\n  content: '\\f356';\n}\n\n.bp5-icon-endorsed::before {\n  content: '\\f197';\n}\n\n.bp5-icon-envelope::before {\n  content: '\\f198';\n}\n\n.bp5-icon-equals::before {\n  content: '\\f199';\n}\n\n.bp5-icon-eraser::before {\n  content: '\\f19a';\n}\n\n.bp5-icon-error::before {\n  content: '\\f19b';\n}\n\n.bp5-icon-euro::before {\n  content: '\\f19c';\n}\n\n.bp5-icon-excavator::before {\n  content: '\\f36d';\n}\n\n.bp5-icon-exchange::before {\n  content: '\\f19d';\n}\n\n.bp5-icon-exclude-row::before {\n  content: '\\f19e';\n}\n\n.bp5-icon-expand-all::before {\n  content: '\\f19f';\n}\n\n.bp5-icon-explain::before {\n  content: '\\f34d';\n}\n\n.bp5-icon-export::before {\n  content: '\\f1a0';\n}\n\n.bp5-icon-eye-off::before {\n  content: '\\f1a1';\n}\n\n.bp5-icon-eye-on::before {\n  content: '\\f1a2';\n}\n\n.bp5-icon-eye-open::before {\n  content: '\\f1a3';\n}\n\n.bp5-icon-fast-backward::before {\n  content: '\\f1a4';\n}\n\n.bp5-icon-fast-forward::before {\n  content: '\\f1a5';\n}\n\n.bp5-icon-feed::before {\n  content: '\\f1a7';\n}\n\n.bp5-icon-feed-subscribed::before {\n  content: '\\f1a6';\n}\n\n.bp5-icon-film::before {\n  content: '\\f1a8';\n}\n\n.bp5-icon-filter::before {\n  content: '\\f1ad';\n}\n\n.bp5-icon-filter-keep::before {\n  content: '\\f1a9';\n}\n\n.bp5-icon-filter-list::before {\n  content: '\\f1aa';\n}\n\n.bp5-icon-filter-open::before {\n  content: '\\f1ab';\n}\n\n.bp5-icon-filter-remove::before {\n  content: '\\f1ac';\n}\n\n.bp5-icon-flag::before {\n  content: '\\f1ae';\n}\n\n.bp5-icon-flame::before {\n  content: '\\f1af';\n}\n\n.bp5-icon-flash::before {\n  content: '\\f1b0';\n}\n\n.bp5-icon-floating-point::before {\n  content: '\\f32c';\n}\n\n.bp5-icon-floppy-disk::before {\n  content: '\\f1b1';\n}\n\n.bp5-icon-flow-branch::before {\n  content: '\\f1b2';\n}\n\n.bp5-icon-flow-end::before {\n  content: '\\f1b3';\n}\n\n.bp5-icon-flow-linear::before {\n  content: '\\f1b4';\n}\n\n.bp5-icon-flow-review::before {\n  content: '\\f1b6';\n}\n\n.bp5-icon-flow-review-branch::before {\n  content: '\\f1b5';\n}\n\n.bp5-icon-flows::before {\n  content: '\\f1b7';\n}\n\n.bp5-icon-folder-close::before {\n  content: '\\f1b8';\n}\n\n.bp5-icon-folder-new::before {\n  content: '\\f1b9';\n}\n\n.bp5-icon-folder-open::before {\n  content: '\\f1ba';\n}\n\n.bp5-icon-folder-shared::before {\n  content: '\\f1bc';\n}\n\n.bp5-icon-folder-shared-open::before {\n  content: '\\f1bb';\n}\n\n.bp5-icon-follower::before {\n  content: '\\f1bd';\n}\n\n.bp5-icon-following::before {\n  content: '\\f1be';\n}\n\n.bp5-icon-font::before {\n  content: '\\f1bf';\n}\n\n.bp5-icon-fork::before {\n  content: '\\f1c0';\n}\n\n.bp5-icon-form::before {\n  content: '\\f1c1';\n}\n\n.bp5-icon-forward-ten::before {\n  content: '\\f35d';\n}\n\n.bp5-icon-fuel::before {\n  content: '\\f323';\n}\n\n.bp5-icon-full-circle::before {\n  content: '\\f1c2';\n}\n\n.bp5-icon-full-stacked-chart::before {\n  content: '\\f1c3';\n}\n\n.bp5-icon-fullscreen::before {\n  content: '\\f1c4';\n}\n\n.bp5-icon-function::before {\n  content: '\\f1c5';\n}\n\n.bp5-icon-gantt-chart::before {\n  content: '\\f1c6';\n}\n\n.bp5-icon-generate::before {\n  content: '\\f34c';\n}\n\n.bp5-icon-geofence::before {\n  content: '\\f1c7';\n}\n\n.bp5-icon-geolocation::before {\n  content: '\\f1c8';\n}\n\n.bp5-icon-geosearch::before {\n  content: '\\f1c9';\n}\n\n.bp5-icon-geotime::before {\n  content: '\\f344';\n}\n\n.bp5-icon-git-branch::before {\n  content: '\\f1ca';\n}\n\n.bp5-icon-git-commit::before {\n  content: '\\f1cb';\n}\n\n.bp5-icon-git-merge::before {\n  content: '\\f1cc';\n}\n\n.bp5-icon-git-new-branch::before {\n  content: '\\f1cd';\n}\n\n.bp5-icon-git-pull::before {\n  content: '\\f1ce';\n}\n\n.bp5-icon-git-push::before {\n  content: '\\f1cf';\n}\n\n.bp5-icon-git-repo::before {\n  content: '\\f1d0';\n}\n\n.bp5-icon-glass::before {\n  content: '\\f1d1';\n}\n\n.bp5-icon-globe::before {\n  content: '\\f1d3';\n}\n\n.bp5-icon-globe-network::before {\n  content: '\\f1d2';\n}\n\n.bp5-icon-graph::before {\n  content: '\\f1d5';\n}\n\n.bp5-icon-graph-remove::before {\n  content: '\\f1d4';\n}\n\n.bp5-icon-greater-than::before {\n  content: '\\f1d7';\n}\n\n.bp5-icon-greater-than-or-equal-to::before {\n  content: '\\f1d6';\n}\n\n.bp5-icon-grid::before {\n  content: '\\f1d9';\n}\n\n.bp5-icon-grid-view::before {\n  content: '\\f1d8';\n}\n\n.bp5-icon-group-item::before {\n  content: '\\f34a';\n}\n\n.bp5-icon-group-objects::before {\n  content: '\\f1da';\n}\n\n.bp5-icon-grouped-bar-chart::before {\n  content: '\\f1db';\n}\n\n.bp5-icon-hand::before {\n  content: '\\f1e0';\n}\n\n.bp5-icon-hand-down::before {\n  content: '\\f1dc';\n}\n\n.bp5-icon-hand-left::before {\n  content: '\\f1dd';\n}\n\n.bp5-icon-hand-right::before {\n  content: '\\f1de';\n}\n\n.bp5-icon-hand-up::before {\n  content: '\\f1df';\n}\n\n.bp5-icon-hat::before {\n  content: '\\f1e1';\n}\n\n.bp5-icon-header::before {\n  content: '\\f1e5';\n}\n\n.bp5-icon-header-one::before {\n  content: '\\f1e2';\n}\n\n.bp5-icon-header-three::before {\n  content: '\\f1e3';\n}\n\n.bp5-icon-header-two::before {\n  content: '\\f1e4';\n}\n\n.bp5-icon-headset::before {\n  content: '\\f1e6';\n}\n\n.bp5-icon-heart::before {\n  content: '\\f1e8';\n}\n\n.bp5-icon-heart-broken::before {\n  content: '\\f1e7';\n}\n\n.bp5-icon-heat-grid::before {\n  content: '\\f1e9';\n}\n\n.bp5-icon-heatmap::before {\n  content: '\\f1ea';\n}\n\n.bp5-icon-helicopter::before {\n  content: '\\f1eb';\n}\n\n.bp5-icon-help::before {\n  content: '\\f1ec';\n}\n\n.bp5-icon-helper-management::before {\n  content: '\\f1ed';\n}\n\n.bp5-icon-high-priority::before {\n  content: '\\f1ee';\n}\n\n.bp5-icon-high-voltage-pole::before {\n  content: '\\f333';\n}\n\n.bp5-icon-highlight::before {\n  content: '\\f1ef';\n}\n\n.bp5-icon-history::before {\n  content: '\\f1f0';\n}\n\n.bp5-icon-home::before {\n  content: '\\f1f1';\n}\n\n.bp5-icon-horizontal-bar-chart::before {\n  content: '\\f1f4';\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before {\n  content: '\\f1f2';\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before {\n  content: '\\f1f3';\n}\n\n.bp5-icon-horizontal-distribution::before {\n  content: '\\f1f5';\n}\n\n.bp5-icon-horizontal-inbetween::before {\n  content: '\\f329';\n}\n\n.bp5-icon-hurricane::before {\n  content: '\\f1f6';\n}\n\n.bp5-icon-id-number::before {\n  content: '\\f1f7';\n}\n\n.bp5-icon-image-rotate-left::before {\n  content: '\\f1f8';\n}\n\n.bp5-icon-image-rotate-right::before {\n  content: '\\f1f9';\n}\n\n.bp5-icon-import::before {\n  content: '\\f1fa';\n}\n\n.bp5-icon-inbox::before {\n  content: '\\f1ff';\n}\n\n.bp5-icon-inbox-filtered::before {\n  content: '\\f1fb';\n}\n\n.bp5-icon-inbox-geo::before {\n  content: '\\f1fc';\n}\n\n.bp5-icon-inbox-search::before {\n  content: '\\f1fd';\n}\n\n.bp5-icon-inbox-update::before {\n  content: '\\f1fe';\n}\n\n.bp5-icon-info-sign::before {\n  content: '\\f200';\n}\n\n.bp5-icon-inheritance::before {\n  content: '\\f201';\n}\n\n.bp5-icon-inherited-group::before {\n  content: '\\f202';\n}\n\n.bp5-icon-inner-join::before {\n  content: '\\f203';\n}\n\n.bp5-icon-input::before {\n  content: '\\f34b';\n}\n\n.bp5-icon-insert::before {\n  content: '\\f204';\n}\n\n.bp5-icon-intelligence::before {\n  content: '\\f337';\n}\n\n.bp5-icon-intersection::before {\n  content: '\\f205';\n}\n\n.bp5-icon-ip-address::before {\n  content: '\\f206';\n}\n\n.bp5-icon-issue::before {\n  content: '\\f209';\n}\n\n.bp5-icon-issue-closed::before {\n  content: '\\f207';\n}\n\n.bp5-icon-issue-new::before {\n  content: '\\f208';\n}\n\n.bp5-icon-italic::before {\n  content: '\\f20a';\n}\n\n.bp5-icon-join-table::before {\n  content: '\\f20b';\n}\n\n.bp5-icon-key::before {\n  content: '\\f215';\n}\n\n.bp5-icon-key-backspace::before {\n  content: '\\f20c';\n}\n\n.bp5-icon-key-command::before {\n  content: '\\f20d';\n}\n\n.bp5-icon-key-control::before {\n  content: '\\f20e';\n}\n\n.bp5-icon-key-delete::before {\n  content: '\\f20f';\n}\n\n.bp5-icon-key-enter::before {\n  content: '\\f210';\n}\n\n.bp5-icon-key-escape::before {\n  content: '\\f211';\n}\n\n.bp5-icon-key-option::before {\n  content: '\\f212';\n}\n\n.bp5-icon-key-shift::before {\n  content: '\\f213';\n}\n\n.bp5-icon-key-tab::before {\n  content: '\\f214';\n}\n\n.bp5-icon-known-vehicle::before {\n  content: '\\f216';\n}\n\n.bp5-icon-lab-test::before {\n  content: '\\f217';\n}\n\n.bp5-icon-label::before {\n  content: '\\f218';\n}\n\n.bp5-icon-layer::before {\n  content: '\\f21a';\n}\n\n.bp5-icon-layer-outline::before {\n  content: '\\f219';\n}\n\n.bp5-icon-layers::before {\n  content: '\\f21b';\n}\n\n.bp5-icon-layout::before {\n  content: '\\f225';\n}\n\n.bp5-icon-layout-auto::before {\n  content: '\\f21c';\n}\n\n.bp5-icon-layout-balloon::before {\n  content: '\\f21d';\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before {\n  content: '\\f364';\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before {\n  content: '\\f363';\n}\n\n.bp5-icon-layout-circle::before {\n  content: '\\f21e';\n}\n\n.bp5-icon-layout-grid::before {\n  content: '\\f21f';\n}\n\n.bp5-icon-layout-group-by::before {\n  content: '\\f220';\n}\n\n.bp5-icon-layout-hierarchy::before {\n  content: '\\f221';\n}\n\n.bp5-icon-layout-left-column-three-tiles::before {\n  content: '\\f366';\n}\n\n.bp5-icon-layout-left-column-two-tiles::before {\n  content: '\\f365';\n}\n\n.bp5-icon-layout-linear::before {\n  content: '\\f222';\n}\n\n.bp5-icon-layout-right-column-three-tiles::before {\n  content: '\\f368';\n}\n\n.bp5-icon-layout-right-column-two-tiles::before {\n  content: '\\f367';\n}\n\n.bp5-icon-layout-skew-grid::before {\n  content: '\\f223';\n}\n\n.bp5-icon-layout-sorted-clusters::before {\n  content: '\\f224';\n}\n\n.bp5-icon-layout-three-columns::before {\n  content: '\\f361';\n}\n\n.bp5-icon-layout-three-rows::before {\n  content: '\\f362';\n}\n\n.bp5-icon-layout-top-row-three-tiles::before {\n  content: '\\f36a';\n}\n\n.bp5-icon-layout-top-row-two-tiles::before {\n  content: '\\f369';\n}\n\n.bp5-icon-layout-two-columns::before {\n  content: '\\f35f';\n}\n\n.bp5-icon-layout-two-rows::before {\n  content: '\\f360';\n}\n\n.bp5-icon-learning::before {\n  content: '\\f226';\n}\n\n.bp5-icon-left-join::before {\n  content: '\\f227';\n}\n\n.bp5-icon-lengthen-text::before {\n  content: '\\f33e';\n}\n\n.bp5-icon-less-than::before {\n  content: '\\f229';\n}\n\n.bp5-icon-less-than-or-equal-to::before {\n  content: '\\f228';\n}\n\n.bp5-icon-lifesaver::before {\n  content: '\\f22a';\n}\n\n.bp5-icon-lightbulb::before {\n  content: '\\f22b';\n}\n\n.bp5-icon-lightning::before {\n  content: '\\f22c';\n}\n\n.bp5-icon-link::before {\n  content: '\\f22d';\n}\n\n.bp5-icon-list::before {\n  content: '\\f230';\n}\n\n.bp5-icon-list-columns::before {\n  content: '\\f22e';\n}\n\n.bp5-icon-list-detail-view::before {\n  content: '\\f22f';\n}\n\n.bp5-icon-locate::before {\n  content: '\\f231';\n}\n\n.bp5-icon-lock::before {\n  content: '\\f232';\n}\n\n.bp5-icon-locomotive::before {\n  content: '\\f33b';\n}\n\n.bp5-icon-log-in::before {\n  content: '\\f233';\n}\n\n.bp5-icon-log-out::before {\n  content: '\\f234';\n}\n\n.bp5-icon-low-voltage-pole::before {\n  content: '\\f332';\n}\n\n.bp5-icon-manual::before {\n  content: '\\f235';\n}\n\n.bp5-icon-manually-entered-data::before {\n  content: '\\f236';\n}\n\n.bp5-icon-many-to-many::before {\n  content: '\\f237';\n}\n\n.bp5-icon-many-to-one::before {\n  content: '\\f238';\n}\n\n.bp5-icon-map::before {\n  content: '\\f23b';\n}\n\n.bp5-icon-map-create::before {\n  content: '\\f239';\n}\n\n.bp5-icon-map-marker::before {\n  content: '\\f23a';\n}\n\n.bp5-icon-maximize::before {\n  content: '\\f23c';\n}\n\n.bp5-icon-media::before {\n  content: '\\f23d';\n}\n\n.bp5-icon-menu::before {\n  content: '\\f240';\n}\n\n.bp5-icon-menu-closed::before {\n  content: '\\f23e';\n}\n\n.bp5-icon-menu-open::before {\n  content: '\\f23f';\n}\n\n.bp5-icon-merge-columns::before {\n  content: '\\f241';\n}\n\n.bp5-icon-merge-links::before {\n  content: '\\f242';\n}\n\n.bp5-icon-microphone::before {\n  content: '\\f343';\n}\n\n.bp5-icon-minimize::before {\n  content: '\\f243';\n}\n\n.bp5-icon-minus::before {\n  content: '\\f244';\n}\n\n.bp5-icon-mobile-phone::before {\n  content: '\\f245';\n}\n\n.bp5-icon-mobile-video::before {\n  content: '\\f246';\n}\n\n.bp5-icon-modal::before {\n  content: '\\f248';\n}\n\n.bp5-icon-modal-filled::before {\n  content: '\\f247';\n}\n\n.bp5-icon-model::before {\n  content: '\\f33d';\n}\n\n.bp5-icon-moon::before {\n  content: '\\f249';\n}\n\n.bp5-icon-more::before {\n  content: '\\f24a';\n}\n\n.bp5-icon-mountain::before {\n  content: '\\f24b';\n}\n\n.bp5-icon-move::before {\n  content: '\\f24c';\n}\n\n.bp5-icon-mugshot::before {\n  content: '\\f24d';\n}\n\n.bp5-icon-multi-select::before {\n  content: '\\f24e';\n}\n\n.bp5-icon-music::before {\n  content: '\\f24f';\n}\n\n.bp5-icon-nest::before {\n  content: '\\f250';\n}\n\n.bp5-icon-new-drawing::before {\n  content: '\\f251';\n}\n\n.bp5-icon-new-grid-item::before {\n  content: '\\f252';\n}\n\n.bp5-icon-new-layer::before {\n  content: '\\f253';\n}\n\n.bp5-icon-new-layers::before {\n  content: '\\f254';\n}\n\n.bp5-icon-new-link::before {\n  content: '\\f255';\n}\n\n.bp5-icon-new-object::before {\n  content: '\\f256';\n}\n\n.bp5-icon-new-person::before {\n  content: '\\f257';\n}\n\n.bp5-icon-new-prescription::before {\n  content: '\\f258';\n}\n\n.bp5-icon-new-shield::before {\n  content: '\\f349';\n}\n\n.bp5-icon-new-text-box::before {\n  content: '\\f259';\n}\n\n.bp5-icon-ninja::before {\n  content: '\\f25a';\n}\n\n.bp5-icon-not-equal-to::before {\n  content: '\\f25b';\n}\n\n.bp5-icon-notifications::before {\n  content: '\\f25e';\n}\n\n.bp5-icon-notifications-snooze::before {\n  content: '\\f25c';\n}\n\n.bp5-icon-notifications-updated::before {\n  content: '\\f25d';\n}\n\n.bp5-icon-numbered-list::before {\n  content: '\\f25f';\n}\n\n.bp5-icon-numerical::before {\n  content: '\\f260';\n}\n\n.bp5-icon-office::before {\n  content: '\\f261';\n}\n\n.bp5-icon-offline::before {\n  content: '\\f262';\n}\n\n.bp5-icon-oil-field::before {\n  content: '\\f263';\n}\n\n.bp5-icon-one-column::before {\n  content: '\\f264';\n}\n\n.bp5-icon-one-to-many::before {\n  content: '\\f265';\n}\n\n.bp5-icon-one-to-one::before {\n  content: '\\f266';\n}\n\n.bp5-icon-open-application::before {\n  content: '\\f32b';\n}\n\n.bp5-icon-outdated::before {\n  content: '\\f267';\n}\n\n.bp5-icon-page-layout::before {\n  content: '\\f268';\n}\n\n.bp5-icon-panel-stats::before {\n  content: '\\f269';\n}\n\n.bp5-icon-panel-table::before {\n  content: '\\f26a';\n}\n\n.bp5-icon-paperclip::before {\n  content: '\\f26b';\n}\n\n.bp5-icon-paragraph::before {\n  content: '\\f26c';\n}\n\n.bp5-icon-paste-variable::before {\n  content: '\\f346';\n}\n\n.bp5-icon-path::before {\n  content: '\\f26e';\n}\n\n.bp5-icon-path-search::before {\n  content: '\\f26d';\n}\n\n.bp5-icon-pause::before {\n  content: '\\f26f';\n}\n\n.bp5-icon-people::before {\n  content: '\\f270';\n}\n\n.bp5-icon-percentage::before {\n  content: '\\f271';\n}\n\n.bp5-icon-person::before {\n  content: '\\f272';\n}\n\n.bp5-icon-phone::before {\n  content: '\\f273';\n}\n\n.bp5-icon-phone-call::before {\n  content: '\\f347';\n}\n\n.bp5-icon-phone-forward::before {\n  content: '\\f348';\n}\n\n.bp5-icon-pie-chart::before {\n  content: '\\f274';\n}\n\n.bp5-icon-pin::before {\n  content: '\\f275';\n}\n\n.bp5-icon-pivot::before {\n  content: '\\f277';\n}\n\n.bp5-icon-pivot-table::before {\n  content: '\\f276';\n}\n\n.bp5-icon-play::before {\n  content: '\\f278';\n}\n\n.bp5-icon-playbook::before {\n  content: '\\f324';\n}\n\n.bp5-icon-plus::before {\n  content: '\\f279';\n}\n\n.bp5-icon-polygon-filter::before {\n  content: '\\f27a';\n}\n\n.bp5-icon-power::before {\n  content: '\\f27b';\n}\n\n.bp5-icon-predictive-analysis::before {\n  content: '\\f27c';\n}\n\n.bp5-icon-prescription::before {\n  content: '\\f27d';\n}\n\n.bp5-icon-presentation::before {\n  content: '\\f27e';\n}\n\n.bp5-icon-print::before {\n  content: '\\f27f';\n}\n\n.bp5-icon-projects::before {\n  content: '\\f280';\n}\n\n.bp5-icon-properties::before {\n  content: '\\f281';\n}\n\n.bp5-icon-property::before {\n  content: '\\f282';\n}\n\n.bp5-icon-publish-function::before {\n  content: '\\f283';\n}\n\n.bp5-icon-pulse::before {\n  content: '\\f284';\n}\n\n.bp5-icon-rain::before {\n  content: '\\f285';\n}\n\n.bp5-icon-random::before {\n  content: '\\f286';\n}\n\n.bp5-icon-record::before {\n  content: '\\f287';\n}\n\n.bp5-icon-rect-height::before {\n  content: '\\f325';\n}\n\n.bp5-icon-rect-width::before {\n  content: '\\f326';\n}\n\n.bp5-icon-rectangle::before {\n  content: '\\f321';\n}\n\n.bp5-icon-redo::before {\n  content: '\\f288';\n}\n\n.bp5-icon-refresh::before {\n  content: '\\f289';\n}\n\n.bp5-icon-regex::before {\n  content: '\\f32f';\n}\n\n.bp5-icon-regression-chart::before {\n  content: '\\f28a';\n}\n\n.bp5-icon-remove::before {\n  content: '\\f290';\n}\n\n.bp5-icon-remove-column::before {\n  content: '\\f28d';\n}\n\n.bp5-icon-remove-column-left::before {\n  content: '\\f28b';\n}\n\n.bp5-icon-remove-column-right::before {\n  content: '\\f28c';\n}\n\n.bp5-icon-remove-row-bottom::before {\n  content: '\\f28e';\n}\n\n.bp5-icon-remove-row-top::before {\n  content: '\\f28f';\n}\n\n.bp5-icon-repeat::before {\n  content: '\\f291';\n}\n\n.bp5-icon-reset::before {\n  content: '\\f292';\n}\n\n.bp5-icon-resolve::before {\n  content: '\\f293';\n}\n\n.bp5-icon-rig::before {\n  content: '\\f294';\n}\n\n.bp5-icon-right-join::before {\n  content: '\\f295';\n}\n\n.bp5-icon-ring::before {\n  content: '\\f296';\n}\n\n.bp5-icon-rocket::before {\n  content: '\\f298';\n}\n\n.bp5-icon-rocket-slant::before {\n  content: '\\f297';\n}\n\n.bp5-icon-rotate-document::before {\n  content: '\\f299';\n}\n\n.bp5-icon-rotate-page::before {\n  content: '\\f29a';\n}\n\n.bp5-icon-route::before {\n  content: '\\f29b';\n}\n\n.bp5-icon-satellite::before {\n  content: '\\f29c';\n}\n\n.bp5-icon-saved::before {\n  content: '\\f29d';\n}\n\n.bp5-icon-scatter-plot::before {\n  content: '\\f29e';\n}\n\n.bp5-icon-search::before {\n  content: '\\f2a2';\n}\n\n.bp5-icon-search-around::before {\n  content: '\\f29f';\n}\n\n.bp5-icon-search-template::before {\n  content: '\\f2a0';\n}\n\n.bp5-icon-search-text::before {\n  content: '\\f2a1';\n}\n\n.bp5-icon-segmented-control::before {\n  content: '\\f2a3';\n}\n\n.bp5-icon-select::before {\n  content: '\\f2a4';\n}\n\n.bp5-icon-selection::before {\n  content: '\\f2a5';\n}\n\n.bp5-icon-send-backward::before {\n  content: '\\f355';\n}\n\n.bp5-icon-send-message::before {\n  content: '\\f2a6';\n}\n\n.bp5-icon-send-to::before {\n  content: '\\f2a9';\n}\n\n.bp5-icon-send-to-graph::before {\n  content: '\\f2a7';\n}\n\n.bp5-icon-send-to-map::before {\n  content: '\\f2a8';\n}\n\n.bp5-icon-sensor::before {\n  content: '\\f33c';\n}\n\n.bp5-icon-series-add::before {\n  content: '\\f2aa';\n}\n\n.bp5-icon-series-configuration::before {\n  content: '\\f2ab';\n}\n\n.bp5-icon-series-derived::before {\n  content: '\\f2ac';\n}\n\n.bp5-icon-series-filtered::before {\n  content: '\\f2ad';\n}\n\n.bp5-icon-series-search::before {\n  content: '\\f2ae';\n}\n\n.bp5-icon-settings::before {\n  content: '\\f2af';\n}\n\n.bp5-icon-shapes::before {\n  content: '\\f2b0';\n}\n\n.bp5-icon-share::before {\n  content: '\\f2b1';\n}\n\n.bp5-icon-shared-filter::before {\n  content: '\\f2b2';\n}\n\n.bp5-icon-shield::before {\n  content: '\\f2b3';\n}\n\n.bp5-icon-ship::before {\n  content: '\\f2b4';\n}\n\n.bp5-icon-shop::before {\n  content: '\\f2b5';\n}\n\n.bp5-icon-shopping-cart::before {\n  content: '\\f2b6';\n}\n\n.bp5-icon-shorten-text::before {\n  content: '\\f33f';\n}\n\n.bp5-icon-signal-search::before {\n  content: '\\f2b7';\n}\n\n.bp5-icon-sim-card::before {\n  content: '\\f2b8';\n}\n\n.bp5-icon-slash::before {\n  content: '\\f2b9';\n}\n\n.bp5-icon-small-cross::before {\n  content: '\\f2ba';\n}\n\n.bp5-icon-small-info-sign::before {\n  content: '\\f334';\n}\n\n.bp5-icon-small-minus::before {\n  content: '\\f2bb';\n}\n\n.bp5-icon-small-plus::before {\n  content: '\\f2bc';\n}\n\n.bp5-icon-small-square::before {\n  content: '\\f2bd';\n}\n\n.bp5-icon-small-tick::before {\n  content: '\\f2be';\n}\n\n.bp5-icon-snowflake::before {\n  content: '\\f2bf';\n}\n\n.bp5-icon-soccer-ball::before {\n  content: '\\f350';\n}\n\n.bp5-icon-social-media::before {\n  content: '\\f2c0';\n}\n\n.bp5-icon-sort::before {\n  content: '\\f2c7';\n}\n\n.bp5-icon-sort-alphabetical::before {\n  content: '\\f2c2';\n}\n\n.bp5-icon-sort-alphabetical-desc::before {\n  content: '\\f2c1';\n}\n\n.bp5-icon-sort-asc::before {\n  content: '\\f2c3';\n}\n\n.bp5-icon-sort-desc::before {\n  content: '\\f2c4';\n}\n\n.bp5-icon-sort-numerical::before {\n  content: '\\f2c6';\n}\n\n.bp5-icon-sort-numerical-desc::before {\n  content: '\\f2c5';\n}\n\n.bp5-icon-spell-check::before {\n  content: '\\f340';\n}\n\n.bp5-icon-split-columns::before {\n  content: '\\f2c8';\n}\n\n.bp5-icon-sports-stadium::before {\n  content: '\\f351';\n}\n\n.bp5-icon-square::before {\n  content: '\\f2c9';\n}\n\n.bp5-icon-stacked-chart::before {\n  content: '\\f2ca';\n}\n\n.bp5-icon-stadium-geometry::before {\n  content: '\\f2cb';\n}\n\n.bp5-icon-star::before {\n  content: '\\f2cd';\n}\n\n.bp5-icon-star-empty::before {\n  content: '\\f2cc';\n}\n\n.bp5-icon-step-backward::before {\n  content: '\\f2ce';\n}\n\n.bp5-icon-step-chart::before {\n  content: '\\f2cf';\n}\n\n.bp5-icon-step-forward::before {\n  content: '\\f2d0';\n}\n\n.bp5-icon-stop::before {\n  content: '\\f2d1';\n}\n\n.bp5-icon-stopwatch::before {\n  content: '\\f2d2';\n}\n\n.bp5-icon-strikethrough::before {\n  content: '\\f2d3';\n}\n\n.bp5-icon-style::before {\n  content: '\\f2d4';\n}\n\n.bp5-icon-subscript::before {\n  content: '\\f339';\n}\n\n.bp5-icon-superscript::before {\n  content: '\\f33a';\n}\n\n.bp5-icon-swap-horizontal::before {\n  content: '\\f2d5';\n}\n\n.bp5-icon-swap-vertical::before {\n  content: '\\f2d6';\n}\n\n.bp5-icon-switch::before {\n  content: '\\f2d7';\n}\n\n.bp5-icon-symbol-circle::before {\n  content: '\\f2d8';\n}\n\n.bp5-icon-symbol-cross::before {\n  content: '\\f2d9';\n}\n\n.bp5-icon-symbol-diamond::before {\n  content: '\\f2da';\n}\n\n.bp5-icon-symbol-rectangle::before {\n  content: '\\f322';\n}\n\n.bp5-icon-symbol-square::before {\n  content: '\\f2db';\n}\n\n.bp5-icon-symbol-triangle-down::before {\n  content: '\\f2dc';\n}\n\n.bp5-icon-symbol-triangle-up::before {\n  content: '\\f2dd';\n}\n\n.bp5-icon-syringe::before {\n  content: '\\f2de';\n}\n\n.bp5-icon-tag::before {\n  content: '\\f2df';\n}\n\n.bp5-icon-take-action::before {\n  content: '\\f2e0';\n}\n\n.bp5-icon-tank::before {\n  content: '\\f2e1';\n}\n\n.bp5-icon-target::before {\n  content: '\\f2e2';\n}\n\n.bp5-icon-taxi::before {\n  content: '\\f2e3';\n}\n\n.bp5-icon-team::before {\n  content: '\\f352';\n}\n\n.bp5-icon-temperature::before {\n  content: '\\f2e4';\n}\n\n.bp5-icon-text-highlight::before {\n  content: '\\f2e5';\n}\n\n.bp5-icon-th::before {\n  content: '\\f2ea';\n}\n\n.bp5-icon-th-derived::before {\n  content: '\\f2e6';\n}\n\n.bp5-icon-th-disconnect::before {\n  content: '\\f2e7';\n}\n\n.bp5-icon-th-filtered::before {\n  content: '\\f2e8';\n}\n\n.bp5-icon-th-list::before {\n  content: '\\f2e9';\n}\n\n.bp5-icon-third-party::before {\n  content: '\\f2eb';\n}\n\n.bp5-icon-thumbs-down::before {\n  content: '\\f2ec';\n}\n\n.bp5-icon-thumbs-up::before {\n  content: '\\f2ed';\n}\n\n.bp5-icon-tick::before {\n  content: '\\f2ef';\n}\n\n.bp5-icon-tick-circle::before {\n  content: '\\f2ee';\n}\n\n.bp5-icon-time::before {\n  content: '\\f2f0';\n}\n\n.bp5-icon-timeline-area-chart::before {\n  content: '\\f2f1';\n}\n\n.bp5-icon-timeline-bar-chart::before {\n  content: '\\f2f2';\n}\n\n.bp5-icon-timeline-events::before {\n  content: '\\f2f3';\n}\n\n.bp5-icon-timeline-line-chart::before {\n  content: '\\f2f4';\n}\n\n.bp5-icon-tint::before {\n  content: '\\f2f5';\n}\n\n.bp5-icon-torch::before {\n  content: '\\f2f6';\n}\n\n.bp5-icon-tractor::before {\n  content: '\\f2f7';\n}\n\n.bp5-icon-train::before {\n  content: '\\f2f8';\n}\n\n.bp5-icon-translate::before {\n  content: '\\f2f9';\n}\n\n.bp5-icon-trash::before {\n  content: '\\f2fa';\n}\n\n.bp5-icon-tree::before {\n  content: '\\f2fb';\n}\n\n.bp5-icon-trending-down::before {\n  content: '\\f2fc';\n}\n\n.bp5-icon-trending-up::before {\n  content: '\\f2fd';\n}\n\n.bp5-icon-trophy::before {\n  content: '\\f34f';\n}\n\n.bp5-icon-truck::before {\n  content: '\\f2fe';\n}\n\n.bp5-icon-two-columns::before {\n  content: '\\f2ff';\n}\n\n.bp5-icon-unarchive::before {\n  content: '\\f300';\n}\n\n.bp5-icon-underline::before {\n  content: '\\f301';\n}\n\n.bp5-icon-undo::before {\n  content: '\\f302';\n}\n\n.bp5-icon-ungroup-objects::before {\n  content: '\\f303';\n}\n\n.bp5-icon-unknown-vehicle::before {\n  content: '\\f304';\n}\n\n.bp5-icon-unlink::before {\n  content: '\\f345';\n}\n\n.bp5-icon-unlock::before {\n  content: '\\f305';\n}\n\n.bp5-icon-unpin::before {\n  content: '\\f306';\n}\n\n.bp5-icon-unresolve::before {\n  content: '\\f307';\n}\n\n.bp5-icon-updated::before {\n  content: '\\f308';\n}\n\n.bp5-icon-upload::before {\n  content: '\\f309';\n}\n\n.bp5-icon-user::before {\n  content: '\\f30a';\n}\n\n.bp5-icon-variable::before {\n  content: '\\f30b';\n}\n\n.bp5-icon-vector::before {\n  content: '\\f35e';\n}\n\n.bp5-icon-vertical-bar-chart-asc::before {\n  content: '\\f30c';\n}\n\n.bp5-icon-vertical-bar-chart-desc::before {\n  content: '\\f30d';\n}\n\n.bp5-icon-vertical-distribution::before {\n  content: '\\f30e';\n}\n\n.bp5-icon-vertical-inbetween::before {\n  content: '\\f32a';\n}\n\n.bp5-icon-video::before {\n  content: '\\f30f';\n}\n\n.bp5-icon-virus::before {\n  content: '\\f310';\n}\n\n.bp5-icon-volume-down::before {\n  content: '\\f311';\n}\n\n.bp5-icon-volume-off::before {\n  content: '\\f312';\n}\n\n.bp5-icon-volume-up::before {\n  content: '\\f313';\n}\n\n.bp5-icon-walk::before {\n  content: '\\f314';\n}\n\n.bp5-icon-warning-sign::before {\n  content: '\\f315';\n}\n\n.bp5-icon-waterfall-chart::before {\n  content: '\\f316';\n}\n\n.bp5-icon-waves::before {\n  content: '\\f317';\n}\n\n.bp5-icon-widget::before {\n  content: '\\f31b';\n}\n\n.bp5-icon-widget-button::before {\n  content: '\\f318';\n}\n\n.bp5-icon-widget-footer::before {\n  content: '\\f319';\n}\n\n.bp5-icon-widget-header::before {\n  content: '\\f31a';\n}\n\n.bp5-icon-wind::before {\n  content: '\\f31c';\n}\n\n.bp5-icon-wrench::before {\n  content: '\\f31d';\n}\n\n.bp5-icon-zoom-in::before {\n  content: '\\f31e';\n}\n\n.bp5-icon-zoom-out::before {\n  content: '\\f31f';\n}\n\n.bp5-icon-zoom-to-fit::before {\n  content: '\\f320';\n}\n\n.bp5-text-muted {\n  color: #5f6b7c;\n}\n\n.bp5-text-disabled {\n  color: rgba(95, 107, 124, 0.6);\n}\n\n.bp5-running-text hr {\n  border-color: rgba(17, 20, 24, 0.15);\n}\n\n.bp5-code,\n.bp5-running-text code {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\na > .bp5-code,\na > .bp5-running-text code {\n  color: #2d72d2;\n}\n\n.bp5-code-block,\n.bp5-running-text pre {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color: #1c2127;\n}\n.bp5-code-block > code,\n.bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-code-block,\n  .bp5-running-text pre {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n\n.bp5-key,\n.bp5-running-text kbd {\n  background: #ffffff;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\n\n.bp5-icon.bp5-intent-primary,\n.bp5-icon-standard.bp5-intent-primary,\n.bp5-icon-large.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-icon.bp5-intent-success,\n.bp5-icon-standard.bp5-intent-success,\n.bp5-icon-large.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-icon.bp5-intent-warning,\n.bp5-icon-standard.bp5-intent-warning,\n.bp5-icon-large.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-icon.bp5-intent-danger,\n.bp5-icon-standard.bp5-intent-danger,\n.bp5-icon-large.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-heading {\n  color: #1c2127;\n  font-weight: 600;\n  margin: 0 0 10px;\n  padding: 0;\n}\n.bp5-dark .bp5-heading {\n  color: #f6f7f9;\n}\n.bp5-heading.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-heading.bp5-text-muted {\n  color: #abb3bf;\n}\n\n.bp5-running-text h1,\nh1.bp5-heading {\n  font-size: 36px;\n  line-height: 40px;\n}\n\n.bp5-running-text h2,\nh2.bp5-heading {\n  font-size: 28px;\n  line-height: 32px;\n}\n\n.bp5-running-text h3,\nh3.bp5-heading {\n  font-size: 22px;\n  line-height: 25px;\n}\n\n.bp5-running-text h4,\nh4.bp5-heading {\n  font-size: 18px;\n  line-height: 21px;\n}\n\n.bp5-running-text h5,\nh5.bp5-heading {\n  font-size: 16px;\n  line-height: 19px;\n}\n\n.bp5-running-text h6,\nh6.bp5-heading {\n  font-size: 14px;\n  line-height: 16px;\n}\n.bp5-ui-text {\n  font-size: 14px;\n  font-weight: 400;\n  letter-spacing: 0;\n  line-height: 1.28581;\n  text-transform: none;\n}\n\n.bp5-monospace-text {\n  font-family: monospace;\n  text-transform: none;\n}\n\n.bp5-text-overflow-ellipsis {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n}\n.bp5-running-text {\n  font-size: 14px;\n  line-height: 1.5;\n}\n.bp5-running-text h1 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h1 {\n  color: #f6f7f9;\n}\n.bp5-running-text h1.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h1.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text h2 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h2 {\n  color: #f6f7f9;\n}\n.bp5-running-text h2.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h2.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text h3 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h3 {\n  color: #f6f7f9;\n}\n.bp5-running-text h3.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h3.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text h4 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h4 {\n  color: #f6f7f9;\n}\n.bp5-running-text h4.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h4.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text h5 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h5 {\n  color: #f6f7f9;\n}\n.bp5-running-text h5.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h5.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text h6 {\n  color: #1c2127;\n  font-weight: 600;\n  margin-bottom: 20px;\n  margin-top: 40px;\n}\n.bp5-dark .bp5-running-text h6 {\n  color: #f6f7f9;\n}\n.bp5-running-text h6.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-running-text h6.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-running-text hr {\n  border: none;\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n  margin: 20px 0;\n}\n.bp5-running-text p {\n  margin: 0 0 10px;\n  padding: 0;\n}\n.bp5-text-large {\n  font-size: 16px;\n}\n\n.bp5-text-small {\n  font-size: 12px;\n}\n\n.bp5-code,\n.bp5-running-text code {\n  font-family: monospace;\n  text-transform: none;\n  border-radius: 2px;\n  font-size: smaller;\n  padding: 2px 5px;\n}\n\n.bp5-code-block,\n.bp5-running-text pre {\n  font-family: monospace;\n  text-transform: none;\n  border-radius: 2px;\n  display: block;\n  font-size: 13px;\n  line-height: 1.4;\n  margin: 10px 0;\n  padding: 13px 15px 12px;\n  word-break: break-all;\n  word-wrap: break-word;\n}\n.bp5-code-block > code,\n.bp5-running-text pre > code {\n  font-size: inherit;\n  padding: 0;\n}\n\n.bp5-key,\n.bp5-running-text kbd {\n  align-items: center;\n  border-radius: 2px;\n  display: inline-flex;\n  font-family: inherit;\n  font-size: 12px;\n  height: 24px;\n  justify-content: center;\n  line-height: 24px;\n  min-width: 24px;\n  padding: 2px 4px;\n  vertical-align: middle;\n}\n.bp5-key .bp5-icon,\n.bp5-running-text kbd .bp5-icon,\n.bp5-key .bp5-icon-standard,\n.bp5-running-text kbd .bp5-icon-standard,\n.bp5-key .bp5-icon-large,\n.bp5-running-text kbd .bp5-icon-large {\n  margin-right: 5px;\n}\n.bp5-blockquote,\n.bp5-running-text blockquote {\n  border-left: solid 4px rgba(171, 179, 191, 0.5);\n  margin: 0 0 10px;\n  padding: 0 20px;\n}\n.bp5-dark .bp5-blockquote,\n.bp5-dark .bp5-running-text blockquote,\n.bp5-running-text .bp5-dark blockquote {\n  border-color: rgba(115, 128, 145, 0.5);\n}\n.bp5-list,\n.bp5-running-text ul,\n.bp5-running-text ol {\n  margin: 10px 0;\n  padding-left: 30px;\n}\n.bp5-list li:not(:last-child),\n.bp5-running-text ul li:not(:last-child),\n.bp5-running-text ol li:not(:last-child) {\n  margin-bottom: 5px;\n}\n.bp5-list ol,\n.bp5-running-text ul ol,\n.bp5-running-text ol ol,\n.bp5-list ul,\n.bp5-running-text ul ul,\n.bp5-running-text ol ul {\n  margin-top: 5px;\n}\n\n.bp5-list-unstyled {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.bp5-list-unstyled li {\n  padding: 0;\n}\n.bp5-rtl {\n  text-align: right;\n}\n.bp5-dark {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-text-disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-running-text hr {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-dark a {\n  color: #8abbff;\n}\n.bp5-dark a:hover {\n  color: #8abbff;\n}\n.bp5-dark a .bp5-icon,\n.bp5-dark a .bp5-icon-standard,\n.bp5-dark a .bp5-icon-large {\n  color: inherit;\n}\n.bp5-dark a code {\n  color: inherit;\n}\n.bp5-dark .bp5-code,\n.bp5-dark .bp5-running-text code {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\na > .bp5-dark .bp5-code,\na > .bp5-dark .bp5-running-text code {\n  color: inherit;\n}\n.bp5-dark .bp5-code-block,\n.bp5-dark .bp5-running-text pre {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-code-block > code,\n.bp5-dark .bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n.bp5-dark .bp5-key,\n.bp5-dark .bp5-running-text kbd {\n  background: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\n.bp5-dark .bp5-icon.bp5-intent-primary,\n.bp5-dark .bp5-icon-standard.bp5-intent-primary,\n.bp5-dark .bp5-icon-large.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-icon.bp5-intent-success,\n.bp5-dark .bp5-icon-standard.bp5-intent-success,\n.bp5-dark .bp5-icon-large.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-icon.bp5-intent-warning,\n.bp5-dark .bp5-icon-standard.bp5-intent-warning,\n.bp5-dark .bp5-icon-large.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-icon.bp5-intent-danger,\n.bp5-dark .bp5-icon-standard.bp5-intent-danger,\n.bp5-dark .bp5-icon-large.bp5-intent-danger {\n  color: #fa999c;\n}\n\n.polonto :focus {\n  outline: rgba(45, 114, 210, 0.6) solid 2px;\n  outline-offset: 2px;\n  -moz-outline-radius: 6px;\n}\n\n.bp5-focus-disabled :focus:not(.bp5-focus-style-manager-ignore *) {\n  outline: none !important;\n}\n.bp5-focus-disabled\n  :focus:not(.bp5-focus-style-manager-ignore *)\n  ~ .bp5-control-indicator {\n  outline: none !important;\n}\n\n.bp5-dark {\n  color-scheme: dark;\n}\n.bp5-alert {\n  max-width: 400px;\n  padding: 20px;\n}\n\n.bp5-alert-body {\n  display: flex;\n}\n.bp5-alert-body .bp5-icon {\n  font-size: 40px;\n  margin-right: 20px;\n  margin-top: 0;\n}\n\n.bp5-alert-contents {\n  word-break: break-word;\n}\n\n.bp5-alert-footer {\n  display: flex;\n  flex-direction: row-reverse;\n  margin-top: 10px;\n}\n.bp5-alert-footer .bp5-button {\n  margin-left: 10px;\n}\n.bp5-breadcrumbs {\n  align-items: center;\n  cursor: default;\n  display: flex;\n  flex-wrap: wrap;\n  height: 30px;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.bp5-breadcrumbs > li {\n  align-items: center;\n  display: flex;\n}\n.bp5-breadcrumbs > li::after {\n  background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%235f6b7c'/%3e%3c/svg%3e\");\n  content: '';\n  display: block;\n  height: 16px;\n  margin: 0 5px;\n  width: 16px;\n}\n.bp5-breadcrumbs > li:last-of-type::after {\n  display: none;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumb-current,\n.bp5-breadcrumbs-collapsed {\n  align-items: center;\n  display: inline-flex;\n  font-size: 16px;\n}\n\n.bp5-breadcrumb,\n.bp5-breadcrumbs-collapsed {\n  color: #5f6b7c;\n}\n\n.bp5-breadcrumb:hover {\n  text-decoration: none;\n}\n.bp5-breadcrumb.bp5-disabled {\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-breadcrumb .bp5-icon {\n  margin-right: 5px;\n}\n\n.bp5-breadcrumb-current {\n  color: inherit;\n  font-weight: 600;\n}\n.bp5-breadcrumb-current .bp5-input {\n  font-size: inherit;\n  font-weight: inherit;\n  vertical-align: baseline;\n}\n\n.bp5-breadcrumbs-collapsed {\n  background: rgba(143, 153, 168, 0.15);\n  border: none;\n  border-radius: 2px;\n  cursor: pointer;\n  margin-right: 2px;\n  padding: 1px 5px;\n  vertical-align: text-bottom;\n}\n.bp5-breadcrumbs-collapsed::before {\n  background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%235f6b7c'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\")\n    center no-repeat;\n  content: '';\n  display: block;\n  height: 16px;\n  width: 16px;\n}\n.bp5-breadcrumbs-collapsed:hover {\n  background: rgba(143, 153, 168, 0.3);\n  color: #1c2127;\n  text-decoration: none;\n}\n\n.bp5-dark .bp5-breadcrumb,\n.bp5-dark .bp5-breadcrumbs-collapsed {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-breadcrumbs > li::after {\n  background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.71 7.29l-4-4a1.003 1.003 0 00-1.42 1.42L8.59 8 5.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4-4c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71z' fill='%23abb3bf'/%3e%3c/svg%3e\");\n  color: #abb3bf;\n}\n.bp5-dark .bp5-breadcrumb.bp5-disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-breadcrumb-current {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed {\n  background: rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-breadcrumbs-collapsed::before {\n  background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cg fill='%23abb3bf'%3e%3cpath d='M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z'/%3e%3c/g%3e%3c/svg%3e\")\n    center no-repeat;\n}\n.bp5-dark .bp5-breadcrumbs-collapsed:hover {\n  background: rgba(143, 153, 168, 0.3);\n  color: #f6f7f9;\n}\n.bp5-button {\n  display: inline-flex;\n  flex-direction: row;\n  align-items: center;\n  border: none;\n  border-radius: 2px;\n  cursor: pointer;\n  font-size: 14px;\n  justify-content: center;\n  padding: 5px 10px;\n  text-align: left;\n  vertical-align: middle;\n  min-height: 30px;\n  min-width: 30px;\n}\n.bp5-button > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-button > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-button::before,\n.bp5-button > * {\n  margin-right: 7px;\n}\n.bp5-button:empty::before,\n.bp5-button > :last-child {\n  margin-right: 0;\n}\n.bp5-button:empty {\n  padding: 0 !important;\n}\n.bp5-button:disabled,\n.bp5-button.bp5-disabled {\n  cursor: not-allowed;\n}\n.bp5-button.bp5-fill {\n  display: flex;\n  width: 100%;\n}\n.bp5-button.bp5-align-right,\n.bp5-align-right .bp5-button {\n  text-align: right;\n}\n.bp5-button.bp5-align-left,\n.bp5-align-left .bp5-button {\n  text-align: left;\n}\n.bp5-button:not([class*='bp5-intent-']) {\n  background-color: #f6f7f9;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #1c2127;\n}\n.bp5-button:not([class*='bp5-intent-']):hover {\n  background-clip: padding-box;\n  background-color: #edeff2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button:not([class*='bp5-intent-']):active,\n.bp5-button:not([class*='bp5-intent-']).bp5-active {\n  background-color: #dce0e5;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button:not([class*='bp5-intent-']):active,\n  .bp5-button:not([class*='bp5-intent-']).bp5-active {\n    background: highlight;\n  }\n}\n.bp5-button:not([class*='bp5-intent-']):disabled,\n.bp5-button:not([class*='bp5-intent-']).bp5-disabled {\n  background-color: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  outline: none;\n}\n.bp5-button:not([class*='bp5-intent-']):disabled.bp5-active,\n.bp5-button:not([class*='bp5-intent-']):disabled.bp5-active:hover,\n.bp5-button:not([class*='bp5-intent-']).bp5-disabled.bp5-active,\n.bp5-button:not([class*='bp5-intent-']).bp5-disabled.bp5-active:hover {\n  background: rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button:not([class*='bp5-intent-']) {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-button.bp5-intent-primary {\n  background-color: #2d72d2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-primary:hover,\n.bp5-button.bp5-intent-primary:active,\n.bp5-button.bp5-intent-primary.bp5-active {\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-primary:hover {\n  background-color: #215db0;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:active,\n.bp5-button.bp5-intent-primary.bp5-active {\n  background-color: #184a90;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-primary:disabled,\n.bp5-button.bp5-intent-primary.bp5-disabled {\n  background-color: rgba(45, 114, 210, 0.5);\n  border-color: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-primary:disabled,\n  .bp5-button.bp5-intent-primary.bp5-disabled {\n    border-color: graytext;\n    color: graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-primary {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-button.bp5-intent-success {\n  background-color: #238551;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-success:hover,\n.bp5-button.bp5-intent-success:active,\n.bp5-button.bp5-intent-success.bp5-active {\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-success:hover {\n  background-color: #1c6e42;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:active,\n.bp5-button.bp5-intent-success.bp5-active {\n  background-color: #165a36;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-success:disabled,\n.bp5-button.bp5-intent-success.bp5-disabled {\n  background-color: rgba(35, 133, 81, 0.5);\n  border-color: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-success:disabled,\n  .bp5-button.bp5-intent-success.bp5-disabled {\n    border-color: graytext;\n    color: graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-success {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-button.bp5-intent-warning {\n  background-color: #c87619;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-warning:hover,\n.bp5-button.bp5-intent-warning:active,\n.bp5-button.bp5-intent-warning.bp5-active {\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-warning:hover {\n  background-color: #935610;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:active,\n.bp5-button.bp5-intent-warning.bp5-active {\n  background-color: #77450d;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-warning:disabled,\n.bp5-button.bp5-intent-warning.bp5-disabled {\n  background-color: rgba(200, 118, 25, 0.5);\n  border-color: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-warning:disabled,\n  .bp5-button.bp5-intent-warning.bp5-disabled {\n    border-color: graytext;\n    color: graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-warning {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-button.bp5-intent-danger {\n  background-color: #cd4246;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-danger:hover,\n.bp5-button.bp5-intent-danger:active,\n.bp5-button.bp5-intent-danger.bp5-active {\n  color: #ffffff;\n}\n.bp5-button.bp5-intent-danger:hover {\n  background-color: #ac2f33;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:active,\n.bp5-button.bp5-intent-danger.bp5-active {\n  background-color: #8e292c;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-button.bp5-intent-danger:disabled,\n.bp5-button.bp5-intent-danger.bp5-disabled {\n  background-color: rgba(205, 66, 70, 0.5);\n  border-color: transparent;\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-danger:disabled,\n  .bp5-button.bp5-intent-danger.bp5-disabled {\n    border-color: graytext;\n    color: graytext;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-danger {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-button[class*='bp5-intent-'] .bp5-button-spinner .bp5-spinner-head {\n  stroke: #ffffff;\n}\n.bp5-button.bp5-large,\n.bp5-large .bp5-button {\n  min-height: 40px;\n  min-width: 40px;\n  font-size: 16px;\n  padding: 5px 15px;\n}\n.bp5-button.bp5-large::before,\n.bp5-button.bp5-large > *,\n.bp5-large .bp5-button::before,\n.bp5-large .bp5-button > * {\n  margin-right: 10px;\n}\n.bp5-button.bp5-large:empty::before,\n.bp5-button.bp5-large > :last-child,\n.bp5-large .bp5-button:empty::before,\n.bp5-large .bp5-button > :last-child {\n  margin-right: 0;\n}\n.bp5-button.bp5-small,\n.bp5-small .bp5-button {\n  min-height: 24px;\n  min-width: 24px;\n  padding: 0 7px;\n}\n.bp5-button.bp5-loading {\n  position: relative;\n}\n.bp5-button.bp5-loading[class*='bp5-icon-']::before {\n  visibility: hidden;\n}\n.bp5-button.bp5-loading .bp5-button-spinner {\n  margin: 0;\n  position: absolute;\n}\n.bp5-button.bp5-loading > :not(.bp5-button-spinner) {\n  visibility: hidden;\n}\n.bp5-button[class*='bp5-icon-']::before {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  color: #5f6b7c;\n}\n.bp5-button .bp5-icon,\n.bp5-button .bp5-icon-standard,\n.bp5-button .bp5-icon-large {\n  color: #5f6b7c;\n}\n.bp5-button .bp5-icon.bp5-align-right,\n.bp5-button .bp5-icon-standard.bp5-align-right,\n.bp5-button .bp5-icon-large.bp5-align-right {\n  margin-left: 7px;\n}\n.bp5-button .bp5-icon:first-child:last-child,\n.bp5-button .bp5-spinner + .bp5-icon:last-child {\n  margin: 0 -7px;\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']) {\n  background-color: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):hover,\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):active,\n.bp5-dark .bp5-button:not([class*='bp5-intent-']).bp5-active {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):hover {\n  background-color: #2f343c;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):active,\n.bp5-dark .bp5-button:not([class*='bp5-intent-']).bp5-active {\n  background-color: #1c2127;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):disabled,\n.bp5-dark .bp5-button:not([class*='bp5-intent-']).bp5-disabled {\n  background-color: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-']):disabled.bp5-active,\n.bp5-dark .bp5-button:not([class*='bp5-intent-']).bp5-disabled.bp5-active {\n  background: rgba(64, 72, 84, 0.7);\n}\n.bp5-dark\n  .bp5-button:not([class*='bp5-intent-'])\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  background: rgba(17, 20, 24, 0.5);\n  stroke: #8f99a8;\n}\n.bp5-dark .bp5-button:not([class*='bp5-intent-'])[class*='bp5-icon-']::before {\n  color: #abb3bf;\n}\n.bp5-dark\n  .bp5-button:not([class*='bp5-intent-'])\n  .bp5-icon:not([class*='bp5-intent-']),\n.bp5-dark\n  .bp5-button:not([class*='bp5-intent-'])\n  .bp5-icon-standard:not([class*='bp5-intent-']),\n.bp5-dark\n  .bp5-button:not([class*='bp5-intent-'])\n  .bp5-icon-large:not([class*='bp5-intent-']) {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-button[class*='bp5-intent-'] {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*='bp5-intent-']:hover {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-button[class*='bp5-intent-']:active,\n.bp5-dark .bp5-button[class*='bp5-intent-'].bp5-active {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-button[class*='bp5-intent-']:disabled,\n.bp5-dark .bp5-button[class*='bp5-intent-'].bp5-disabled {\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.3);\n}\n.bp5-dark\n  .bp5-button[class*='bp5-intent-']\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #8f99a8;\n}\n.bp5-button:disabled::before,\n.bp5-button:disabled .bp5-icon,\n.bp5-button:disabled .bp5-icon-standard,\n.bp5-button:disabled .bp5-icon-large,\n.bp5-button.bp5-disabled::before,\n.bp5-button.bp5-disabled .bp5-icon,\n.bp5-button.bp5-disabled .bp5-icon-standard,\n.bp5-button.bp5-disabled .bp5-icon-large,\n.bp5-button[class*='bp5-intent-']::before,\n.bp5-button[class*='bp5-intent-'] .bp5-icon,\n.bp5-button[class*='bp5-intent-'] .bp5-icon-standard,\n.bp5-button[class*='bp5-intent-'] .bp5-icon-large {\n  color: inherit !important;\n}\n.bp5-button.bp5-minimal {\n  background: none;\n  box-shadow: none;\n}\n.bp5-button.bp5-minimal:hover {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  color: #1c2127;\n  text-decoration: none;\n}\n.bp5-button.bp5-minimal:active,\n.bp5-button.bp5-minimal.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: none;\n  color: #1c2127;\n}\n.bp5-button.bp5-minimal:disabled,\n.bp5-button.bp5-minimal:disabled:hover,\n.bp5-button.bp5-minimal.bp5-disabled,\n.bp5-button.bp5-minimal.bp5-disabled:hover {\n  background: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-button.bp5-minimal:disabled.bp5-active,\n.bp5-button.bp5-minimal:disabled:hover.bp5-active,\n.bp5-button.bp5-minimal.bp5-disabled.bp5-active,\n.bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover,\n.bp5-dark .bp5-button.bp5-minimal:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button.bp5-minimal:hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-minimal:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled,\n.bp5-dark .bp5-button.bp5-minimal:disabled:hover,\n.bp5-dark .bp5-button.bp5-minimal.bp5-disabled,\n.bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover {\n  background: none;\n  color: rgba(171, 179, 191, 0.6);\n  cursor: not-allowed;\n}\n.bp5-dark .bp5-button.bp5-minimal:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal:disabled:hover.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover,\n.bp5-button.bp5-minimal.bp5-intent-primary:active,\n.bp5-button.bp5-minimal.bp5-intent-primary.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.15);\n  color: #215db0;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:active,\n.bp5-button.bp5-minimal.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #184a90;\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled,\n.bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active,\n.bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-primary\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #215db0;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover,\n.bp5-button.bp5-minimal.bp5-intent-success:active,\n.bp5-button.bp5-minimal.bp5-intent-success.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.15);\n  color: #1c6e42;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:active,\n.bp5-button.bp5-minimal.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #165a36;\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled,\n.bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active,\n.bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-success\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #1c6e42;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover,\n.bp5-button.bp5-minimal.bp5-intent-warning:active,\n.bp5-button.bp5-minimal.bp5-intent-warning.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.15);\n  color: #935610;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:active,\n.bp5-button.bp5-minimal.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #77450d;\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled,\n.bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active,\n.bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-warning\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #935610;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover,\n.bp5-button.bp5-minimal.bp5-intent-danger:active,\n.bp5-button.bp5-minimal.bp5-intent-danger.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.15);\n  color: #ac2f33;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:active,\n.bp5-button.bp5-minimal.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #8e292c;\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled,\n.bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active,\n.bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-minimal.bp5-intent-danger\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #ac2f33;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-minimal.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined {\n  background: none;\n  box-shadow: none;\n  border: 1px solid rgba(28, 33, 39, 0.2);\n  box-sizing: border-box;\n}\n.bp5-button.bp5-outlined:hover {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  color: #1c2127;\n  text-decoration: none;\n}\n.bp5-button.bp5-outlined:active,\n.bp5-button.bp5-outlined.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: none;\n  color: #1c2127;\n}\n.bp5-button.bp5-outlined:disabled,\n.bp5-button.bp5-outlined:disabled:hover,\n.bp5-button.bp5-outlined.bp5-disabled,\n.bp5-button.bp5-outlined.bp5-disabled:hover {\n  background: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-button.bp5-outlined:disabled.bp5-active,\n.bp5-button.bp5-outlined:disabled:hover.bp5-active,\n.bp5-button.bp5-outlined.bp5-disabled.bp5-active,\n.bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover,\n.bp5-dark .bp5-button.bp5-outlined:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button.bp5-outlined:hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button.bp5-outlined:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled,\n.bp5-dark .bp5-button.bp5-outlined:disabled:hover,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover {\n  background: none;\n  color: rgba(171, 179, 191, 0.6);\n  cursor: not-allowed;\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined:disabled:hover.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover,\n.bp5-button.bp5-outlined.bp5-intent-primary:active,\n.bp5-button.bp5-outlined.bp5-intent-primary.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.15);\n  color: #215db0;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:active,\n.bp5-button.bp5-outlined.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #184a90;\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled,\n.bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(33, 93, 176, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active,\n.bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #215db0;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(138, 187, 255, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover,\n.bp5-button.bp5-outlined.bp5-intent-success:active,\n.bp5-button.bp5-outlined.bp5-intent-success.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.15);\n  color: #1c6e42;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:active,\n.bp5-button.bp5-outlined.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #165a36;\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled,\n.bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(28, 110, 66, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active,\n.bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-success\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #1c6e42;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(114, 202, 155, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover,\n.bp5-button.bp5-outlined.bp5-intent-warning:active,\n.bp5-button.bp5-outlined.bp5-intent-warning.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.15);\n  color: #935610;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:active,\n.bp5-button.bp5-outlined.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #77450d;\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled,\n.bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(147, 86, 16, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active,\n.bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #935610;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(251, 179, 96, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover,\n.bp5-button.bp5-outlined.bp5-intent-danger:active,\n.bp5-button.bp5-outlined.bp5-intent-danger.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.15);\n  color: #ac2f33;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:active,\n.bp5-button.bp5-outlined.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #8e292c;\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled,\n.bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(172, 47, 51, 0.5);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active,\n.bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #ac2f33;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(250, 153, 156, 0.5);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-button.bp5-outlined:disabled,\n.bp5-button.bp5-outlined.bp5-disabled,\n.bp5-button.bp5-outlined:disabled:hover,\n.bp5-button.bp5-outlined.bp5-disabled:hover {\n  border-color: rgba(95, 107, 124, 0.1);\n}\n.bp5-dark .bp5-button.bp5-outlined {\n  border-color: rgba(255, 255, 255, 0.4);\n}\n.bp5-dark .bp5-button.bp5-outlined:disabled,\n.bp5-dark .bp5-button.bp5-outlined:disabled:hover,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-disabled:hover {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary {\n  border-color: rgba(33, 93, 176, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-primary:disabled,\n.bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled {\n  border-color: rgba(33, 93, 176, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary {\n  border-color: rgba(138, 187, 255, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-primary.bp5-disabled {\n  border-color: rgba(138, 187, 255, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-success {\n  border-color: rgba(28, 110, 66, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-success:disabled,\n.bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled {\n  border-color: rgba(28, 110, 66, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success {\n  border-color: rgba(114, 202, 155, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-success.bp5-disabled {\n  border-color: rgba(114, 202, 155, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning {\n  border-color: rgba(147, 86, 16, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-warning:disabled,\n.bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled {\n  border-color: rgba(147, 86, 16, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning {\n  border-color: rgba(251, 179, 96, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-warning.bp5-disabled {\n  border-color: rgba(251, 179, 96, 0.2);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger {\n  border-color: rgba(172, 47, 51, 0.6);\n}\n.bp5-button.bp5-outlined.bp5-intent-danger:disabled,\n.bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled {\n  border-color: rgba(172, 47, 51, 0.2);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger {\n  border-color: rgba(250, 153, 156, 0.6);\n}\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger:disabled,\n.bp5-dark .bp5-button.bp5-outlined.bp5-intent-danger.bp5-disabled {\n  border-color: rgba(250, 153, 156, 0.2);\n}\n.bp5-button.bp5-intent-warning {\n  background: #fbb360;\n  color: #1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled).bp5-icon > svg {\n  fill: rgba(28, 33, 39, 0.7);\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(\n    .bp5-outlined\n  ):hover {\n  background: #ec9a3c;\n  color: #1c2127;\n}\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(\n    .bp5-outlined\n  ):active,\n.bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(\n    .bp5-outlined\n  ).bp5-active {\n  background: #c87619;\n  color: #1c2127;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(\n      .bp5-outlined\n    ):active,\n  .bp5-button.bp5-intent-warning:not(.bp5-disabled):not(.bp5-minimal):not(\n      .bp5-outlined\n    ).bp5-active {\n    background: highlight;\n  }\n}\n.bp5-button.bp5-intent-warning:disabled,\n.bp5-button.bp5-intent-warning.bp5-disabled {\n  background: rgba(200, 118, 25, 0.5);\n  color: rgba(28, 33, 39, 0.35);\n}\n.bp5-dark .bp5-button.bp5-intent-warning:disabled,\n.bp5-dark .bp5-button.bp5-intent-warning.bp5-disabled {\n  color: rgba(28, 33, 39, 0.6);\n}\n.bp5-button.bp5-intent-warning.bp5-minimal,\n.bp5-button.bp5-intent-warning.bp5-outlined {\n  background: none;\n}\n.bp5-dark\n  .bp5-button.bp5-intent-warning.bp5-minimal:not(.bp5-disabled).bp5-icon\n  > svg,\n.bp5-dark\n  .bp5-button.bp5-intent-warning.bp5-outlined:not(.bp5-disabled).bp5-icon\n  > svg {\n  fill: #fbb360;\n}\n\na.bp5-button {\n  text-align: center;\n  text-decoration: none;\n  transition: none;\n}\na.bp5-button,\na.bp5-button:hover,\na.bp5-button:active {\n  color: #1c2127;\n}\na.bp5-button.bp5-disabled {\n  color: rgba(95, 107, 124, 0.6);\n}\n\n.bp5-button-text {\n  flex: 0 1 auto;\n}\n\n.bp5-button.bp5-align-left .bp5-button-text,\n.bp5-button.bp5-align-right .bp5-button-text,\n.bp5-button-group.bp5-align-left .bp5-button-text,\n.bp5-button-group.bp5-align-right .bp5-button-text {\n  flex: 1 1 auto;\n}\n.bp5-button-group {\n  display: inline-flex;\n}\n.bp5-button-group .bp5-button {\n  flex: 0 0 auto;\n  position: relative;\n  z-index: 4;\n}\n.bp5-button-group .bp5-button:focus {\n  z-index: 5;\n}\n.bp5-button-group .bp5-button:hover {\n  z-index: 6;\n}\n.bp5-button-group .bp5-button:active,\n.bp5-button-group .bp5-button.bp5-active {\n  z-index: 7;\n}\n.bp5-button-group .bp5-button:disabled,\n.bp5-button-group .bp5-button.bp5-disabled {\n  z-index: 3;\n}\n.bp5-button-group .bp5-button[class*='bp5-intent-'] {\n  z-index: 9;\n}\n.bp5-button-group .bp5-button[class*='bp5-intent-']:focus {\n  z-index: 10;\n}\n.bp5-button-group .bp5-button[class*='bp5-intent-']:hover {\n  z-index: 11;\n}\n.bp5-button-group .bp5-button[class*='bp5-intent-']:active,\n.bp5-button-group .bp5-button[class*='bp5-intent-'].bp5-active {\n  z-index: 12;\n}\n.bp5-button-group .bp5-button[class*='bp5-intent-']:disabled,\n.bp5-button-group .bp5-button[class*='bp5-intent-'].bp5-disabled {\n  z-index: 8;\n}\n.bp5-button-group:not(.bp5-minimal)\n  > .bp5-popover-wrapper:not(:first-child)\n  .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:first-child) {\n  border-bottom-left-radius: 0;\n  border-top-left-radius: 0;\n}\n.bp5-button-group:not(.bp5-minimal)\n  > .bp5-popover-wrapper:not(:last-child)\n  .bp5-button,\n.bp5-button-group:not(.bp5-minimal) > .bp5-button:not(:last-child) {\n  border-bottom-right-radius: 0;\n  border-top-right-radius: 0;\n  margin-right: -1px;\n}\n.bp5-button-group.bp5-minimal .bp5-button {\n  background: none;\n  box-shadow: none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:hover {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  color: #1c2127;\n  text-decoration: none;\n}\n.bp5-button-group.bp5-minimal .bp5-button:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: none;\n  color: #1c2127;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled,\n.bp5-button-group.bp5-minimal .bp5-button:disabled:hover,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-disabled,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover {\n  background: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active,\n.bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:active,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled:hover {\n  background: none;\n  color: rgba(171, 179, 191, 0.6);\n  cursor: not-allowed;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled.bp5-active,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button:disabled:hover.bp5-active,\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-disabled.bp5-active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-disabled:hover.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.15);\n  color: #215db0;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #184a90;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(33, 93, 176, 0.5);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary:disabled.bp5-active,\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #215db0;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-primary:disabled,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(138, 187, 255, 0.5);\n}\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.15);\n  color: #1c6e42;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #165a36;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(28, 110, 66, 0.5);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success:disabled.bp5-active,\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #1c6e42;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-success:disabled,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(114, 202, 155, 0.5);\n}\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success:disabled.bp5-active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.15);\n  color: #935610;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #77450d;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(147, 86, 16, 0.5);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning:disabled.bp5-active,\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #935610;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-warning:disabled,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(251, 179, 96, 0.5);\n}\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.15);\n  color: #ac2f33;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #8e292c;\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled,\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(172, 47, 51, 0.5);\n}\n.bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled.bp5-active,\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #ac2f33;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-button-group.bp5-minimal .bp5-button.bp5-intent-danger:disabled,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(250, 153, 156, 0.5);\n}\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark\n  .bp5-button-group.bp5-minimal\n  .bp5-button.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-button-group.bp5-minimal:not(:first-child) {\n    border-bottom-left-radius: 0;\n    border-left: none;\n    border-top-left-radius: 0;\n  }\n  .bp5-button-group.bp5-minimal:not(:last-child) {\n    border-bottom-right-radius: 0;\n    border-top-right-radius: 0;\n    margin-right: -1px;\n  }\n}\n.bp5-button-group .bp5-popover-wrapper,\n.bp5-button-group .bp5-popover-target {\n  display: flex;\n  flex: 1 1 auto;\n}\n.bp5-button-group.bp5-fill {\n  display: flex;\n  width: 100%;\n}\n.bp5-button-group .bp5-button.bp5-fill,\n.bp5-button-group.bp5-fill .bp5-button:not(.bp5-fixed) {\n  flex: 1 1 auto;\n}\n.bp5-button-group.bp5-vertical {\n  align-items: stretch;\n  flex-direction: column;\n  vertical-align: top;\n}\n.bp5-button-group.bp5-vertical.bp5-fill {\n  height: 100%;\n  width: unset;\n}\n.bp5-button-group.bp5-vertical .bp5-button {\n  margin-right: 0 !important;\n  width: 100%;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-wrapper:first-child\n  .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:first-child {\n  border-radius: 2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-wrapper:last-child\n  .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal) > .bp5-button:last-child {\n  border-radius: 0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-wrapper:not(:last-child)\n  .bp5-button,\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-button:not(:last-child) {\n  margin-bottom: -1px;\n}\n.bp5-button-group.bp5-align-left .bp5-button {\n  text-align: left;\n}\n.bp5-callout {\n  font-size: 14px;\n  line-height: 1.5;\n  background-color: rgba(143, 153, 168, 0.15);\n  border-radius: 2px;\n  padding: 15px;\n  position: relative;\n  width: 100%;\n}\n.bp5-callout[class*='bp5-icon-'] {\n  padding-left: 38px;\n}\n.bp5-callout[class*='bp5-icon-']::before {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  color: #5f6b7c;\n  left: 15px;\n  position: absolute;\n  top: 17px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-callout {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-callout-icon {\n  padding-left: 38px;\n}\n.bp5-callout.bp5-callout-icon > .bp5-icon:first-child {\n  color: #5f6b7c;\n  left: 15px;\n  position: absolute;\n  top: 17px;\n}\n.bp5-callout .bp5-heading {\n  line-height: 16px;\n  margin-bottom: 0;\n  margin-top: 2px;\n}\n.bp5-callout.bp5-callout-has-body-content .bp5-heading {\n  margin-bottom: 5px;\n}\n.bp5-callout.bp5-compact {\n  padding: 10px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon {\n  padding-left: 33px;\n}\n.bp5-callout.bp5-compact.bp5-callout-icon > .bp5-icon:first-child {\n  left: 10px;\n  top: 12px;\n}\n.bp5-dark .bp5-callout {\n  background-color: rgba(143, 153, 168, 0.2);\n}\n.bp5-dark .bp5-callout[class*='bp5-icon-']::before,\n.bp5-dark .bp5-callout.bp5-callout-icon > .bp5-icon:first-child {\n  color: #abb3bf;\n}\n.bp5-callout.bp5-intent-primary {\n  background-color: rgba(45, 114, 210, 0.1);\n  color: #215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-callout.bp5-intent-primary {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-primary[class*='bp5-icon-']::before,\n.bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-primary .bp5-heading {\n  color: #215db0;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary[class*='bp5-icon-']::before,\n.bp5-dark .bp5-callout.bp5-intent-primary > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-primary .bp5-heading {\n  color: #8abbff;\n}\n.bp5-callout.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.1);\n  color: #1c6e42;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-callout.bp5-intent-success {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-success[class*='bp5-icon-']::before,\n.bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-success .bp5-heading {\n  color: #1c6e42;\n}\n.bp5-dark .bp5-callout.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-callout.bp5-intent-success[class*='bp5-icon-']::before,\n.bp5-dark .bp5-callout.bp5-intent-success > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-success .bp5-heading {\n  color: #72ca9b;\n}\n.bp5-callout.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.1);\n  color: #935610;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-callout.bp5-intent-warning {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-warning[class*='bp5-icon-']::before,\n.bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-warning .bp5-heading {\n  color: #935610;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-callout.bp5-intent-warning[class*='bp5-icon-']::before,\n.bp5-dark .bp5-callout.bp5-intent-warning > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-warning .bp5-heading {\n  color: #fbb360;\n}\n.bp5-callout.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.1);\n  color: #ac2f33;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-callout.bp5-intent-danger {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-callout.bp5-intent-danger[class*='bp5-icon-']::before,\n.bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-callout.bp5-intent-danger .bp5-heading {\n  color: #ac2f33;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-callout.bp5-intent-danger[class*='bp5-icon-']::before,\n.bp5-dark .bp5-callout.bp5-intent-danger > .bp5-icon:first-child,\n.bp5-dark .bp5-callout.bp5-intent-danger .bp5-heading {\n  color: #fa999c;\n}\n.bp5-callout.bp5-intent-primary a {\n  text-decoration: underline;\n}\n.bp5-callout.bp5-intent-primary a:hover {\n  color: #184a90;\n}\n.bp5-dark .bp5-callout.bp5-intent-primary a:hover {\n  color: #99c4ff;\n}\n.bp5-running-text .bp5-callout {\n  margin: 20px 0;\n}\n.bp5-card {\n  background-color: #ffffff;\n  border-radius: 2px;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.15);\n  padding: 20px;\n  transition: transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9),\n    box-shadow 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-card.bp5-dark,\n.bp5-dark .bp5-card {\n  background-color: #2f343c;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-card {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n\n.bp5-elevation-0 {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-elevation-0.bp5-dark,\n.bp5-dark .bp5-elevation-0 {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-elevation-0 {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-1 {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-1.bp5-dark,\n.bp5-dark .bp5-elevation-1 {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-elevation-1 {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-2 {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2),\n    0 2px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-2.bp5-dark,\n.bp5-dark .bp5-elevation-2 {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px rgba(17, 20, 24, 0.4), 0 2px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-elevation-2 {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-3 {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-3.bp5-dark,\n.bp5-dark .bp5-elevation-3 {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-elevation-3 {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-elevation-4 {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2),\n    0 18px 46px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-elevation-4.bp5-dark,\n.bp5-dark .bp5-elevation-4 {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 4px 8px rgba(17, 20, 24, 0.4), 0 18px 46px 6px rgba(17, 20, 24, 0.4);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-elevation-4 {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-card.bp5-compact {\n  padding: 15px;\n}\n\n.bp5-card.bp5-interactive:hover {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  cursor: pointer;\n}\n.bp5-card.bp5-interactive:hover.bp5-dark,\n.bp5-dark .bp5-card.bp5-interactive:hover {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-card.bp5-interactive.bp5-selected {\n  box-shadow: 0 0 0 3px rgba(76, 144, 240, 0.2), 0 0 0 1px #4c90f0;\n}\n.bp5-card.bp5-interactive.bp5-selected.bp5-dark,\n.bp5-dark .bp5-card.bp5-interactive.bp5-selected {\n  box-shadow: 0 0 0 3px rgba(138, 187, 255, 0.4), 0 0 0 1px #8abbff;\n}\n.bp5-card.bp5-interactive:active {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  transition-duration: 0;\n}\n.bp5-card.bp5-interactive:active.bp5-dark,\n.bp5-dark .bp5-card.bp5-interactive:active {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-card-list {\n  overflow: auto;\n  padding: 0;\n  width: 100%;\n}\n.bp5-card-list > .bp5-card {\n  align-items: center;\n  border-radius: 0;\n  box-shadow: none;\n  display: flex;\n  min-height: 51px;\n  padding: 10px 20px;\n}\n.bp5-card-list > .bp5-card.bp5-interactive:hover,\n.bp5-card-list > .bp5-card.bp5-interactive:active {\n  background-color: #f6f7f9;\n  box-shadow: none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:hover,\n.bp5-dark .bp5-card-list > .bp5-card.bp5-interactive:active {\n  background-color: #383e47;\n}\n.bp5-card-list > .bp5-card.bp5-selected {\n  background-color: #edeff2;\n  box-shadow: none;\n}\n.bp5-dark .bp5-card-list > .bp5-card.bp5-selected {\n  background-color: #404854;\n  box-shadow: none;\n}\n.bp5-card-list > .bp5-card:not(:last-child) {\n  border-bottom: 1px solid rgba(17, 20, 24, 0.1);\n}\n.bp5-dark .bp5-card-list > .bp5-card:not(:last-child) {\n  border-color: rgba(255, 255, 255, 0.1);\n}\n.bp5-card-list.bp5-compact {\n  padding: 0;\n}\n.bp5-card-list.bp5-compact > .bp5-card {\n  min-height: 45px;\n  padding: 7px 15px;\n}\n.bp5-dark .bp5-card-list {\n  padding: 1px;\n}\n.bp5-card-list:not(.bp5-card-list-bordered) {\n  border-radius: 0;\n  box-shadow: none;\n}\n.bp5-dark .bp5-card-list:not(.bp5-card-list-bordered) {\n  margin: 1px;\n  width: calc(100% - 2px);\n}\n.bp5-collapse {\n  height: 0;\n  overflow-y: hidden;\n  transition: height 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body {\n  transition: transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-collapse .bp5-collapse-body[aria-hidden='true'] {\n  display: none;\n}\n.bp5-context-menu-virtual-target {\n  position: fixed;\n}\n.bp5-card.bp5-control-card,\n.bp5-card-list > .bp5-card.bp5-control-card {\n  min-height: auto;\n  padding: 0;\n}\n\n.bp5-control-card .bp5-control.bp5-control.bp5-control {\n  align-items: flex-start;\n  display: flex;\n  gap: 10px;\n  margin: 0;\n  padding: 20px;\n  width: 100%;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-left {\n  flex-direction: row;\n  justify-content: flex-start;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control.bp5-align-right {\n  flex-direction: row-reverse;\n  justify-content: space-between;\n}\n.bp5-card-list .bp5-control-card .bp5-control.bp5-control.bp5-control {\n  padding: 20px;\n}\n.bp5-card-list.bp5-compact\n  .bp5-control-card\n  .bp5-control.bp5-control.bp5-control {\n  padding: 15px;\n}\n.bp5-control-card .bp5-control.bp5-control.bp5-control .bp5-control-indicator {\n  margin: 0;\n}\n.bp5-control-card.bp5-compact .bp5-control.bp5-control.bp5-control {\n  padding: 15px;\n}\n.bp5-divider {\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n  border-right: 1px solid rgba(17, 20, 24, 0.15);\n  margin: 5px;\n}\n.bp5-dark .bp5-divider {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-container {\n  opacity: 1;\n  transform: scale(1);\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  min-height: 100%;\n  pointer-events: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  width: 100%;\n}\n.bp5-dialog-container.bp5-overlay-enter > .bp5-dialog,\n.bp5-dialog-container.bp5-overlay-appear > .bp5-dialog {\n  opacity: 0;\n  transform: scale(0.5);\n}\n.bp5-dialog-container.bp5-overlay-enter-active > .bp5-dialog,\n.bp5-dialog-container.bp5-overlay-appear-active > .bp5-dialog {\n  opacity: 1;\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: opacity, transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-dialog-container.bp5-overlay-exit > .bp5-dialog {\n  opacity: 1;\n  transform: scale(1);\n}\n.bp5-dialog-container.bp5-overlay-exit-active > .bp5-dialog {\n  opacity: 0;\n  transform: scale(0.5);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: opacity, transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n\n.bp5-dialog {\n  background: #f6f7f9;\n  border-radius: 4px;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  display: flex;\n  flex-direction: column;\n  margin: 30px 0;\n  pointer-events: all;\n  -webkit-user-select: text;\n  -moz-user-select: text;\n  -ms-user-select: text;\n  user-select: text;\n  width: 500px;\n}\n.bp5-dialog:focus {\n  outline: 0;\n}\n.bp5-dialog.bp5-dark,\n.bp5-dark .bp5-dialog {\n  background: #252a31;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color: #f6f7f9;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dialog {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-header {\n  align-items: center;\n  background: #ffffff;\n  border-radius: 4px 4px 0 0;\n  box-shadow: 0 1px 0 rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  min-height: 40px;\n  padding: 5px;\n  padding-left: 15px;\n  z-index: 0;\n}\n.bp5-dialog-header .bp5-icon-large,\n.bp5-dialog-header .bp5-icon {\n  color: #5f6b7c;\n  flex: 0 0 auto;\n  margin-left: -3px;\n  margin-right: 7.5px;\n}\n.bp5-dialog-header .bp5-heading {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  flex: 1 1 auto;\n  line-height: inherit;\n  margin: 0;\n}\n.bp5-dialog-header .bp5-heading:last-child {\n  margin-right: 15px;\n}\n.bp5-dark .bp5-dialog-header {\n  background: #2f343c;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-dialog-header .bp5-icon-large,\n.bp5-dark .bp5-dialog-header .bp5-icon {\n  color: #abb3bf;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dialog-header {\n    border-bottom: 1px solid buttonborder;\n  }\n}\n\n.bp5-dialog-body {\n  flex: 1 1 auto;\n  margin: 15px;\n}\n\n.bp5-dialog-body-scroll-container {\n  margin: 0;\n  max-height: 70vh;\n  overflow: auto;\n  padding: 15px;\n}\n\n.bp5-dialog-footer {\n  flex: 0 0 auto;\n  margin: 15px;\n}\n\n.bp5-dialog-footer-fixed {\n  align-items: center;\n  background-color: #ffffff;\n  border-radius: 0 0 4px 4px;\n  border-top: 1px solid rgba(17, 20, 24, 0.15);\n  display: flex;\n  gap: 15px;\n  justify-content: space-between;\n  margin: 0;\n  padding: 10px 10px 10px 15px;\n}\n.bp5-dark .bp5-dialog-footer-fixed {\n  background: #383e47;\n  border-top: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-footer-main-section {\n  flex: 1 1 auto;\n}\n\n.bp5-dialog-footer-actions {\n  display: flex;\n  justify-content: flex-end;\n}\n.bp5-dialog-footer-actions .bp5-button {\n  margin-left: 10px;\n}\n.bp5-multistep-dialog-panels {\n  display: flex;\n}\n.bp5-multistep-dialog-panels:first-child\n  .bp5-dialog-step-container:first-child {\n  border-radius: 4px 0 0 0;\n}\n.bp5-multistep-dialog-panels:first-child .bp5-multistep-dialog-right-panel {\n  border-top-right-radius: 4px;\n}\n.bp5-multistep-dialog-nav-top .bp5-multistep-dialog-panels {\n  flex-direction: column;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels:first-child\n  .bp5-dialog-step-container:first-child {\n  border-radius: 4px 0 0 0;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels:first-child\n  .bp5-dialog-step-container:last-child {\n  border-radius: 0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-left-panel {\n  flex-direction: row;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-dialog-step-container {\n  flex-grow: 1;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-dialog-step-container:not(:first-child) {\n  border-left: 1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark\n  .bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-dialog-step-container {\n  border-color: rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-right-panel {\n  border-left: none;\n}\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-right-panel,\n.bp5-multistep-dialog-nav-top\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-footer {\n  border-radius: 0 0 4px 4px;\n}\n.bp5-multistep-dialog-nav-right .bp5-multistep-dialog-panels {\n  flex-direction: row-reverse;\n}\n.bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels:first-child\n  .bp5-multistep-dialog-right-panel {\n  border-radius: 4px 0 0 4px;\n}\n.bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels:first-child\n  .bp5-dialog-step-container:first-child {\n  border-radius: 0 4px 0 0;\n}\n.bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-left-panel {\n  border-radius: 0 0 4px 0;\n}\n.bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-right-panel {\n  border-left: none;\n  border-radius: 4px 0 0 4px;\n  border-right: 1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark\n  .bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels\n  .bp5-multistep-dialog-right-panel {\n  border-color: rgba(17, 20, 24, 0.4);\n}\n.bp5-multistep-dialog-nav-right\n  .bp5-multistep-dialog-panels\n  .bp5-dialog-footer {\n  border-bottom-left-radius: 0;\n}\n\n.bp5-multistep-dialog-left-panel {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n}\n.bp5-dark .bp5-multistep-dialog-left-panel {\n  background: #252a31;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-left-radius: 4px;\n  border-left: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-multistep-dialog-right-panel {\n  background-color: #f6f7f9;\n  border-left: 1px solid rgba(17, 20, 24, 0.15);\n  border-radius: 0 0 4px 0;\n  flex: 3;\n  min-width: 0;\n}\n.bp5-dark .bp5-multistep-dialog-right-panel {\n  background-color: #2f343c;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n  border-bottom-right-radius: 4px;\n  border-left: 1px solid rgba(255, 255, 255, 0.2);\n  border-right: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.bp5-dialog-step-container {\n  background-color: #f6f7f9;\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-dark .bp5-dialog-step-container {\n  background: #2f343c;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n}\n.bp5-dialog-step-container.bp5-dialog-step-viewed {\n  background-color: #ffffff;\n}\n.bp5-dark .bp5-dialog-step-container.bp5-dialog-step-viewed {\n  background: #383e47;\n}\n\n.bp5-dialog-step {\n  align-items: center;\n  border-radius: 4px;\n  cursor: not-allowed;\n  display: flex;\n  margin: 4px;\n  padding: 6px 14px;\n}\n.bp5-dark .bp5-dialog-step {\n  background: #2f343c;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step {\n  background-color: #ffffff;\n  cursor: pointer;\n}\n.bp5-dark .bp5-dialog-step-viewed .bp5-dialog-step {\n  background: #383e47;\n}\n.bp5-dialog-step:hover {\n  background-color: #f6f7f9;\n}\n.bp5-dark .bp5-dialog-step:hover {\n  background: #2f343c;\n}\n\n.bp5-dialog-step-icon {\n  align-items: center;\n  background-color: rgba(95, 107, 124, 0.6);\n  border-radius: 50%;\n  color: #ffffff;\n  display: flex;\n  height: 25px;\n  justify-content: center;\n  width: 25px;\n}\n.bp5-dark .bp5-dialog-step-icon {\n  background-color: rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-icon {\n  background-color: #4c90f0;\n}\n.bp5-dialog-step-viewed .bp5-dialog-step-icon {\n  background-color: #8f99a8;\n}\n\n.bp5-dialog-step-title {\n  color: rgba(95, 107, 124, 0.6);\n  flex: 1;\n  padding-left: 10px;\n}\n.bp5-dark .bp5-dialog-step-title {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-active.bp5-dialog-step-viewed .bp5-dialog-step-title {\n  color: #4c90f0;\n}\n.bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title {\n  color: #1c2127;\n}\n.bp5-dark .bp5-dialog-step-viewed:not(.bp5-active) .bp5-dialog-step-title {\n  color: #f6f7f9;\n}\n.bp5-drawer {\n  background: #ffffff;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 4px 8px rgba(17, 20, 24, 0.2),\n    0 18px 46px 6px rgba(17, 20, 24, 0.2);\n  display: flex;\n  flex-direction: column;\n  margin: 0;\n  padding: 0;\n}\n.bp5-drawer:focus {\n  outline: 0;\n}\n.bp5-drawer.bp5-position-top {\n  height: 50%;\n  left: 0;\n  right: 0;\n  top: 0;\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter,\n.bp5-drawer.bp5-position-top.bp5-overlay-appear {\n  transform: translateY(-100%);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-enter-active,\n.bp5-drawer.bp5-position-top.bp5-overlay-appear-active {\n  transform: translateY(0);\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit {\n  transform: translateY(0);\n}\n.bp5-drawer.bp5-position-top.bp5-overlay-exit-active {\n  transform: translateY(-100%);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-drawer.bp5-position-top {\n    border-bottom: 1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-bottom {\n  bottom: 0;\n  height: 50%;\n  left: 0;\n  right: 0;\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter,\n.bp5-drawer.bp5-position-bottom.bp5-overlay-appear {\n  transform: translateY(100%);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-enter-active,\n.bp5-drawer.bp5-position-bottom.bp5-overlay-appear-active {\n  transform: translateY(0);\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit {\n  transform: translateY(0);\n}\n.bp5-drawer.bp5-position-bottom.bp5-overlay-exit-active {\n  transform: translateY(100%);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-drawer.bp5-position-bottom {\n    border-top: 1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-left {\n  bottom: 0;\n  left: 0;\n  top: 0;\n  width: 50%;\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter,\n.bp5-drawer.bp5-position-left.bp5-overlay-appear {\n  transform: translateX(-100%);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-enter-active,\n.bp5-drawer.bp5-position-left.bp5-overlay-appear-active {\n  transform: translateX(0);\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit {\n  transform: translateX(0);\n}\n.bp5-drawer.bp5-position-left.bp5-overlay-exit-active {\n  transform: translateX(-100%);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-drawer.bp5-position-left {\n    border-right: 1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-position-right {\n  bottom: 0;\n  right: 0;\n  top: 0;\n  width: 50%;\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter,\n.bp5-drawer.bp5-position-right.bp5-overlay-appear {\n  transform: translateX(100%);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-enter-active,\n.bp5-drawer.bp5-position-right.bp5-overlay-appear-active {\n  transform: translateX(0);\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit {\n  transform: translateX(0);\n}\n.bp5-drawer.bp5-position-right.bp5-overlay-exit-active {\n  transform: translateX(100%);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-drawer.bp5-position-right {\n    border-left: 1px solid buttonborder;\n  }\n}\n.bp5-drawer.bp5-dark,\n.bp5-dark .bp5-drawer {\n  background: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color: #f6f7f9;\n}\n\n.bp5-drawer-header {\n  align-items: center;\n  border-radius: 0;\n  box-shadow: 0 1px 0 rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex: 0 0 auto;\n  min-height: 40px;\n  padding: 5px;\n  padding-left: 20px;\n  position: relative;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-drawer-header {\n    border-bottom: 1px solid buttonborder;\n  }\n}\n.bp5-drawer-header .bp5-icon-large,\n.bp5-drawer-header .bp5-icon {\n  color: #5f6b7c;\n  flex: 0 0 auto;\n  margin-right: 10px;\n}\n.bp5-drawer-header .bp5-heading {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  flex: 1 1 auto;\n  line-height: inherit;\n  margin: 0;\n}\n.bp5-drawer-header .bp5-heading:last-child {\n  margin-right: 20px;\n}\n.bp5-dark .bp5-drawer-header {\n  box-shadow: 0 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-drawer-header .bp5-icon-large,\n.bp5-dark .bp5-drawer-header .bp5-icon {\n  color: #abb3bf;\n}\n\n.bp5-drawer-body {\n  flex: 1 1 auto;\n  line-height: 18px;\n  overflow: auto;\n}\n\n.bp5-drawer-footer {\n  box-shadow: inset 0 1px 0 rgba(17, 20, 24, 0.15);\n  flex: 0 0 auto;\n  padding: 10px 20px;\n  position: relative;\n}\n.bp5-dark .bp5-drawer-footer {\n  box-shadow: inset 0 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-editable-text {\n  cursor: text;\n  display: inline-block;\n  max-width: 100%;\n  position: relative;\n  vertical-align: top;\n  white-space: nowrap;\n}\n.bp5-editable-text::before {\n  bottom: -2px;\n  left: -2px;\n  position: absolute;\n  right: -2px;\n  top: -2px;\n  border-radius: 2px;\n  content: '';\n  transition: background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9),\n    box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-editable-text:hover::before {\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-editable-text.bp5-editable-text-editing::before {\n  background-color: #ffffff;\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-editable-text:not(.bp5-disabled)::before {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-editable-text.bp5-intent-primary\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-primary .bp5-editable-text-input::placeholder {\n  color: #2d72d2;\n}\n.bp5-editable-text.bp5-intent-primary:hover::before {\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px rgba(45, 114, 210, 0.4);\n}\n.bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-success\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #238551;\n}\n.bp5-editable-text.bp5-intent-success\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #238551;\n}\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-success .bp5-editable-text-input::placeholder {\n  color: #238551;\n}\n.bp5-editable-text.bp5-intent-success:hover::before {\n  box-shadow: 0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0),\n    inset 0 0 0 1px rgba(35, 133, 81, 0.4);\n}\n.bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #238551, 0 0 0 2px rgba(35, 133, 81, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-warning\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #c87619;\n}\n.bp5-editable-text.bp5-intent-warning\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #c87619;\n}\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-warning .bp5-editable-text-input::placeholder {\n  color: #c87619;\n}\n.bp5-editable-text.bp5-intent-warning:hover::before {\n  box-shadow: 0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0),\n    inset 0 0 0 1px rgba(200, 118, 25, 0.4);\n}\n.bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #c87619, 0 0 0 2px rgba(200, 118, 25, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-editable-text.bp5-intent-danger\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #cd4246;\n}\n.bp5-editable-text.bp5-intent-danger\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #cd4246;\n}\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-editable-text.bp5-intent-danger .bp5-editable-text-input::placeholder {\n  color: #cd4246;\n}\n.bp5-editable-text.bp5-intent-danger:hover::before {\n  box-shadow: 0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0),\n    inset 0 0 0 1px rgba(205, 66, 70, 0.4);\n}\n.bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #cd4246, 0 0 0 2px rgba(205, 66, 70, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-editable-text:hover::before {\n  box-shadow: 0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-editable-text.bp5-editable-text-editing::before {\n  background-color: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px #4c90f0, 0 0 0 2px rgba(76, 144, 240, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-editable-text.bp5-disabled::before {\n  box-shadow: none;\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-primary\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #8abbff;\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-primary\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-primary .bp5-editable-text-input,\n.bp5-dark\n  .bp5-editable-text.bp5-intent-primary\n  .bp5-editable-text-input::placeholder {\n  color: #8abbff;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-primary:hover::before {\n  box-shadow: 0 0 0 0 rgba(138, 187, 255, 0), 0 0 0 0 rgba(138, 187, 255, 0),\n    inset 0 0 0 1px rgba(138, 187, 255, 0.4);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-primary.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #8abbff, 0 0 0 2px rgba(138, 187, 255, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-success\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #72ca9b;\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-success\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-success .bp5-editable-text-input,\n.bp5-dark\n  .bp5-editable-text.bp5-intent-success\n  .bp5-editable-text-input::placeholder {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-success:hover::before {\n  box-shadow: 0 0 0 0 rgba(114, 202, 155, 0), 0 0 0 0 rgba(114, 202, 155, 0),\n    inset 0 0 0 1px rgba(114, 202, 155, 0.4);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-success.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #72ca9b, 0 0 0 2px rgba(114, 202, 155, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-warning\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #fbb360;\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-warning\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-warning .bp5-editable-text-input,\n.bp5-dark\n  .bp5-editable-text.bp5-intent-warning\n  .bp5-editable-text-input::placeholder {\n  color: #fbb360;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-warning:hover::before {\n  box-shadow: 0 0 0 0 rgba(251, 179, 96, 0), 0 0 0 0 rgba(251, 179, 96, 0),\n    inset 0 0 0 1px rgba(251, 179, 96, 0.4);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-warning.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #fbb360, 0 0 0 2px rgba(251, 179, 96, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-danger\n  .bp5-editable-text-input::-moz-placeholder {\n  color: #fa999c;\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-danger\n  .bp5-editable-text-input:-ms-input-placeholder {\n  color: #fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-content,\n.bp5-dark .bp5-editable-text.bp5-intent-danger .bp5-editable-text-input,\n.bp5-dark\n  .bp5-editable-text.bp5-intent-danger\n  .bp5-editable-text-input::placeholder {\n  color: #fa999c;\n}\n.bp5-dark .bp5-editable-text.bp5-intent-danger:hover::before {\n  box-shadow: 0 0 0 0 rgba(250, 153, 156, 0), 0 0 0 0 rgba(250, 153, 156, 0),\n    inset 0 0 0 1px rgba(250, 153, 156, 0.4);\n}\n.bp5-dark\n  .bp5-editable-text.bp5-intent-danger.bp5-editable-text-editing::before {\n  box-shadow: inset 0 0 0 1px #fa999c, 0 0 0 2px rgba(250, 153, 156, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-editable-text.bp5-disabled::before {\n  box-shadow: none !important;\n}\n\n.bp5-editable-text-input,\n.bp5-editable-text-content {\n  color: inherit;\n  display: inherit;\n  font: inherit;\n  letter-spacing: inherit;\n  max-width: inherit;\n  min-width: inherit;\n  position: relative;\n  resize: none;\n  text-transform: inherit;\n  vertical-align: top;\n}\n\n.bp5-editable-text-input {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n  white-space: pre-wrap;\n  width: 100%;\n}\n.bp5-editable-text-input::-moz-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-editable-text-input:-ms-input-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-editable-text-input::placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-editable-text-input:focus {\n  outline: none;\n}\n.bp5-editable-text-input::-ms-clear {\n  display: none;\n}\n\n.bp5-editable-text-content {\n  overflow: hidden;\n  padding-right: 2px;\n  text-overflow: ellipsis;\n  white-space: pre;\n}\n.bp5-editable-text-editing > .bp5-editable-text-content {\n  left: 0;\n  position: absolute;\n  visibility: hidden;\n}\n.bp5-editable-text-placeholder > .bp5-editable-text-content {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-editable-text-placeholder > .bp5-editable-text-content {\n  color: #abb3bf;\n}\n\n.bp5-editable-text.bp5-multiline {\n  display: block;\n}\n.bp5-editable-text.bp5-multiline .bp5-editable-text-content {\n  overflow: auto;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n}\n.bp5-entity-title {\n  align-items: center;\n  display: flex;\n  gap: 7px;\n  min-width: 0;\n}\n.bp5-entity-title-icon-container.bp5-entity-title-has-subtitle {\n  align-self: flex-start;\n}\n.bp5-entity-title-icon-container:not(.bp5-entity-title-has-subtitle) {\n  align-items: center;\n  display: flex;\n}\n.bp5-entity-title-text {\n  display: flex;\n  flex-direction: column;\n}\n.bp5-entity-title-title-and-tags {\n  align-items: center;\n  display: flex;\n  flex-direction: row;\n  gap: 5px;\n}\n.bp5-entity-title-tags-container {\n  display: flex;\n  gap: 2px;\n  margin-left: 5px;\n}\n.bp5-entity-title-title {\n  margin-bottom: 0;\n  min-width: 0;\n  overflow-wrap: break-word;\n}\n.bp5-entity-title-subtitle {\n  font-size: 12px;\n  margin-top: 2px;\n}\n.bp5-entity-title-ellipsize,\n.bp5-entity-title-ellipsize .bp5-entity-title-text {\n  overflow: hidden;\n}\n.bp5-entity-title-heading-h1 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 40px;\n}\n.bp5-entity-title-heading-h2 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 32px;\n}\n.bp5-entity-title-heading-h3 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 25px;\n}\n.bp5-entity-title-heading-h4 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 21px;\n}\n.bp5-entity-title-heading-h5 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 19px;\n}\n.bp5-entity-title-heading-h6 .bp5-icon-container {\n  align-items: center;\n  display: flex;\n  height: 16px;\n}\n.bp5-entity-title-heading-h1,\n.bp5-entity-title-heading-h2,\n.bp5-entity-title-heading-h3 {\n  gap: 15px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-status-tag,\n.bp5-entity-title-heading-h2 .bp5-entity-title-status-tag,\n.bp5-entity-title-heading-h3 .bp5-entity-title-status-tag {\n  margin-left: 10px;\n}\n.bp5-entity-title-heading-h1 .bp5-entity-title-subtitle,\n.bp5-entity-title-heading-h2 .bp5-entity-title-subtitle,\n.bp5-entity-title-heading-h3 .bp5-entity-title-subtitle {\n  font-size: 14px;\n}\n.bp5-entity-title-heading-h4,\n.bp5-entity-title-heading-h5,\n.bp5-entity-title-heading-h6 {\n  gap: 10px;\n}\n.bp5-entity-title-heading-h4 .bp5-entity-title-subtitle,\n.bp5-entity-title-heading-h5 .bp5-entity-title-subtitle,\n.bp5-entity-title-heading-h6 .bp5-entity-title-subtitle {\n  font-size: 12px;\n}\n.bp5-divider {\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n  border-right: 1px solid rgba(17, 20, 24, 0.15);\n  margin: 5px;\n}\n.bp5-dark .bp5-divider {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-control-group {\n  transform: translateZ(0);\n  display: flex;\n  flex-direction: row;\n  align-items: stretch;\n}\n.bp5-control-group > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-control-group > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select,\n.bp5-control-group .bp5-input,\n.bp5-control-group .bp5-select {\n  position: relative;\n}\n.bp5-control-group .bp5-input {\n  z-index: 2;\n}\n.bp5-control-group .bp5-input:focus {\n  z-index: 14;\n}\n.bp5-control-group .bp5-input[class*='bp5-intent'] {\n  z-index: 13;\n}\n.bp5-control-group .bp5-input[class*='bp5-intent']:focus {\n  z-index: 15;\n}\n.bp5-control-group .bp5-input[readonly],\n.bp5-control-group .bp5-input:disabled,\n.bp5-control-group .bp5-input.bp5-disabled {\n  z-index: 1;\n}\n.bp5-control-group .bp5-input-group[class*='bp5-intent'] .bp5-input {\n  z-index: 13;\n}\n.bp5-control-group .bp5-input-group[class*='bp5-intent'] .bp5-input:focus {\n  z-index: 15;\n}\n.bp5-control-group .bp5-button,\n.bp5-control-group .bp5-html-select select,\n.bp5-control-group .bp5-select select {\n  transform: translateZ(0);\n  z-index: 4;\n}\n.bp5-control-group .bp5-button:focus,\n.bp5-control-group .bp5-html-select select:focus,\n.bp5-control-group .bp5-select select:focus {\n  z-index: 5;\n}\n.bp5-control-group .bp5-button:hover,\n.bp5-control-group .bp5-html-select select:hover,\n.bp5-control-group .bp5-select select:hover {\n  z-index: 6;\n}\n.bp5-control-group .bp5-button:active,\n.bp5-control-group .bp5-html-select select:active,\n.bp5-control-group .bp5-select select:active {\n  z-index: 7;\n}\n.bp5-control-group .bp5-button[readonly],\n.bp5-control-group .bp5-button:disabled,\n.bp5-control-group .bp5-button.bp5-disabled,\n.bp5-control-group .bp5-html-select select[readonly],\n.bp5-control-group .bp5-html-select select:disabled,\n.bp5-control-group .bp5-html-select select.bp5-disabled,\n.bp5-control-group .bp5-select select[readonly],\n.bp5-control-group .bp5-select select:disabled,\n.bp5-control-group .bp5-select select.bp5-disabled {\n  z-index: 3;\n}\n.bp5-control-group .bp5-button[class*='bp5-intent'],\n.bp5-control-group .bp5-html-select select[class*='bp5-intent'],\n.bp5-control-group .bp5-select select[class*='bp5-intent'] {\n  z-index: 9;\n}\n.bp5-control-group .bp5-button[class*='bp5-intent']:focus,\n.bp5-control-group .bp5-html-select select[class*='bp5-intent']:focus,\n.bp5-control-group .bp5-select select[class*='bp5-intent']:focus {\n  z-index: 10;\n}\n.bp5-control-group .bp5-button[class*='bp5-intent']:hover,\n.bp5-control-group .bp5-html-select select[class*='bp5-intent']:hover,\n.bp5-control-group .bp5-select select[class*='bp5-intent']:hover {\n  z-index: 11;\n}\n.bp5-control-group .bp5-button[class*='bp5-intent']:active,\n.bp5-control-group .bp5-html-select select[class*='bp5-intent']:active,\n.bp5-control-group .bp5-select select[class*='bp5-intent']:active {\n  z-index: 12;\n}\n.bp5-control-group .bp5-button[class*='bp5-intent'][readonly],\n.bp5-control-group .bp5-button[class*='bp5-intent']:disabled,\n.bp5-control-group .bp5-button[class*='bp5-intent'].bp5-disabled,\n.bp5-control-group .bp5-html-select select[class*='bp5-intent'][readonly],\n.bp5-control-group .bp5-html-select select[class*='bp5-intent']:disabled,\n.bp5-control-group .bp5-html-select select[class*='bp5-intent'].bp5-disabled,\n.bp5-control-group .bp5-select select[class*='bp5-intent'][readonly],\n.bp5-control-group .bp5-select select[class*='bp5-intent']:disabled,\n.bp5-control-group .bp5-select select[class*='bp5-intent'].bp5-disabled {\n  z-index: 8;\n}\n.bp5-control-group .bp5-input-group > .bp5-icon,\n.bp5-control-group .bp5-input-group > .bp5-button,\n.bp5-control-group .bp5-input-group > .bp5-input-left-container,\n.bp5-control-group .bp5-input-group > .bp5-input-action {\n  z-index: 16;\n}\n.bp5-control-group .bp5-select::after,\n.bp5-control-group .bp5-html-select::after,\n.bp5-control-group .bp5-select > .bp5-icon,\n.bp5-control-group .bp5-html-select > .bp5-icon {\n  z-index: 17;\n}\n.bp5-control-group .bp5-html-select:focus-within,\n.bp5-control-group .bp5-select:focus-within {\n  z-index: 5;\n}\n.bp5-control-group:not(.bp5-vertical) > :not(:last-child) {\n  margin-right: 2px;\n}\n.bp5-control-group .bp5-numeric-input:not(:first-child) .bp5-input-group {\n  border-bottom-left-radius: 0;\n  border-top-left-radius: 0;\n}\n.bp5-control-group.bp5-fill {\n  width: 100%;\n}\n.bp5-control-group > .bp5-fill {\n  flex: 1 1 auto;\n}\n.bp5-control-group.bp5-fill > *:not(.bp5-fixed) {\n  flex: 1 1 auto;\n}\n.bp5-control-group.bp5-vertical {\n  flex-direction: column;\n}\n.bp5-control-group.bp5-vertical > :not(:last-child) {\n  margin-bottom: 2px;\n}\n.bp5-control {\n  cursor: pointer;\n  display: block;\n  margin-bottom: 10px;\n  position: relative;\n  text-transform: none;\n}\n.bp5-control input:checked ~ .bp5-control-indicator {\n  background-color: #2d72d2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color: #ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control input:checked ~ .bp5-control-indicator {\n    background-color: highlight;\n    border: 1px solid highlight;\n  }\n}\n.bp5-control:hover input:checked ~ .bp5-control-indicator {\n  background-color: #215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control:hover input:checked ~ .bp5-control-indicator {\n    background-color: highlight;\n  }\n}\n.bp5-control input:not(:disabled):active:checked ~ .bp5-control-indicator {\n  background: #184a90;\n}\n.bp5-control input:disabled:checked ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control input:disabled:checked ~ .bp5-control-indicator {\n    background-color: graytext;\n    border-color: graytext;\n  }\n}\n.bp5-dark .bp5-control input:checked ~ .bp5-control-indicator {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-control input:checked ~ .bp5-control-indicator {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-dark .bp5-control:hover input:checked ~ .bp5-control-indicator {\n  background-color: #215db0;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark\n  .bp5-control\n  input:not(:disabled):active:checked\n  ~ .bp5-control-indicator {\n  background-color: #184a90;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark .bp5-control input:disabled:checked ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n.bp5-control:not(.bp5-align-right) {\n  padding-left: 26px;\n}\n.bp5-control:not(.bp5-align-right) .bp5-control-indicator {\n  margin-left: -26px;\n}\n.bp5-control.bp5-align-right {\n  padding-right: 26px;\n}\n.bp5-control.bp5-align-right .bp5-control-indicator {\n  margin-right: -26px;\n}\n.bp5-control.bp5-disabled {\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-control.bp5-inline {\n  display: inline-block;\n  margin-right: 20px;\n}\n.bp5-control input {\n  left: 0;\n  opacity: 0;\n  position: absolute;\n  top: 0;\n  z-index: -1;\n}\n.bp5-control .bp5-control-indicator {\n  background-clip: padding-box;\n  background-color: transparent;\n  border: none;\n  box-shadow: inset 0 0 0 1px #738091;\n  cursor: pointer;\n  display: inline-block;\n  font-size: 16px;\n  height: 1em;\n  margin-right: 10px;\n  margin-top: -3px;\n  position: relative;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  vertical-align: middle;\n  width: 1em;\n}\n.bp5-control .bp5-control-indicator::before {\n  content: '';\n  display: block;\n  height: 1em;\n  width: 1em;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control .bp5-control-indicator {\n    border: 1px solid buttonborder;\n  }\n  .bp5-control .bp5-control-indicator::before {\n    margin-left: -1px;\n    margin-top: -1px;\n  }\n}\n.bp5-control:hover .bp5-control-indicator {\n  background-color: rgba(143, 153, 168, 0.15);\n}\n.bp5-control input:not(:disabled):active ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: inset 0 0 0 1px #738091;\n}\n.bp5-control input:disabled ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  cursor: not-allowed;\n}\n.bp5-control input:focus ~ .bp5-control-indicator {\n  outline: rgba(45, 114, 210, 0.6) solid 2px;\n  outline-offset: 2px;\n  -moz-outline-radius: 6px;\n  outline: #2d72d2 solid 2px;\n}\n.bp5-control.bp5-align-right .bp5-control-indicator {\n  float: right;\n  margin-left: 10px;\n  margin-top: 1px;\n}\n.bp5-control.bp5-large {\n  font-size: 16px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right) {\n  padding-left: 30px;\n}\n.bp5-control.bp5-large:not(.bp5-align-right) .bp5-control-indicator {\n  margin-left: -30px;\n}\n.bp5-control.bp5-large.bp5-align-right {\n  padding-right: 30px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator {\n  margin-right: -30px;\n}\n.bp5-control.bp5-large .bp5-control-indicator {\n  font-size: 20px;\n}\n.bp5-control.bp5-large.bp5-align-right .bp5-control-indicator {\n  margin-top: 0;\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator {\n  background-color: #2d72d2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color: #ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator {\n    background-color: highlight;\n    border: 1px solid highlight;\n  }\n}\n.bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator {\n  background-color: #215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-checkbox:hover input:indeterminate ~ .bp5-control-indicator {\n    background-color: highlight;\n  }\n}\n.bp5-control.bp5-checkbox\n  input:not(:disabled):active:indeterminate\n  ~ .bp5-control-indicator {\n  background: #184a90;\n}\n.bp5-control.bp5-checkbox\n  input:disabled:indeterminate\n  ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-checkbox\n    input:disabled:indeterminate\n    ~ .bp5-control-indicator {\n    background-color: graytext;\n    border-color: graytext;\n  }\n}\n.bp5-dark\n  .bp5-control.bp5-checkbox\n  input:indeterminate\n  ~ .bp5-control-indicator {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark\n    .bp5-control.bp5-checkbox\n    input:indeterminate\n    ~ .bp5-control-indicator {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-dark\n  .bp5-control.bp5-checkbox:hover\n  input:indeterminate\n  ~ .bp5-control-indicator {\n  background-color: #215db0;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark\n  .bp5-control.bp5-checkbox\n  input:not(:disabled):active:indeterminate\n  ~ .bp5-control-indicator {\n  background-color: #184a90;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);\n}\n.bp5-dark\n  .bp5-control.bp5-checkbox\n  input:disabled:indeterminate\n  ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  box-shadow: none;\n  color: rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-checkbox .bp5-control-indicator {\n  border-radius: 2px;\n}\n.bp5-control.bp5-checkbox input:checked ~ .bp5-control-indicator::before {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='white'/%3e%3c/svg%3e\");\n}\n.bp5-control.bp5-checkbox input:indeterminate ~ .bp5-control-indicator::before {\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='white'/%3e%3c/svg%3e\");\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-checkbox\n    input:checked:not(:disabled)\n    ~ .bp5-control-indicator::before {\n    background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 5c-.28 0-.53.11-.71.29L7 9.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29s.53-.11.71-.29l5-5A1.003 1.003 0 0012 5z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox\n    input:indeterminate:not(:disabled)\n    ~ .bp5-control-indicator::before {\n    background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 7H5c-.55 0-1 .45-1 1s.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1z' fill='%23111418'/%3e%3c/svg%3e\");\n  }\n  .bp5-control.bp5-checkbox input:disabled ~ .bp5-control-indicator {\n    border-color: graytext;\n  }\n}\n.bp5-control.bp5-radio .bp5-control-indicator {\n  border-radius: 50%;\n}\n.bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before {\n  background-image: radial-gradient(#ffffff, #ffffff 28%, transparent 32%);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-radio input:checked ~ .bp5-control-indicator::before {\n    background: highlight;\n    height: 12px;\n    margin-left: 1px;\n    margin-top: 1px;\n    width: 12px;\n  }\n}\n.bp5-control.bp5-radio input:checked:disabled ~ .bp5-control-indicator::before {\n  opacity: 0.5;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-radio\n    input:checked:disabled\n    ~ .bp5-control-indicator::before {\n    background: graytext;\n  }\n}\n.bp5-control.bp5-radio input:focus ~ .bp5-control-indicator {\n  -moz-outline-radius: 16px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-radio input:disabled ~ .bp5-control-indicator {\n    border-color: graytext;\n  }\n}\n.bp5-control.bp5-switch input ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.3);\n  color: #1c2127;\n}\n.bp5-control.bp5-switch:hover input ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.4);\n}\n.bp5-control.bp5-switch input:not(:disabled):active ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.5);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.15);\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator::before {\n  background: rgba(255, 255, 255, 0.8);\n  box-shadow: none;\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator {\n  background: #2d72d2;\n  color: #ffffff;\n}\n.bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator {\n  background: #215db0;\n}\n.bp5-control.bp5-switch\n  input:checked:not(:disabled):active\n  ~ .bp5-control-indicator {\n  background: #184a90;\n}\n.bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  color: rgba(255, 255, 255, 0.6);\n}\n.bp5-control.bp5-switch\n  input:checked:disabled\n  ~ .bp5-control-indicator::before {\n  background: rgba(255, 255, 255, 0.5);\n  box-shadow: none;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right) {\n  padding-left: 38px;\n}\n.bp5-control.bp5-switch:not(.bp5-align-right) .bp5-control-indicator {\n  margin-left: -38px;\n}\n.bp5-control.bp5-switch.bp5-align-right {\n  padding-right: 38px;\n}\n.bp5-control.bp5-switch.bp5-align-right .bp5-control-indicator {\n  margin-right: -38px;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator {\n    background: highlight;\n    border: 1px solid buttonborder;\n  }\n  .bp5-control.bp5-switch input:checked:disabled ~ .bp5-control-indicator {\n    background-color: graytext;\n  }\n  .bp5-control.bp5-switch\n    input:not(:checked):disabled\n    ~ .bp5-control-indicator {\n    border-color: graytext;\n  }\n  .bp5-control.bp5-switch\n    input:not(:checked):disabled\n    ~ .bp5-control-indicator::before {\n    border-color: graytext;\n  }\n  .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator {\n    background: highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-control-indicator {\n  border: none;\n  border-radius: 1.75em;\n  box-shadow: none !important;\n  min-width: 1.75em;\n  transition: background-color 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width: auto;\n}\n.bp5-control.bp5-switch .bp5-control-indicator::before {\n  background: #ffffff;\n  border-radius: 50%;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.5);\n  height: calc(1em - 4px);\n  left: 0;\n  margin: 2px;\n  position: absolute;\n  transition: left 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width: calc(1em - 4px);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-switch .bp5-control-indicator::before {\n    border: 1px solid buttonborder;\n    margin-top: 1px;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-control.bp5-switch .bp5-control-indicator {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-control.bp5-switch input:checked ~ .bp5-control-indicator::before {\n  left: calc(100% - 1em);\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) {\n  padding-left: 45px;\n}\n.bp5-control.bp5-switch.bp5-large:not(.bp5-align-right) .bp5-control-indicator {\n  margin-left: -45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right {\n  padding-right: 45px;\n}\n.bp5-control.bp5-switch.bp5-large.bp5-align-right .bp5-control-indicator {\n  margin-right: -45px;\n}\n.bp5-dark .bp5-control.bp5-switch input ~ .bp5-control-indicator {\n  background: rgba(17, 20, 24, 0.5);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input ~ .bp5-control-indicator {\n  background: rgba(17, 20, 24, 0.8);\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:not(:disabled):active\n  ~ .bp5-control-indicator {\n  background: rgba(17, 20, 24, 0.9);\n}\n.bp5-dark .bp5-control.bp5-switch input:disabled ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.15);\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:disabled\n  ~ .bp5-control-indicator::before {\n  background: rgba(171, 179, 191, 0.5);\n  box-shadow: none;\n}\n.bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator {\n  background: #2d72d2;\n  color: #ffffff;\n}\n.bp5-dark .bp5-control.bp5-switch:hover input:checked ~ .bp5-control-indicator {\n  background: #215db0;\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:checked:not(:disabled):active\n  ~ .bp5-control-indicator {\n  background: #184a90;\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:checked:disabled\n  ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:checked:disabled\n  ~ .bp5-control-indicator::before {\n  background: rgba(255, 255, 255, 0.3);\n  box-shadow: none;\n}\n.bp5-dark .bp5-control.bp5-switch .bp5-control-indicator::before {\n  background: #abb3bf;\n}\n.bp5-dark\n  .bp5-control.bp5-switch\n  input:checked\n  ~ .bp5-control-indicator::before {\n  background: #ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-control.bp5-switch input:checked ~ .bp5-control-indicator {\n    background: highlight;\n    border: 1px solid buttonborder;\n  }\n  .bp5-dark\n    .bp5-control.bp5-switch\n    input:checked:disabled\n    ~ .bp5-control-indicator {\n    background-color: graytext;\n  }\n  .bp5-dark\n    .bp5-control.bp5-switch\n    input:not(:checked):disabled\n    ~ .bp5-control-indicator {\n    border-color: graytext;\n  }\n  .bp5-dark\n    .bp5-control.bp5-switch\n    input:not(:checked):disabled\n    ~ .bp5-control-indicator::before {\n    border-color: graytext;\n  }\n  .bp5-dark\n    .bp5-control.bp5-switch:hover\n    input:checked\n    ~ .bp5-control-indicator {\n    background: highlight;\n  }\n}\n.bp5-control.bp5-switch .bp5-switch-inner-text {\n  font-size: 0.7em;\n  text-align: center;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:first-child {\n  line-height: 0;\n  margin-left: 0.5em;\n  margin-right: 1.2em;\n  visibility: hidden;\n}\n.bp5-control.bp5-switch .bp5-control-indicator-child:last-child {\n  line-height: 1em;\n  margin-left: 1.2em;\n  margin-right: 0.5em;\n  visibility: visible;\n}\n.bp5-control.bp5-switch\n  input:checked\n  ~ .bp5-control-indicator\n  .bp5-control-indicator-child:first-child {\n  line-height: 1em;\n  visibility: visible;\n}\n.bp5-control.bp5-switch\n  input:checked\n  ~ .bp5-control-indicator\n  .bp5-control-indicator-child:last-child {\n  line-height: 0;\n  visibility: hidden;\n}\n.bp5-dark .bp5-control {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-control.bp5-disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-control .bp5-control-indicator {\n  background-color: transparent;\n  box-shadow: inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control:hover .bp5-control-indicator {\n  background-color: rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-control input:focus ~ .bp5-control-indicator {\n  outline: #8abbff solid 2px;\n}\n.bp5-dark .bp5-control input:not(:disabled):active ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: inset 0 0 0 1px #8f99a8;\n}\n.bp5-dark .bp5-control input:disabled ~ .bp5-control-indicator {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  cursor: not-allowed;\n}\n.bp5-dark\n  .bp5-control.bp5-checkbox\n  input:disabled:checked\n  ~ .bp5-control-indicator,\n.bp5-dark\n  .bp5-control.bp5-checkbox\n  input:disabled:indeterminate\n  ~ .bp5-control-indicator {\n  background: rgba(45, 114, 210, 0.5);\n}\n.bp5-file-input {\n  cursor: pointer;\n  display: inline-block;\n  height: 30px;\n  position: relative;\n}\n.bp5-file-input input {\n  margin: 0;\n  min-width: 200px;\n  opacity: 0;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input,\n.bp5-file-input input.bp5-disabled + .bp5-file-upload-input {\n  background: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  resize: none;\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::-moz-placeholder,\n.bp5-file-input input.bp5-disabled + .bp5-file-upload-input::-moz-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input:-ms-input-placeholder,\n.bp5-file-input\n  input.bp5-disabled\n  + .bp5-file-upload-input:-ms-input-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::placeholder,\n.bp5-file-input input.bp5-disabled + .bp5-file-upload-input::placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-file-input input:disabled + .bp5-file-upload-input::after,\n.bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after {\n  background-color: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  outline: none;\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input,\n.bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input {\n  background: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-input input:disabled + .bp5-file-upload-input::after,\n.bp5-dark .bp5-file-input input.bp5-disabled + .bp5-file-upload-input::after {\n  background-color: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input {\n  color: #1c2127;\n}\n.bp5-dark .bp5-file-input.bp5-file-input-has-selection .bp5-file-upload-input {\n  color: #f6f7f9;\n}\n.bp5-file-input.bp5-fill {\n  width: 100%;\n}\n.bp5-file-input.bp5-large,\n.bp5-large .bp5-file-input {\n  height: 40px;\n}\n.bp5-file-input.bp5-small,\n.bp5-small .bp5-file-input {\n  height: 24px;\n}\n.bp5-file-input .bp5-file-upload-input-custom-text::after {\n  content: attr(bp5-button-text);\n}\n\n.bp5-file-upload-input {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  background: #ffffff;\n  border: none;\n  border-radius: 2px;\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color: #1c2127;\n  font-size: 14px;\n  font-weight: 400;\n  height: 30px;\n  line-height: 30px;\n  outline: none;\n  padding: 0 10px;\n  transition: box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align: middle;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  color: rgba(95, 107, 124, 0.6);\n  left: 0;\n  padding-right: 80px;\n  position: absolute;\n  right: 0;\n  top: 0;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-file-upload-input::-moz-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-file-upload-input:-ms-input-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-file-upload-input::placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-file-upload-input:focus,\n.bp5-file-upload-input.bp5-active {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input[type='search'],\n.bp5-file-upload-input.bp5-round {\n  border-radius: 30px;\n  box-sizing: border-box;\n  padding-left: 10px;\n}\n.bp5-file-upload-input[readonly] {\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-file-upload-input:disabled,\n.bp5-file-upload-input.bp5-disabled {\n  background: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  resize: none;\n}\n.bp5-file-upload-input:disabled::-moz-placeholder,\n.bp5-file-upload-input.bp5-disabled::-moz-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled:-ms-input-placeholder,\n.bp5-file-upload-input.bp5-disabled:-ms-input-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-file-upload-input:disabled::placeholder,\n.bp5-file-upload-input.bp5-disabled::placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-file-upload-input {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-file-upload-input::after {\n  background-color: #f6f7f9;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #1c2127;\n  min-height: 24px;\n  min-width: 24px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  border-radius: 2px;\n  content: 'Browse';\n  line-height: 24px;\n  margin: 3px;\n  position: absolute;\n  right: 0;\n  text-align: center;\n  top: 0;\n  width: 70px;\n}\n.bp5-file-upload-input:hover::after {\n  background-clip: padding-box;\n  background-color: #edeff2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-file-upload-input:active::after {\n  background-color: #dce0e5;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-file-upload-input:active::after {\n    background: highlight;\n  }\n}\n.bp5-large .bp5-file-upload-input {\n  font-size: 16px;\n  height: 40px;\n  line-height: 40px;\n  padding-right: 95px;\n}\n.bp5-large .bp5-file-upload-input[type='search'],\n.bp5-large .bp5-file-upload-input.bp5-round {\n  padding: 0 15px;\n}\n.bp5-large .bp5-file-upload-input::after {\n  min-height: 30px;\n  min-width: 30px;\n  line-height: 30px;\n  margin: 5px;\n  width: 85px;\n}\n.bp5-small .bp5-file-upload-input {\n  font-size: 12px;\n  height: 24px;\n  line-height: 24px;\n  padding-left: 8px;\n  padding-right: 8px;\n  padding-right: 65px;\n}\n.bp5-small .bp5-file-upload-input[type='search'],\n.bp5-small .bp5-file-upload-input.bp5-round {\n  padding: 0 12px;\n}\n.bp5-small .bp5-file-upload-input::after {\n  min-height: 20px;\n  min-width: 20px;\n  line-height: 20px;\n  margin: 2px;\n  width: 55px;\n}\n.bp5-dark .bp5-file-upload-input {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: 0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0),\n    0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color: #f6f7f9;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::-moz-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:-ms-input-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-file-upload-input::placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-file-upload-input:focus {\n  box-shadow: inset 0 0 0 1px #4c90f0, inset 0 0 0 1px #4c90f0,\n    0 0 0 2px rgba(76, 144, 240, 0.3);\n}\n.bp5-dark .bp5-file-upload-input[readonly] {\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:disabled,\n.bp5-dark .bp5-file-upload-input.bp5-disabled {\n  background: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-file-upload-input::after {\n  background-color: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-file-upload-input:hover::after {\n  background-color: #2f343c;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-file-upload-input:active::after {\n  background-color: #1c2127;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-file-upload-input::after {\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n}\n.bp5-form-group {\n  display: flex;\n  flex-direction: column;\n  margin: 0 0 15px;\n}\n.bp5-form-group label.bp5-label {\n  margin-bottom: 5px;\n}\n.bp5-form-group .bp5-control {\n  margin-top: 7px;\n}\n.bp5-form-group .bp5-form-group-sub-label,\n.bp5-form-group .bp5-form-helper-text {\n  color: #5f6b7c;\n  font-size: 12px;\n}\n.bp5-form-group .bp5-form-group-sub-label {\n  margin-bottom: 5px;\n}\n.bp5-form-group .bp5-form-helper-text {\n  margin-top: 5px;\n}\n.bp5-form-group.bp5-intent-primary .bp5-form-helper-text {\n  color: #215db0;\n}\n.bp5-form-group.bp5-intent-success .bp5-form-helper-text {\n  color: #1c6e42;\n}\n.bp5-form-group.bp5-intent-warning .bp5-form-helper-text {\n  color: #935610;\n}\n.bp5-form-group.bp5-intent-danger .bp5-form-helper-text {\n  color: #ac2f33;\n}\n.bp5-form-group.bp5-fill {\n  width: 100%;\n}\n.bp5-form-group.bp5-inline {\n  align-items: flex-start;\n  flex-direction: row;\n}\n.bp5-form-group.bp5-inline.bp5-large label.bp5-label {\n  line-height: 40px;\n  margin: 0 10px 0 0;\n}\n.bp5-form-group.bp5-inline label.bp5-label {\n  line-height: 30px;\n  margin: 0 10px 0 0;\n}\n.bp5-form-group.bp5-disabled .bp5-label,\n.bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-form-group.bp5-disabled .bp5-form-helper-text {\n  color: rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-dark .bp5-form-group.bp5-intent-primary .bp5-form-helper-text {\n  color: #8abbff;\n}\n.bp5-dark .bp5-form-group.bp5-intent-success .bp5-form-helper-text {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-form-group.bp5-intent-warning .bp5-form-helper-text {\n  color: #fbb360;\n}\n.bp5-dark .bp5-form-group.bp5-intent-danger .bp5-form-helper-text {\n  color: #fa999c;\n}\n.bp5-dark .bp5-form-group .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group .bp5-form-helper-text {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-text-muted,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-group-sub-label,\n.bp5-dark .bp5-form-group.bp5-disabled .bp5-form-helper-text {\n  color: rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-input-group {\n  display: block;\n  position: relative;\n}\n.bp5-input-group .bp5-input {\n  position: relative;\n  width: 100%;\n}\n.bp5-input-group .bp5-input:not(:first-child) {\n  padding-left: 30px;\n}\n.bp5-input-group .bp5-input:not(:last-child) {\n  padding-right: 30px;\n}\n.bp5-input-group .bp5-input-action,\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-button,\n.bp5-input-group > .bp5-icon {\n  position: absolute;\n  top: 0;\n}\n.bp5-input-group .bp5-input-action:first-child,\n.bp5-input-group > .bp5-input-left-container:first-child,\n.bp5-input-group > .bp5-button:first-child,\n.bp5-input-group > .bp5-icon:first-child {\n  left: 0;\n}\n.bp5-input-group .bp5-input-action:last-child,\n.bp5-input-group > .bp5-input-left-container:last-child,\n.bp5-input-group > .bp5-button:last-child,\n.bp5-input-group > .bp5-icon:last-child {\n  right: 0;\n}\n.bp5-input-group .bp5-button {\n  min-height: 24px;\n  min-width: 24px;\n  margin: 3px;\n  padding: 0 7px;\n}\n.bp5-input-group .bp5-button:empty {\n  padding: 0;\n}\n.bp5-input-group > .bp5-input-left-container,\n.bp5-input-group > .bp5-icon {\n  z-index: 1;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon {\n  color: #5f6b7c;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon:empty,\n.bp5-input-group > .bp5-icon:empty {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n}\n.bp5-input-group > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group > .bp5-icon,\n.bp5-input-group .bp5-input-action > .bp5-spinner {\n  margin: 7px;\n}\n.bp5-input-group .bp5-tag {\n  margin: 5px;\n}\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:not(:hover):not(:focus) {\n  color: #5f6b7c;\n}\n.bp5-dark\n  .bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:not(:hover):not(:focus),\n.bp5-dark\n  .bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:not(:hover):not(:focus) {\n  color: #abb3bf;\n}\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon-standard,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon-large,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon-standard,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:not(:hover):not(:focus)\n  .bp5-icon-large {\n  color: #5f6b7c;\n}\n.bp5-input-group .bp5-input:not(:focus) + .bp5-button.bp5-minimal:disabled,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:disabled {\n  color: rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:disabled\n  .bp5-icon,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:disabled\n  .bp5-icon-standard,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-button.bp5-minimal:disabled\n  .bp5-icon-large,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:disabled\n  .bp5-icon,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:disabled\n  .bp5-icon-standard,\n.bp5-input-group\n  .bp5-input:not(:focus)\n  + .bp5-input-action\n  .bp5-button.bp5-minimal:disabled\n  .bp5-icon-large {\n  color: rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-input-group.bp5-disabled {\n  cursor: not-allowed;\n}\n.bp5-input-group.bp5-disabled .bp5-icon {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-input-group.bp5-large .bp5-button {\n  min-height: 30px;\n  min-width: 30px;\n  margin: 5px;\n}\n.bp5-input-group.bp5-large > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-large > .bp5-icon,\n.bp5-input-group.bp5-large .bp5-input-action > .bp5-spinner {\n  margin: 12px;\n}\n.bp5-input-group.bp5-large .bp5-input {\n  font-size: 16px;\n  height: 40px;\n  line-height: 40px;\n}\n.bp5-input-group.bp5-large .bp5-input[type='search'],\n.bp5-input-group.bp5-large .bp5-input.bp5-round {\n  padding: 0 15px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:first-child) {\n  padding-left: 40px;\n}\n.bp5-input-group.bp5-large .bp5-input:not(:last-child) {\n  padding-right: 40px;\n}\n.bp5-input-group.bp5-small .bp5-button {\n  min-height: 20px;\n  min-width: 20px;\n  margin: 2px;\n}\n.bp5-input-group.bp5-small .bp5-tag {\n  min-height: 20px;\n  min-width: 20px;\n  margin: 2px;\n}\n.bp5-input-group.bp5-small > .bp5-input-left-container > .bp5-icon,\n.bp5-input-group.bp5-small > .bp5-icon,\n.bp5-input-group.bp5-small .bp5-input-action > .bp5-spinner {\n  margin: 4px;\n}\n.bp5-input-group.bp5-small .bp5-input {\n  font-size: 12px;\n  height: 24px;\n  line-height: 24px;\n  padding-left: 8px;\n  padding-right: 8px;\n}\n.bp5-input-group.bp5-small .bp5-input[type='search'],\n.bp5-input-group.bp5-small .bp5-input.bp5-round {\n  padding: 0 12px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:first-child) {\n  padding-left: 24px;\n}\n.bp5-input-group.bp5-small .bp5-input:not(:last-child) {\n  padding-right: 24px;\n}\n.bp5-input-group.bp5-fill {\n  flex: 1 1 auto;\n  width: 100%;\n}\n.bp5-input-group.bp5-round .bp5-button,\n.bp5-input-group.bp5-round .bp5-input,\n.bp5-input-group.bp5-round .bp5-tag {\n  border-radius: 30px;\n}\n.bp5-dark .bp5-input-group .bp5-icon {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-input-group.bp5-disabled .bp5-icon {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input {\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-primary .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #2d72d2;\n}\n.bp5-input-group.bp5-intent-primary .bp5-input:disabled,\n.bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input {\n  box-shadow: 0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0),\n    0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #4c90f0, inset 0 0 0 1px #4c90f0,\n    0 0 0 2px rgba(76, 144, 240, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input:disabled,\n.bp5-dark .bp5-input-group.bp5-intent-primary .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input-group.bp5-intent-primary > .bp5-icon {\n  color: #215db0;\n}\n.bp5-dark .bp5-input-group.bp5-intent-primary > .bp5-icon {\n  color: #8abbff;\n}\n.bp5-input-group.bp5-intent-success .bp5-input {\n  box-shadow: 0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0),\n    inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-success .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #238551, 0 0 0 2px rgba(35, 133, 81, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-success .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #238551;\n}\n.bp5-input-group.bp5-intent-success .bp5-input:disabled,\n.bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input {\n  box-shadow: 0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0),\n    0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #32a467, inset 0 0 0 1px #32a467,\n    0 0 0 2px rgba(50, 164, 103, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input:disabled,\n.bp5-dark .bp5-input-group.bp5-intent-success .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input-group.bp5-intent-success > .bp5-icon {\n  color: #1c6e42;\n}\n.bp5-dark .bp5-input-group.bp5-intent-success > .bp5-icon {\n  color: #72ca9b;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input {\n  box-shadow: 0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0),\n    inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #c87619, 0 0 0 2px rgba(200, 118, 25, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-warning .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #c87619;\n}\n.bp5-input-group.bp5-intent-warning .bp5-input:disabled,\n.bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input {\n  box-shadow: 0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0),\n    0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px #ec9a3c,\n    0 0 0 2px rgba(236, 154, 60, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input:disabled,\n.bp5-dark .bp5-input-group.bp5-intent-warning .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input-group.bp5-intent-warning > .bp5-icon {\n  color: #935610;\n}\n.bp5-dark .bp5-input-group.bp5-intent-warning > .bp5-icon {\n  color: #fbb360;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input {\n  box-shadow: 0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0),\n    inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #cd4246, 0 0 0 2px rgba(205, 66, 70, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input-group.bp5-intent-danger .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #cd4246;\n}\n.bp5-input-group.bp5-intent-danger .bp5-input:disabled,\n.bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input {\n  box-shadow: 0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0),\n    0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #e76a6e, inset 0 0 0 1px #e76a6e,\n    0 0 0 2px rgba(231, 106, 110, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input:disabled,\n.bp5-dark .bp5-input-group.bp5-intent-danger .bp5-input.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input-group.bp5-intent-danger > .bp5-icon {\n  color: #ac2f33;\n}\n.bp5-dark .bp5-input-group.bp5-intent-danger > .bp5-icon {\n  color: #fa999c;\n}\n.bp5-input {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  background: #ffffff;\n  border: none;\n  border-radius: 2px;\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3);\n  color: #1c2127;\n  font-size: 14px;\n  font-weight: 400;\n  height: 30px;\n  line-height: 30px;\n  outline: none;\n  padding: 0 10px;\n  transition: box-shadow 100ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  vertical-align: middle;\n}\n.bp5-input::-moz-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input:-ms-input-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input::placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input:focus,\n.bp5-input.bp5-active {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input[type='search'],\n.bp5-input.bp5-round {\n  border-radius: 30px;\n  box-sizing: border-box;\n  padding-left: 10px;\n}\n.bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n}\n.bp5-input:disabled,\n.bp5-input.bp5-disabled {\n  background: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  resize: none;\n}\n.bp5-input:disabled::-moz-placeholder,\n.bp5-input.bp5-disabled::-moz-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled:-ms-input-placeholder,\n.bp5-input.bp5-disabled:-ms-input-placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-input:disabled::placeholder,\n.bp5-input.bp5-disabled::placeholder {\n  color: rgba(95, 107, 124, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-input {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-input.bp5-large {\n  font-size: 16px;\n  height: 40px;\n  line-height: 40px;\n}\n.bp5-input.bp5-large[type='search'],\n.bp5-input.bp5-large.bp5-round {\n  padding: 0 15px;\n}\n.bp5-input.bp5-small {\n  font-size: 12px;\n  height: 24px;\n  line-height: 24px;\n  padding-left: 8px;\n  padding-right: 8px;\n}\n.bp5-input.bp5-small[type='search'],\n.bp5-input.bp5-small.bp5-round {\n  padding: 0 12px;\n}\n.bp5-input.bp5-fill {\n  flex: 1 1 auto;\n  width: 100%;\n}\n.bp5-dark .bp5-input {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: 0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0),\n    0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-input::-moz-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-input:-ms-input-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-input::placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-input:focus {\n  box-shadow: inset 0 0 0 1px #4c90f0, inset 0 0 0 1px #4c90f0,\n    0 0 0 2px rgba(76, 144, 240, 0.3);\n}\n.bp5-dark .bp5-input[readonly] {\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-input:disabled,\n.bp5-dark .bp5-input.bp5-disabled {\n  background: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-input.bp5-intent-primary {\n  box-shadow: 0 0 0 0 rgba(45, 114, 210, 0), 0 0 0 0 rgba(45, 114, 210, 0),\n    inset 0 0 0 1px #2d72d2, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-primary:focus {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-primary[readonly] {\n  box-shadow: inset 0 0 0 1px #2d72d2;\n}\n.bp5-input.bp5-intent-primary:disabled,\n.bp5-input.bp5-intent-primary.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input.bp5-intent-primary {\n  box-shadow: 0 0 0 0 rgba(76, 144, 240, 0), 0 0 0 0 rgba(76, 144, 240, 0),\n    0 0 0 0 rgba(76, 144, 240, 0), inset 0 0 0 1px #4c90f0,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary:focus {\n  box-shadow: inset 0 0 0 1px #4c90f0, inset 0 0 0 1px #4c90f0,\n    0 0 0 2px rgba(76, 144, 240, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-primary[readonly] {\n  box-shadow: inset 0 0 0 1px #4c90f0;\n}\n.bp5-dark .bp5-input.bp5-intent-primary:disabled,\n.bp5-dark .bp5-input.bp5-intent-primary.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input.bp5-intent-success {\n  box-shadow: 0 0 0 0 rgba(35, 133, 81, 0), 0 0 0 0 rgba(35, 133, 81, 0),\n    inset 0 0 0 1px #238551, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-success:focus {\n  box-shadow: inset 0 0 0 1px #238551, 0 0 0 2px rgba(35, 133, 81, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-success[readonly] {\n  box-shadow: inset 0 0 0 1px #238551;\n}\n.bp5-input.bp5-intent-success:disabled,\n.bp5-input.bp5-intent-success.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input.bp5-intent-success {\n  box-shadow: 0 0 0 0 rgba(50, 164, 103, 0), 0 0 0 0 rgba(50, 164, 103, 0),\n    0 0 0 0 rgba(50, 164, 103, 0), inset 0 0 0 1px #32a467,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success:focus {\n  box-shadow: inset 0 0 0 1px #32a467, inset 0 0 0 1px #32a467,\n    0 0 0 2px rgba(50, 164, 103, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-success[readonly] {\n  box-shadow: inset 0 0 0 1px #32a467;\n}\n.bp5-dark .bp5-input.bp5-intent-success:disabled,\n.bp5-dark .bp5-input.bp5-intent-success.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input.bp5-intent-warning {\n  box-shadow: 0 0 0 0 rgba(200, 118, 25, 0), 0 0 0 0 rgba(200, 118, 25, 0),\n    inset 0 0 0 1px #c87619, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-warning:focus {\n  box-shadow: inset 0 0 0 1px #c87619, 0 0 0 2px rgba(200, 118, 25, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-warning[readonly] {\n  box-shadow: inset 0 0 0 1px #c87619;\n}\n.bp5-input.bp5-intent-warning:disabled,\n.bp5-input.bp5-intent-warning.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input.bp5-intent-warning {\n  box-shadow: 0 0 0 0 rgba(236, 154, 60, 0), 0 0 0 0 rgba(236, 154, 60, 0),\n    0 0 0 0 rgba(236, 154, 60, 0), inset 0 0 0 1px #ec9a3c,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning:focus {\n  box-shadow: inset 0 0 0 1px #ec9a3c, inset 0 0 0 1px #ec9a3c,\n    0 0 0 2px rgba(236, 154, 60, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-warning[readonly] {\n  box-shadow: inset 0 0 0 1px #ec9a3c;\n}\n.bp5-dark .bp5-input.bp5-intent-warning:disabled,\n.bp5-dark .bp5-input.bp5-intent-warning.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input.bp5-intent-danger {\n  box-shadow: 0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0),\n    inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    inset 0 1px 1px rgba(17, 20, 24, 0.3);\n}\n.bp5-input.bp5-intent-danger:focus {\n  box-shadow: inset 0 0 0 1px #cd4246, 0 0 0 2px rgba(205, 66, 70, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-input.bp5-intent-danger[readonly] {\n  box-shadow: inset 0 0 0 1px #cd4246;\n}\n.bp5-input.bp5-intent-danger:disabled,\n.bp5-input.bp5-intent-danger.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-dark .bp5-input.bp5-intent-danger {\n  box-shadow: 0 0 0 0 rgba(231, 106, 110, 0), 0 0 0 0 rgba(231, 106, 110, 0),\n    0 0 0 0 rgba(231, 106, 110, 0), inset 0 0 0 1px #e76a6e,\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger:focus {\n  box-shadow: inset 0 0 0 1px #e76a6e, inset 0 0 0 1px #e76a6e,\n    0 0 0 2px rgba(231, 106, 110, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-input.bp5-intent-danger[readonly] {\n  box-shadow: inset 0 0 0 1px #e76a6e;\n}\n.bp5-dark .bp5-input.bp5-intent-danger:disabled,\n.bp5-dark .bp5-input.bp5-intent-danger.bp5-disabled {\n  box-shadow: none;\n}\n.bp5-input::-ms-clear {\n  display: none;\n}\n\n@supports (-webkit-touch-callout: none) {\n  input.bp5-input:disabled,\n  input.bp5-input.bp5-disabled {\n    opacity: 1;\n    -webkit-text-fill-color: rgba(95, 107, 124, 0.6);\n  }\n  .bp5-dark input.bp5-input:disabled,\n  .bp5-dark input.bp5-input.bp5-disabled {\n    -webkit-text-fill-color: rgba(171, 179, 191, 0.6);\n  }\n}\ntextarea.bp5-input {\n  max-width: 100%;\n  padding: 10px;\n}\ntextarea.bp5-input,\ntextarea.bp5-input.bp5-large,\ntextarea.bp5-input.bp5-small {\n  height: auto;\n  line-height: inherit;\n}\ntextarea.bp5-input.bp5-small {\n  padding: 8px;\n}\n\n.bp5-text-area.bp5-text-area-auto-resize {\n  resize: horizontal;\n}\nlabel.bp5-label {\n  display: block;\n  margin-bottom: 15px;\n  margin-top: 0;\n}\nlabel.bp5-label .bp5-html-select,\nlabel.bp5-label .bp5-input,\nlabel.bp5-label .bp5-select,\nlabel.bp5-label .bp5-slider,\nlabel.bp5-label .bp5-popover-wrapper {\n  display: block;\n  margin-top: 5px;\n  text-transform: none;\n}\nlabel.bp5-label .bp5-button-group {\n  margin-top: 5px;\n}\nlabel.bp5-label .bp5-select select,\nlabel.bp5-label .bp5-html-select select {\n  font-weight: 400;\n  vertical-align: top;\n  width: 100%;\n}\nlabel.bp5-label .bp5-control-group {\n  margin-top: 5px;\n}\nlabel.bp5-label .bp5-control-group > .bp5-button-group,\nlabel.bp5-label .bp5-control-group > .bp5-html-select,\nlabel.bp5-label .bp5-control-group > .bp5-input,\nlabel.bp5-label .bp5-control-group > .bp5-select,\nlabel.bp5-label .bp5-control-group > .bp5-slider,\nlabel.bp5-label .bp5-control-group > .bp5-popover-wrapper {\n  margin-top: 0;\n}\nlabel.bp5-label.bp5-disabled,\nlabel.bp5-label.bp5-disabled .bp5-text-muted {\n  color: rgba(95, 107, 124, 0.6);\n}\nlabel.bp5-label.bp5-inline {\n  line-height: 30px;\n}\nlabel.bp5-label.bp5-inline .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-input-group,\nlabel.bp5-label.bp5-inline .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-popover-wrapper {\n  display: inline-block;\n  margin: 0 0 0 5px;\n  vertical-align: top;\n}\nlabel.bp5-label.bp5-inline .bp5-button-group {\n  margin: 0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-input-group .bp5-input {\n  margin-left: 0;\n}\nlabel.bp5-label.bp5-inline.bp5-large {\n  line-height: 40px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group {\n  margin: 0 0 0 5px;\n}\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-button-group,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-html-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-input,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-select,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-slider,\nlabel.bp5-label.bp5-inline .bp5-control-group > .bp5-popover-wrapper {\n  margin: 0;\n}\nlabel.bp5-label:not(.bp5-inline) .bp5-popover-target {\n  display: block;\n}\n.bp5-dark label.bp5-label {\n  color: #f6f7f9;\n}\n.bp5-dark label.bp5-label.bp5-disabled,\n.bp5-dark label.bp5-label.bp5-disabled .bp5-text-muted {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-numeric-input .bp5-button-group.bp5-vertical > .bp5-button {\n  flex: 1 1 11px;\n  min-height: 0;\n  padding: 0;\n  width: 24px;\n}\n.bp5-numeric-input.bp5-large .bp5-button-group.bp5-vertical > .bp5-button {\n  width: 40px;\n}\n.bp5-numeric-input.bp5-small .bp5-button-group.bp5-vertical > .bp5-button {\n  width: 24px;\n}\n\nform {\n  display: block;\n}\n.bp5-html-select select,\n.bp5-select select {\n  display: inline-flex;\n  flex-direction: row;\n  align-items: center;\n  border: none;\n  border-radius: 2px;\n  cursor: pointer;\n  font-size: 14px;\n  justify-content: center;\n  padding: 5px 10px;\n  text-align: left;\n  vertical-align: middle;\n  background-color: #f6f7f9;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #1c2127;\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  border-radius: 2px;\n  height: 30px;\n  padding: 0 30px 0 10px;\n  width: 100%;\n}\n.bp5-html-select select > *,\n.bp5-select select > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-html-select select > .bp5-fill,\n.bp5-select select > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-html-select select::before,\n.bp5-select select::before,\n.bp5-html-select select > *,\n.bp5-select select > * {\n  margin-right: 7px;\n}\n.bp5-html-select select:empty::before,\n.bp5-select select:empty::before,\n.bp5-html-select select > :last-child,\n.bp5-select select > :last-child {\n  margin-right: 0;\n}\n.bp5-html-select select:hover,\n.bp5-select select:hover {\n  background-clip: padding-box;\n  background-color: #edeff2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-html-select select:active,\n.bp5-select select:active,\n.bp5-html-select select.bp5-active,\n.bp5-select select.bp5-active {\n  background-color: #dce0e5;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-html-select select:active,\n  .bp5-select select:active,\n  .bp5-html-select select.bp5-active,\n  .bp5-select select.bp5-active {\n    background: highlight;\n  }\n}\n.bp5-html-select select:disabled,\n.bp5-select select:disabled,\n.bp5-html-select select.bp5-disabled,\n.bp5-select select.bp5-disabled {\n  background-color: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  outline: none;\n}\n.bp5-html-select select:disabled.bp5-active,\n.bp5-select select:disabled.bp5-active,\n.bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-select select.bp5-disabled.bp5-active {\n  background: rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-html-select select,\n  .bp5-select select {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-html-select.bp5-minimal select,\n.bp5-select.bp5-minimal select {\n  background: none;\n  box-shadow: none;\n}\n.bp5-html-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal select:hover {\n  background: rgba(143, 153, 168, 0.15);\n  box-shadow: none;\n  color: #1c2127;\n  text-decoration: none;\n}\n.bp5-html-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal select:active,\n.bp5-html-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal select.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n  box-shadow: none;\n  color: #1c2127;\n}\n.bp5-html-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal select:disabled,\n.bp5-html-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-disabled {\n  background: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-html-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal select:disabled.bp5-active,\n.bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-disabled.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select,\n.bp5-html-select.bp5-minimal .bp5-dark select,\n.bp5-dark .bp5-select.bp5-minimal select,\n.bp5-select.bp5-minimal .bp5-dark select {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-html-select.bp5-minimal select:active,\n.bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ffffff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select:hover,\n.bp5-dark .bp5-select.bp5-minimal select:hover,\n.bp5-select.bp5-minimal .bp5-dark select:hover {\n  background: rgba(143, 153, 168, 0.15);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:active,\n.bp5-html-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-select.bp5-minimal select:active,\n.bp5-select.bp5-minimal .bp5-dark select:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select:disabled,\n.bp5-dark .bp5-select.bp5-minimal select:disabled,\n.bp5-select.bp5-minimal .bp5-dark select:disabled,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled {\n  background: none;\n  color: rgba(171, 179, 191, 0.6);\n  cursor: not-allowed;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select:disabled.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select:disabled.bp5-active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-disabled.bp5-active {\n  background: rgba(143, 153, 168, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal select.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal select.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.15);\n  color: #215db0;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #184a90;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(33, 93, 176, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal\n  select.bp5-intent-primary\n  .bp5-button-spinner\n  .bp5-spinner-head,\n.bp5-select.bp5-minimal\n  select.bp5-intent-primary\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #215db0;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:hover {\n  background: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary.bp5-disabled {\n  background: none;\n  color: rgba(138, 187, 255, 0.5);\n}\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-primary:disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-primary:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-primary:disabled.bp5-active,\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-dark\n  .bp5-select.bp5-minimal\n  select.bp5-intent-primary.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-primary.bp5-disabled.bp5-active {\n  background: rgba(45, 114, 210, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal select.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal select.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.15);\n  color: #1c6e42;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #165a36;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(28, 110, 66, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal\n  select.bp5-intent-success\n  .bp5-button-spinner\n  .bp5-spinner-head,\n.bp5-select.bp5-minimal\n  select.bp5-intent-success\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #1c6e42;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:hover {\n  background: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success.bp5-disabled {\n  background: none;\n  color: rgba(114, 202, 155, 0.5);\n}\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-success:disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-success:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-success:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-success:disabled.bp5-active,\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-dark\n  .bp5-select.bp5-minimal\n  select.bp5-intent-success.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-success.bp5-disabled.bp5-active {\n  background: rgba(35, 133, 81, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal select.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal select.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.15);\n  color: #935610;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #77450d;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(147, 86, 16, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal\n  select.bp5-intent-warning\n  .bp5-button-spinner\n  .bp5-spinner-head,\n.bp5-select.bp5-minimal\n  select.bp5-intent-warning\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #935610;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:hover {\n  background: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning.bp5-disabled {\n  background: none;\n  color: rgba(251, 179, 96, 0.5);\n}\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-warning:disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-warning:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-warning:disabled.bp5-active,\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-dark\n  .bp5-select.bp5-minimal\n  select.bp5-intent-warning.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-warning.bp5-disabled.bp5-active {\n  background: rgba(200, 118, 25, 0.3);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal select.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active {\n  background: none;\n  box-shadow: none;\n  color: #ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal select.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.15);\n  color: #ac2f33;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #8e292c;\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(172, 47, 51, 0.5);\n}\n.bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n.bp5-html-select.bp5-minimal\n  select.bp5-intent-danger\n  .bp5-button-spinner\n  .bp5-spinner-head,\n.bp5-select.bp5-minimal\n  select.bp5-intent-danger\n  .bp5-button-spinner\n  .bp5-spinner-head {\n  stroke: #ac2f33;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:hover,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:hover {\n  background: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:active,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled,\n.bp5-dark .bp5-html-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-html-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger.bp5-disabled,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger.bp5-disabled {\n  background: none;\n  color: rgba(250, 153, 156, 0.5);\n}\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-danger:disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark .bp5-select.bp5-minimal select.bp5-intent-danger:disabled.bp5-active,\n.bp5-select.bp5-minimal .bp5-dark select.bp5-intent-danger:disabled.bp5-active,\n.bp5-dark\n  .bp5-html-select.bp5-minimal\n  select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-html-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-dark\n  .bp5-select.bp5-minimal\n  select.bp5-intent-danger.bp5-disabled.bp5-active,\n.bp5-select.bp5-minimal\n  .bp5-dark\n  select.bp5-intent-danger.bp5-disabled.bp5-active {\n  background: rgba(205, 66, 70, 0.3);\n}\n\n.bp5-html-select.bp5-large select,\n.bp5-select.bp5-large select {\n  font-size: 16px;\n  height: 40px;\n  padding-right: 35px;\n}\n\n.bp5-dark .bp5-html-select select,\n.bp5-dark .bp5-select select {\n  background-color: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover,\n.bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active,\n.bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-html-select select:hover,\n.bp5-dark .bp5-select select:hover {\n  background-color: #2f343c;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:active,\n.bp5-dark .bp5-select select:active,\n.bp5-dark .bp5-html-select select.bp5-active,\n.bp5-dark .bp5-select select.bp5-active {\n  background-color: #1c2127;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-html-select select:disabled,\n.bp5-dark .bp5-select select:disabled,\n.bp5-dark .bp5-html-select select.bp5-disabled,\n.bp5-dark .bp5-select select.bp5-disabled {\n  background-color: rgba(64, 72, 84, 0.5);\n  box-shadow: none;\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select select:disabled.bp5-active,\n.bp5-dark .bp5-select select:disabled.bp5-active,\n.bp5-dark .bp5-html-select select.bp5-disabled.bp5-active,\n.bp5-dark .bp5-select select.bp5-disabled.bp5-active {\n  background: rgba(64, 72, 84, 0.7);\n}\n.bp5-dark .bp5-html-select select .bp5-button-spinner .bp5-spinner-head,\n.bp5-dark .bp5-select select .bp5-button-spinner .bp5-spinner-head {\n  background: rgba(17, 20, 24, 0.5);\n  stroke: #8f99a8;\n}\n\n.bp5-html-select select:disabled,\n.bp5-select select:disabled {\n  background-color: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n\n.bp5-select::after,\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon {\n  color: #5f6b7c;\n  pointer-events: none;\n  position: absolute;\n  right: 10px;\n  top: 7px;\n}\n.bp5-disabled.bp5-select::after,\n.bp5-html-select .bp5-disabled.bp5-icon,\n.bp5-select .bp5-disabled.bp5-icon {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-html-select,\n.bp5-select {\n  display: inline-block;\n  letter-spacing: normal;\n  position: relative;\n  vertical-align: middle;\n}\n.bp5-html-select .bp5-icon,\n.bp5-select .bp5-icon {\n  color: #5f6b7c;\n}\n.bp5-html-select .bp5-icon:hover,\n.bp5-select .bp5-icon:hover {\n  color: #1c2127;\n}\n.bp5-dark .bp5-html-select .bp5-icon,\n.bp5-dark .bp5-select .bp5-icon {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-html-select .bp5-icon:hover,\n.bp5-dark .bp5-select .bp5-icon:hover {\n  color: #f6f7f9;\n}\n.bp5-html-select.bp5-large::after,\n.bp5-html-select.bp5-large .bp5-icon,\n.bp5-select.bp5-large::after,\n.bp5-select.bp5-large .bp5-icon {\n  right: 12px;\n  top: 12px;\n}\n.bp5-html-select.bp5-fill,\n.bp5-html-select.bp5-fill select,\n.bp5-select.bp5-fill,\n.bp5-select.bp5-fill select {\n  width: 100%;\n}\n.bp5-dark .bp5-html-select option,\n.bp5-dark .bp5-select option {\n  background-color: #2f343c;\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-html-select option:disabled,\n.bp5-dark .bp5-select option:disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-html-select::after,\n.bp5-dark .bp5-select::after {\n  color: #abb3bf;\n}\n\n.bp5-select::after {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  content: '\\f184';\n}\ntable.bp5-html-table,\n.bp5-running-text table {\n  border-spacing: 0;\n  font-size: 14px;\n}\ntable.bp5-html-table th,\n.bp5-running-text table th,\ntable.bp5-html-table td,\n.bp5-running-text table td {\n  padding: 11px;\n  text-align: left;\n  vertical-align: top;\n}\ntable.bp5-html-table th,\n.bp5-running-text table th {\n  color: #1c2127;\n  font-weight: 600;\n}\ntable.bp5-html-table td,\n.bp5-running-text table td {\n  color: #1c2127;\n}\ntable.bp5-html-table tbody tr:first-child th,\n.bp5-running-text table tbody tr:first-child th,\ntable.bp5-html-table tbody tr:first-child td,\n.bp5-running-text table tbody tr:first-child td,\ntable.bp5-html-table tfoot tr:first-child th,\n.bp5-running-text table tfoot tr:first-child th,\ntable.bp5-html-table tfoot tr:first-child td,\n.bp5-running-text table tfoot tr:first-child td {\n  box-shadow: inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n.bp5-dark table.bp5-html-table th,\n.bp5-dark .bp5-running-text table th,\n.bp5-running-text .bp5-dark table th {\n  color: #f6f7f9;\n}\n.bp5-dark table.bp5-html-table td,\n.bp5-dark .bp5-running-text table td,\n.bp5-running-text .bp5-dark table td {\n  color: #f6f7f9;\n}\n.bp5-dark table.bp5-html-table tbody tr:first-child th,\n.bp5-dark .bp5-running-text table tbody tr:first-child th,\n.bp5-running-text .bp5-dark table tbody tr:first-child th,\n.bp5-dark table.bp5-html-table tbody tr:first-child td,\n.bp5-dark .bp5-running-text table tbody tr:first-child td,\n.bp5-running-text .bp5-dark table tbody tr:first-child td,\n.bp5-dark table.bp5-html-table tfoot tr:first-child th,\n.bp5-dark .bp5-running-text table tfoot tr:first-child th,\n.bp5-running-text .bp5-dark table tfoot tr:first-child th,\n.bp5-dark table.bp5-html-table tfoot tr:first-child td,\n.bp5-dark .bp5-running-text table tfoot tr:first-child td,\n.bp5-running-text .bp5-dark table tfoot tr:first-child td {\n  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n\ntable.bp5-html-table.bp5-compact th,\ntable.bp5-html-table.bp5-compact td {\n  padding-bottom: 6px;\n  padding-top: 6px;\n}\ntable.bp5-html-table.bp5-html-table-striped tbody tr:nth-child(odd) td {\n  background: rgba(143, 153, 168, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered th:not(:first-child) {\n  box-shadow: inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  table.bp5-html-table.bp5-html-table-bordered th:not(:first-child) {\n    border-left: 1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td,\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td {\n  box-shadow: inset 0 1px 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\ntable.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child) {\n  box-shadow: inset 1px 1px 0 0 rgba(17, 20, 24, 0.15);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td:not(:first-child),\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td:not(:first-child) {\n    border-left: 1px solid buttonborder;\n    border-top: 1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n  table.bp5-html-table.bp5-html-table-bordered tfoot tr td {\n    border-top: 1px solid buttonborder;\n  }\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped\n  tbody\n  tr:not(:first-child)\n  td {\n  box-shadow: none;\n}\ntable.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped\n  tbody\n  tr:not(:first-child)\n  td:not(:first-child) {\n  box-shadow: inset 1px 0 0 0 rgba(17, 20, 24, 0.15);\n}\ntable.bp5-html-table.bp5-interactive tbody tr:hover td {\n  background-color: rgba(143, 153, 168, 0.3);\n  cursor: pointer;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  table.bp5-html-table.bp5-interactive tbody tr:hover td {\n    background-color: highlight;\n  }\n}\ntable.bp5-html-table.bp5-interactive tbody tr:active td {\n  background-color: rgba(143, 153, 168, 0.35);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  table.bp5-html-table.bp5-interactive tbody tr:active td {\n    background-color: highlight;\n  }\n}\n.bp5-dark table.bp5-html-table {\n}\n.bp5-dark\n  table.bp5-html-table.bp5-html-table-striped\n  tbody\n  tr:nth-child(odd)\n  td {\n  background: rgba(95, 107, 124, 0.15);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered th:not(:first-child) {\n  box-shadow: inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tbody tr td,\n.bp5-dark table.bp5-html-table.bp5-html-table-bordered tfoot tr td {\n  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark\n  table.bp5-html-table.bp5-html-table-bordered\n  tbody\n  tr\n  td:not(:first-child),\n.bp5-dark\n  table.bp5-html-table.bp5-html-table-bordered\n  tfoot\n  tr\n  td:not(:first-child) {\n  box-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark\n  table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped\n  tbody\n  tr:not(:first-child)\n  td {\n  box-shadow: inset 1px 0 0 0 rgba(255, 255, 255, 0.2);\n}\n.bp5-dark\n  table.bp5-html-table.bp5-html-table-bordered.bp5-html-table-striped\n  tbody\n  tr:not(:first-child)\n  td:first-child {\n  box-shadow: none;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:hover td {\n  background-color: rgba(95, 107, 124, 0.3);\n  cursor: pointer;\n}\n.bp5-dark table.bp5-html-table.bp5-interactive tbody tr:active td {\n  background-color: rgba(95, 107, 124, 0.4);\n}\n.bp5-key-combo {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n}\n.bp5-key-combo > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-key-combo > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-key-combo::before,\n.bp5-key-combo > * {\n  margin-right: 5px;\n}\n.bp5-key-combo:empty::before,\n.bp5-key-combo > :last-child {\n  margin-right: 0;\n}\n\n.bp5-hotkey-dialog {\n  padding-bottom: 0;\n  top: 40px;\n}\n.bp5-hotkey-dialog .bp5-dialog-body {\n  margin: 0;\n  padding: 0;\n}\n.bp5-hotkey-dialog .bp5-hotkey-label {\n  flex-grow: 1;\n}\n\n.bp5-hotkey-column {\n  margin: auto;\n  padding: 30px;\n}\n.bp5-hotkey-column .bp5-heading {\n  margin-bottom: 20px;\n}\n.bp5-hotkey-column .bp5-heading:not(:first-child) {\n  margin-top: 40px;\n}\n\n.bp5-hotkey {\n  align-items: center;\n  display: flex;\n  justify-content: space-between;\n  margin-left: 0;\n  margin-right: 0;\n}\n.bp5-hotkey:not(:last-child) {\n  margin-bottom: 10px;\n}\n.bp5-icon {\n  display: inline-block;\n  flex: 0 0 auto;\n  vertical-align: text-bottom;\n}\n.bp5-icon:not(:empty)::before {\n  content: '' !important;\n  content: unset !important;\n}\n.bp5-icon > svg {\n  display: block;\n}\n.bp5-icon > svg:not([fill]) {\n  fill: currentcolor;\n}\n.bp5-icon.bp5-icon-muted svg {\n  fill-opacity: 15%;\n  overflow: visible;\n}\n.bp5-icon.bp5-icon-muted svg path {\n  stroke: #8f99a8;\n  stroke-opacity: 50%;\n  stroke-width: 0.5px;\n}\n.bp5-dark .bp5-icon .bp5-icon-muted svg {\n  fill-opacity: 20%;\n}\n\nspan.bp5-icon-standard {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  display: inline-block;\n}\n\nspan.bp5-icon-large {\n  font-family: 'blueprint-icons-20', sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 20px;\n  line-height: 1;\n  width: 20px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  display: inline-block;\n}\n\nspan.bp5-icon:empty {\n  font-family: 'blueprint-icons-20';\n  font-size: inherit;\n  font-style: normal;\n  font-weight: 400;\n  line-height: 1;\n}\nspan.bp5-icon:empty::before {\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n}\nspan.bp5-icon:empty.bp5-icon-standard {\n  font-size: 16px;\n}\nspan.bp5-icon:empty.bp5-icon-large {\n  font-size: 20px;\n}\n\n.bp5-icon-add::before {\n  content: '\\f109';\n}\n\n.bp5-icon-add-clip::before {\n  content: '\\f101';\n}\n\n.bp5-icon-add-column-left::before {\n  content: '\\f102';\n}\n\n.bp5-icon-add-column-right::before {\n  content: '\\f103';\n}\n\n.bp5-icon-add-location::before {\n  content: '\\f104';\n}\n\n.bp5-icon-add-row-bottom::before {\n  content: '\\f105';\n}\n\n.bp5-icon-add-row-top::before {\n  content: '\\f106';\n}\n\n.bp5-icon-add-to-artifact::before {\n  content: '\\f107';\n}\n\n.bp5-icon-add-to-folder::before {\n  content: '\\f108';\n}\n\n.bp5-icon-aimpoints-target::before {\n  content: '\\f335';\n}\n\n.bp5-icon-airplane::before {\n  content: '\\f10a';\n}\n\n.bp5-icon-align-center::before {\n  content: '\\f10b';\n}\n\n.bp5-icon-align-justify::before {\n  content: '\\f10c';\n}\n\n.bp5-icon-align-left::before {\n  content: '\\f10d';\n}\n\n.bp5-icon-align-right::before {\n  content: '\\f10e';\n}\n\n.bp5-icon-alignment-bottom::before {\n  content: '\\f10f';\n}\n\n.bp5-icon-alignment-horizontal-center::before {\n  content: '\\f110';\n}\n\n.bp5-icon-alignment-left::before {\n  content: '\\f111';\n}\n\n.bp5-icon-alignment-right::before {\n  content: '\\f112';\n}\n\n.bp5-icon-alignment-top::before {\n  content: '\\f113';\n}\n\n.bp5-icon-alignment-vertical-center::before {\n  content: '\\f114';\n}\n\n.bp5-icon-ammunition::before {\n  content: '\\f342';\n}\n\n.bp5-icon-anchor::before {\n  content: '\\f330';\n}\n\n.bp5-icon-annotation::before {\n  content: '\\f115';\n}\n\n.bp5-icon-antenna::before {\n  content: '\\f116';\n}\n\n.bp5-icon-app-header::before {\n  content: '\\f117';\n}\n\n.bp5-icon-application::before {\n  content: '\\f118';\n}\n\n.bp5-icon-applications::before {\n  content: '\\f119';\n}\n\n.bp5-icon-archive::before {\n  content: '\\f11a';\n}\n\n.bp5-icon-area-of-interest::before {\n  content: '\\f11b';\n}\n\n.bp5-icon-array::before {\n  content: '\\f121';\n}\n\n.bp5-icon-array-boolean::before {\n  content: '\\f11c';\n}\n\n.bp5-icon-array-date::before {\n  content: '\\f11d';\n}\n\n.bp5-icon-array-floating-point::before {\n  content: '\\f32d';\n}\n\n.bp5-icon-array-numeric::before {\n  content: '\\f11e';\n}\n\n.bp5-icon-array-string::before {\n  content: '\\f11f';\n}\n\n.bp5-icon-array-timestamp::before {\n  content: '\\f120';\n}\n\n.bp5-icon-arrow-bottom-left::before {\n  content: '\\f122';\n}\n\n.bp5-icon-arrow-bottom-right::before {\n  content: '\\f123';\n}\n\n.bp5-icon-arrow-down::before {\n  content: '\\f124';\n}\n\n.bp5-icon-arrow-left::before {\n  content: '\\f125';\n}\n\n.bp5-icon-arrow-right::before {\n  content: '\\f126';\n}\n\n.bp5-icon-arrow-top-left::before {\n  content: '\\f127';\n}\n\n.bp5-icon-arrow-top-right::before {\n  content: '\\f128';\n}\n\n.bp5-icon-arrow-up::before {\n  content: '\\f129';\n}\n\n.bp5-icon-arrows-horizontal::before {\n  content: '\\f12a';\n}\n\n.bp5-icon-arrows-vertical::before {\n  content: '\\f12b';\n}\n\n.bp5-icon-asterisk::before {\n  content: '\\f12c';\n}\n\n.bp5-icon-at::before {\n  content: '\\f331';\n}\n\n.bp5-icon-automatic-updates::before {\n  content: '\\f12d';\n}\n\n.bp5-icon-axle::before {\n  content: '\\f338';\n}\n\n.bp5-icon-backlink::before {\n  content: '\\f12e';\n}\n\n.bp5-icon-backward-ten::before {\n  content: '\\f35c';\n}\n\n.bp5-icon-badge::before {\n  content: '\\f12f';\n}\n\n.bp5-icon-ban-circle::before {\n  content: '\\f130';\n}\n\n.bp5-icon-bank-account::before {\n  content: '\\f131';\n}\n\n.bp5-icon-barcode::before {\n  content: '\\f132';\n}\n\n.bp5-icon-binary-number::before {\n  content: '\\f357';\n}\n\n.bp5-icon-blank::before {\n  content: '\\f133';\n}\n\n.bp5-icon-blocked-person::before {\n  content: '\\f134';\n}\n\n.bp5-icon-bold::before {\n  content: '\\f135';\n}\n\n.bp5-icon-book::before {\n  content: '\\f136';\n}\n\n.bp5-icon-bookmark::before {\n  content: '\\f137';\n}\n\n.bp5-icon-box::before {\n  content: '\\f138';\n}\n\n.bp5-icon-briefcase::before {\n  content: '\\f139';\n}\n\n.bp5-icon-bring-data::before {\n  content: '\\f13a';\n}\n\n.bp5-icon-bring-forward::before {\n  content: '\\f354';\n}\n\n.bp5-icon-bug::before {\n  content: '\\f32e';\n}\n\n.bp5-icon-buggy::before {\n  content: '\\f13b';\n}\n\n.bp5-icon-build::before {\n  content: '\\f13c';\n}\n\n.bp5-icon-bullseye::before {\n  content: '\\f359';\n}\n\n.bp5-icon-calculator::before {\n  content: '\\f13d';\n}\n\n.bp5-icon-calendar::before {\n  content: '\\f13e';\n}\n\n.bp5-icon-camera::before {\n  content: '\\f13f';\n}\n\n.bp5-icon-caret-down::before {\n  content: '\\f140';\n}\n\n.bp5-icon-caret-left::before {\n  content: '\\f141';\n}\n\n.bp5-icon-caret-right::before {\n  content: '\\f142';\n}\n\n.bp5-icon-caret-up::before {\n  content: '\\f143';\n}\n\n.bp5-icon-cargo-ship::before {\n  content: '\\f144';\n}\n\n.bp5-icon-cell-tower::before {\n  content: '\\f145';\n}\n\n.bp5-icon-changes::before {\n  content: '\\f146';\n}\n\n.bp5-icon-chart::before {\n  content: '\\f147';\n}\n\n.bp5-icon-chat::before {\n  content: '\\f148';\n}\n\n.bp5-icon-chevron-backward::before {\n  content: '\\f149';\n}\n\n.bp5-icon-chevron-down::before {\n  content: '\\f14a';\n}\n\n.bp5-icon-chevron-forward::before {\n  content: '\\f14b';\n}\n\n.bp5-icon-chevron-left::before {\n  content: '\\f14c';\n}\n\n.bp5-icon-chevron-right::before {\n  content: '\\f14d';\n}\n\n.bp5-icon-chevron-up::before {\n  content: '\\f14e';\n}\n\n.bp5-icon-circle::before {\n  content: '\\f153';\n}\n\n.bp5-icon-circle-arrow-down::before {\n  content: '\\f14f';\n}\n\n.bp5-icon-circle-arrow-left::before {\n  content: '\\f150';\n}\n\n.bp5-icon-circle-arrow-right::before {\n  content: '\\f151';\n}\n\n.bp5-icon-circle-arrow-up::before {\n  content: '\\f152';\n}\n\n.bp5-icon-citation::before {\n  content: '\\f154';\n}\n\n.bp5-icon-clean::before {\n  content: '\\f155';\n}\n\n.bp5-icon-clip::before {\n  content: '\\f156';\n}\n\n.bp5-icon-clipboard::before {\n  content: '\\f157';\n}\n\n.bp5-icon-clipboard-file::before {\n  content: '\\f35b';\n}\n\n.bp5-icon-cloud::before {\n  content: '\\f15a';\n}\n\n.bp5-icon-cloud-download::before {\n  content: '\\f158';\n}\n\n.bp5-icon-cloud-server::before {\n  content: '\\f35a';\n}\n\n.bp5-icon-cloud-tick::before {\n  content: '\\f34e';\n}\n\n.bp5-icon-cloud-upload::before {\n  content: '\\f159';\n}\n\n.bp5-icon-code::before {\n  content: '\\f15c';\n}\n\n.bp5-icon-code-block::before {\n  content: '\\f15b';\n}\n\n.bp5-icon-cog::before {\n  content: '\\f15d';\n}\n\n.bp5-icon-collapse-all::before {\n  content: '\\f15e';\n}\n\n.bp5-icon-color-fill::before {\n  content: '\\f328';\n}\n\n.bp5-icon-column-layout::before {\n  content: '\\f15f';\n}\n\n.bp5-icon-comment::before {\n  content: '\\f160';\n}\n\n.bp5-icon-comparison::before {\n  content: '\\f161';\n}\n\n.bp5-icon-compass::before {\n  content: '\\f162';\n}\n\n.bp5-icon-compressed::before {\n  content: '\\f163';\n}\n\n.bp5-icon-confirm::before {\n  content: '\\f164';\n}\n\n.bp5-icon-console::before {\n  content: '\\f165';\n}\n\n.bp5-icon-contrast::before {\n  content: '\\f166';\n}\n\n.bp5-icon-control::before {\n  content: '\\f167';\n}\n\n.bp5-icon-credit-card::before {\n  content: '\\f168';\n}\n\n.bp5-icon-crop::before {\n  content: '\\f353';\n}\n\n.bp5-icon-cross::before {\n  content: '\\f169';\n}\n\n.bp5-icon-cross-circle::before {\n  content: '\\f336';\n}\n\n.bp5-icon-crown::before {\n  content: '\\f16a';\n}\n\n.bp5-icon-css-style::before {\n  content: '\\f36b';\n}\n\n.bp5-icon-cube::before {\n  content: '\\f16d';\n}\n\n.bp5-icon-cube-add::before {\n  content: '\\f16b';\n}\n\n.bp5-icon-cube-remove::before {\n  content: '\\f16c';\n}\n\n.bp5-icon-curly-braces::before {\n  content: '\\f358';\n}\n\n.bp5-icon-curved-range-chart::before {\n  content: '\\f16e';\n}\n\n.bp5-icon-cut::before {\n  content: '\\f16f';\n}\n\n.bp5-icon-cycle::before {\n  content: '\\f170';\n}\n\n.bp5-icon-dashboard::before {\n  content: '\\f171';\n}\n\n.bp5-icon-data-connection::before {\n  content: '\\f172';\n}\n\n.bp5-icon-data-lineage::before {\n  content: '\\f173';\n}\n\n.bp5-icon-data-sync::before {\n  content: '\\f36c';\n}\n\n.bp5-icon-database::before {\n  content: '\\f174';\n}\n\n.bp5-icon-delete::before {\n  content: '\\f175';\n}\n\n.bp5-icon-delta::before {\n  content: '\\f176';\n}\n\n.bp5-icon-derive-column::before {\n  content: '\\f177';\n}\n\n.bp5-icon-desktop::before {\n  content: '\\f178';\n}\n\n.bp5-icon-detection::before {\n  content: '\\f341';\n}\n\n.bp5-icon-diagnosis::before {\n  content: '\\f179';\n}\n\n.bp5-icon-diagram-tree::before {\n  content: '\\f17a';\n}\n\n.bp5-icon-direction-left::before {\n  content: '\\f17b';\n}\n\n.bp5-icon-direction-right::before {\n  content: '\\f17c';\n}\n\n.bp5-icon-disable::before {\n  content: '\\f17d';\n}\n\n.bp5-icon-divide::before {\n  content: '\\f327';\n}\n\n.bp5-icon-document::before {\n  content: '\\f180';\n}\n\n.bp5-icon-document-open::before {\n  content: '\\f17e';\n}\n\n.bp5-icon-document-share::before {\n  content: '\\f17f';\n}\n\n.bp5-icon-dollar::before {\n  content: '\\f181';\n}\n\n.bp5-icon-dot::before {\n  content: '\\f182';\n}\n\n.bp5-icon-double-caret-horizontal::before {\n  content: '\\f183';\n}\n\n.bp5-icon-double-caret-vertical::before {\n  content: '\\f184';\n}\n\n.bp5-icon-double-chevron-down::before {\n  content: '\\f185';\n}\n\n.bp5-icon-double-chevron-left::before {\n  content: '\\f186';\n}\n\n.bp5-icon-double-chevron-right::before {\n  content: '\\f187';\n}\n\n.bp5-icon-double-chevron-up::before {\n  content: '\\f188';\n}\n\n.bp5-icon-doughnut-chart::before {\n  content: '\\f189';\n}\n\n.bp5-icon-download::before {\n  content: '\\f18a';\n}\n\n.bp5-icon-drag-handle-horizontal::before {\n  content: '\\f18b';\n}\n\n.bp5-icon-drag-handle-vertical::before {\n  content: '\\f18c';\n}\n\n.bp5-icon-draw::before {\n  content: '\\f18d';\n}\n\n.bp5-icon-drawer-left::before {\n  content: '\\f18f';\n}\n\n.bp5-icon-drawer-left-filled::before {\n  content: '\\f18e';\n}\n\n.bp5-icon-drawer-right::before {\n  content: '\\f191';\n}\n\n.bp5-icon-drawer-right-filled::before {\n  content: '\\f190';\n}\n\n.bp5-icon-drive-time::before {\n  content: '\\f192';\n}\n\n.bp5-icon-duplicate::before {\n  content: '\\f193';\n}\n\n.bp5-icon-edit::before {\n  content: '\\f194';\n}\n\n.bp5-icon-eject::before {\n  content: '\\f195';\n}\n\n.bp5-icon-emoji::before {\n  content: '\\f196';\n}\n\n.bp5-icon-endnote::before {\n  content: '\\f356';\n}\n\n.bp5-icon-endorsed::before {\n  content: '\\f197';\n}\n\n.bp5-icon-envelope::before {\n  content: '\\f198';\n}\n\n.bp5-icon-equals::before {\n  content: '\\f199';\n}\n\n.bp5-icon-eraser::before {\n  content: '\\f19a';\n}\n\n.bp5-icon-error::before {\n  content: '\\f19b';\n}\n\n.bp5-icon-euro::before {\n  content: '\\f19c';\n}\n\n.bp5-icon-excavator::before {\n  content: '\\f36d';\n}\n\n.bp5-icon-exchange::before {\n  content: '\\f19d';\n}\n\n.bp5-icon-exclude-row::before {\n  content: '\\f19e';\n}\n\n.bp5-icon-expand-all::before {\n  content: '\\f19f';\n}\n\n.bp5-icon-explain::before {\n  content: '\\f34d';\n}\n\n.bp5-icon-export::before {\n  content: '\\f1a0';\n}\n\n.bp5-icon-eye-off::before {\n  content: '\\f1a1';\n}\n\n.bp5-icon-eye-on::before {\n  content: '\\f1a2';\n}\n\n.bp5-icon-eye-open::before {\n  content: '\\f1a3';\n}\n\n.bp5-icon-fast-backward::before {\n  content: '\\f1a4';\n}\n\n.bp5-icon-fast-forward::before {\n  content: '\\f1a5';\n}\n\n.bp5-icon-feed::before {\n  content: '\\f1a7';\n}\n\n.bp5-icon-feed-subscribed::before {\n  content: '\\f1a6';\n}\n\n.bp5-icon-film::before {\n  content: '\\f1a8';\n}\n\n.bp5-icon-filter::before {\n  content: '\\f1ad';\n}\n\n.bp5-icon-filter-keep::before {\n  content: '\\f1a9';\n}\n\n.bp5-icon-filter-list::before {\n  content: '\\f1aa';\n}\n\n.bp5-icon-filter-open::before {\n  content: '\\f1ab';\n}\n\n.bp5-icon-filter-remove::before {\n  content: '\\f1ac';\n}\n\n.bp5-icon-flag::before {\n  content: '\\f1ae';\n}\n\n.bp5-icon-flame::before {\n  content: '\\f1af';\n}\n\n.bp5-icon-flash::before {\n  content: '\\f1b0';\n}\n\n.bp5-icon-floating-point::before {\n  content: '\\f32c';\n}\n\n.bp5-icon-floppy-disk::before {\n  content: '\\f1b1';\n}\n\n.bp5-icon-flow-branch::before {\n  content: '\\f1b2';\n}\n\n.bp5-icon-flow-end::before {\n  content: '\\f1b3';\n}\n\n.bp5-icon-flow-linear::before {\n  content: '\\f1b4';\n}\n\n.bp5-icon-flow-review::before {\n  content: '\\f1b6';\n}\n\n.bp5-icon-flow-review-branch::before {\n  content: '\\f1b5';\n}\n\n.bp5-icon-flows::before {\n  content: '\\f1b7';\n}\n\n.bp5-icon-folder-close::before {\n  content: '\\f1b8';\n}\n\n.bp5-icon-folder-new::before {\n  content: '\\f1b9';\n}\n\n.bp5-icon-folder-open::before {\n  content: '\\f1ba';\n}\n\n.bp5-icon-folder-shared::before {\n  content: '\\f1bc';\n}\n\n.bp5-icon-folder-shared-open::before {\n  content: '\\f1bb';\n}\n\n.bp5-icon-follower::before {\n  content: '\\f1bd';\n}\n\n.bp5-icon-following::before {\n  content: '\\f1be';\n}\n\n.bp5-icon-font::before {\n  content: '\\f1bf';\n}\n\n.bp5-icon-fork::before {\n  content: '\\f1c0';\n}\n\n.bp5-icon-form::before {\n  content: '\\f1c1';\n}\n\n.bp5-icon-forward-ten::before {\n  content: '\\f35d';\n}\n\n.bp5-icon-fuel::before {\n  content: '\\f323';\n}\n\n.bp5-icon-full-circle::before {\n  content: '\\f1c2';\n}\n\n.bp5-icon-full-stacked-chart::before {\n  content: '\\f1c3';\n}\n\n.bp5-icon-fullscreen::before {\n  content: '\\f1c4';\n}\n\n.bp5-icon-function::before {\n  content: '\\f1c5';\n}\n\n.bp5-icon-gantt-chart::before {\n  content: '\\f1c6';\n}\n\n.bp5-icon-generate::before {\n  content: '\\f34c';\n}\n\n.bp5-icon-geofence::before {\n  content: '\\f1c7';\n}\n\n.bp5-icon-geolocation::before {\n  content: '\\f1c8';\n}\n\n.bp5-icon-geosearch::before {\n  content: '\\f1c9';\n}\n\n.bp5-icon-geotime::before {\n  content: '\\f344';\n}\n\n.bp5-icon-git-branch::before {\n  content: '\\f1ca';\n}\n\n.bp5-icon-git-commit::before {\n  content: '\\f1cb';\n}\n\n.bp5-icon-git-merge::before {\n  content: '\\f1cc';\n}\n\n.bp5-icon-git-new-branch::before {\n  content: '\\f1cd';\n}\n\n.bp5-icon-git-pull::before {\n  content: '\\f1ce';\n}\n\n.bp5-icon-git-push::before {\n  content: '\\f1cf';\n}\n\n.bp5-icon-git-repo::before {\n  content: '\\f1d0';\n}\n\n.bp5-icon-glass::before {\n  content: '\\f1d1';\n}\n\n.bp5-icon-globe::before {\n  content: '\\f1d3';\n}\n\n.bp5-icon-globe-network::before {\n  content: '\\f1d2';\n}\n\n.bp5-icon-graph::before {\n  content: '\\f1d5';\n}\n\n.bp5-icon-graph-remove::before {\n  content: '\\f1d4';\n}\n\n.bp5-icon-greater-than::before {\n  content: '\\f1d7';\n}\n\n.bp5-icon-greater-than-or-equal-to::before {\n  content: '\\f1d6';\n}\n\n.bp5-icon-grid::before {\n  content: '\\f1d9';\n}\n\n.bp5-icon-grid-view::before {\n  content: '\\f1d8';\n}\n\n.bp5-icon-group-item::before {\n  content: '\\f34a';\n}\n\n.bp5-icon-group-objects::before {\n  content: '\\f1da';\n}\n\n.bp5-icon-grouped-bar-chart::before {\n  content: '\\f1db';\n}\n\n.bp5-icon-hand::before {\n  content: '\\f1e0';\n}\n\n.bp5-icon-hand-down::before {\n  content: '\\f1dc';\n}\n\n.bp5-icon-hand-left::before {\n  content: '\\f1dd';\n}\n\n.bp5-icon-hand-right::before {\n  content: '\\f1de';\n}\n\n.bp5-icon-hand-up::before {\n  content: '\\f1df';\n}\n\n.bp5-icon-hat::before {\n  content: '\\f1e1';\n}\n\n.bp5-icon-header::before {\n  content: '\\f1e5';\n}\n\n.bp5-icon-header-one::before {\n  content: '\\f1e2';\n}\n\n.bp5-icon-header-three::before {\n  content: '\\f1e3';\n}\n\n.bp5-icon-header-two::before {\n  content: '\\f1e4';\n}\n\n.bp5-icon-headset::before {\n  content: '\\f1e6';\n}\n\n.bp5-icon-heart::before {\n  content: '\\f1e8';\n}\n\n.bp5-icon-heart-broken::before {\n  content: '\\f1e7';\n}\n\n.bp5-icon-heat-grid::before {\n  content: '\\f1e9';\n}\n\n.bp5-icon-heatmap::before {\n  content: '\\f1ea';\n}\n\n.bp5-icon-helicopter::before {\n  content: '\\f1eb';\n}\n\n.bp5-icon-help::before {\n  content: '\\f1ec';\n}\n\n.bp5-icon-helper-management::before {\n  content: '\\f1ed';\n}\n\n.bp5-icon-high-priority::before {\n  content: '\\f1ee';\n}\n\n.bp5-icon-high-voltage-pole::before {\n  content: '\\f333';\n}\n\n.bp5-icon-highlight::before {\n  content: '\\f1ef';\n}\n\n.bp5-icon-history::before {\n  content: '\\f1f0';\n}\n\n.bp5-icon-home::before {\n  content: '\\f1f1';\n}\n\n.bp5-icon-horizontal-bar-chart::before {\n  content: '\\f1f4';\n}\n\n.bp5-icon-horizontal-bar-chart-asc::before {\n  content: '\\f1f2';\n}\n\n.bp5-icon-horizontal-bar-chart-desc::before {\n  content: '\\f1f3';\n}\n\n.bp5-icon-horizontal-distribution::before {\n  content: '\\f1f5';\n}\n\n.bp5-icon-horizontal-inbetween::before {\n  content: '\\f329';\n}\n\n.bp5-icon-hurricane::before {\n  content: '\\f1f6';\n}\n\n.bp5-icon-id-number::before {\n  content: '\\f1f7';\n}\n\n.bp5-icon-image-rotate-left::before {\n  content: '\\f1f8';\n}\n\n.bp5-icon-image-rotate-right::before {\n  content: '\\f1f9';\n}\n\n.bp5-icon-import::before {\n  content: '\\f1fa';\n}\n\n.bp5-icon-inbox::before {\n  content: '\\f1ff';\n}\n\n.bp5-icon-inbox-filtered::before {\n  content: '\\f1fb';\n}\n\n.bp5-icon-inbox-geo::before {\n  content: '\\f1fc';\n}\n\n.bp5-icon-inbox-search::before {\n  content: '\\f1fd';\n}\n\n.bp5-icon-inbox-update::before {\n  content: '\\f1fe';\n}\n\n.bp5-icon-info-sign::before {\n  content: '\\f200';\n}\n\n.bp5-icon-inheritance::before {\n  content: '\\f201';\n}\n\n.bp5-icon-inherited-group::before {\n  content: '\\f202';\n}\n\n.bp5-icon-inner-join::before {\n  content: '\\f203';\n}\n\n.bp5-icon-input::before {\n  content: '\\f34b';\n}\n\n.bp5-icon-insert::before {\n  content: '\\f204';\n}\n\n.bp5-icon-intelligence::before {\n  content: '\\f337';\n}\n\n.bp5-icon-intersection::before {\n  content: '\\f205';\n}\n\n.bp5-icon-ip-address::before {\n  content: '\\f206';\n}\n\n.bp5-icon-issue::before {\n  content: '\\f209';\n}\n\n.bp5-icon-issue-closed::before {\n  content: '\\f207';\n}\n\n.bp5-icon-issue-new::before {\n  content: '\\f208';\n}\n\n.bp5-icon-italic::before {\n  content: '\\f20a';\n}\n\n.bp5-icon-join-table::before {\n  content: '\\f20b';\n}\n\n.bp5-icon-key::before {\n  content: '\\f215';\n}\n\n.bp5-icon-key-backspace::before {\n  content: '\\f20c';\n}\n\n.bp5-icon-key-command::before {\n  content: '\\f20d';\n}\n\n.bp5-icon-key-control::before {\n  content: '\\f20e';\n}\n\n.bp5-icon-key-delete::before {\n  content: '\\f20f';\n}\n\n.bp5-icon-key-enter::before {\n  content: '\\f210';\n}\n\n.bp5-icon-key-escape::before {\n  content: '\\f211';\n}\n\n.bp5-icon-key-option::before {\n  content: '\\f212';\n}\n\n.bp5-icon-key-shift::before {\n  content: '\\f213';\n}\n\n.bp5-icon-key-tab::before {\n  content: '\\f214';\n}\n\n.bp5-icon-known-vehicle::before {\n  content: '\\f216';\n}\n\n.bp5-icon-lab-test::before {\n  content: '\\f217';\n}\n\n.bp5-icon-label::before {\n  content: '\\f218';\n}\n\n.bp5-icon-layer::before {\n  content: '\\f21a';\n}\n\n.bp5-icon-layer-outline::before {\n  content: '\\f219';\n}\n\n.bp5-icon-layers::before {\n  content: '\\f21b';\n}\n\n.bp5-icon-layout::before {\n  content: '\\f225';\n}\n\n.bp5-icon-layout-auto::before {\n  content: '\\f21c';\n}\n\n.bp5-icon-layout-balloon::before {\n  content: '\\f21d';\n}\n\n.bp5-icon-layout-bottom-row-three-tiles::before {\n  content: '\\f364';\n}\n\n.bp5-icon-layout-bottom-row-two-tiles::before {\n  content: '\\f363';\n}\n\n.bp5-icon-layout-circle::before {\n  content: '\\f21e';\n}\n\n.bp5-icon-layout-grid::before {\n  content: '\\f21f';\n}\n\n.bp5-icon-layout-group-by::before {\n  content: '\\f220';\n}\n\n.bp5-icon-layout-hierarchy::before {\n  content: '\\f221';\n}\n\n.bp5-icon-layout-left-column-three-tiles::before {\n  content: '\\f366';\n}\n\n.bp5-icon-layout-left-column-two-tiles::before {\n  content: '\\f365';\n}\n\n.bp5-icon-layout-linear::before {\n  content: '\\f222';\n}\n\n.bp5-icon-layout-right-column-three-tiles::before {\n  content: '\\f368';\n}\n\n.bp5-icon-layout-right-column-two-tiles::before {\n  content: '\\f367';\n}\n\n.bp5-icon-layout-skew-grid::before {\n  content: '\\f223';\n}\n\n.bp5-icon-layout-sorted-clusters::before {\n  content: '\\f224';\n}\n\n.bp5-icon-layout-three-columns::before {\n  content: '\\f361';\n}\n\n.bp5-icon-layout-three-rows::before {\n  content: '\\f362';\n}\n\n.bp5-icon-layout-top-row-three-tiles::before {\n  content: '\\f36a';\n}\n\n.bp5-icon-layout-top-row-two-tiles::before {\n  content: '\\f369';\n}\n\n.bp5-icon-layout-two-columns::before {\n  content: '\\f35f';\n}\n\n.bp5-icon-layout-two-rows::before {\n  content: '\\f360';\n}\n\n.bp5-icon-learning::before {\n  content: '\\f226';\n}\n\n.bp5-icon-left-join::before {\n  content: '\\f227';\n}\n\n.bp5-icon-lengthen-text::before {\n  content: '\\f33e';\n}\n\n.bp5-icon-less-than::before {\n  content: '\\f229';\n}\n\n.bp5-icon-less-than-or-equal-to::before {\n  content: '\\f228';\n}\n\n.bp5-icon-lifesaver::before {\n  content: '\\f22a';\n}\n\n.bp5-icon-lightbulb::before {\n  content: '\\f22b';\n}\n\n.bp5-icon-lightning::before {\n  content: '\\f22c';\n}\n\n.bp5-icon-link::before {\n  content: '\\f22d';\n}\n\n.bp5-icon-list::before {\n  content: '\\f230';\n}\n\n.bp5-icon-list-columns::before {\n  content: '\\f22e';\n}\n\n.bp5-icon-list-detail-view::before {\n  content: '\\f22f';\n}\n\n.bp5-icon-locate::before {\n  content: '\\f231';\n}\n\n.bp5-icon-lock::before {\n  content: '\\f232';\n}\n\n.bp5-icon-locomotive::before {\n  content: '\\f33b';\n}\n\n.bp5-icon-log-in::before {\n  content: '\\f233';\n}\n\n.bp5-icon-log-out::before {\n  content: '\\f234';\n}\n\n.bp5-icon-low-voltage-pole::before {\n  content: '\\f332';\n}\n\n.bp5-icon-manual::before {\n  content: '\\f235';\n}\n\n.bp5-icon-manually-entered-data::before {\n  content: '\\f236';\n}\n\n.bp5-icon-many-to-many::before {\n  content: '\\f237';\n}\n\n.bp5-icon-many-to-one::before {\n  content: '\\f238';\n}\n\n.bp5-icon-map::before {\n  content: '\\f23b';\n}\n\n.bp5-icon-map-create::before {\n  content: '\\f239';\n}\n\n.bp5-icon-map-marker::before {\n  content: '\\f23a';\n}\n\n.bp5-icon-maximize::before {\n  content: '\\f23c';\n}\n\n.bp5-icon-media::before {\n  content: '\\f23d';\n}\n\n.bp5-icon-menu::before {\n  content: '\\f240';\n}\n\n.bp5-icon-menu-closed::before {\n  content: '\\f23e';\n}\n\n.bp5-icon-menu-open::before {\n  content: '\\f23f';\n}\n\n.bp5-icon-merge-columns::before {\n  content: '\\f241';\n}\n\n.bp5-icon-merge-links::before {\n  content: '\\f242';\n}\n\n.bp5-icon-microphone::before {\n  content: '\\f343';\n}\n\n.bp5-icon-minimize::before {\n  content: '\\f243';\n}\n\n.bp5-icon-minus::before {\n  content: '\\f244';\n}\n\n.bp5-icon-mobile-phone::before {\n  content: '\\f245';\n}\n\n.bp5-icon-mobile-video::before {\n  content: '\\f246';\n}\n\n.bp5-icon-modal::before {\n  content: '\\f248';\n}\n\n.bp5-icon-modal-filled::before {\n  content: '\\f247';\n}\n\n.bp5-icon-model::before {\n  content: '\\f33d';\n}\n\n.bp5-icon-moon::before {\n  content: '\\f249';\n}\n\n.bp5-icon-more::before {\n  content: '\\f24a';\n}\n\n.bp5-icon-mountain::before {\n  content: '\\f24b';\n}\n\n.bp5-icon-move::before {\n  content: '\\f24c';\n}\n\n.bp5-icon-mugshot::before {\n  content: '\\f24d';\n}\n\n.bp5-icon-multi-select::before {\n  content: '\\f24e';\n}\n\n.bp5-icon-music::before {\n  content: '\\f24f';\n}\n\n.bp5-icon-nest::before {\n  content: '\\f250';\n}\n\n.bp5-icon-new-drawing::before {\n  content: '\\f251';\n}\n\n.bp5-icon-new-grid-item::before {\n  content: '\\f252';\n}\n\n.bp5-icon-new-layer::before {\n  content: '\\f253';\n}\n\n.bp5-icon-new-layers::before {\n  content: '\\f254';\n}\n\n.bp5-icon-new-link::before {\n  content: '\\f255';\n}\n\n.bp5-icon-new-object::before {\n  content: '\\f256';\n}\n\n.bp5-icon-new-person::before {\n  content: '\\f257';\n}\n\n.bp5-icon-new-prescription::before {\n  content: '\\f258';\n}\n\n.bp5-icon-new-shield::before {\n  content: '\\f349';\n}\n\n.bp5-icon-new-text-box::before {\n  content: '\\f259';\n}\n\n.bp5-icon-ninja::before {\n  content: '\\f25a';\n}\n\n.bp5-icon-not-equal-to::before {\n  content: '\\f25b';\n}\n\n.bp5-icon-notifications::before {\n  content: '\\f25e';\n}\n\n.bp5-icon-notifications-snooze::before {\n  content: '\\f25c';\n}\n\n.bp5-icon-notifications-updated::before {\n  content: '\\f25d';\n}\n\n.bp5-icon-numbered-list::before {\n  content: '\\f25f';\n}\n\n.bp5-icon-numerical::before {\n  content: '\\f260';\n}\n\n.bp5-icon-office::before {\n  content: '\\f261';\n}\n\n.bp5-icon-offline::before {\n  content: '\\f262';\n}\n\n.bp5-icon-oil-field::before {\n  content: '\\f263';\n}\n\n.bp5-icon-one-column::before {\n  content: '\\f264';\n}\n\n.bp5-icon-one-to-many::before {\n  content: '\\f265';\n}\n\n.bp5-icon-one-to-one::before {\n  content: '\\f266';\n}\n\n.bp5-icon-open-application::before {\n  content: '\\f32b';\n}\n\n.bp5-icon-outdated::before {\n  content: '\\f267';\n}\n\n.bp5-icon-page-layout::before {\n  content: '\\f268';\n}\n\n.bp5-icon-panel-stats::before {\n  content: '\\f269';\n}\n\n.bp5-icon-panel-table::before {\n  content: '\\f26a';\n}\n\n.bp5-icon-paperclip::before {\n  content: '\\f26b';\n}\n\n.bp5-icon-paragraph::before {\n  content: '\\f26c';\n}\n\n.bp5-icon-paste-variable::before {\n  content: '\\f346';\n}\n\n.bp5-icon-path::before {\n  content: '\\f26e';\n}\n\n.bp5-icon-path-search::before {\n  content: '\\f26d';\n}\n\n.bp5-icon-pause::before {\n  content: '\\f26f';\n}\n\n.bp5-icon-people::before {\n  content: '\\f270';\n}\n\n.bp5-icon-percentage::before {\n  content: '\\f271';\n}\n\n.bp5-icon-person::before {\n  content: '\\f272';\n}\n\n.bp5-icon-phone::before {\n  content: '\\f273';\n}\n\n.bp5-icon-phone-call::before {\n  content: '\\f347';\n}\n\n.bp5-icon-phone-forward::before {\n  content: '\\f348';\n}\n\n.bp5-icon-pie-chart::before {\n  content: '\\f274';\n}\n\n.bp5-icon-pin::before {\n  content: '\\f275';\n}\n\n.bp5-icon-pivot::before {\n  content: '\\f277';\n}\n\n.bp5-icon-pivot-table::before {\n  content: '\\f276';\n}\n\n.bp5-icon-play::before {\n  content: '\\f278';\n}\n\n.bp5-icon-playbook::before {\n  content: '\\f324';\n}\n\n.bp5-icon-plus::before {\n  content: '\\f279';\n}\n\n.bp5-icon-polygon-filter::before {\n  content: '\\f27a';\n}\n\n.bp5-icon-power::before {\n  content: '\\f27b';\n}\n\n.bp5-icon-predictive-analysis::before {\n  content: '\\f27c';\n}\n\n.bp5-icon-prescription::before {\n  content: '\\f27d';\n}\n\n.bp5-icon-presentation::before {\n  content: '\\f27e';\n}\n\n.bp5-icon-print::before {\n  content: '\\f27f';\n}\n\n.bp5-icon-projects::before {\n  content: '\\f280';\n}\n\n.bp5-icon-properties::before {\n  content: '\\f281';\n}\n\n.bp5-icon-property::before {\n  content: '\\f282';\n}\n\n.bp5-icon-publish-function::before {\n  content: '\\f283';\n}\n\n.bp5-icon-pulse::before {\n  content: '\\f284';\n}\n\n.bp5-icon-rain::before {\n  content: '\\f285';\n}\n\n.bp5-icon-random::before {\n  content: '\\f286';\n}\n\n.bp5-icon-record::before {\n  content: '\\f287';\n}\n\n.bp5-icon-rect-height::before {\n  content: '\\f325';\n}\n\n.bp5-icon-rect-width::before {\n  content: '\\f326';\n}\n\n.bp5-icon-rectangle::before {\n  content: '\\f321';\n}\n\n.bp5-icon-redo::before {\n  content: '\\f288';\n}\n\n.bp5-icon-refresh::before {\n  content: '\\f289';\n}\n\n.bp5-icon-regex::before {\n  content: '\\f32f';\n}\n\n.bp5-icon-regression-chart::before {\n  content: '\\f28a';\n}\n\n.bp5-icon-remove::before {\n  content: '\\f290';\n}\n\n.bp5-icon-remove-column::before {\n  content: '\\f28d';\n}\n\n.bp5-icon-remove-column-left::before {\n  content: '\\f28b';\n}\n\n.bp5-icon-remove-column-right::before {\n  content: '\\f28c';\n}\n\n.bp5-icon-remove-row-bottom::before {\n  content: '\\f28e';\n}\n\n.bp5-icon-remove-row-top::before {\n  content: '\\f28f';\n}\n\n.bp5-icon-repeat::before {\n  content: '\\f291';\n}\n\n.bp5-icon-reset::before {\n  content: '\\f292';\n}\n\n.bp5-icon-resolve::before {\n  content: '\\f293';\n}\n\n.bp5-icon-rig::before {\n  content: '\\f294';\n}\n\n.bp5-icon-right-join::before {\n  content: '\\f295';\n}\n\n.bp5-icon-ring::before {\n  content: '\\f296';\n}\n\n.bp5-icon-rocket::before {\n  content: '\\f298';\n}\n\n.bp5-icon-rocket-slant::before {\n  content: '\\f297';\n}\n\n.bp5-icon-rotate-document::before {\n  content: '\\f299';\n}\n\n.bp5-icon-rotate-page::before {\n  content: '\\f29a';\n}\n\n.bp5-icon-route::before {\n  content: '\\f29b';\n}\n\n.bp5-icon-satellite::before {\n  content: '\\f29c';\n}\n\n.bp5-icon-saved::before {\n  content: '\\f29d';\n}\n\n.bp5-icon-scatter-plot::before {\n  content: '\\f29e';\n}\n\n.bp5-icon-search::before {\n  content: '\\f2a2';\n}\n\n.bp5-icon-search-around::before {\n  content: '\\f29f';\n}\n\n.bp5-icon-search-template::before {\n  content: '\\f2a0';\n}\n\n.bp5-icon-search-text::before {\n  content: '\\f2a1';\n}\n\n.bp5-icon-segmented-control::before {\n  content: '\\f2a3';\n}\n\n.bp5-icon-select::before {\n  content: '\\f2a4';\n}\n\n.bp5-icon-selection::before {\n  content: '\\f2a5';\n}\n\n.bp5-icon-send-backward::before {\n  content: '\\f355';\n}\n\n.bp5-icon-send-message::before {\n  content: '\\f2a6';\n}\n\n.bp5-icon-send-to::before {\n  content: '\\f2a9';\n}\n\n.bp5-icon-send-to-graph::before {\n  content: '\\f2a7';\n}\n\n.bp5-icon-send-to-map::before {\n  content: '\\f2a8';\n}\n\n.bp5-icon-sensor::before {\n  content: '\\f33c';\n}\n\n.bp5-icon-series-add::before {\n  content: '\\f2aa';\n}\n\n.bp5-icon-series-configuration::before {\n  content: '\\f2ab';\n}\n\n.bp5-icon-series-derived::before {\n  content: '\\f2ac';\n}\n\n.bp5-icon-series-filtered::before {\n  content: '\\f2ad';\n}\n\n.bp5-icon-series-search::before {\n  content: '\\f2ae';\n}\n\n.bp5-icon-settings::before {\n  content: '\\f2af';\n}\n\n.bp5-icon-shapes::before {\n  content: '\\f2b0';\n}\n\n.bp5-icon-share::before {\n  content: '\\f2b1';\n}\n\n.bp5-icon-shared-filter::before {\n  content: '\\f2b2';\n}\n\n.bp5-icon-shield::before {\n  content: '\\f2b3';\n}\n\n.bp5-icon-ship::before {\n  content: '\\f2b4';\n}\n\n.bp5-icon-shop::before {\n  content: '\\f2b5';\n}\n\n.bp5-icon-shopping-cart::before {\n  content: '\\f2b6';\n}\n\n.bp5-icon-shorten-text::before {\n  content: '\\f33f';\n}\n\n.bp5-icon-signal-search::before {\n  content: '\\f2b7';\n}\n\n.bp5-icon-sim-card::before {\n  content: '\\f2b8';\n}\n\n.bp5-icon-slash::before {\n  content: '\\f2b9';\n}\n\n.bp5-icon-small-cross::before {\n  content: '\\f2ba';\n}\n\n.bp5-icon-small-info-sign::before {\n  content: '\\f334';\n}\n\n.bp5-icon-small-minus::before {\n  content: '\\f2bb';\n}\n\n.bp5-icon-small-plus::before {\n  content: '\\f2bc';\n}\n\n.bp5-icon-small-square::before {\n  content: '\\f2bd';\n}\n\n.bp5-icon-small-tick::before {\n  content: '\\f2be';\n}\n\n.bp5-icon-snowflake::before {\n  content: '\\f2bf';\n}\n\n.bp5-icon-soccer-ball::before {\n  content: '\\f350';\n}\n\n.bp5-icon-social-media::before {\n  content: '\\f2c0';\n}\n\n.bp5-icon-sort::before {\n  content: '\\f2c7';\n}\n\n.bp5-icon-sort-alphabetical::before {\n  content: '\\f2c2';\n}\n\n.bp5-icon-sort-alphabetical-desc::before {\n  content: '\\f2c1';\n}\n\n.bp5-icon-sort-asc::before {\n  content: '\\f2c3';\n}\n\n.bp5-icon-sort-desc::before {\n  content: '\\f2c4';\n}\n\n.bp5-icon-sort-numerical::before {\n  content: '\\f2c6';\n}\n\n.bp5-icon-sort-numerical-desc::before {\n  content: '\\f2c5';\n}\n\n.bp5-icon-spell-check::before {\n  content: '\\f340';\n}\n\n.bp5-icon-split-columns::before {\n  content: '\\f2c8';\n}\n\n.bp5-icon-sports-stadium::before {\n  content: '\\f351';\n}\n\n.bp5-icon-square::before {\n  content: '\\f2c9';\n}\n\n.bp5-icon-stacked-chart::before {\n  content: '\\f2ca';\n}\n\n.bp5-icon-stadium-geometry::before {\n  content: '\\f2cb';\n}\n\n.bp5-icon-star::before {\n  content: '\\f2cd';\n}\n\n.bp5-icon-star-empty::before {\n  content: '\\f2cc';\n}\n\n.bp5-icon-step-backward::before {\n  content: '\\f2ce';\n}\n\n.bp5-icon-step-chart::before {\n  content: '\\f2cf';\n}\n\n.bp5-icon-step-forward::before {\n  content: '\\f2d0';\n}\n\n.bp5-icon-stop::before {\n  content: '\\f2d1';\n}\n\n.bp5-icon-stopwatch::before {\n  content: '\\f2d2';\n}\n\n.bp5-icon-strikethrough::before {\n  content: '\\f2d3';\n}\n\n.bp5-icon-style::before {\n  content: '\\f2d4';\n}\n\n.bp5-icon-subscript::before {\n  content: '\\f339';\n}\n\n.bp5-icon-superscript::before {\n  content: '\\f33a';\n}\n\n.bp5-icon-swap-horizontal::before {\n  content: '\\f2d5';\n}\n\n.bp5-icon-swap-vertical::before {\n  content: '\\f2d6';\n}\n\n.bp5-icon-switch::before {\n  content: '\\f2d7';\n}\n\n.bp5-icon-symbol-circle::before {\n  content: '\\f2d8';\n}\n\n.bp5-icon-symbol-cross::before {\n  content: '\\f2d9';\n}\n\n.bp5-icon-symbol-diamond::before {\n  content: '\\f2da';\n}\n\n.bp5-icon-symbol-rectangle::before {\n  content: '\\f322';\n}\n\n.bp5-icon-symbol-square::before {\n  content: '\\f2db';\n}\n\n.bp5-icon-symbol-triangle-down::before {\n  content: '\\f2dc';\n}\n\n.bp5-icon-symbol-triangle-up::before {\n  content: '\\f2dd';\n}\n\n.bp5-icon-syringe::before {\n  content: '\\f2de';\n}\n\n.bp5-icon-tag::before {\n  content: '\\f2df';\n}\n\n.bp5-icon-take-action::before {\n  content: '\\f2e0';\n}\n\n.bp5-icon-tank::before {\n  content: '\\f2e1';\n}\n\n.bp5-icon-target::before {\n  content: '\\f2e2';\n}\n\n.bp5-icon-taxi::before {\n  content: '\\f2e3';\n}\n\n.bp5-icon-team::before {\n  content: '\\f352';\n}\n\n.bp5-icon-temperature::before {\n  content: '\\f2e4';\n}\n\n.bp5-icon-text-highlight::before {\n  content: '\\f2e5';\n}\n\n.bp5-icon-th::before {\n  content: '\\f2ea';\n}\n\n.bp5-icon-th-derived::before {\n  content: '\\f2e6';\n}\n\n.bp5-icon-th-disconnect::before {\n  content: '\\f2e7';\n}\n\n.bp5-icon-th-filtered::before {\n  content: '\\f2e8';\n}\n\n.bp5-icon-th-list::before {\n  content: '\\f2e9';\n}\n\n.bp5-icon-third-party::before {\n  content: '\\f2eb';\n}\n\n.bp5-icon-thumbs-down::before {\n  content: '\\f2ec';\n}\n\n.bp5-icon-thumbs-up::before {\n  content: '\\f2ed';\n}\n\n.bp5-icon-tick::before {\n  content: '\\f2ef';\n}\n\n.bp5-icon-tick-circle::before {\n  content: '\\f2ee';\n}\n\n.bp5-icon-time::before {\n  content: '\\f2f0';\n}\n\n.bp5-icon-timeline-area-chart::before {\n  content: '\\f2f1';\n}\n\n.bp5-icon-timeline-bar-chart::before {\n  content: '\\f2f2';\n}\n\n.bp5-icon-timeline-events::before {\n  content: '\\f2f3';\n}\n\n.bp5-icon-timeline-line-chart::before {\n  content: '\\f2f4';\n}\n\n.bp5-icon-tint::before {\n  content: '\\f2f5';\n}\n\n.bp5-icon-torch::before {\n  content: '\\f2f6';\n}\n\n.bp5-icon-tractor::before {\n  content: '\\f2f7';\n}\n\n.bp5-icon-train::before {\n  content: '\\f2f8';\n}\n\n.bp5-icon-translate::before {\n  content: '\\f2f9';\n}\n\n.bp5-icon-trash::before {\n  content: '\\f2fa';\n}\n\n.bp5-icon-tree::before {\n  content: '\\f2fb';\n}\n\n.bp5-icon-trending-down::before {\n  content: '\\f2fc';\n}\n\n.bp5-icon-trending-up::before {\n  content: '\\f2fd';\n}\n\n.bp5-icon-trophy::before {\n  content: '\\f34f';\n}\n\n.bp5-icon-truck::before {\n  content: '\\f2fe';\n}\n\n.bp5-icon-two-columns::before {\n  content: '\\f2ff';\n}\n\n.bp5-icon-unarchive::before {\n  content: '\\f300';\n}\n\n.bp5-icon-underline::before {\n  content: '\\f301';\n}\n\n.bp5-icon-undo::before {\n  content: '\\f302';\n}\n\n.bp5-icon-ungroup-objects::before {\n  content: '\\f303';\n}\n\n.bp5-icon-unknown-vehicle::before {\n  content: '\\f304';\n}\n\n.bp5-icon-unlink::before {\n  content: '\\f345';\n}\n\n.bp5-icon-unlock::before {\n  content: '\\f305';\n}\n\n.bp5-icon-unpin::before {\n  content: '\\f306';\n}\n\n.bp5-icon-unresolve::before {\n  content: '\\f307';\n}\n\n.bp5-icon-updated::before {\n  content: '\\f308';\n}\n\n.bp5-icon-upload::before {\n  content: '\\f309';\n}\n\n.bp5-icon-user::before {\n  content: '\\f30a';\n}\n\n.bp5-icon-variable::before {\n  content: '\\f30b';\n}\n\n.bp5-icon-vector::before {\n  content: '\\f35e';\n}\n\n.bp5-icon-vertical-bar-chart-asc::before {\n  content: '\\f30c';\n}\n\n.bp5-icon-vertical-bar-chart-desc::before {\n  content: '\\f30d';\n}\n\n.bp5-icon-vertical-distribution::before {\n  content: '\\f30e';\n}\n\n.bp5-icon-vertical-inbetween::before {\n  content: '\\f32a';\n}\n\n.bp5-icon-video::before {\n  content: '\\f30f';\n}\n\n.bp5-icon-virus::before {\n  content: '\\f310';\n}\n\n.bp5-icon-volume-down::before {\n  content: '\\f311';\n}\n\n.bp5-icon-volume-off::before {\n  content: '\\f312';\n}\n\n.bp5-icon-volume-up::before {\n  content: '\\f313';\n}\n\n.bp5-icon-walk::before {\n  content: '\\f314';\n}\n\n.bp5-icon-warning-sign::before {\n  content: '\\f315';\n}\n\n.bp5-icon-waterfall-chart::before {\n  content: '\\f316';\n}\n\n.bp5-icon-waves::before {\n  content: '\\f317';\n}\n\n.bp5-icon-widget::before {\n  content: '\\f31b';\n}\n\n.bp5-icon-widget-button::before {\n  content: '\\f318';\n}\n\n.bp5-icon-widget-footer::before {\n  content: '\\f319';\n}\n\n.bp5-icon-widget-header::before {\n  content: '\\f31a';\n}\n\n.bp5-icon-wind::before {\n  content: '\\f31c';\n}\n\n.bp5-icon-wrench::before {\n  content: '\\f31d';\n}\n\n.bp5-icon-zoom-in::before {\n  content: '\\f31e';\n}\n\n.bp5-icon-zoom-out::before {\n  content: '\\f31f';\n}\n\n.bp5-icon-zoom-to-fit::before {\n  content: '\\f320';\n}\n.bp5-submenu > .bp5-popover-wrapper {\n  display: block;\n}\n.bp5-submenu .bp5-popover-target {\n  display: block;\n}\n.bp5-submenu .bp5-popover-target.bp5-popover-open > .bp5-menu-item {\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item:not([class*='bp5-intent-']) {\n  background-color: rgba(143, 153, 168, 0.15);\n  color: inherit;\n  cursor: pointer;\n  text-decoration: none;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item:not([class*='bp5-intent-']) {\n  color: inherit;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item:not([class*='bp5-intent-'])\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item:not([class*='bp5-intent-'])\n  .bp5-submenu-icon {\n  color: #abb3bf;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'],\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active {\n  background-color: rgba(45, 114, 210, 0.1);\n  color: #215db0;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-menu-item-label,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-menu-item-label,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-menu-item-selected-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-menu-item-selected-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-menu-item-selected-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-submenu-icon {\n  color: #215db0;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.1);\n  color: #1c6e42;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success\n  .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.1);\n  color: #935610;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning\n  .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.1);\n  color: #ac2f33;\n}\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger\n  .bp5-submenu-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger::before,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger\n  .bp5-submenu-icon {\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-'],\n  .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-']:hover,\n  .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-']:active {\n    background-color: highlight;\n  }\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'],\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-menu-item-selected-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-menu-item-selected-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-menu-item-selected-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active\n  .bp5-submenu-icon {\n  color: #8abbff;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-success\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-success\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-success\n  .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-warning\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-warning\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-warning\n  .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-'].bp5-intent-danger\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:hover.bp5-intent-danger\n  .bp5-submenu-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger::before,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger\n  .bp5-menu-item-icon,\n.bp5-dark\n  .bp5-submenu\n  .bp5-popover-target.bp5-popover-open\n  > .bp5-menu-item[class*='bp5-intent-']:active.bp5-intent-danger\n  .bp5-submenu-icon {\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark\n    .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-'],\n  .bp5-dark\n    .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-']:hover,\n  .bp5-dark\n    .bp5-submenu\n    .bp5-popover-target.bp5-popover-open\n    > .bp5-menu-item[class*='bp5-intent-']:active {\n    background-color: highlight;\n  }\n}\n.bp5-submenu.bp5-popover {\n  box-shadow: none;\n  padding: 0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover,\n.bp5-submenu.bp5-popover.bp5-dark {\n  box-shadow: none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content,\n.bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content {\n  box-shadow: 0 0 0 1px hsl(215, 3%, 38%),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4),\n    0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-menu {\n  background: #ffffff;\n  border-radius: 2px;\n  color: #1c2127;\n  list-style: none;\n  margin: 0;\n  min-width: 180px;\n  padding: 5px;\n  text-align: left;\n}\n\n.bp5-menu-divider {\n  border-top: 1px solid rgba(17, 20, 24, 0.15);\n  display: block;\n  margin: 5px -5px;\n}\n.bp5-dark .bp5-menu-divider {\n  border-top-color: rgba(255, 255, 255, 0.2);\n}\n\n.bp5-menu-item {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  border-radius: 2px;\n  color: inherit;\n  line-height: 20px;\n  padding: 5px 7px;\n  text-decoration: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-menu-item > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-menu-item > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-menu-item::before,\n.bp5-menu-item > * {\n  margin-right: 7px;\n}\n.bp5-menu-item:empty::before,\n.bp5-menu-item > :last-child {\n  margin-right: 0;\n}\n.bp5-menu-item > .bp5-fill {\n  word-break: break-word;\n}\n.bp5-menu-item .bp5-menu-item-icon {\n  display: flex;\n  flex-direction: column;\n  height: 20px;\n  justify-content: center;\n}\n.bp5-menu-item .bp5-menu-item-label {\n  color: #5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-menu-item-icon,\n.bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-menu-item .bp5-submenu-icon {\n  color: #5f6b7c;\n}\n.bp5-menu-item::before,\n.bp5-menu-item .bp5-submenu-icon {\n  margin-top: 2px;\n}\n.bp5-menu-item:hover {\n  background-color: rgba(143, 153, 168, 0.15);\n  color: inherit;\n  cursor: pointer;\n  text-decoration: none;\n}\n.bp5-menu-item:active {\n  background-color: rgba(143, 153, 168, 0.3);\n}\n.bp5-menu-item:active .bp5-menu-item-label {\n  color: #1c2127;\n}\n.bp5-menu-item.bp5-active {\n  background-color: rgba(45, 114, 210, 0.1);\n  color: #215db0;\n}\n.bp5-menu-item.bp5-active .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-menu-item.bp5-active::before,\n.bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-active .bp5-submenu-icon {\n  color: #215db0;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.1);\n  color: #1c6e42;\n}\n.bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.1);\n  color: #935610;\n}\n.bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.1);\n  color: #ac2f33;\n}\n.bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon {\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-menu-item.bp5-active {\n    background-color: highlight;\n  }\n}\n.bp5-menu-item.bp5-menu-item-is-selectable {\n  padding-left: 20px;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable.bp5-selected {\n  padding-left: 0;\n}\n.bp5-menu-item.bp5-menu-item-is-selectable .bp5-menu-item-selected-icon {\n  align-self: center;\n  margin: 0 2px;\n}\n.bp5-menu-item.bp5-disabled {\n  background-color: inherit !important;\n  color: rgba(95, 107, 124, 0.6) !important;\n  cursor: not-allowed !important;\n  outline: none !important;\n}\n.bp5-menu-item.bp5-disabled::before,\n.bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-menu-item.bp5-disabled .bp5-submenu-icon {\n  color: rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-disabled .bp5-menu-item-label {\n  color: rgba(95, 107, 124, 0.6) !important;\n}\n.bp5-menu-item.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-menu-item.bp5-intent-primary::before,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-primary .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-menu-item.bp5-intent-primary:hover {\n  background-color: rgba(45, 114, 210, 0.1);\n}\n.bp5-menu-item.bp5-intent-primary:active,\n.bp5-menu-item.bp5-intent-primary.bp5-active {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #184a90;\n}\n.bp5-menu-item.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-menu-item.bp5-intent-success::before,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-success .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-menu-item.bp5-intent-success:hover {\n  background-color: rgba(35, 133, 81, 0.1);\n}\n.bp5-menu-item.bp5-intent-success:active,\n.bp5-menu-item.bp5-intent-success.bp5-active {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #165a36;\n}\n.bp5-menu-item.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-menu-item.bp5-intent-warning::before,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-warning .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-menu-item.bp5-intent-warning:hover {\n  background-color: rgba(200, 118, 25, 0.1);\n}\n.bp5-menu-item.bp5-intent-warning:active,\n.bp5-menu-item.bp5-intent-warning.bp5-active {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #77450d;\n}\n.bp5-menu-item.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-menu-item.bp5-intent-danger::before,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-menu-item.bp5-intent-danger .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-menu-item.bp5-intent-danger:hover {\n  background-color: rgba(205, 66, 70, 0.1);\n}\n.bp5-menu-item.bp5-intent-danger:active,\n.bp5-menu-item.bp5-intent-danger.bp5-active {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #8e292c;\n}\n.bp5-menu-item::before {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  margin-right: 7px;\n}\n.bp5-large .bp5-menu-item {\n  font-size: 16px;\n  line-height: 22px;\n  padding-bottom: 9px;\n  padding-top: 9px;\n}\n.bp5-large .bp5-menu-item .bp5-menu-item-icon {\n  height: 22px;\n}\n.bp5-large .bp5-menu-item::before,\n.bp5-large .bp5-menu-item .bp5-submenu-icon {\n  margin-top: 3px;\n}\n.bp5-large .bp5-menu-item::before {\n  font-family: 'blueprint-icons-20', sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 20px;\n  line-height: 1;\n  width: 20px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  margin-right: 10px;\n}\n.bp5-small .bp5-menu-item {\n  padding-bottom: 2px;\n  padding-top: 2px;\n}\n\nbutton.bp5-menu-item {\n  background: none;\n  border: none;\n  text-align: left;\n  width: 100%;\n}\n.bp5-menu-header {\n  border-top: 1px solid rgba(17, 20, 24, 0.15);\n  display: block;\n  margin: 5px -5px;\n  cursor: default;\n  padding-left: 2px;\n}\n.bp5-dark .bp5-menu-header {\n  border-top-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-menu-header:first-of-type {\n  border-top: none;\n}\n.bp5-menu-header > h6 {\n  color: #1c2127;\n  font-weight: 600;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  line-height: 17px;\n  margin: 0;\n  padding: 10px 7px 0 6px;\n}\n.bp5-dark .bp5-menu-header > h6 {\n  color: #f6f7f9;\n}\n.bp5-menu-header > h6.bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-menu-header > h6.bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-menu-header:first-of-type > h6 {\n  padding-top: 0;\n}\n.bp5-large .bp5-menu-header > h6 {\n  font-size: 18px;\n  padding-bottom: 5px;\n  padding-top: 15px;\n}\n.bp5-large .bp5-menu-header:first-of-type > h6 {\n  padding-top: 0;\n}\n\n.bp5-dark .bp5-menu {\n  background: #2f343c;\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-menu-item {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item .bp5-menu-item-label {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-menu-item::before,\n.bp5-dark .bp5-menu-item .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item .bp5-submenu-icon {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-menu-item:hover {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item:hover .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item:hover .bp5-submenu-icon {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-menu-item:active {\n  background-color: rgba(143, 153, 168, 0.3);\n}\n.bp5-dark .bp5-menu-item:active .bp5-menu-item-label {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-menu-item.bp5-active {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active::before,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-active .bp5-submenu-icon {\n  color: #8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-success .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-warning .bp5-submenu-icon {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-active.bp5-intent-danger .bp5-submenu-icon {\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-menu-item.bp5-active {\n    background-color: highlight;\n  }\n}\n.bp5-dark .bp5-menu-item.bp5-disabled {\n  color: rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled::before,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-submenu-icon {\n  color: rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-disabled .bp5-menu-item-label {\n  color: rgba(171, 179, 191, 0.6) !important;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary::before,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-primary .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:hover {\n  background-color: rgba(45, 114, 210, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-primary:active,\n.bp5-dark .bp5-menu-item.bp5-intent-primary.bp5-active {\n  background-color: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success::before,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-success .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:hover {\n  background-color: rgba(35, 133, 81, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-success:active,\n.bp5-dark .bp5-menu-item.bp5-intent-success.bp5-active {\n  background-color: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning::before,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-warning .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:hover {\n  background-color: rgba(200, 118, 25, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-warning:active,\n.bp5-dark .bp5-menu-item.bp5-intent-warning.bp5-active {\n  background-color: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger::before,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-selected-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-submenu-icon,\n.bp5-dark .bp5-menu-item.bp5-intent-danger .bp5-menu-item-label {\n  color: inherit;\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:hover {\n  background-color: rgba(205, 66, 70, 0.2);\n}\n.bp5-dark .bp5-menu-item.bp5-intent-danger:active,\n.bp5-dark .bp5-menu-item.bp5-intent-danger.bp5-active {\n  background-color: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-menu-divider,\n.bp5-dark .bp5-menu-header {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-dark .bp5-menu-header > h6 {\n  color: #f6f7f9;\n}\n\n.bp5-label .bp5-menu {\n  margin-top: 5px;\n}\n.bp5-navbar {\n  background-color: #ffffff;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  height: 50px;\n  padding: 0 15px;\n  position: relative;\n  width: 100%;\n  z-index: 10;\n}\n.bp5-navbar.bp5-dark,\n.bp5-dark .bp5-navbar {\n  background-color: #2f343c;\n}\n.bp5-navbar.bp5-dark {\n  box-shadow: inset inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-navbar {\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n}\n.bp5-navbar.bp5-fixed-top {\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-navbar {\n    border: 1px solid buttonborder;\n  }\n}\n\n.bp5-navbar-heading {\n  font-size: 16px;\n  margin-right: 15px;\n}\n\n.bp5-navbar-group {\n  align-items: center;\n  display: flex;\n  height: 50px;\n}\n.bp5-navbar-group.bp5-align-left {\n  float: left;\n}\n.bp5-navbar-group.bp5-align-right {\n  float: right;\n}\n\n.bp5-navbar-divider {\n  border-left: 1px solid rgba(17, 20, 24, 0.15);\n  height: 20px;\n  margin: 0 10px;\n}\n.bp5-dark .bp5-navbar-divider {\n  border-left-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-non-ideal-state {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  color: #5f6b7c;\n  height: 100%;\n  justify-content: center;\n  text-align: center;\n  width: 100%;\n}\n.bp5-non-ideal-state > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-non-ideal-state > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-non-ideal-state::before,\n.bp5-non-ideal-state > * {\n  margin-bottom: 20px;\n}\n.bp5-non-ideal-state:empty::before,\n.bp5-non-ideal-state > :last-child {\n  margin-bottom: 0;\n}\n.bp5-non-ideal-state > * {\n  max-width: 400px;\n}\n.bp5-non-ideal-state .bp5-heading {\n  color: #5f6b7c;\n  line-height: 20px;\n  margin-bottom: 10px;\n}\n.bp5-non-ideal-state .bp5-heading:only-child {\n  margin-bottom: 0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal {\n  display: flex;\n  flex-direction: row;\n  text-align: left;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > * {\n  margin-right: 20px;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal:empty::before,\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > :last-child {\n  margin-right: 0;\n}\n.bp5-non-ideal-state.bp5-non-ideal-state-horizontal > * {\n  margin-bottom: 0;\n}\n.bp5-dark .bp5-non-ideal-state {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-non-ideal-state .bp5-heading {\n  color: #abb3bf;\n}\n\n.bp5-non-ideal-state-visual {\n  color: #8f99a8;\n}\n\n.bp5-overflow-list {\n  display: flex;\n  flex-wrap: nowrap;\n  min-width: 0;\n}\n\n.bp5-overflow-list-spacer {\n  flex-shrink: 1;\n  width: 1px;\n}\nbody.bp5-overlay-open {\n  overflow: hidden;\n}\n\n.bp5-overlay {\n  bottom: 0;\n  left: 0;\n  position: static;\n  right: 0;\n  top: 0;\n  z-index: 20;\n}\n.bp5-overlay:not(.bp5-overlay-open) {\n  pointer-events: none;\n}\n.bp5-overlay.bp5-overlay-container {\n  overflow: hidden;\n  position: fixed;\n}\n.bp5-overlay.bp5-overlay-container.bp5-overlay-inline {\n  position: absolute;\n}\n.bp5-overlay.bp5-overlay-scroll-container {\n  overflow: auto;\n  position: fixed;\n}\n.bp5-overlay.bp5-overlay-scroll-container.bp5-overlay-inline {\n  position: absolute;\n}\n.bp5-overlay.bp5-overlay-inline {\n  display: inline;\n  overflow: visible;\n}\n\n.bp5-overlay-content {\n  position: fixed;\n  z-index: 20;\n}\n.bp5-overlay-inline .bp5-overlay-content,\n.bp5-overlay-scroll-container .bp5-overlay-content {\n  position: absolute;\n}\n\n.bp5-overlay-backdrop {\n  bottom: 0;\n  left: 0;\n  position: fixed;\n  right: 0;\n  top: 0;\n  opacity: 1;\n  background-color: rgba(17, 20, 24, 0.7);\n  overflow: auto;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  z-index: 20;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter,\n.bp5-overlay-backdrop.bp5-overlay-appear {\n  opacity: 0;\n}\n.bp5-overlay-backdrop.bp5-overlay-enter-active,\n.bp5-overlay-backdrop.bp5-overlay-appear-active {\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop.bp5-overlay-exit {\n  opacity: 1;\n}\n.bp5-overlay-backdrop.bp5-overlay-exit-active {\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 200ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-overlay-backdrop:focus {\n  outline: none;\n}\n.bp5-overlay-inline .bp5-overlay-backdrop {\n  position: absolute;\n}\n.bp5-panel-stack {\n  overflow: hidden;\n  position: relative;\n}\n\n.bp5-panel-stack-header {\n  align-items: center;\n  box-shadow: 0 1px rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex-shrink: 0;\n  height: 30px;\n  z-index: 1;\n}\n.bp5-dark .bp5-panel-stack-header {\n  box-shadow: 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack-header > span {\n  align-items: stretch;\n  display: flex;\n  flex: 1;\n}\n.bp5-panel-stack-header .bp5-heading {\n  margin: 0 5px;\n}\n\n.bp5-button.bp5-panel-stack-header-back {\n  margin-left: 5px;\n  padding-left: 0;\n  white-space: nowrap;\n}\n.bp5-button.bp5-panel-stack-header-back .bp5-icon {\n  margin: 0 2px;\n}\n\n.bp5-panel-stack-view {\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  background-color: #ffffff;\n  border-right: 1px solid rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex-direction: column;\n  margin-right: -1px;\n  overflow-y: auto;\n  z-index: 1;\n}\n.bp5-dark .bp5-panel-stack-view {\n  background-color: #2f343c;\n}\n.bp5-panel-stack-view:nth-last-child(n + 4) {\n  display: none;\n}\n\n.bp5-panel-stack-push .bp5-panel-stack-enter,\n.bp5-panel-stack-push .bp5-panel-stack-appear {\n  transform: translateX(100%);\n  opacity: 0;\n}\n.bp5-panel-stack-push .bp5-panel-stack-enter-active,\n.bp5-panel-stack-push .bp5-panel-stack-appear-active {\n  transform: translate(0%);\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit {\n  transform: translate(0%);\n  opacity: 1;\n}\n.bp5-panel-stack-push .bp5-panel-stack-exit-active {\n  transform: translateX(-50%);\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n\n.bp5-panel-stack-pop .bp5-panel-stack-enter,\n.bp5-panel-stack-pop .bp5-panel-stack-appear {\n  transform: translateX(-50%);\n  opacity: 0;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-enter-active,\n.bp5-panel-stack-pop .bp5-panel-stack-appear-active {\n  transform: translate(0%);\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit {\n  transform: translate(0%);\n  opacity: 1;\n}\n.bp5-panel-stack-pop .bp5-panel-stack-exit-active {\n  transform: translateX(100%);\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-panel-stack2 {\n  overflow: hidden;\n  position: relative;\n}\n\n.bp5-panel-stack2-header {\n  align-items: center;\n  box-shadow: 0 1px rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex-shrink: 0;\n  height: 30px;\n  z-index: 1;\n}\n.bp5-dark .bp5-panel-stack2-header {\n  box-shadow: 0 1px rgba(255, 255, 255, 0.2);\n}\n.bp5-panel-stack2-header > span {\n  align-items: stretch;\n  display: flex;\n  flex: 1;\n}\n.bp5-panel-stack2-header .bp5-heading {\n  margin: 0 5px;\n}\n\n.bp5-button.bp5-panel-stack2-header-back {\n  margin-left: 5px;\n  padding-left: 0;\n  white-space: nowrap;\n}\n.bp5-button.bp5-panel-stack2-header-back .bp5-icon {\n  margin: 0 2px;\n}\n\n.bp5-panel-stack2-view {\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n  background-color: #ffffff;\n  border-right: 1px solid rgba(17, 20, 24, 0.15);\n  display: flex;\n  flex-direction: column;\n  margin-right: -1px;\n  overflow-y: auto;\n  z-index: 1;\n}\n.bp5-dark .bp5-panel-stack2-view {\n  background-color: #2f343c;\n}\n.bp5-panel-stack2-view:nth-last-child(n + 4) {\n  display: none;\n}\n\n.bp5-panel-stack2-push .bp5-panel-stack2-enter,\n.bp5-panel-stack2-push .bp5-panel-stack2-appear {\n  transform: translateX(100%);\n  opacity: 0;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-enter-active,\n.bp5-panel-stack2-push .bp5-panel-stack2-appear-active {\n  transform: translate(0%);\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit {\n  transform: translate(0%);\n  opacity: 1;\n}\n.bp5-panel-stack2-push .bp5-panel-stack2-exit-active {\n  transform: translateX(-50%);\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter,\n.bp5-panel-stack2-pop .bp5-panel-stack2-appear {\n  transform: translateX(-50%);\n  opacity: 0;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-enter-active,\n.bp5-panel-stack2-pop .bp5-panel-stack2-appear-active {\n  transform: translate(0%);\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit {\n  transform: translate(0%);\n  opacity: 1;\n}\n.bp5-panel-stack2-pop .bp5-panel-stack2-exit-active {\n  transform: translateX(100%);\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 400ms;\n  transition-property: transform, opacity;\n  transition-timing-function: ease;\n}\n.bp5-button-group:not(.bp5-minimal)\n  > .bp5-popover-target:not(:first-child)\n  .bp5-button {\n  border-bottom-left-radius: 0;\n  border-top-left-radius: 0;\n}\n.bp5-button-group:not(.bp5-minimal)\n  > .bp5-popover-target:not(:last-child)\n  .bp5-button {\n  border-bottom-right-radius: 0;\n  border-top-right-radius: 0;\n  margin-right: -1px;\n}\n.bp5-button-group .bp5-popover-target {\n  display: flex;\n  flex: 1 1 auto;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-target:first-child\n  .bp5-button {\n  border-radius: 2px 2px 0 0;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-target:last-child\n  .bp5-button {\n  border-radius: 0 0 2px 2px;\n}\n.bp5-button-group.bp5-vertical:not(.bp5-minimal)\n  > .bp5-popover-target:not(:last-child)\n  .bp5-button {\n  margin-bottom: -1px;\n}\n.bp5-control-group .bp5-popover-target {\n  border-radius: inherit;\n}\nlabel.bp5-label .bp5-popover-target {\n  display: block;\n  margin-top: 5px;\n  text-transform: none;\n}\n.bp5-submenu .bp5-popover-target {\n  display: block;\n}\n.bp5-submenu.bp5-popover {\n  box-shadow: none;\n  padding: 0 5px;\n}\n.bp5-submenu.bp5-popover > .bp5-popover-content {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-submenu.bp5-popover,\n.bp5-submenu.bp5-popover.bp5-dark {\n  box-shadow: none;\n}\n.bp5-dark .bp5-submenu.bp5-popover > .bp5-popover-content,\n.bp5-submenu.bp5-popover.bp5-dark > .bp5-popover-content {\n  box-shadow: 0 0 0 1px hsl(215, 3%, 38%),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4),\n    0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-tree-node-secondary-label .bp5-popover-target {\n  align-items: center;\n  display: flex;\n}\n\n.bp5-popover {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  transform: scale(1);\n  border-radius: 2px;\n  display: inline-block;\n  z-index: 20;\n}\n.bp5-popover .bp5-popover-arrow {\n  height: 30px;\n  position: absolute;\n  width: 30px;\n}\n.bp5-popover .bp5-popover-arrow::before {\n  height: 20px;\n  margin: 5px;\n  width: 20px;\n}\n.bp5-popover .bp5-popover-content {\n  background: #ffffff;\n}\n.bp5-popover .bp5-popover-content,\n.bp5-popover .bp5-heading {\n  color: inherit;\n}\n.bp5-popover .bp5-popover-arrow::before {\n  box-shadow: 1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-popover .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.1;\n}\n.bp5-popover .bp5-popover-arrow-fill {\n  fill: #ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-popover .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-popover {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-popover,\n.bp5-popover-appear > .bp5-popover {\n  transform: scale(0.3);\n}\n.bp5-popover-enter-active > .bp5-popover,\n.bp5-popover-appear-active > .bp5-popover {\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover-exit > .bp5-popover {\n  transform: scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover {\n  transform: scale(0.3);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-popover .bp5-popover-content {\n  border-radius: 2px;\n  position: relative;\n}\n.bp5-popover.bp5-popover-content-sizing .bp5-popover-content {\n  max-width: 350px;\n  padding: 20px;\n}\n.bp5-popover-target + .bp5-overlay .bp5-popover.bp5-popover-content-sizing {\n  width: 350px;\n}\n.bp5-popover.bp5-minimal {\n  margin: 0 !important;\n}\n.bp5-popover.bp5-minimal .bp5-popover-arrow {\n  display: none;\n}\n.bp5-popover.bp5-minimal.bp5-popover {\n  transform: scale(1);\n}\n.bp5-popover-enter > .bp5-popover.bp5-minimal.bp5-popover,\n.bp5-popover-appear > .bp5-popover.bp5-minimal.bp5-popover {\n  transform: scale(1);\n}\n.bp5-popover-enter-active > .bp5-popover.bp5-minimal.bp5-popover,\n.bp5-popover-appear-active > .bp5-popover.bp5-minimal.bp5-popover {\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-popover.bp5-minimal.bp5-popover {\n  transform: scale(1);\n}\n.bp5-popover-exit-active > .bp5-popover.bp5-minimal.bp5-popover {\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover.bp5-popover-match-target-width {\n  width: 100%;\n}\n.bp5-popover.bp5-dark,\n.bp5-dark .bp5-popover {\n  box-shadow: 0 0 0 1px hsl(215, 3%, 38%),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2), 0 2px 4px rgba(17, 20, 24, 0.4),\n    0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-content,\n.bp5-dark .bp5-popover .bp5-popover-content {\n  background: #2f343c;\n}\n.bp5-popover.bp5-dark .bp5-popover-content,\n.bp5-popover.bp5-dark .bp5-heading,\n.bp5-dark .bp5-popover .bp5-popover-content,\n.bp5-dark .bp5-popover .bp5-heading {\n  color: inherit;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow::before,\n.bp5-dark .bp5-popover .bp5-popover-arrow::before {\n  box-shadow: 0 0 0 1px #777a7e, 1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-border,\n.bp5-dark .bp5-popover .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.2;\n}\n.bp5-popover.bp5-dark .bp5-popover-arrow-fill,\n.bp5-dark .bp5-popover .bp5-popover-arrow-fill {\n  fill: #2f343c;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-popover.bp5-dark .bp5-popover-arrow-fill,\n  .bp5-dark .bp5-popover .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-popover.bp5-dark,\n  .bp5-dark .bp5-popover {\n    border: 1px solid buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-popover {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n\n.bp5-popover-arrow::before {\n  border-radius: 1px;\n  content: '';\n  display: block;\n  position: absolute;\n  transform: rotate(45deg);\n}\n\n.bp5-overlay-backdrop.bp5-popover-backdrop {\n  background: rgba(255, 255, 255, 0);\n}\n\n.bp5-popover-transition-container {\n  opacity: 1;\n  display: flex;\n  z-index: 20;\n}\n.bp5-popover-transition-container.bp5-popover-enter,\n.bp5-popover-transition-container.bp5-popover-appear {\n  opacity: 0;\n}\n.bp5-popover-transition-container.bp5-popover-enter-active,\n.bp5-popover-transition-container.bp5-popover-appear-active {\n  opacity: 1;\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container.bp5-popover-exit {\n  opacity: 1;\n}\n.bp5-popover-transition-container.bp5-popover-exit-active {\n  opacity: 0;\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-transition-container:focus {\n  outline: none;\n}\n.bp5-popover-transition-container.bp5-popover-leave .bp5-popover-content {\n  pointer-events: none;\n}\n\nspan.bp5-popover-target {\n  display: inline-block;\n}\n\n.bp5-popover-target.bp5-fill {\n  width: 100%;\n}\n\n.bp5-portal {\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n@keyframes linear-progress-bar-stripes {\n  from {\n    background-position: 0 0;\n  }\n  to {\n    background-position: 30px 0;\n  }\n}\n.bp5-progress-bar {\n  background: rgba(95, 107, 124, 0.2);\n  border-radius: 40px;\n  display: block;\n  height: 8px;\n  overflow: hidden;\n  position: relative;\n  width: 100%;\n}\n.bp5-progress-bar .bp5-progress-meter {\n  background: linear-gradient(\n    -45deg,\n    rgba(255, 255, 255, 0.2) 25%,\n    transparent 25%,\n    transparent 50%,\n    rgba(255, 255, 255, 0.2) 50%,\n    rgba(255, 255, 255, 0.2) 75%,\n    transparent 75%\n  );\n  background-color: rgba(95, 107, 124, 0.8);\n  background-size: 30px 30px;\n  border-radius: 40px;\n  height: 100%;\n  position: absolute;\n  transition: width 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n  width: 100%;\n}\n.bp5-progress-bar:not(.bp5-no-animation):not(.bp5-no-stripes)\n  .bp5-progress-meter {\n  animation: linear-progress-bar-stripes 300ms linear infinite reverse;\n}\n.bp5-progress-bar.bp5-no-stripes .bp5-progress-meter {\n  background-image: none;\n}\n\n.bp5-dark .bp5-progress-bar {\n  background: rgba(17, 20, 24, 0.5);\n}\n.bp5-dark .bp5-progress-bar .bp5-progress-meter {\n  background-color: #8f99a8;\n}\n\n.bp5-progress-bar.bp5-intent-primary .bp5-progress-meter {\n  background-color: #2d72d2;\n}\n\n.bp5-progress-bar.bp5-intent-success .bp5-progress-meter {\n  background-color: #238551;\n}\n\n.bp5-progress-bar.bp5-intent-warning .bp5-progress-meter {\n  background-color: #c87619;\n}\n\n.bp5-progress-bar.bp5-intent-danger .bp5-progress-meter {\n  background-color: #cd4246;\n}\n.bp5-section {\n  overflow: hidden;\n  width: 100%;\n}\n.bp5-section,\n.bp5-section.bp5-compact {\n  padding: 0;\n}\n.bp5-section-header {\n  align-items: center;\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n  display: flex;\n  gap: 20px;\n  justify-content: space-between;\n  min-height: 50px;\n  padding: 0 20px;\n  position: relative;\n  width: 100%;\n}\n.bp5-section-header.bp5-dark,\n.bp5-dark .bp5-section-header {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-section-header-left {\n  align-items: center;\n  display: flex;\n  gap: 10px;\n  padding: 10px 0;\n}\n.bp5-section-header-title {\n  margin-bottom: 0;\n}\n.bp5-section-header-sub-title {\n  margin-top: 2px;\n}\n.bp5-section-header-right {\n  align-items: center;\n  display: flex;\n  gap: 10px;\n  margin-left: auto;\n}\n.bp5-section-header-divider {\n  align-self: stretch;\n  margin: 15px 0;\n}\n.bp5-section-header.bp5-interactive {\n  cursor: pointer;\n}\n.bp5-section-header.bp5-interactive:hover,\n.bp5-section-header.bp5-interactive:active {\n  background: #f6f7f9;\n}\n.bp5-section-header.bp5-interactive:hover.bp5-dark,\n.bp5-dark .bp5-section-header.bp5-interactive:hover,\n.bp5-section-header.bp5-interactive:active.bp5-dark,\n.bp5-dark .bp5-section-header.bp5-interactive:active {\n  background: #383e47;\n}\n.bp5-section-card.bp5-padded {\n  padding: 20px;\n}\n.bp5-section-card:not(:last-child) {\n  border-bottom: 1px solid rgba(17, 20, 24, 0.15);\n}\n.bp5-section-card:not(:last-child).bp5-dark,\n.bp5-dark .bp5-section-card:not(:last-child) {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-section.bp5-section-collapsed .bp5-section-header {\n  border: none;\n}\n.bp5-section.bp5-compact .bp5-section-header {\n  min-height: 40px;\n  padding: 0 15px;\n}\n.bp5-section.bp5-compact .bp5-section-header-left {\n  padding: 7px 0;\n}\n.bp5-section.bp5-compact .bp5-section-card.bp5-padded {\n  padding: 15px;\n}\n.bp5-segmented-control {\n  background-color: #f6f7f9;\n  border-radius: 2px;\n  display: flex;\n  gap: 3px;\n  padding: 3px;\n}\n.bp5-segmented-control.bp5-inline {\n  display: inline-flex;\n}\n.bp5-segmented-control.bp5-fill {\n  width: 100%;\n}\n.bp5-segmented-control.bp5-fill > .bp5-button {\n  flex-grow: 1;\n}\n.bp5-segmented-control\n  > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary) {\n  background-color: #ffffff;\n}\n.bp5-dark\n  .bp5-segmented-control\n  > .bp5-button:not(.bp5-minimal):not(.bp5-intent-primary) {\n  background-color: #404854;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal {\n  color: #5f6b7c;\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal {\n  color: #abb3bf;\n}\n.bp5-segmented-control > .bp5-button.bp5-minimal:disabled {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-dark .bp5-segmented-control > .bp5-button.bp5-minimal:disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-dark .bp5-segmented-control {\n  background-color: #2f343c;\n}\n@keyframes skeleton-glow {\n  from {\n    background: rgba(211, 216, 222, 0.2);\n    border-color: rgba(211, 216, 222, 0.2);\n  }\n  to {\n    background: rgba(95, 107, 124, 0.2);\n    border-color: rgba(95, 107, 124, 0.2);\n  }\n}\n.bp5-skeleton {\n  animation: 1000ms linear infinite alternate skeleton-glow;\n  background: rgba(211, 216, 222, 0.2);\n  background-clip: padding-box !important;\n  border-color: rgba(211, 216, 222, 0.2) !important;\n  border-radius: 2px;\n  box-shadow: none !important;\n  color: transparent !important;\n  cursor: default;\n  pointer-events: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-skeleton::before,\n.bp5-skeleton::after,\n.bp5-skeleton * {\n  visibility: hidden !important;\n}\n.bp5-tooltip {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  transform: scale(1);\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow {\n  height: 22px;\n  position: absolute;\n  width: 22px;\n}\n.bp5-tooltip .bp5-popover-arrow::before {\n  height: 14px;\n  margin: 4px;\n  width: 14px;\n}\n.bp5-tooltip .bp5-popover-content {\n  background: #404854;\n}\n.bp5-tooltip .bp5-popover-content,\n.bp5-tooltip .bp5-heading {\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow::before {\n  box-shadow: 1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-tooltip .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.1;\n}\n.bp5-tooltip .bp5-popover-arrow-fill {\n  fill: #404854;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-tooltip,\n.bp5-popover-appear > .bp5-tooltip {\n  transform: scale(0.8);\n}\n.bp5-popover-enter-active > .bp5-tooltip,\n.bp5-popover-appear-active > .bp5-tooltip {\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-tooltip {\n  transform: scale(1);\n}\n.bp5-popover-exit-active > .bp5-tooltip {\n  transform: scale(0.8);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tooltip .bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-tooltip .bp5-text-disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-tooltip .bp5-running-text hr {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-tooltip a {\n  color: #8abbff;\n}\n.bp5-tooltip a:hover {\n  color: #8abbff;\n}\n.bp5-tooltip a .bp5-icon,\n.bp5-tooltip a .bp5-icon-standard,\n.bp5-tooltip a .bp5-icon-large {\n  color: inherit;\n}\n.bp5-tooltip a code {\n  color: inherit;\n}\n.bp5-tooltip .bp5-code,\n.bp5-tooltip .bp5-running-text code {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\na > .bp5-tooltip .bp5-code,\na > .bp5-tooltip .bp5-running-text code {\n  color: inherit;\n}\n.bp5-tooltip .bp5-code-block,\n.bp5-tooltip .bp5-running-text pre {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-code-block > code,\n.bp5-tooltip .bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n.bp5-tooltip .bp5-key,\n.bp5-tooltip .bp5-running-text kbd {\n  background: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-primary,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-primary,\n.bp5-tooltip .bp5-icon-large.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-success,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-success,\n.bp5-tooltip .bp5-icon-large.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-warning,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-warning,\n.bp5-tooltip .bp5-icon-large.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-danger,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-danger,\n.bp5-tooltip .bp5-icon-large.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-tooltip .bp5-popover-content {\n  padding: 10px 12px;\n}\n.bp5-tooltip.bp5-compact .bp5-popover-content {\n  line-height: 1rem;\n  padding: 5px 7px;\n}\n.bp5-tooltip.bp5-compact .bp5-code {\n  vertical-align: text-bottom;\n}\n.bp5-tooltip.bp5-popover-placement-top .bp5-popover-arrow {\n  transform: translateY(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-left .bp5-popover-arrow {\n  transform: translateX(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-bottom .bp5-popover-arrow {\n  transform: translateY(3px);\n}\n.bp5-tooltip.bp5-popover-placement-right .bp5-popover-arrow {\n  transform: translateX(3px);\n}\n.bp5-tooltip.bp5-dark,\n.bp5-dark .bp5-tooltip {\n  box-shadow: 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-popover-content {\n  background: #e5e8eb;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-tooltip.bp5-dark .bp5-heading,\n.bp5-dark .bp5-tooltip .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-heading {\n  color: #404854;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow::before,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow::before {\n  box-shadow: 1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-border,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.2;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow-fill {\n  fill: #e5e8eb;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n  .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark,\n  .bp5-dark .bp5-tooltip {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-text-muted,\n.bp5-dark .bp5-tooltip .bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-text-disabled,\n.bp5-dark .bp5-tooltip .bp5-text-disabled {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-tooltip.bp5-dark .bp5-running-text hr,\n.bp5-dark .bp5-tooltip .bp5-running-text hr {\n  border-color: rgba(17, 20, 24, 0.15);\n}\n.bp5-tooltip.bp5-dark a,\n.bp5-dark .bp5-tooltip a {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark a:hover,\n.bp5-dark .bp5-tooltip a:hover {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark a .bp5-icon,\n.bp5-tooltip.bp5-dark a .bp5-icon-standard,\n.bp5-tooltip.bp5-dark a .bp5-icon-large,\n.bp5-dark .bp5-tooltip a .bp5-icon,\n.bp5-dark .bp5-tooltip a .bp5-icon-standard,\n.bp5-dark .bp5-tooltip a .bp5-icon-large {\n  color: inherit;\n}\n.bp5-tooltip.bp5-dark a code,\n.bp5-dark .bp5-tooltip a code {\n  color: inherit;\n}\n.bp5-tooltip.bp5-dark .bp5-code,\n.bp5-tooltip.bp5-dark .bp5-running-text code,\n.bp5-dark .bp5-tooltip .bp5-code,\n.bp5-dark .bp5-tooltip .bp5-running-text code {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\na > .bp5-tooltip.bp5-dark .bp5-code,\na > .bp5-tooltip.bp5-dark .bp5-running-text code,\na > .bp5-dark .bp5-tooltip .bp5-code,\na > .bp5-dark .bp5-tooltip .bp5-running-text code {\n  color: #2d72d2;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block,\n.bp5-tooltip.bp5-dark .bp5-running-text pre,\n.bp5-dark .bp5-tooltip .bp5-code-block,\n.bp5-dark .bp5-tooltip .bp5-running-text pre {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color: #1c2127;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block > code,\n.bp5-tooltip.bp5-dark .bp5-running-text pre > code,\n.bp5-dark .bp5-tooltip .bp5-code-block > code,\n.bp5-dark .bp5-tooltip .bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark .bp5-code-block,\n  .bp5-tooltip.bp5-dark .bp5-running-text pre,\n  .bp5-dark .bp5-tooltip .bp5-code-block,\n  .bp5-dark .bp5-tooltip .bp5-running-text pre {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-key,\n.bp5-tooltip.bp5-dark .bp5-running-text kbd,\n.bp5-dark .bp5-tooltip .bp5-key,\n.bp5-dark .bp5-tooltip .bp5-running-text kbd {\n  background: #ffffff;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-primary,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-primary,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-success,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-success,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-warning,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-warning,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-danger,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-danger,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-content {\n  background: #2d72d2;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill {\n  fill: #2d72d2;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-content {\n  background: #238551;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill {\n  fill: #238551;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-content {\n  background: #c87619;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill {\n  fill: #c87619;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-content {\n  background: #cd4246;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill {\n  fill: #cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n\n.bp5-tooltip-indicator {\n  border-bottom: dotted 1px;\n  cursor: help;\n}\n.bp5-slider {\n  height: 40px;\n  min-width: 150px;\n  width: 100%;\n  cursor: default;\n  outline: none;\n  position: relative;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-slider:hover {\n  cursor: pointer;\n}\n.bp5-slider:active {\n  cursor: grabbing;\n}\n.bp5-slider.bp5-disabled {\n  cursor: not-allowed;\n  opacity: 0.5;\n}\n.bp5-slider.bp5-slider-unlabeled {\n  height: 16px;\n}\n\n.bp5-slider-track,\n.bp5-slider-progress {\n  height: 6px;\n  left: 0;\n  right: 0;\n  top: 5px;\n  position: absolute;\n}\n\n.bp5-slider-track {\n  border-radius: 2px;\n  overflow: hidden;\n}\n\n.bp5-slider-progress {\n  background: rgba(95, 107, 124, 0.2);\n}\n.bp5-dark .bp5-slider-progress {\n  background: rgba(17, 20, 24, 0.5);\n}\n.bp5-slider-progress.bp5-intent-primary {\n  background-color: #2d72d2;\n}\n.bp5-slider-progress.bp5-intent-success {\n  background-color: #238551;\n}\n.bp5-slider-progress.bp5-intent-warning {\n  background-color: #c87619;\n}\n.bp5-slider-progress.bp5-intent-danger {\n  background-color: #cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-slider-progress {\n    background: ButtonText;\n  }\n}\n\n.bp5-slider-handle {\n  background-color: #f6f7f9;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.1);\n  color: #1c2127;\n  border-radius: 2px;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 1px rgba(17, 20, 24, 0.5);\n  cursor: pointer;\n  height: 16px;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 16px;\n}\n.bp5-slider-handle:hover {\n  background-clip: padding-box;\n  background-color: #edeff2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-slider-handle:active,\n.bp5-slider-handle.bp5-active {\n  background-color: #dce0e5;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-slider-handle:active,\n  .bp5-slider-handle.bp5-active {\n    background: highlight;\n  }\n}\n.bp5-slider-handle:disabled,\n.bp5-slider-handle.bp5-disabled {\n  background-color: rgba(211, 216, 222, 0.5);\n  box-shadow: none;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n  outline: none;\n}\n.bp5-slider-handle:disabled.bp5-active,\n.bp5-slider-handle:disabled.bp5-active:hover,\n.bp5-slider-handle.bp5-disabled.bp5-active,\n.bp5-slider-handle.bp5-disabled.bp5-active:hover {\n  background: rgba(211, 216, 222, 0.7);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-slider-handle {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-slider-handle:focus {\n  z-index: 1;\n}\n.bp5-slider-handle:hover {\n  background-clip: padding-box;\n  background-color: #edeff2;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.6);\n  cursor: grab;\n  z-index: 2;\n}\n.bp5-slider-handle.bp5-active {\n  background-color: #dce0e5;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n  box-shadow: inset 0 1px 1px rgba(17, 20, 24, 0.1),\n    0 0 0 1px rgba(17, 20, 24, 0.5), 0 1px 2px rgba(17, 20, 24, 0.2);\n  cursor: grabbing;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-slider-handle.bp5-active {\n    background: highlight;\n  }\n}\n.bp5-disabled .bp5-slider-handle {\n  background: #c5cbd3;\n  box-shadow: none;\n  pointer-events: none;\n}\n.bp5-dark .bp5-slider-handle {\n  background-color: #abb3bf;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-slider-handle:hover {\n  background-color: #8f99a8;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1),\n    0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-slider-handle.bp5-active {\n  background-color: #738091;\n  box-shadow: inset 0 1px 1px rgba(17, 20, 24, 0.1),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 1px 2px rgba(17, 20, 24, 0.4);\n}\n.bp5-dark .bp5-disabled .bp5-slider-handle {\n  background: #5f6b7c;\n  border-color: #5f6b7c;\n  box-shadow: none;\n}\n.bp5-slider-handle .bp5-slider-label {\n  background: #404854;\n  border-radius: 2px;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  color: #f6f7f9;\n  margin-left: 8px;\n}\n.bp5-dark .bp5-slider-handle .bp5-slider-label {\n  background: #e5e8eb;\n  box-shadow: 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n  color: #404854;\n}\n.bp5-disabled .bp5-slider-handle .bp5-slider-label {\n  box-shadow: none;\n}\n.bp5-slider-handle.bp5-start,\n.bp5-slider-handle.bp5-end {\n  width: 8px;\n}\n.bp5-slider-handle.bp5-start {\n  border-bottom-right-radius: 0;\n  border-top-right-radius: 0;\n}\n.bp5-slider-handle.bp5-end {\n  border-bottom-left-radius: 0;\n  border-top-left-radius: 0;\n  margin-left: 8px;\n}\n.bp5-slider-handle.bp5-end .bp5-slider-label {\n  margin-left: 0;\n}\n\n.bp5-slider-label {\n  transform: translate(-50%, 20px);\n  display: inline-block;\n  font-size: 12px;\n  line-height: 1;\n  padding: 2px 5px;\n  position: absolute;\n  vertical-align: top;\n}\n\n.bp5-slider.bp5-vertical {\n  height: 150px;\n  min-width: 40px;\n  width: 40px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-track,\n.bp5-slider.bp5-vertical .bp5-slider-progress {\n  bottom: 0;\n  height: auto;\n  left: 5px;\n  top: 0;\n  width: 6px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-progress {\n  top: auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-label {\n  transform: translate(20px, 50%);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle {\n  top: auto;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle .bp5-slider-label {\n  margin-left: 0;\n  margin-top: -8px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end,\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start {\n  height: 8px;\n  margin-left: 0;\n  width: 16px;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start {\n  border-bottom-right-radius: 2px;\n  border-top-left-radius: 0;\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-start .bp5-slider-label {\n  transform: translate(20px);\n}\n.bp5-slider.bp5-vertical .bp5-slider-handle.bp5-end {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n  border-top-left-radius: 2px;\n  margin-bottom: 8px;\n}\n@keyframes pt-spinner-animation {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n.bp5-spinner {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  overflow: visible;\n  vertical-align: middle;\n}\n.bp5-spinner svg {\n  display: block;\n}\n.bp5-spinner path {\n  fill-opacity: 0%;\n}\n.bp5-spinner .bp5-spinner-head {\n  stroke: rgba(95, 107, 124, 0.8);\n  stroke-linecap: round;\n  transform-origin: center;\n  transition: stroke-dashoffset 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-spinner .bp5-spinner-track {\n  stroke: rgba(95, 107, 124, 0.2);\n}\n\n.bp5-spinner-animation {\n  animation: pt-spinner-animation 500ms linear infinite;\n}\n.bp5-no-spin > .bp5-spinner-animation {\n  animation: none;\n}\n\n.bp5-dark .bp5-spinner .bp5-spinner-head {\n  stroke: #8f99a8;\n}\n.bp5-dark .bp5-spinner .bp5-spinner-track {\n  stroke: rgba(17, 20, 24, 0.5);\n}\n\n.bp5-spinner.bp5-intent-primary .bp5-spinner-head {\n  stroke: #2d72d2;\n}\n\n.bp5-spinner.bp5-intent-success .bp5-spinner-head {\n  stroke: #238551;\n}\n\n.bp5-spinner.bp5-intent-warning .bp5-spinner-head {\n  stroke: #c87619;\n}\n\n.bp5-spinner.bp5-intent-danger .bp5-spinner-head {\n  stroke: #cd4246;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill {\n  height: 100%;\n}\n.bp5-tabs:not(.bp5-vertical).bp5-fill .bp5-tab-list {\n  height: 100%;\n}\n\n.bp5-tabs.bp5-vertical {\n  display: flex;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list {\n  align-items: flex-start;\n  flex-direction: column;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab {\n  align-items: center;\n  border-radius: 2px;\n  display: flex;\n  padding: 0 10px;\n  width: 100%;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected='true'] {\n  background-color: rgba(45, 114, 210, 0.2);\n  box-shadow: none;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tabs.bp5-vertical > .bp5-tab-list .bp5-tab[aria-selected='true'] {\n    background-color: highlight;\n    color: #111418;\n  }\n}\n.bp5-tabs.bp5-vertical\n  > .bp5-tab-list\n  .bp5-tab-indicator-wrapper\n  .bp5-tab-indicator {\n  background-color: rgba(45, 114, 210, 0.2);\n  border-radius: 2px;\n  bottom: 0;\n  height: auto;\n  left: 0;\n  right: 0;\n  top: 0;\n}\n.bp5-tabs.bp5-vertical > .bp5-tab-panel {\n  margin-top: 0;\n  padding-left: 20px;\n}\n\n.bp5-tab-list {\n  align-items: flex-end;\n  border: none;\n  -moz-column-gap: 20px;\n  column-gap: 20px;\n  display: flex;\n  flex: 0 0 auto;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  position: relative;\n}\n\n.bp5-tab {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  align-items: center;\n  align-self: stretch;\n  color: #1c2127;\n  cursor: pointer;\n  display: flex;\n  flex: 0 0 auto;\n  font-size: 14px;\n  line-height: 30px;\n  max-width: 100%;\n  position: relative;\n  vertical-align: top;\n}\n.bp5-tab a {\n  color: inherit;\n  display: block;\n  text-decoration: none;\n}\n.bp5-tab-indicator-wrapper ~ .bp5-tab {\n  background-color: transparent !important;\n  box-shadow: none !important;\n}\n.bp5-tab[aria-disabled='true'] {\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tab[aria-disabled='true'] {\n    color: graytext;\n  }\n}\n.bp5-tab[aria-selected='true'] {\n  border-radius: 0;\n  box-shadow: inset 0 -3px 0 #215db0;\n}\n.bp5-tab[aria-selected='true'],\n.bp5-tab:not([aria-disabled='true']):hover {\n  color: #215db0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tab[aria-selected='true'],\n  .bp5-tab:not([aria-disabled='true']):hover {\n    color: highlight;\n  }\n}\n.bp5-tab:focus {\n  -moz-outline-radius: 0;\n}\n.bp5-large > .bp5-tab {\n  font-size: 16px;\n  line-height: 40px;\n}\n\n.bp5-tab-panel {\n  margin-top: 20px;\n}\n.bp5-tab-panel[aria-hidden='true'] {\n  display: none;\n}\n\n.bp5-tab-icon {\n  margin-right: 7px;\n}\n\n.bp5-tab-tag {\n  margin-left: 7px;\n}\n\n.bp5-tab-indicator-wrapper {\n  left: 0;\n  pointer-events: none;\n  position: absolute;\n  top: 0;\n  transform: translateX(0), translateY(0);\n  transition: height, transform, width;\n  transition-duration: 200ms;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tab-indicator-wrapper .bp5-tab-indicator {\n  background-color: #215db0;\n  bottom: 0;\n  height: 3px;\n  left: 0;\n  position: absolute;\n  right: 0;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tab-indicator-wrapper .bp5-tab-indicator {\n    background-color: highlight;\n  }\n}\n.bp5-tab-indicator-wrapper.bp5-no-animation {\n  transition: none;\n}\n.bp5-tabs:not(.bp5-vertical) .bp5-tab-indicator-wrapper {\n  height: 100%;\n}\n\n.bp5-dark .bp5-tab {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-tab[aria-disabled='true'] {\n  color: rgba(171, 179, 191, 0.6);\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-tab[aria-disabled='true'] {\n    color: graytext;\n  }\n}\n.bp5-dark .bp5-tab[aria-selected='true'] {\n  box-shadow: inset 0 -3px 0 #8abbff;\n}\n.bp5-dark .bp5-tab[aria-selected='true'],\n.bp5-dark .bp5-tab:not([aria-disabled='true']):hover {\n  color: #8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-tab[aria-selected='true'],\n  .bp5-dark .bp5-tab:not([aria-disabled='true']):hover {\n    color: highlight;\n  }\n}\n.bp5-dark .bp5-tab-indicator {\n  background-color: #8abbff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-dark .bp5-tab-indicator {\n    background-color: highlight;\n  }\n}\n\n.bp5-flex-expander {\n  flex: 1 1;\n}\n.bp5-tag {\n  display: inline-flex;\n  flex-direction: row;\n  align-items: center;\n  background-color: #5f6b7c;\n  border: none;\n  border-radius: 2px;\n  box-shadow: none;\n  color: #ffffff;\n  font-size: 12px;\n  line-height: 16px;\n  max-width: 100%;\n  min-height: 20px;\n  min-width: 20px;\n  padding: 2px 6px;\n  position: relative;\n}\n.bp5-tag > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-tag > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-tag::before,\n.bp5-tag > * {\n  margin-right: 4px;\n}\n.bp5-tag:empty::before,\n.bp5-tag > :last-child {\n  margin-right: 0;\n}\n.bp5-tag:focus {\n  outline: rgba(45, 114, 210, 0.6) solid 2px;\n  outline-offset: 0;\n  -moz-outline-radius: 6px;\n}\n.bp5-tag.bp5-interactive {\n  cursor: pointer;\n}\n.bp5-tag.bp5-interactive:hover {\n  background: #404854;\n}\n.bp5-tag.bp5-interactive:active,\n.bp5-tag.bp5-interactive.bp5-active {\n  background: #383e47;\n}\n.bp5-tag.bp5-round {\n  border-radius: 30px;\n  padding-left: 8px;\n  padding-right: 8px;\n}\n.bp5-dark .bp5-tag {\n  background-color: #c5cbd3;\n  color: #1c2127;\n}\n.bp5-dark .bp5-tag > .bp5-icon,\n.bp5-dark .bp5-tag .bp5-icon-standard,\n.bp5-dark .bp5-tag .bp5-icon-large {\n  fill: currentcolor;\n}\n.bp5-dark .bp5-tag.bp5-interactive:hover {\n  background: #abb3bf;\n}\n.bp5-dark .bp5-tag.bp5-interactive:active,\n.bp5-dark .bp5-tag.bp5-interactive.bp5-active {\n  background: #8f99a8;\n}\n.bp5-tag > .bp5-icon,\n.bp5-tag .bp5-icon-standard,\n.bp5-tag .bp5-icon-large {\n  fill: #ffffff;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tag {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-tag.bp5-large,\n.bp5-large .bp5-tag {\n  font-size: 14px;\n  line-height: 20px;\n  min-height: 30px;\n  min-width: 30px;\n  padding: 5px 10px;\n}\n.bp5-tag.bp5-large::before,\n.bp5-tag.bp5-large > *,\n.bp5-large .bp5-tag::before,\n.bp5-large .bp5-tag > * {\n  margin-right: 7px;\n}\n.bp5-tag.bp5-large:empty::before,\n.bp5-tag.bp5-large > :last-child,\n.bp5-large .bp5-tag:empty::before,\n.bp5-large .bp5-tag > :last-child {\n  margin-right: 0;\n}\n.bp5-tag.bp5-large.bp5-round,\n.bp5-large .bp5-tag.bp5-round {\n  padding-left: 12px;\n  padding-right: 12px;\n}\n.bp5-tag.bp5-intent-primary {\n  background: #2d72d2;\n  color: #ffffff;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:hover {\n  background-color: #215db0;\n}\n.bp5-tag.bp5-intent-primary.bp5-interactive:active,\n.bp5-tag.bp5-intent-primary.bp5-interactive.bp5-active {\n  background-color: #184a90;\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-primary .bp5-tag-remove:hover,\n.bp5-tag.bp5-intent-primary .bp5-tag-remove:active {\n  color: #ffffff;\n}\n.bp5-tag.bp5-intent-success {\n  background: #238551;\n  color: #ffffff;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:hover {\n  background-color: #1c6e42;\n}\n.bp5-tag.bp5-intent-success.bp5-interactive:active,\n.bp5-tag.bp5-intent-success.bp5-interactive.bp5-active {\n  background-color: #165a36;\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-success .bp5-tag-remove:hover,\n.bp5-tag.bp5-intent-success .bp5-tag-remove:active {\n  color: #ffffff;\n}\n.bp5-tag.bp5-intent-warning {\n  background: #fbb360;\n  color: #1c2127;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:hover {\n  background-color: #ec9a3c;\n}\n.bp5-tag.bp5-intent-warning.bp5-interactive:active,\n.bp5-tag.bp5-intent-warning.bp5-interactive.bp5-active {\n  background-color: #c87619;\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove {\n  color: rgba(28, 33, 39, 0.7);\n}\n.bp5-tag.bp5-intent-warning .bp5-tag-remove:hover,\n.bp5-tag.bp5-intent-warning .bp5-tag-remove:active {\n  color: #1c2127;\n}\n.bp5-tag.bp5-intent-danger {\n  background: #cd4246;\n  color: #ffffff;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:hover {\n  background-color: #ac2f33;\n}\n.bp5-tag.bp5-intent-danger.bp5-interactive:active,\n.bp5-tag.bp5-intent-danger.bp5-interactive.bp5-active {\n  background-color: #8e292c;\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-tag.bp5-intent-danger .bp5-tag-remove:hover,\n.bp5-tag.bp5-intent-danger .bp5-tag-remove:active {\n  color: #ffffff;\n}\n.bp5-tag.bp5-fill {\n  display: flex;\n  width: 100%;\n}\n.bp5-tag.bp5-minimal > .bp5-icon,\n.bp5-tag.bp5-minimal .bp5-icon-standard,\n.bp5-tag.bp5-minimal .bp5-icon-large {\n  fill: #5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']) {\n  background-color: rgba(143, 153, 168, 0.15);\n  color: #1c2127;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive {\n  cursor: pointer;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:hover {\n  background-color: rgba(143, 153, 168, 0.3);\n  color: #111418;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive.bp5-active,\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:active {\n  background-color: rgba(143, 153, 168, 0.35);\n  color: #111418;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']) .bp5-tag-remove {\n  color: #5f6b7c;\n}\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']) .bp5-tag-remove:hover,\n.bp5-tag.bp5-minimal:not([class*='bp5-intent-']) .bp5-tag-remove:active {\n  color: #404854;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*='bp5-intent-']) {\n  background-color: rgba(143, 153, 168, 0.15);\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive {\n  cursor: pointer;\n}\n.bp5-dark\n  .bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:hover {\n  background-color: rgba(143, 153, 168, 0.3);\n  color: #ffffff;\n}\n.bp5-dark\n  .bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive.bp5-active,\n.bp5-dark\n  .bp5-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:active {\n  background-color: rgba(143, 153, 168, 0.35);\n  color: #ffffff;\n}\n.bp5-dark .bp5-tag.bp5-minimal:not([class*='bp5-intent-']) .bp5-tag-remove {\n  color: #abb3bf;\n}\n.bp5-dark\n  .bp5-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-tag-remove:hover,\n.bp5-dark\n  .bp5-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-tag-remove:active {\n  color: #d3d8de;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary {\n  background-color: rgba(45, 114, 210, 0.1);\n  color: #215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary > .bp5-icon,\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-standard,\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-icon-large {\n  fill: #215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active,\n.bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active {\n  background-color: rgba(45, 114, 210, 0.3);\n  color: #184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove {\n  color: #215db0;\n}\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover,\n.bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active {\n  color: #184a90;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.1);\n  color: #1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success > .bp5-icon,\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-standard,\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-icon-large {\n  fill: #1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active,\n.bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active {\n  background-color: rgba(35, 133, 81, 0.3);\n  color: #165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove {\n  color: #1c6e42;\n}\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover,\n.bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active {\n  color: #165a36;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.1);\n  color: #935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning > .bp5-icon,\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-standard,\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-icon-large {\n  fill: #935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active,\n.bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active {\n  background-color: rgba(200, 118, 25, 0.3);\n  color: #77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove {\n  color: #935610;\n}\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover,\n.bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active {\n  color: #77450d;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.1);\n  color: #ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger > .bp5-icon,\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-standard,\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-icon-large {\n  fill: #ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active,\n.bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active {\n  background-color: rgba(205, 66, 70, 0.3);\n  color: #8e292c;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove {\n  color: #ac2f33;\n}\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover,\n.bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active {\n  color: #8e292c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary {\n  background-color: rgba(45, 114, 210, 0.2);\n  color: #8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover {\n  background-color: rgba(45, 114, 210, 0.3);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active {\n  background-color: rgba(45, 114, 210, 0.35);\n  color: #99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove {\n  color: #8abbff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:hover,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-primary .bp5-tag-remove:active {\n  color: #99c4ff;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success {\n  background-color: rgba(35, 133, 81, 0.2);\n  color: #72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover {\n  background-color: rgba(35, 133, 81, 0.3);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active {\n  background-color: rgba(35, 133, 81, 0.35);\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:hover,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-success .bp5-tag-remove:active {\n  color: #7cd7a2;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning {\n  background-color: rgba(200, 118, 25, 0.2);\n  color: #fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover {\n  background-color: rgba(200, 118, 25, 0.3);\n  color: #f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active {\n  background-color: rgba(200, 118, 25, 0.35);\n  color: #f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove {\n  color: #fbb360;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:hover,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-warning .bp5-tag-remove:active {\n  color: #f5c186;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger {\n  background-color: rgba(205, 66, 70, 0.2);\n  color: #fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover {\n  background-color: rgba(205, 66, 70, 0.3);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active {\n  background-color: rgba(205, 66, 70, 0.35);\n  color: #ffa1a4;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove {\n  color: #fa999c;\n}\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:hover,\n.bp5-dark .bp5-tag.bp5-minimal.bp5-intent-danger .bp5-tag-remove:active {\n  color: #ffa1a4;\n}\n\n.bp5-tag-remove {\n  background: none;\n  border: none;\n  color: rgba(255, 255, 255, 0.7);\n  cursor: pointer;\n  display: flex;\n  margin-bottom: -2px;\n  margin-right: -6px !important;\n  margin-top: -2px;\n  padding: 2px;\n  padding-left: 0;\n}\n.bp5-tag-remove:hover {\n  background: none;\n  color: inherit;\n  text-decoration: none;\n}\n.bp5-tag-remove .bp5-icon:first-child {\n  color: inherit;\n}\n.bp5-tag-remove:hover,\n.bp5-tag-remove:active {\n  color: #ffffff;\n}\n.bp5-tag-remove:empty::before {\n  font-family: 'blueprint-icons-16', sans-serif;\n  font-size: 16px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 16px;\n  line-height: 1;\n  width: 16px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  content: '\\f2ba';\n}\n.bp5-large .bp5-tag-remove {\n  margin-right: -10px !important;\n  padding: 0 5px 0 0;\n}\n.bp5-large .bp5-tag-remove:empty::before {\n  font-family: 'blueprint-icons-20', sans-serif;\n  font-size: 20px;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 400;\n  height: 20px;\n  line-height: 1;\n  width: 20px;\n}\n.bp5-dark .bp5-tag-remove {\n  color: rgba(28, 33, 39, 0.7);\n}\n.bp5-dark .bp5-tag-remove:hover,\n.bp5-dark .bp5-tag-remove:active {\n  color: #1c2127;\n}\n.bp5-compound-tag {\n  background: none;\n  padding: 0;\n}\n.bp5-compound-tag .bp5-compound-tag-left {\n  background-color: #404854;\n}\n.bp5-compound-tag .bp5-compound-tag-right {\n  background-color: #5f6b7c;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-left {\n  background-color: #383e47;\n}\n.bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-right {\n  background-color: #404854;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-left {\n  background-color: #2f343c;\n}\n.bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-right {\n  background-color: #383e47;\n}\n.bp5-compound-tag .bp5-compound-tag-left,\n.bp5-compound-tag .bp5-compound-tag-right {\n  align-items: center;\n  display: inline-flex;\n  padding: 2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-left {\n  border-bottom-left-radius: 2px;\n  border-top-left-radius: 2px;\n  margin-right: 0;\n}\n.bp5-compound-tag .bp5-compound-tag-left > .bp5-icon,\n.bp5-compound-tag .bp5-compound-tag-left .bp5-icon-standard,\n.bp5-compound-tag .bp5-compound-tag-left .bp5-icon-large {\n  margin-right: 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right {\n  border-bottom-right-radius: 2px;\n  border-top-right-radius: 2px;\n  flex-grow: 1;\n  padding: 2px 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right > .bp5-icon,\n.bp5-compound-tag .bp5-compound-tag-right .bp5-icon-standard,\n.bp5-compound-tag .bp5-compound-tag-right .bp5-icon-large {\n  margin-left: 4px;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-compound-tag-right-text {\n  flex-grow: 1;\n}\n.bp5-compound-tag .bp5-compound-tag-right .bp5-tag-remove {\n  margin-left: 2px;\n  margin-right: -4px !important;\n}\n.bp5-compound-tag.bp5-round {\n  padding: 0;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-left {\n  border-bottom-left-radius: 20px;\n  border-top-left-radius: 20px;\n  padding-left: 8px;\n}\n.bp5-compound-tag.bp5-round .bp5-compound-tag-right {\n  border-bottom-right-radius: 20px;\n  border-top-right-radius: 20px;\n  padding-right: 8px;\n}\n.bp5-compound-tag.bp5-large {\n  padding: 0;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right {\n  padding: 5px 8px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left > .bp5-icon,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-standard,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-left .bp5-icon-large {\n  margin-right: 7px;\n}\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right > .bp5-icon,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-standard,\n.bp5-compound-tag.bp5-large .bp5-compound-tag-right .bp5-icon-large {\n  margin-left: 7px;\n}\n.bp5-compound-tag.bp5-large .bp5-tag-remove {\n  margin-left: 7px;\n  margin-right: -10px !important;\n}\n.bp5-compound-tag.bp5-large.bp5-round {\n  padding: 0;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-left {\n  border-bottom-left-radius: 30px;\n  border-top-left-radius: 30px;\n  padding-left: 12px;\n}\n.bp5-compound-tag.bp5-large.bp5-round .bp5-compound-tag-right {\n  border-bottom-right-radius: 30px;\n  border-top-right-radius: 30px;\n  padding-right: 12px;\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']) {\n  background: none;\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.1);\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.2);\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.4);\n}\n.bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']).bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary {\n  background: none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success {\n  background: none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning {\n  background: none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger {\n  background: none;\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.1);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.3);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.2);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.4);\n}\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.3);\n}\n.bp5-dark .bp5-compound-tag {\n  background: none;\n}\n.bp5-dark .bp5-compound-tag .bp5-compound-tag-left {\n  background-color: #abb3bf;\n}\n.bp5-dark .bp5-compound-tag .bp5-compound-tag-right {\n  background-color: #c5cbd3;\n}\n.bp5-dark .bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-left {\n  background-color: #8f99a8;\n}\n.bp5-dark .bp5-compound-tag.bp5-interactive:hover .bp5-compound-tag-right {\n  background-color: #abb3bf;\n}\n.bp5-dark .bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-left,\n.bp5-dark .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-left {\n  background-color: #738091;\n}\n.bp5-dark .bp5-compound-tag.bp5-interactive:active .bp5-compound-tag-right,\n.bp5-dark .bp5-compound-tag.bp5-interactive.bp5-active .bp5-compound-tag-right {\n  background-color: gray3;\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-']) {\n  background: none;\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.4);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not([class*='bp5-intent-'])\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.2);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.5);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.3);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(95, 107, 124, 0.55);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal:not(\n    [class*='bp5-intent-']\n  ).bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(95, 107, 124, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-primary {\n  background: none;\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary\n  .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.4);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary\n  .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.2);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.5);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.3);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(45, 114, 210, 0.55);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(45, 114, 210, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-success {\n  background: none;\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success\n  .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.4);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success\n  .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.2);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.5);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.3);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(35, 133, 81, 0.55);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(35, 133, 81, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-warning {\n  background: none;\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning\n  .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.4);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning\n  .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.2);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.5);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.3);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(200, 118, 25, 0.55);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(200, 118, 25, 0.35);\n}\n.bp5-dark .bp5-compound-tag.bp5-minimal.bp5-intent-danger {\n  background: none;\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger\n  .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.4);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger\n  .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.2);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.5);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.3);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: rgba(205, 66, 70, 0.55);\n}\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-dark\n  .bp5-compound-tag.bp5-minimal.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: rgba(205, 66, 70, 0.35);\n}\n.bp5-compound-tag.bp5-intent-primary {\n  background: none;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-left {\n  background-color: #215db0;\n}\n.bp5-compound-tag.bp5-intent-primary .bp5-compound-tag-right {\n  background-color: #2d72d2;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: #184a90;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: #215db0;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: #11376b;\n}\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-intent-primary.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: #184a90;\n}\n.bp5-compound-tag.bp5-intent-success {\n  background: none;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-left {\n  background-color: #1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success .bp5-compound-tag-right {\n  background-color: #238551;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: #165a36;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: #1c6e42;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: #0f3e25;\n}\n.bp5-compound-tag.bp5-intent-success.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-intent-success.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: #165a36;\n}\n.bp5-compound-tag.bp5-intent-warning {\n  background: none;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-left {\n  background-color: #ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning .bp5-compound-tag-right {\n  background-color: #fbb360;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: #c87619;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: #ec9a3c;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: #935610;\n}\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-intent-warning.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: #c87619;\n}\n.bp5-compound-tag.bp5-intent-danger {\n  background: none;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-left {\n  background-color: #ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger .bp5-compound-tag-right {\n  background-color: #cd4246;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-left {\n  background-color: #8e292c;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:hover\n  .bp5-compound-tag-right {\n  background-color: #ac2f33;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-left,\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-left {\n  background-color: #782326;\n}\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive:active\n  .bp5-compound-tag-right,\n.bp5-compound-tag.bp5-intent-danger.bp5-interactive.bp5-active\n  .bp5-compound-tag-right {\n  background-color: #8e292c;\n}\n.bp5-tag-input {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  cursor: text;\n  height: auto;\n  line-height: inherit;\n  min-height: 30px;\n  padding-left: 5px;\n  padding-right: 0;\n}\n.bp5-tag-input > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-tag-input > .bp5-tag-input-values {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-tag-input .bp5-tag-input-icon {\n  color: #5f6b7c;\n  margin-left: 2px;\n  margin-right: 7px;\n  margin-top: 7px;\n}\n.bp5-tag-input .bp5-tag-input-values {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  align-self: stretch;\n  flex-wrap: wrap;\n  margin-right: 7px;\n  margin-top: 5px;\n  min-width: 0;\n  position: relative;\n}\n.bp5-tag-input .bp5-tag-input-values > * {\n  flex-grow: 0;\n  flex-shrink: 0;\n}\n.bp5-tag-input .bp5-tag-input-values > .bp5-fill {\n  flex-grow: 1;\n  flex-shrink: 1;\n}\n.bp5-tag-input .bp5-tag-input-values::before,\n.bp5-tag-input .bp5-tag-input-values > * {\n  margin-right: 5px;\n}\n.bp5-tag-input .bp5-tag-input-values:empty::before,\n.bp5-tag-input .bp5-tag-input-values > :last-child {\n  margin-right: 0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-tag ~ .bp5-input-ghost {\n  padding-left: 0;\n}\n.bp5-tag-input .bp5-tag-input-values:first-child .bp5-input-ghost {\n  padding-left: 5px;\n}\n.bp5-tag-input .bp5-tag-input-values > * {\n  margin-bottom: 5px;\n}\n.bp5-tag-input .bp5-tag {\n  overflow-wrap: break-word;\n}\n.bp5-tag-input .bp5-tag.bp5-active {\n  outline: rgba(45, 114, 210, 0.6) solid 2px;\n  outline-offset: 0;\n  -moz-outline-radius: 6px;\n}\n.bp5-tag-input .bp5-input-ghost {\n  flex: 1 1 auto;\n  line-height: 20px;\n  width: 80px;\n}\n.bp5-tag-input .bp5-input-ghost:disabled,\n.bp5-tag-input .bp5-input-ghost.bp5-disabled {\n  cursor: not-allowed;\n}\n.bp5-tag-input .bp5-button,\n.bp5-tag-input .bp5-spinner {\n  margin: 3px;\n  margin-left: 0;\n}\n.bp5-tag-input .bp5-button {\n  min-height: 24px;\n  min-width: 24px;\n  padding: 0 7px;\n}\n.bp5-tag-input.bp5-large {\n  height: auto;\n  min-height: 40px;\n}\n.bp5-tag-input.bp5-large::before,\n.bp5-tag-input.bp5-large > * {\n  margin-right: 10px;\n}\n.bp5-tag-input.bp5-large:empty::before,\n.bp5-tag-input.bp5-large > :last-child {\n  margin-right: 0;\n}\n.bp5-tag-input.bp5-large .bp5-tag-input-icon {\n  margin-left: 5px;\n  margin-top: 10px;\n}\n.bp5-tag-input.bp5-large .bp5-input-ghost {\n  line-height: 30px;\n}\n.bp5-tag-input.bp5-large .bp5-button {\n  min-height: 30px;\n  min-width: 30px;\n  padding: 5px 10px;\n  margin: 5px;\n  margin-left: 0;\n}\n.bp5-tag-input.bp5-large .bp5-spinner {\n  margin: 8px;\n  margin-left: 0;\n}\n.bp5-tag-input.bp5-active {\n  background-color: #ffffff;\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-primary {\n  box-shadow: inset 0 0 0 1px #2d72d2, 0 0 0 2px rgba(45, 114, 210, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-success {\n  box-shadow: inset 0 0 0 1px #238551, 0 0 0 2px rgba(35, 133, 81, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-warning {\n  box-shadow: inset 0 0 0 1px #c87619, 0 0 0 2px rgba(200, 118, 25, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-tag-input.bp5-active.bp5-intent-danger {\n  box-shadow: inset 0 0 0 1px #cd4246, 0 0 0 2px rgba(205, 66, 70, 0.3),\n    inset 0 1px 1px rgba(17, 20, 24, 0.2);\n}\n.bp5-dark .bp5-tag-input .bp5-tag-input-icon,\n.bp5-tag-input.bp5-dark .bp5-tag-input-icon {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost,\n.bp5-tag-input.bp5-dark .bp5-input-ghost {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::-moz-placeholder,\n.bp5-tag-input.bp5-dark .bp5-input-ghost::-moz-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost:-ms-input-placeholder,\n.bp5-tag-input.bp5-dark .bp5-input-ghost:-ms-input-placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tag-input .bp5-input-ghost::placeholder,\n.bp5-tag-input.bp5-dark .bp5-input-ghost::placeholder {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tag-input.bp5-active,\n.bp5-tag-input.bp5-dark.bp5-active {\n  background-color: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px #4c90f0, inset 0 0 0 1px #4c90f0,\n    0 0 0 2px rgba(76, 144, 240, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-primary,\n.bp5-tag-input.bp5-dark.bp5-active.bp5-intent-primary {\n  box-shadow: inset 0 0 0 1px #4c90f0, 0 0 0 2px rgba(76, 144, 240, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-success,\n.bp5-tag-input.bp5-dark.bp5-active.bp5-intent-success {\n  box-shadow: inset 0 0 0 1px #32a467, 0 0 0 2px rgba(50, 164, 103, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-warning,\n.bp5-tag-input.bp5-dark.bp5-active.bp5-intent-warning {\n  box-shadow: inset 0 0 0 1px #ec9a3c, 0 0 0 2px rgba(236, 154, 60, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n.bp5-dark .bp5-tag-input.bp5-active.bp5-intent-danger,\n.bp5-tag-input.bp5-dark.bp5-active.bp5-intent-danger {\n  box-shadow: inset 0 0 0 1px #e76a6e, 0 0 0 2px rgba(231, 106, 110, 0.3),\n    inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    inset 0 -1px 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.bp5-input-ghost {\n  background: none;\n  border: none;\n  box-shadow: none;\n  padding: 0;\n}\n.bp5-input-ghost::-moz-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input-ghost:-ms-input-placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input-ghost::placeholder {\n  color: #5f6b7c;\n  opacity: 1;\n}\n.bp5-input-ghost:focus {\n  outline: none !important;\n}\n.bp5-resizable-input-span {\n  max-height: 0;\n  max-width: 100%;\n  min-width: 80px;\n  opacity: 0;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  z-index: -1;\n}\n.bp5-toast {\n  align-items: flex-start;\n  background-color: #ffffff;\n  border-radius: 2px;\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.2), 0 8px 24px rgba(17, 20, 24, 0.2);\n  display: flex;\n  margin: 20px 0 0;\n  max-width: 500px;\n  min-width: 300px;\n  pointer-events: all;\n  position: relative !important;\n}\n.bp5-toast.bp5-toast-enter,\n.bp5-toast.bp5-toast-appear {\n  transform: translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active,\n.bp5-toast.bp5-toast-appear-active {\n  transform: translateY(0);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-enter ~ .bp5-toast,\n.bp5-toast.bp5-toast-appear ~ .bp5-toast {\n  transform: translateY(-40px);\n}\n.bp5-toast.bp5-toast-enter-active ~ .bp5-toast,\n.bp5-toast.bp5-toast-appear-active ~ .bp5-toast {\n  transform: translateY(0);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.54, 1.12, 0.38, 1.11);\n}\n.bp5-toast.bp5-toast-exit {\n  opacity: 1;\n  filter: blur(0);\n}\n.bp5-toast.bp5-toast-exit-active {\n  opacity: 0;\n  filter: blur(10px);\n  transition-delay: 0;\n  transition-duration: 300ms;\n  transition-property: opacity, filter;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast.bp5-toast-exit ~ .bp5-toast {\n  transform: translateY(0);\n}\n.bp5-toast.bp5-toast-exit-active ~ .bp5-toast {\n  transform: translateY(-40px);\n  transition-delay: 50ms;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-toast .bp5-button-group {\n  flex: 0 0 auto;\n  padding: 5px;\n  padding-left: 0;\n}\n.bp5-toast > .bp5-icon {\n  color: #5f6b7c;\n  margin: 12px;\n  margin-right: 0;\n}\n.bp5-toast.bp5-dark,\n.bp5-dark .bp5-toast {\n  background-color: #404854;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-toast.bp5-dark > .bp5-icon,\n.bp5-dark .bp5-toast > .bp5-icon {\n  color: #abb3bf;\n}\n.bp5-toast.bp5-dark .bp5-button .bp5-icon,\n.bp5-dark .bp5-toast .bp5-button .bp5-icon {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*='bp5-intent-'] a {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-toast[class*='bp5-intent-'] a:hover {\n  color: #ffffff;\n}\n.bp5-toast[class*='bp5-intent-'] > .bp5-icon {\n  color: #ffffff;\n}\n.bp5-toast.bp5-intent-primary {\n  background-color: #2d72d2;\n  color: #ffffff;\n}\n.bp5-toast.bp5-intent-primary .bp5-icon:first-child {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-primary .bp5-button {\n  background-color: #2d72d2 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:hover {\n  background-color: #215db0 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:active {\n  background-color: #184a90 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-primary .bp5-button:focus {\n  outline-color: rgba(255, 255, 255, 0.5);\n}\n.bp5-toast.bp5-intent-primary .bp5-button:last-child > .bp5-icon-cross {\n  color: rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-success {\n  background-color: #238551;\n  color: #ffffff;\n}\n.bp5-toast.bp5-intent-success .bp5-icon:first-child {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-success .bp5-button {\n  background-color: #238551 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:hover {\n  background-color: #1c6e42 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:active {\n  background-color: #165a36 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-success .bp5-button:focus {\n  outline-color: rgba(255, 255, 255, 0.5);\n}\n.bp5-toast.bp5-intent-success .bp5-button:last-child > .bp5-icon-cross {\n  color: rgba(255, 255, 255, 0.7) !important;\n}\n.bp5-toast.bp5-intent-warning {\n  background-color: #fbb360;\n  color: #1c2127;\n}\n.bp5-toast.bp5-intent-warning .bp5-icon:first-child {\n  color: rgba(28, 33, 39, 0.7);\n}\n.bp5-toast.bp5-intent-warning .bp5-button {\n  background-color: #fbb360 !important;\n  color: #1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:hover {\n  background-color: #ec9a3c !important;\n  color: #1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:active {\n  background-color: #c87619 !important;\n  color: #1c2127 !important;\n}\n.bp5-toast.bp5-intent-warning .bp5-button:focus {\n  outline-color: rgba(255, 255, 255, 0.5);\n}\n.bp5-toast.bp5-intent-warning .bp5-button:last-child > .bp5-icon-cross {\n  color: rgba(28, 33, 39, 0.7) !important;\n}\n.bp5-toast.bp5-intent-danger {\n  background-color: #cd4246;\n  color: #ffffff;\n}\n.bp5-toast.bp5-intent-danger .bp5-icon:first-child {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-toast.bp5-intent-danger .bp5-button {\n  background-color: #cd4246 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:hover {\n  background-color: #ac2f33 !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:active {\n  background-color: #8e292c !important;\n  color: #ffffff !important;\n}\n.bp5-toast.bp5-intent-danger .bp5-button:focus {\n  outline-color: rgba(255, 255, 255, 0.5);\n}\n.bp5-toast.bp5-intent-danger .bp5-button:last-child > .bp5-icon-cross {\n  color: rgba(255, 255, 255, 0.7) !important;\n}\n\n.bp5-toast-message {\n  flex: 1 1 auto;\n  padding: 11px;\n  word-break: break-word;\n}\n\n.bp5-toast-container {\n  align-items: center;\n  display: flex !important;\n  flex-direction: column;\n  left: 0;\n  overflow: hidden;\n  padding: 0 20px 20px;\n  pointer-events: none;\n  right: 0;\n  z-index: 40;\n}\n.bp5-toast-container.bp5-toast-container-in-portal {\n  position: fixed;\n}\n.bp5-toast-container.bp5-toast-container-inline {\n  position: absolute;\n}\n.bp5-toast-container.bp5-toast-container-top {\n  top: 0;\n}\n.bp5-toast-container.bp5-toast-container-bottom {\n  bottom: 0;\n  flex-direction: column-reverse;\n  top: auto;\n}\n.bp5-toast-container.bp5-toast-container-left {\n  align-items: flex-start;\n}\n.bp5-toast-container.bp5-toast-container-right {\n  align-items: flex-end;\n}\n\n.bp5-toast-container-bottom\n  .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active),\n.bp5-toast-container-bottom\n  .bp5-toast.bp5-toast-enter:not(.bp5-toast-enter-active)\n  ~ .bp5-toast,\n.bp5-toast-container-bottom\n  .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active),\n.bp5-toast-container-bottom\n  .bp5-toast.bp5-toast-appear:not(.bp5-toast-appear-active)\n  ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-exit-active ~ .bp5-toast,\n.bp5-toast-container-bottom .bp5-toast.bp5-toast-leave-active ~ .bp5-toast {\n  transform: translateY(60px);\n}\n.bp5-tooltip {\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 2px 4px rgba(17, 20, 24, 0.2),\n    0 8px 24px rgba(17, 20, 24, 0.2);\n  transform: scale(1);\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow {\n  height: 22px;\n  position: absolute;\n  width: 22px;\n}\n.bp5-tooltip .bp5-popover-arrow::before {\n  height: 14px;\n  margin: 4px;\n  width: 14px;\n}\n.bp5-tooltip .bp5-popover-content {\n  background: #404854;\n}\n.bp5-tooltip .bp5-popover-content,\n.bp5-tooltip .bp5-heading {\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-popover-arrow::before {\n  box-shadow: 1px 1px 6px rgba(17, 20, 24, 0.2);\n}\n.bp5-tooltip .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.1;\n}\n.bp5-tooltip .bp5-popover-arrow-fill {\n  fill: #404854;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-popover-enter > .bp5-tooltip,\n.bp5-popover-appear > .bp5-tooltip {\n  transform: scale(0.8);\n}\n.bp5-popover-enter-active > .bp5-tooltip,\n.bp5-popover-appear-active > .bp5-tooltip {\n  transform: scale(1);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-popover-exit > .bp5-tooltip {\n  transform: scale(1);\n}\n.bp5-popover-exit-active > .bp5-tooltip {\n  transform: scale(0.8);\n  transition-delay: 0;\n  transition-duration: 100ms;\n  transition-property: transform;\n  transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tooltip .bp5-text-muted {\n  color: #abb3bf;\n}\n.bp5-tooltip .bp5-text-disabled {\n  color: rgba(171, 179, 191, 0.6);\n}\n.bp5-tooltip .bp5-running-text hr {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.bp5-tooltip a {\n  color: #8abbff;\n}\n.bp5-tooltip a:hover {\n  color: #8abbff;\n}\n.bp5-tooltip a .bp5-icon,\n.bp5-tooltip a .bp5-icon-standard,\n.bp5-tooltip a .bp5-icon-large {\n  color: inherit;\n}\n.bp5-tooltip a code {\n  color: inherit;\n}\n.bp5-tooltip .bp5-code,\n.bp5-tooltip .bp5-running-text code {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\na > .bp5-tooltip .bp5-code,\na > .bp5-tooltip .bp5-running-text code {\n  color: inherit;\n}\n.bp5-tooltip .bp5-code-block,\n.bp5-tooltip .bp5-running-text pre {\n  background: rgba(17, 20, 24, 0.3);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.4);\n  color: #f6f7f9;\n}\n.bp5-tooltip .bp5-code-block > code,\n.bp5-tooltip .bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n.bp5-tooltip .bp5-key,\n.bp5-tooltip .bp5-running-text kbd {\n  background: #383e47;\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2),\n    0 1px 1px 0 rgba(17, 20, 24, 0.4);\n  color: #abb3bf;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-primary,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-primary,\n.bp5-tooltip .bp5-icon-large.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-success,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-success,\n.bp5-tooltip .bp5-icon-large.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-warning,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-warning,\n.bp5-tooltip .bp5-icon-large.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-tooltip .bp5-icon.bp5-intent-danger,\n.bp5-tooltip .bp5-icon-standard.bp5-intent-danger,\n.bp5-tooltip .bp5-icon-large.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-tooltip .bp5-popover-content {\n  padding: 10px 12px;\n}\n.bp5-tooltip.bp5-compact .bp5-popover-content {\n  line-height: 1rem;\n  padding: 5px 7px;\n}\n.bp5-tooltip.bp5-compact .bp5-code {\n  vertical-align: text-bottom;\n}\n.bp5-tooltip.bp5-popover-placement-top .bp5-popover-arrow {\n  transform: translateY(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-left .bp5-popover-arrow {\n  transform: translateX(-3px);\n}\n.bp5-tooltip.bp5-popover-placement-bottom .bp5-popover-arrow {\n  transform: translateY(3px);\n}\n.bp5-tooltip.bp5-popover-placement-right .bp5-popover-arrow {\n  transform: translateX(3px);\n}\n.bp5-tooltip.bp5-dark,\n.bp5-dark .bp5-tooltip {\n  box-shadow: 0 2px 4px rgba(17, 20, 24, 0.4), 0 8px 24px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-popover-content {\n  background: #e5e8eb;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-content,\n.bp5-tooltip.bp5-dark .bp5-heading,\n.bp5-dark .bp5-tooltip .bp5-popover-content,\n.bp5-dark .bp5-tooltip .bp5-heading {\n  color: #404854;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow::before,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow::before {\n  box-shadow: 1px 1px 6px rgba(17, 20, 24, 0.4);\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-border,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow-border {\n  fill: #111418;\n  fill-opacity: 0.2;\n}\n.bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n.bp5-dark .bp5-tooltip .bp5-popover-arrow-fill {\n  fill: #e5e8eb;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark .bp5-popover-arrow-fill,\n  .bp5-dark .bp5-tooltip .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark,\n  .bp5-dark .bp5-tooltip {\n    border: 1px solid buttonborder;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-text-muted,\n.bp5-dark .bp5-tooltip .bp5-text-muted {\n  color: #5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-text-disabled,\n.bp5-dark .bp5-tooltip .bp5-text-disabled {\n  color: rgba(95, 107, 124, 0.6);\n}\n.bp5-tooltip.bp5-dark .bp5-running-text hr,\n.bp5-dark .bp5-tooltip .bp5-running-text hr {\n  border-color: rgba(17, 20, 24, 0.15);\n}\n.bp5-tooltip.bp5-dark a,\n.bp5-dark .bp5-tooltip a {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark a:hover,\n.bp5-dark .bp5-tooltip a:hover {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark a .bp5-icon,\n.bp5-tooltip.bp5-dark a .bp5-icon-standard,\n.bp5-tooltip.bp5-dark a .bp5-icon-large,\n.bp5-dark .bp5-tooltip a .bp5-icon,\n.bp5-dark .bp5-tooltip a .bp5-icon-standard,\n.bp5-dark .bp5-tooltip a .bp5-icon-large {\n  color: inherit;\n}\n.bp5-tooltip.bp5-dark a code,\n.bp5-dark .bp5-tooltip a code {\n  color: inherit;\n}\n.bp5-tooltip.bp5-dark .bp5-code,\n.bp5-tooltip.bp5-dark .bp5-running-text code,\n.bp5-dark .bp5-tooltip .bp5-code,\n.bp5-dark .bp5-tooltip .bp5-running-text code {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\na > .bp5-tooltip.bp5-dark .bp5-code,\na > .bp5-tooltip.bp5-dark .bp5-running-text code,\na > .bp5-dark .bp5-tooltip .bp5-code,\na > .bp5-dark .bp5-tooltip .bp5-running-text code {\n  color: #2d72d2;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block,\n.bp5-tooltip.bp5-dark .bp5-running-text pre,\n.bp5-dark .bp5-tooltip .bp5-code-block,\n.bp5-dark .bp5-tooltip .bp5-running-text pre {\n  background: rgba(255, 255, 255, 0.7);\n  box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.15);\n  color: #1c2127;\n}\n.bp5-tooltip.bp5-dark .bp5-code-block > code,\n.bp5-tooltip.bp5-dark .bp5-running-text pre > code,\n.bp5-dark .bp5-tooltip .bp5-code-block > code,\n.bp5-dark .bp5-tooltip .bp5-running-text pre > code {\n  background: none;\n  box-shadow: none;\n  color: inherit;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-dark .bp5-code-block,\n  .bp5-tooltip.bp5-dark .bp5-running-text pre,\n  .bp5-dark .bp5-tooltip .bp5-code-block,\n  .bp5-dark .bp5-tooltip .bp5-running-text pre {\n    border: 1px solid buttonborder;\n    box-shadow: none;\n  }\n}\n.bp5-tooltip.bp5-dark .bp5-key,\n.bp5-tooltip.bp5-dark .bp5-running-text kbd,\n.bp5-dark .bp5-tooltip .bp5-key,\n.bp5-dark .bp5-tooltip .bp5-running-text kbd {\n  background: #ffffff;\n  box-shadow: 0 0 0 1px rgba(17, 20, 24, 0.1), 0 1px 1px rgba(17, 20, 24, 0.2);\n  color: #5f6b7c;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-primary,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-primary,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-primary,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-primary {\n  color: #215db0;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-success,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-success,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-success,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-success {\n  color: #1c6e42;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-warning,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-warning,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-warning,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-warning {\n  color: #935610;\n}\n.bp5-tooltip.bp5-dark .bp5-icon.bp5-intent-danger,\n.bp5-tooltip.bp5-dark .bp5-icon-standard.bp5-intent-danger,\n.bp5-tooltip.bp5-dark .bp5-icon-large.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon-standard.bp5-intent-danger,\n.bp5-dark .bp5-tooltip .bp5-icon-large.bp5-intent-danger {\n  color: #ac2f33;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-content {\n  background: #2d72d2;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill {\n  fill: #2d72d2;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-primary .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-content {\n  background: #238551;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill {\n  fill: #238551;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-success .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-content {\n  background: #c87619;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill {\n  fill: #c87619;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-warning .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-content {\n  background: #cd4246;\n  color: #ffffff;\n}\n.bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill {\n  fill: #cd4246;\n}\n@media (forced-colors: active) and (prefers-color-scheme: dark) {\n  .bp5-tooltip.bp5-intent-danger .bp5-popover-arrow-fill {\n    fill: buttonborder;\n  }\n}\n\n.bp5-tooltip-indicator {\n  border-bottom: dotted 1px;\n  cursor: help;\n}\n.bp5-tree .bp5-icon,\n.bp5-tree .bp5-icon-standard,\n.bp5-tree .bp5-icon-large {\n  color: #5f6b7c;\n}\n.bp5-tree .bp5-icon.bp5-intent-primary,\n.bp5-tree .bp5-icon-standard.bp5-intent-primary,\n.bp5-tree .bp5-icon-large.bp5-intent-primary {\n  color: #2d72d2;\n}\n.bp5-tree .bp5-icon.bp5-intent-success,\n.bp5-tree .bp5-icon-standard.bp5-intent-success,\n.bp5-tree .bp5-icon-large.bp5-intent-success {\n  color: #238551;\n}\n.bp5-tree .bp5-icon.bp5-intent-warning,\n.bp5-tree .bp5-icon-standard.bp5-intent-warning,\n.bp5-tree .bp5-icon-large.bp5-intent-warning {\n  color: #c87619;\n}\n.bp5-tree .bp5-icon.bp5-intent-danger,\n.bp5-tree .bp5-icon-standard.bp5-intent-danger,\n.bp5-tree .bp5-icon-large.bp5-intent-danger {\n  color: #cd4246;\n}\n\n.bp5-tree-node-list {\n  list-style: none;\n  margin: 0;\n  padding-left: 0;\n}\n\n.bp5-tree-root {\n  background-color: transparent;\n  cursor: default;\n  padding-left: 0;\n  position: relative;\n}\n\n.bp5-tree-node-content-0 {\n  padding-left: 0px;\n}\n\n.bp5-tree-node-content-1 {\n  padding-left: 23px;\n}\n\n.bp5-tree-node-content-2 {\n  padding-left: 46px;\n}\n\n.bp5-tree-node-content-3 {\n  padding-left: 69px;\n}\n\n.bp5-tree-node-content-4 {\n  padding-left: 92px;\n}\n\n.bp5-tree-node-content-5 {\n  padding-left: 115px;\n}\n\n.bp5-tree-node-content-6 {\n  padding-left: 138px;\n}\n\n.bp5-tree-node-content-7 {\n  padding-left: 161px;\n}\n\n.bp5-tree-node-content-8 {\n  padding-left: 184px;\n}\n\n.bp5-tree-node-content-9 {\n  padding-left: 207px;\n}\n\n.bp5-tree-node-content-10 {\n  padding-left: 230px;\n}\n\n.bp5-tree-node-content-11 {\n  padding-left: 253px;\n}\n\n.bp5-tree-node-content-12 {\n  padding-left: 276px;\n}\n\n.bp5-tree-node-content-13 {\n  padding-left: 299px;\n}\n\n.bp5-tree-node-content-14 {\n  padding-left: 322px;\n}\n\n.bp5-tree-node-content-15 {\n  padding-left: 345px;\n}\n\n.bp5-tree-node-content-16 {\n  padding-left: 368px;\n}\n\n.bp5-tree-node-content-17 {\n  padding-left: 391px;\n}\n\n.bp5-tree-node-content-18 {\n  padding-left: 414px;\n}\n\n.bp5-tree-node-content-19 {\n  padding-left: 437px;\n}\n\n.bp5-tree-node-content-20 {\n  padding-left: 460px;\n}\n\n.bp5-tree-node-content {\n  align-items: center;\n  background: none;\n  display: flex;\n  height: 30px;\n  padding-right: 5px;\n  width: 100%;\n}\n.bp5-tree-node-content:hover {\n  background-color: rgba(143, 153, 168, 0.15);\n}\n.bp5-tree-node-content:active {\n  background-color: rgba(143, 153, 168, 0.3);\n}\n\n.bp5-tree-node-caret,\n.bp5-tree-node-caret-none {\n  min-width: 30px;\n}\n\n.bp5-tree-node-caret {\n  color: #5f6b7c;\n  cursor: pointer;\n  padding: 7px;\n  transform: rotate(0deg);\n  transition: transform 200ms cubic-bezier(0.4, 1, 0.75, 0.9);\n}\n.bp5-tree-node-caret:hover {\n  color: #1c2127;\n}\n.bp5-dark .bp5-tree-node-caret {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tree-node-caret:hover {\n  color: #f6f7f9;\n}\n.bp5-tree-node-caret:hover {\n  color: #1c2127;\n}\n.bp5-tree-node-caret.bp5-tree-node-caret-open {\n  transform: rotate(90deg);\n}\n.bp5-tree-node-caret.bp5-icon-standard::before {\n  content: '\\f14d';\n}\n\n.bp5-tree-node-icon {\n  margin-right: 7px;\n  position: relative;\n}\n\n.bp5-tree-node-label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  word-wrap: normal;\n  flex: 1 1 auto;\n  position: relative;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-tree-node-label span {\n  display: inline;\n}\n\n.bp5-tree-node-secondary-label {\n  padding: 0 5px;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n.bp5-tree-node-secondary-label .bp5-popover-wrapper,\n.bp5-tree-node-secondary-label .bp5-popover-target {\n  align-items: center;\n  display: flex;\n}\n\n.bp5-tree-node.bp5-disabled .bp5-tree-node-content {\n  background-color: inherit;\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n.bp5-tree-node.bp5-disabled .bp5-tree-node-caret,\n.bp5-tree-node.bp5-disabled .bp5-tree-node-icon {\n  color: rgba(95, 107, 124, 0.6);\n  cursor: not-allowed;\n}\n\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content {\n  background-color: #2d72d2;\n}\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content,\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon,\n.bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-icon-standard,\n.bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content .bp5-icon-large {\n  color: #ffffff;\n}\n.bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-tree-node-caret::before {\n  color: rgba(255, 255, 255, 0.7);\n}\n.bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-tree-node-caret:hover::before {\n  color: #ffffff;\n}\n\n.bp5-tree.bp5-compact .bp5-tree-node-content {\n  height: 24px;\n}\n.bp5-tree.bp5-compact .bp5-tree-node-caret {\n  margin-right: 3px;\n  min-width: 24px;\n  padding: 4px;\n}\n\n.bp5-dark .bp5-tree-node-content:hover {\n  background-color: rgba(95, 107, 124, 0.3);\n}\n.bp5-dark .bp5-tree .bp5-icon,\n.bp5-dark .bp5-tree .bp5-icon-standard,\n.bp5-dark .bp5-tree .bp5-icon-large {\n  color: #abb3bf;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-primary,\n.bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-primary,\n.bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-primary {\n  color: #8abbff;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-success,\n.bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-success,\n.bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-success {\n  color: #72ca9b;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-warning,\n.bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-warning,\n.bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-warning {\n  color: #fbb360;\n}\n.bp5-dark .bp5-tree .bp5-icon.bp5-intent-danger,\n.bp5-dark .bp5-tree .bp5-icon-standard.bp5-intent-danger,\n.bp5-dark .bp5-tree .bp5-icon-large.bp5-intent-danger {\n  color: #fa999c;\n}\n.bp5-dark .bp5-tree-node:not(.bp5-disabled) .bp5-tree-node-caret:hover {\n  color: #f6f7f9;\n}\n.bp5-dark .bp5-tree-node.bp5-tree-node-selected > .bp5-tree-node-content {\n  background-color: #2d72d2;\n}\n.bp5-dark\n  .bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-icon,\n.bp5-dark\n  .bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-icon-standard,\n.bp5-dark\n  .bp5-tree-node.bp5-tree-node-selected\n  > .bp5-tree-node-content\n  .bp5-icon-large {\n  color: #ffffff;\n}\n"
  },
  {
    "path": "apps/frontend/src/chrome.d.ts",
    "content": "/**\n * Minimal Chrome extension API types for externally_connectable messaging.\n * Web pages listed in an extension's externally_connectable can use\n * chrome.runtime.sendMessage to communicate with the extension.\n */\ndeclare namespace chrome {\n  namespace runtime {\n    const lastError: { message?: string } | undefined;\n    function sendMessage(\n      extensionId: string,\n      message: any,\n      callback: (response: any) => void\n    ): void;\n  }\n}\n"
  },
  {
    "path": "apps/frontend/src/components/agents/agent.chat.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { CopilotChat, CopilotKitCSSProperties } from '@copilotkit/react-ui';\nimport {\n  InputProps,\n  UserMessageProps,\n} from '@copilotkit/react-ui/dist/components/chat/props';\nimport { Input } from '@gitroom/frontend/components/agents/agent.input';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport {\n  CopilotKit,\n  useCopilotAction,\n  useCopilotMessagesContext,\n} from '@copilotkit/react-core';\nimport {\n  MediaPortal,\n  PropertiesContext,\n} from '@gitroom/frontend/components/agents/agent';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useParams } from 'next/navigation';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { TextMessage } from '@copilotkit/runtime-client-gql';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport dayjs from 'dayjs';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const AgentChat: FC = () => {\n  const { backendUrl } = useVariables();\n  const params = useParams<{ id: string }>();\n  const { properties } = useContext(PropertiesContext);\n  const t = useT();\n\n  return (\n    <CopilotKit\n      {...(params.id === 'new' ? {} : { threadId: params.id })}\n      credentials=\"include\"\n      runtimeUrl={backendUrl + '/copilot/agent'}\n      showDevConsole={false}\n      agent=\"postiz\"\n      properties={{\n        integrations: properties,\n      }}\n    >\n      <Hooks />\n      <LoadMessages id={params.id} />\n      <div\n        style={\n          {\n            '--copilot-kit-primary-color': 'var(--new-btn-text)',\n            '--copilot-kit-background-color': 'var(--new-bg-color)',\n          } as CopilotKitCSSProperties\n        }\n        className=\"trz agent bg-newBgColorInner flex flex-col gap-[15px] transition-all flex-1 items-center relative\"\n      >\n        <div className=\"absolute left-0 w-full h-full pb-[20px]\">\n          <CopilotChat\n            className=\"w-full h-full\"\n            labels={{\n              title: t('your_assistant', 'Your Assistant'),\n              initial: t('agent_welcome_message', `Hello, I am your Postiz agent 🙌🏻.\n              \nI can schedule a post or multiple posts to multiple channels and generate pictures and videos.\n\nYou can select the channels you want to use from the left menu.\n\nYou can see your previous conversations from the right menu.\n\nYou can also use me as an MCP Server, check Settings >> Public API\n`),\n            }}\n            UserMessage={Message}\n            Input={NewInput}\n          />\n        </div>\n      </div>\n    </CopilotKit>\n  );\n};\n\nconst LoadMessages: FC<{ id: string }> = ({ id }) => {\n  const { setMessages } = useCopilotMessagesContext();\n  const fetch = useFetch();\n\n  const loadMessages = useCallback(async (idToSet: string) => {\n    const data = await (await fetch(`/copilot/${idToSet}/list`)).json();\n    setMessages(\n      data.uiMessages.map((p: any) => {\n        return new TextMessage({\n          content: p.content,\n          role: p.role,\n        });\n      })\n    );\n  }, []);\n\n  useEffect(() => {\n    if (id === 'new') {\n      setMessages([]);\n      return;\n    }\n    loadMessages(id);\n  }, [id]);\n\n  return null;\n};\n\nconst Message: FC<UserMessageProps> = (props) => {\n  const convertContentToImagesAndVideo = useMemo(() => {\n    return (props.message?.content || '')\n      .replace(/Video: (http.*mp4\\n)/g, (match, p1) => {\n        return `<video controls class=\"h-[150px] w-[150px] rounded-[8px] mb-[10px]\"><source src=\"${p1.trim()}\" type=\"video/mp4\">Your browser does not support the video tag.</video>`;\n      })\n      .replace(/Image: (http.*\\n)/g, (match, p1) => {\n        return `<img src=\"${p1.trim()}\" class=\"h-[150px] w-[150px] max-w-full border border-newBgColorInner\" />`;\n      })\n      .replace(/\\[\\-\\-Media\\-\\-\\](.*)\\[\\-\\-Media\\-\\-\\]/g, (match, p1) => {\n        return `<div class=\"flex justify-center mt-[20px]\">${p1}</div>`;\n      })\n      .replace(\n        /(\\[--integrations--\\][\\s\\S]*?\\[--integrations--\\])/g,\n        (match, p1) => {\n          return ``;\n        }\n      );\n  }, [props.message?.content]);\n  return (\n    <div\n      className=\"copilotKitMessage copilotKitUserMessage min-w-[300px]\"\n      dangerouslySetInnerHTML={{ __html: convertContentToImagesAndVideo }}\n    />\n  );\n};\nconst NewInput: FC<InputProps> = (props) => {\n  const [media, setMedia] = useState([] as { path: string; id: string }[]);\n  const [value, setValue] = useState('');\n  const { properties } = useContext(PropertiesContext);\n  return (\n    <>\n      <MediaPortal\n        value={value}\n        media={media}\n        setMedia={(e) => setMedia(e.target.value)}\n      />\n      <Input\n        {...props}\n        onChange={setValue}\n        onSend={(text) => {\n          const send = props.onSend(\n            text +\n              (media.length > 0\n                ? '\\n[--Media--]' +\n                  media\n                    .map((m) =>\n                      m.path.indexOf('mp4') > -1\n                        ? `Video: ${m.path}`\n                        : `Image: ${m.path}`\n                    )\n                    .join('\\n') +\n                  '\\n[--Media--]'\n                : '') +\n              `\n${\n  properties.length\n    ? `[--integrations--]\nUse the following social media platforms: ${JSON.stringify(\n        properties.map((p) => ({\n          id: p.id,\n          platform: p.identifier,\n          profilePicture: p.picture,\n          additionalSettings: p.additionalSettings,\n        }))\n      )}\n[--integrations--]`\n    : ``\n}`\n          );\n          setValue('');\n          setMedia([]);\n          return send;\n        }}\n      />\n    </>\n  );\n};\n\nexport const Hooks: FC = () => {\n  const modals = useModals();\n\n  useCopilotAction({\n    name: 'manualPosting',\n    description:\n      'This tool should be triggered when the user wants to manually add the generated post',\n    parameters: [\n      {\n        name: 'list',\n        type: 'object[]',\n        description:\n          'list of posts to schedule to different social media (integration ids)',\n        attributes: [\n          {\n            name: 'integrationId',\n            type: 'string',\n            description: 'The integration id',\n          },\n          {\n            name: 'date',\n            type: 'string',\n            description: 'UTC date of the scheduled post',\n          },\n          {\n            name: 'settings',\n            type: 'object',\n            description: 'Settings for the integration [input:settings]',\n          },\n          {\n            name: 'posts',\n            type: 'object[]',\n            description: 'list of posts / comments (one under another)',\n            attributes: [\n              {\n                name: 'content',\n                type: 'string',\n                description: 'the content of the post',\n              },\n              {\n                name: 'attachments',\n                type: 'object[]',\n                description: 'list of attachments',\n                attributes: [\n                  {\n                    name: 'id',\n                    type: 'string',\n                    description: 'id of the attachment',\n                  },\n                  {\n                    name: 'path',\n                    type: 'string',\n                    description: 'url of the attachment',\n                  },\n                ],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n    renderAndWaitForResponse: ({ args, status, respond }) => {\n      if (status === 'executing') {\n        return <OpenModal args={args} respond={respond} />;\n      }\n\n      return null;\n    },\n  });\n  return null;\n};\n\nconst OpenModal: FC<{\n  respond: (value: any) => void;\n  args: {\n    list: {\n      integrationId: string;\n      date: string;\n      settings?: Record<string, any>;\n      posts: { content: string; attachments: { id: string; path: string }[] }[];\n    }[];\n  };\n}> = ({ args, respond }) => {\n  const modals = useModals();\n  const { properties } = useContext(PropertiesContext);\n  const startModal = useCallback(async () => {\n    for (const integration of args.list) {\n      await new Promise((res) => {\n        const group = makeId(10);\n        modals.openModal({\n          id: 'add-edit-modal',\n          closeOnClickOutside: false,\n          removeLayout: true,\n          closeOnEscape: false,\n          withCloseButton: false,\n          askClose: true,\n          size: '80%',\n          title: ``,\n          classNames: {\n            modal: 'w-[100%] max-w-[1400px] text-textColor',\n          },\n          children: (\n            <ExistingDataContextProvider\n              value={{\n                group,\n                integration: integration.integrationId,\n                integrationPicture:\n                  properties.find((p) => p.id === integration.integrationId)\n                    .picture || '',\n                settings: integration.settings || {},\n                posts: integration.posts.map((p) => ({\n                  approvedSubmitForOrder: 'NO',\n                  content: p.content,\n                  createdAt: new Date().toISOString(),\n                  state: 'DRAFT',\n                  id: makeId(10),\n                  settings: JSON.stringify(integration.settings || {}),\n                  group,\n                  integrationId: integration.integrationId,\n                  integration: properties.find(\n                    (p) => p.id === integration.integrationId\n                  ),\n                  publishDate: dayjs.utc(integration.date).toISOString(),\n                  image: p.attachments.map((a) => ({\n                    id: a.id,\n                    path: a.path,\n                  })),\n                })),\n              }}\n            >\n              <AddEditModal\n                date={dayjs.utc(integration.date)}\n                allIntegrations={properties}\n                integrations={properties.filter(\n                  (p) => p.id === integration.integrationId\n                )}\n                onlyValues={integration.posts.map((p) => ({\n                  content: p.content,\n                  id: makeId(10),\n                  settings: integration.settings || {},\n                  image: p.attachments.map((a) => ({\n                    id: a.id,\n                    path: a.path,\n                  })),\n                }))}\n                reopenModal={() => {}}\n                mutate={() => res(true)}\n              />\n            </ExistingDataContextProvider>\n          ),\n        });\n      });\n    }\n\n    respond('User scheduled all the posts');\n  }, [args, respond, properties]);\n\n  useEffect(() => {\n    startModal();\n  }, []);\n  return (\n    <div onClick={() => respond('continue')}>\n      Opening manually ${JSON.stringify(args)}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/agents/agent.input.tsx",
    "content": "import React, { useMemo, useRef, useState } from 'react';\nimport { useCopilotContext, useCopilotReadable } from '@copilotkit/react-core';\nimport AutoResizingTextarea from '@gitroom/frontend/components/agents/agent.textarea';\nimport { useChatContext } from '@copilotkit/react-ui';\nimport { InputProps } from '@copilotkit/react-ui/dist/components/chat/props';\nconst MAX_NEWLINES = 6;\n\nexport const Input = ({\n  inProgress,\n  onSend,\n  isVisible = false,\n  onStop,\n  onUpload,\n  hideStopButton = false,\n  onChange,\n}: InputProps & { onChange: (value: string) => void }) => {\n  const context = useChatContext();\n  const copilotContext = useCopilotContext();\n  const showPoweredBy = !copilotContext.copilotApiConfig?.publicApiKey;\n\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n  const [isComposing, setIsComposing] = useState(false);\n\n  const handleDivClick = (event: React.MouseEvent<HTMLDivElement>) => {\n    const target = event.target as HTMLElement;\n\n    // If the user clicked a button or inside a button, don't focus the textarea\n    if (target.closest('button')) return;\n\n    // If the user clicked the textarea, do nothing (it's already focused)\n    if (target.tagName === 'TEXTAREA') return;\n\n    // Otherwise, focus the textarea\n    textareaRef.current?.focus();\n  };\n\n  const [text, setText] = useState('');\n  const send = () => {\n    if (inProgress) return;\n    onSend(text);\n    setText('');\n\n    textareaRef.current?.focus();\n  };\n\n  const isInProgress = inProgress;\n  const buttonIcon =\n    isInProgress && !hideStopButton\n      ? context.icons.stopIcon\n      : context.icons.sendIcon;\n\n  const canSend = useMemo(() => {\n    const interruptEvent = copilotContext.langGraphInterruptAction?.event;\n    const interruptInProgress =\n      interruptEvent?.name === 'LangGraphInterruptEvent' &&\n      !interruptEvent?.response;\n\n    return !isInProgress && text.trim().length > 0 && !interruptInProgress;\n  }, [copilotContext.langGraphInterruptAction?.event, isInProgress, text]);\n\n  const canStop = useMemo(() => {\n    return isInProgress && !hideStopButton;\n  }, [isInProgress, hideStopButton]);\n\n  const sendDisabled = !canSend && !canStop;\n\n  return (\n    <div\n      className={`copilotKitInputContainer ${\n        showPoweredBy ? 'poweredByContainer' : ''\n      }`}\n    >\n      <div className=\"copilotKitInput\" onClick={handleDivClick}>\n        <AutoResizingTextarea\n          ref={textareaRef}\n          placeholder={context.labels.placeholder}\n          autoFocus={false}\n          maxRows={MAX_NEWLINES}\n          value={text}\n          onChange={(event) => {\n            onChange(event.target.value);\n            setText(event.target.value);\n          }}\n          onCompositionStart={() => setIsComposing(true)}\n          onCompositionEnd={() => setIsComposing(false)}\n          onKeyDown={(event) => {\n            if (event.key === 'Enter' && !event.shiftKey && !isComposing) {\n              event.preventDefault();\n              if (canSend) {\n                send();\n              }\n            }\n          }}\n        />\n        <div className=\"copilotKitInputControls\">\n          {onUpload && (\n            <button onClick={onUpload} className=\"copilotKitInputControlButton\">\n              {context.icons.uploadIcon}\n            </button>\n          )}\n\n          <div style={{ flexGrow: 1 }} />\n          <button\n            disabled={sendDisabled}\n            onClick={isInProgress && !hideStopButton ? onStop : send}\n            data-copilotkit-in-progress={inProgress}\n            data-test-id={\n              inProgress\n                ? 'copilot-chat-request-in-progress'\n                : 'copilot-chat-ready'\n            }\n            className=\"copilotKitInputControlButton\"\n          >\n            {buttonIcon}\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/agents/agent.textarea.tsx",
    "content": "import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from \"react\";\n\ninterface AutoResizingTextareaProps {\n  maxRows?: number;\n  placeholder?: string;\n  value: string;\n  onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;\n  onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n  onCompositionStart?: () => void;\n  onCompositionEnd?: () => void;\n  autoFocus?: boolean;\n}\n\nconst AutoResizingTextarea = forwardRef<HTMLTextAreaElement, AutoResizingTextareaProps>(\n  (\n    {\n      maxRows = 1,\n      placeholder,\n      value,\n      onChange,\n      onKeyDown,\n      onCompositionStart,\n      onCompositionEnd,\n      autoFocus,\n    },\n    ref,\n  ) => {\n    const internalTextareaRef = useRef<HTMLTextAreaElement>(null);\n    const [maxHeight, setMaxHeight] = useState<number>(0);\n\n    useImperativeHandle(ref, () => internalTextareaRef.current as HTMLTextAreaElement);\n\n    useEffect(() => {\n      const calculateMaxHeight = () => {\n        const textarea = internalTextareaRef.current;\n        if (textarea) {\n          textarea.style.height = \"auto\";\n          const singleRowHeight = textarea.scrollHeight;\n          setMaxHeight(singleRowHeight * maxRows);\n          if (autoFocus) {\n            textarea.focus();\n          }\n        }\n      };\n\n      calculateMaxHeight();\n    }, [maxRows]);\n\n    useEffect(() => {\n      const textarea = internalTextareaRef.current;\n      if (textarea) {\n        textarea.style.height = \"auto\";\n        textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`;\n      }\n    }, [value, maxHeight]);\n\n    return (\n      <textarea\n        ref={internalTextareaRef}\n        value={value}\n        onChange={onChange}\n        onKeyDown={onKeyDown}\n        onCompositionStart={onCompositionStart}\n        onCompositionEnd={onCompositionEnd}\n        placeholder={placeholder}\n        style={{\n          overflow: \"auto\",\n          resize: \"none\",\n          maxHeight: `${maxHeight}px`,\n        }}\n        rows={1}\n      />\n    );\n  },\n);\n\nexport default AutoResizingTextarea;"
  },
  {
    "path": "apps/frontend/src/components/agents/agent.tsx",
    "content": "'use client';\n\nimport React, {\n  createContext,\n  FC,\n  useCallback,\n  useMemo,\n  useState,\n  ReactNode,\n} from 'react';\nimport clsx from 'clsx';\nimport useCookie from 'react-use-cookie';\nimport useSWR from 'swr';\nimport { orderBy } from 'lodash';\nimport { SVGLine } from '@gitroom/frontend/components/launches/launches.component';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\nimport Image from 'next/image';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useWaitForClass } from '@gitroom/helpers/utils/use.wait.for.class';\nimport { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { Integration } from '@prisma/client';\nimport Link from 'next/link';\nimport { useParams, usePathname, useRouter } from 'next/navigation';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const MediaPortal: FC<{\n  media: { path: string; id: string }[];\n  value: string;\n  setMedia: (event: {\n    target: {\n      name: string;\n      value?: {\n        id: string;\n        path: string;\n        alt?: string;\n        thumbnail?: string;\n        thumbnailTimestamp?: number;\n      }[];\n    };\n  }) => void;\n}> = ({ media, setMedia, value }) => {\n  const waitForClass = useWaitForClass('copilotKitMessages');\n  const t = useT();\n  if (!waitForClass) return null;\n  return (\n    <div className=\"pl-[14px] pr-[24px] whitespace-nowrap editor rm-bg\">\n      <MultiMediaComponent\n        allData={[{ content: value }]}\n        text={value}\n        label={t('attachments', 'Attachments')}\n        description=\"\"\n        value={media}\n        dummy={false}\n        name=\"image\"\n        onChange={setMedia}\n        onOpen={() => {}}\n        onClose={() => {}}\n      />\n    </div>\n  );\n};\n\nexport const AgentList: FC<{ onChange: (arr: any[]) => void }> = ({\n  onChange,\n}) => {\n  const fetch = useFetch();\n  const t = useT();\n  const [selected, setSelected] = useState([]);\n\n  const load = useCallback(async () => {\n    return (await (await fetch('/integrations/list')).json()).integrations;\n  }, []);\n\n  const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0');\n\n  const { data } = useSWR('integrations', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n\n  const setIntegration = useCallback(\n    (integration: Integration) => () => {\n      if (selected.some((p) => p.id === integration.id)) {\n        onChange(selected.filter((p) => p.id !== integration.id));\n        setSelected(selected.filter((p) => p.id !== integration.id));\n      } else {\n        onChange([...selected, integration]);\n        setSelected([...selected, integration]);\n      }\n    },\n    [selected]\n  );\n\n  const sortedIntegrations = useMemo(() => {\n    return orderBy(\n      data || [],\n      ['type', 'disabled', 'identifier'],\n      ['desc', 'asc', 'asc']\n    );\n  }, [data]);\n\n  return (\n    <div\n      className={clsx(\n        'trz bg-newBgColorInner flex flex-col gap-[15px] transition-all relative',\n        collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]'\n      )}\n    >\n      <div className=\"absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor\">\n        <div className=\"flex items-center\">\n          <h2 className=\"group-[.sidebar]:hidden flex-1 text-[20px] font-[500] mb-[15px]\">\n            {t('select_channels', 'Select Channels')}\n          </h2>\n          <div\n            onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')}\n            className=\"-mt-3 group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"7\"\n              height=\"13\"\n              viewBox=\"0 0 7 13\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M6 11.5L1 6.5L6 1.5\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div className={clsx('flex flex-col gap-[15px]')}>\n          {sortedIntegrations.map((integration, index) => (\n            <div\n              onClick={setIntegration(integration)}\n              key={integration.id}\n              className={clsx(\n                'flex gap-[12px] items-center group/profile justify-center hover:bg-boxHover rounded-e-[8px] hover:opacity-100 cursor-pointer',\n                !selected.some((p) => p.id === integration.id) && 'opacity-20'\n              )}\n            >\n              <div\n                className={clsx(\n                  'relative rounded-full flex justify-center items-center gap-[6px]',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {(integration.inBetweenSteps || integration.refreshNeeded) && (\n                  <div className=\"absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer\">\n                    <div className=\"bg-red-500 w-[15px] h-[15px] rounded-full start-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center\">\n                      !\n                    </div>\n                    <div className=\"bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]\" />\n                  </div>\n                )}\n                <div className=\"h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity\">\n                  <SVGLine />\n                </div>\n                <ImageWithFallback\n                  fallbackSrc={`/icons/platforms/${integration.identifier}.png`}\n                  src={integration.picture}\n                  className=\"rounded-[8px]\"\n                  alt={integration.identifier}\n                  width={36}\n                  height={36}\n                />\n                <Image\n                  src={`/icons/platforms/${integration.identifier}.png`}\n                  className=\"rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth\"\n                  alt={integration.identifier}\n                  width={18.41}\n                  height={18.41}\n                />\n              </div>\n              <div\n                className={clsx(\n                  'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {integration.name}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport const PropertiesContext = createContext({ properties: [] });\nexport const Agent: FC<{ children: ReactNode }> = ({ children }) => {\n  const [properties, setProperties] = useState([]);\n\n  return (\n    <PropertiesContext.Provider value={{ properties }}>\n      <AgentList onChange={setProperties} />\n      <div className=\"bg-newBgColorInner flex flex-1\">{children}</div>\n      <Threads />\n    </PropertiesContext.Provider>\n  );\n};\n\nconst Threads: FC = () => {\n  const fetch = useFetch();\n  const router = useRouter();\n  const pathname = usePathname();\n  const t = useT();\n  const threads = useCallback(async () => {\n    return (await fetch('/copilot/list')).json();\n  }, []);\n  const { id } = useParams<{ id: string }>();\n\n  const { data } = useSWR('threads', threads);\n\n  return (\n    <div\n      className={clsx(\n        'trz bg-newBgColorInner flex flex-col gap-[15px] transition-all relative',\n        'w-[260px]'\n      )}\n    >\n      <div className=\"absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor\">\n        <div className=\"mb-[15px] justify-center flex group-[.sidebar]:pb-[15px]\">\n          <Link\n            href={`/agents`}\n            className=\"text-white whitespace-nowrap flex-1 pt-[12px] pb-[14px] ps-[16px] pe-[20px] group-[.sidebar]:p-0 min-h-[44px] max-h-[44px] rounded-md bg-btnPrimary flex justify-center items-center gap-[5px] outline-none\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"21\"\n              height=\"20\"\n              viewBox=\"0 0 21 20\"\n              fill=\"none\"\n              className=\"min-w-[21px] min-h-[20px]\"\n            >\n              <path\n                d=\"M10.5001 4.16699V15.8337M4.66675 10.0003H16.3334\"\n                stroke=\"white\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n            <div className=\"flex-1 text-start text-[16px] group-[.sidebar]:hidden\">\n              {t('start_a_new_chat', 'Start a new chat')}\n            </div>\n          </Link>\n        </div>\n        <div className=\"flex flex-col gap-[1px]\">\n          {data?.threads?.map((p: any) => (\n            <Link\n              className={clsx(\n                'overflow-ellipsis overflow-hidden whitespace-nowrap hover:bg-newBgColor px-[10px] py-[6px] rounded-[10px] cursor-pointer',\n                p.id === id && 'bg-newBgColor'\n              )}\n              href={`/agents/${p.id}`}\n              key={p.id}\n            >\n              {p.title}\n            </Link>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/analytics.component.tsx",
    "content": "'use client';\n\nimport { StarsAndForks } from '@gitroom/frontend/components/analytics/stars.and.forks';\nimport { FC, useCallback } from 'react';\nimport { StarsTableComponent } from '@gitroom/frontend/components/analytics/stars.table.component';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nexport const AnalyticsComponent: FC = () => {\n  const fetch = useFetch();\n  const load = useCallback(async (path: string) => {\n    return await (await fetch(path)).json();\n  }, []);\n  const { isLoading: isLoadingAnalytics, data: analytics } = useSWR(\n    '/analytics',\n    load\n  );\n  const { isLoading: isLoadingTrending, data: trending } = useSWR(\n    '/analytics/trending',\n    load\n  );\n  if (isLoadingAnalytics || isLoadingTrending) {\n    return <LoadingComponent />;\n  }\n  return (\n    <div className=\"flex gap-[24px] flex-1\">\n      <div className=\"flex flex-col gap-[24px] flex-1\">\n        <StarsAndForks list={analytics} trending={trending} />\n        <StarsTableComponent />\n      </div>\n      {/*<div className=\"w-[318px] bg-third mt-[-44px] p-[16px]\">*/}\n      {/*    <h2 className=\"text-[20px]\">News Feed</h2>*/}\n      {/*    <div className=\"my-[30px] flex h-[32px]\">*/}\n      {/*        <div className=\"flex-1 bg-forth flex justify-center items-center\">*/}\n      {/*            Global*/}\n      {/*        </div>*/}\n      {/*        <div className=\"flex-1 bg-primary flex justify-center items-center\">*/}\n      {/*            My Feed*/}\n      {/*        </div>*/}\n      {/*    </div>*/}\n      {/*    <div>*/}\n      {/*        <div className=\"w-full flex-col justify-start items-start gap-4 inline-flex\">*/}\n      {/*            <div className=\"self-stretch justify-start items-start gap-2.5 inline-flex pb-[16.5px] border-b-[1px] border-fifth\">*/}\n      {/*                <img className=\"w-8 h-8 rounded-full\" src=\"https://via.placeholder.com/32x32\"/>*/}\n      {/*                <div className=\"grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex\">*/}\n      {/*                    <div className=\"justify-center items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-textColor text-sm font-medium leading-tight\">Nevo David</div>*/}\n      {/*                        <div className=\"text-neutral-500 text-[10px] font-normal uppercase tracking-wide\">05/06/2024</div>*/}\n      {/*                    </div>*/}\n      {/*                    <div className=\"self-stretch text-neutral-400 text-xs font-normal\">O atual sistema político precisa mudar para valorizar o trabalho e garantir igualdade de oportunidad</div>*/}\n      {/*                    <div className=\"self-stretch justify-start items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-customColor10 text-xs font-normal\">See Tweet</div>*/}\n      {/*                        <div className=\"w-4 h-4 relative\"/>*/}\n      {/*                    </div>*/}\n      {/*                </div>*/}\n      {/*            </div>*/}\n      {/*            <div className=\"self-stretch justify-start items-start gap-2.5 inline-flex pb-[16.5px] border-b-[1px] border-fifth\">*/}\n      {/*                <img className=\"w-8 h-8 rounded-full\" src=\"https://via.placeholder.com/32x32\"/>*/}\n      {/*                <div className=\"grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex\">*/}\n      {/*                    <div className=\"justify-center items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-textColor text-sm font-medium leading-tight\">Nevo David</div>*/}\n      {/*                        <div className=\"text-neutral-500 text-[10px] font-normal uppercase tracking-wide\">05/06/2024</div>*/}\n      {/*                    </div>*/}\n      {/*                    <div className=\"self-stretch text-neutral-400 text-xs font-normal\">O atual sistema político precisa mudar para valorizar o trabalho e garantir igualdade de oportunidad</div>*/}\n      {/*                    <div className=\"self-stretch justify-start items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-customColor10 text-xs font-normal\">See Tweet</div>*/}\n      {/*                        <div className=\"w-4 h-4 relative\"/>*/}\n      {/*                    </div>*/}\n      {/*                </div>*/}\n      {/*            </div>*/}\n      {/*            <div className=\"self-stretch justify-start items-start gap-2.5 inline-flex pb-[16.5px] border-b-[1px] border-fifth\">*/}\n      {/*                <img className=\"w-8 h-8 rounded-full\" src=\"https://via.placeholder.com/32x32\"/>*/}\n      {/*                <div className=\"grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex\">*/}\n      {/*                    <div className=\"justify-center items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-textColor text-sm font-medium leading-tight\">Nevo David</div>*/}\n      {/*                        <div className=\"text-neutral-500 text-[10px] font-normal uppercase tracking-wide\">05/06/2024</div>*/}\n      {/*                    </div>*/}\n      {/*                    <div className=\"self-stretch text-neutral-400 text-xs font-normal\">O atual sistema político precisa mudar para valorizar o trabalho e garantir igualdade de oportunidad</div>*/}\n      {/*                    <div className=\"self-stretch justify-start items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-customColor10 text-xs font-normal\">See Tweet</div>*/}\n      {/*                        <div className=\"w-4 h-4 relative\"/>*/}\n      {/*                    </div>*/}\n      {/*                </div>*/}\n      {/*            </div>*/}\n      {/*            <div className=\"self-stretch justify-start items-start gap-2.5 inline-flex pb-[16.5px] border-b-[1px] border-fifth\">*/}\n      {/*                <img className=\"w-8 h-8 rounded-full\" src=\"https://via.placeholder.com/32x32\"/>*/}\n      {/*                <div className=\"grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex\">*/}\n      {/*                    <div className=\"justify-center items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-textColor text-sm font-medium leading-tight\">Nevo David</div>*/}\n      {/*                        <div className=\"text-neutral-500 text-[10px] font-normal uppercase tracking-wide\">05/06/2024</div>*/}\n      {/*                    </div>*/}\n      {/*                    <div className=\"self-stretch text-neutral-400 text-xs font-normal\">O atual sistema político precisa mudar para valorizar o trabalho e garantir igualdade de oportunidad</div>*/}\n      {/*                    <div className=\"self-stretch justify-start items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-customColor10 text-xs font-normal\">See Tweet</div>*/}\n      {/*                        <div className=\"w-4 h-4 relative\"/>*/}\n      {/*                    </div>*/}\n      {/*                </div>*/}\n      {/*            </div>*/}\n      {/*            <div className=\"self-stretch justify-start items-start gap-2.5 inline-flex pb-[16.5px] border-b-[1px] border-fifth\">*/}\n      {/*                <img className=\"w-8 h-8 rounded-full\" src=\"https://via.placeholder.com/32x32\"/>*/}\n      {/*                <div className=\"grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex\">*/}\n      {/*                    <div className=\"justify-center items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-textColor text-sm font-medium leading-tight\">Nevo David</div>*/}\n      {/*                        <div className=\"text-neutral-500 text-[10px] font-normal uppercase tracking-wide\">05/06/2024</div>*/}\n      {/*                    </div>*/}\n      {/*                    <div className=\"self-stretch text-neutral-400 text-xs font-normal\">O atual sistema político precisa mudar para valorizar o trabalho e garantir igualdade de oportunidad</div>*/}\n      {/*                    <div className=\"self-stretch justify-start items-center gap-1 inline-flex\">*/}\n      {/*                        <div className=\"text-customColor10 text-xs font-normal\">See Tweet</div>*/}\n      {/*                        <div className=\"w-4 h-4 relative\"/>*/}\n      {/*                    </div>*/}\n      {/*                </div>*/}\n      {/*            </div>*/}\n      {/*        </div>*/}\n      {/*    </div>*/}\n      {/*</div>*/}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/chart-social.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useMemo, useRef } from 'react';\nimport DrawChart from 'chart.js/auto';\nimport { TotalList } from '@gitroom/frontend/components/analytics/stars.and.forks.interface';\nimport { chunk } from 'lodash';\nimport useCookie from 'react-use-cookie';\n\nfunction mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] {\n  const res = chunk(data, Math.ceil(data.length / numPoints));\n  return res.map((row) => {\n    return {\n      date: `${row[0].date} - ${row?.at(-1)?.date}`,\n      total: row.reduce((acc, curr) => acc + curr.total, 0),\n    };\n  });\n}\n\nexport const ChartSocial: FC<{\n  data: TotalList[];\n  color?: 'purple' | 'green' | 'blue';\n}> = (props) => {\n  const { data, color = 'purple' } = props;\n  const [mode] = useCookie('mode', 'dark');\n  const list = useMemo(() => {\n    return mergeDataPoints(data, 7);\n  }, [data]);\n  const ref = useRef<any>(null);\n  const chart = useRef<null | DrawChart>(null);\n\n  const colorSchemes = {\n    purple: {\n      start: 'rgba(97, 43, 211, 0.8)',\n      end: 'rgba(97, 43, 211, 0.1)',\n      border: 'rgb(97, 43, 211)',\n    },\n    green: {\n      start: 'rgba(50, 213, 131, 0.8)',\n      end: 'rgba(50, 213, 131, 0.1)',\n      border: 'rgb(50, 213, 131)',\n    },\n    blue: {\n      start: 'rgba(29, 155, 240, 0.8)',\n      end: 'rgba(29, 155, 240, 0.1)',\n      border: 'rgb(29, 155, 240)',\n    },\n  };\n\n  const colors = colorSchemes[color];\n\n  useEffect(() => {\n    const ctx = ref.current.getContext('2d');\n    const gradient = ctx.createLinearGradient(0, 0, 0, ref.current.height);\n    gradient.addColorStop(0, colors.start);\n    gradient.addColorStop(1, colors.end);\n\n    chart.current = new DrawChart(ref.current!, {\n      type: 'line',\n      options: {\n        maintainAspectRatio: false,\n        responsive: true,\n        animation: {\n          duration: 750,\n          easing: 'easeOutQuart',\n        },\n        interaction: {\n          mode: 'index',\n          intersect: false,\n        },\n        layout: {\n          padding: {\n            left: 0,\n            right: 0,\n            top: 4,\n            bottom: 0,\n          },\n        },\n        scales: {\n          y: {\n            beginAtZero: true,\n            display: false,\n          },\n          x: {\n            display: false,\n            ticks: {\n              stepSize: 10,\n              maxTicksLimit: 7,\n            },\n          },\n        },\n        plugins: {\n          legend: {\n            display: false,\n          },\n          tooltip: {\n            enabled: true,\n            backgroundColor: mode === 'dark' ? '#1e1d1d' : '#fff',\n            titleColor: mode === 'dark' ? '#fff' : '#000',\n            bodyColor: mode === 'dark' ? '#9c9c9c' : '#777',\n            borderColor: mode === 'dark' ? '#2b2b2b' : '#e7e9eb',\n            borderWidth: 1,\n            padding: 10,\n            cornerRadius: 8,\n            displayColors: false,\n            titleFont: {\n              size: 12,\n              weight: 'normal',\n            },\n            bodyFont: {\n              size: 14,\n              weight: 'bold',\n            },\n          },\n        },\n      },\n      data: {\n        labels: list.map((row) => row.date),\n        datasets: [\n          {\n            borderColor: colors.border,\n            borderWidth: 2,\n            label: 'Total',\n            backgroundColor: gradient,\n            fill: true,\n            data: list.map((row) => row.total),\n            tension: 0.4,\n            pointRadius: 0,\n            pointHoverRadius: 6,\n            pointHoverBackgroundColor: colors.border,\n            pointHoverBorderColor: mode === 'dark' ? '#1e1d1d' : '#fff',\n            pointHoverBorderWidth: 2,\n          },\n        ],\n      },\n    });\n    return () => {\n      chart?.current?.destroy();\n    };\n  }, []);\n\n  return <canvas className=\"w-full h-full\" ref={ref} />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/chart.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useRef } from 'react';\nimport DrawChart from 'chart.js/auto';\nimport {\n  ForksList,\n  StarsList,\n} from '@gitroom/frontend/components/analytics/stars.and.forks.interface';\nimport dayjs from 'dayjs';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nexport const Chart: FC<{\n  list: StarsList[] | ForksList[];\n}> = (props) => {\n  const { list } = props;\n  const ref = useRef<any>(null);\n  const chart = useRef<null | DrawChart>(null);\n  useEffect(() => {\n    const gradient = ref.current\n      .getContext('2d')\n      .createLinearGradient(0, 0, 0, ref.current.height);\n    gradient.addColorStop(0, 'rgba(114, 118, 137, 1)'); // Start color with some transparency\n    gradient.addColorStop(1, 'rgb(9, 11, 19, 1)');\n    chart.current = new DrawChart(ref.current!, {\n      type: 'line',\n      options: {\n        maintainAspectRatio: false,\n        responsive: true,\n        layout: {\n          padding: {\n            left: 0,\n            right: 0,\n            top: 0,\n            bottom: 0,\n          },\n        },\n        scales: {\n          y: {\n            beginAtZero: true,\n            display: false,\n          },\n          x: {\n            display: false,\n          },\n        },\n        plugins: {\n          legend: {\n            display: false,\n          },\n        },\n      },\n      data: {\n        labels: list.map((row) => newDayjs(row.date).format('DD/MM/YYYY')),\n        datasets: [\n          {\n            borderColor: '#fff',\n            // @ts-ignore\n            label: list?.[0]?.totalForks ? 'Forks by date' : 'Stars by date',\n            backgroundColor: gradient,\n            fill: true,\n            // @ts-ignore\n            data: list.map((row) => row.totalForks || row.totalStars),\n          },\n        ],\n      },\n    });\n    return () => {\n      chart?.current?.destroy();\n    };\n  }, []);\n  return <canvas className=\"w-full h-full\" ref={ref} />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/stars.and.forks.interface.ts",
    "content": "export interface StarsList {\n  totalStars: number;\n  date: string;\n}\nexport interface TotalList {\n  total: number;\n  date: string;\n}\nexport interface ForksList {\n  totalForks: number;\n  date: string;\n}\nexport interface Stars {\n  id: string;\n  stars: number;\n  totalStars: number;\n  login: string;\n  date: string;\n}\nexport interface StarsAndForksInterface {\n  list: Array<{\n    login: string;\n    stars: StarsList[];\n    forks: ForksList[];\n  }>;\n  trending: {\n    last: string;\n    predictions: string;\n  };\n}\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/stars.and.forks.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport { StarsAndForksInterface } from '@gitroom/frontend/components/analytics/stars.and.forks.interface';\nimport { Chart } from '@gitroom/frontend/components/analytics/chart';\nimport { UtcToLocalDateRender } from '@gitroom/react/helpers/utc.date.render';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const StarsAndForks: FC<StarsAndForksInterface> = (props) => {\n  const { list } = props;\n  const t = useT();\n  return (\n    <>\n      {list.map((item) => (\n        <div className=\"flex gap-[24px] h-[272px]\" key={item.login}>\n          <div className=\"flex-1 bg-secondary py-[10px] px-[16px] flex flex-col\">\n            <div className=\"flex items-center gap-[14px]\">\n              <div className=\"bg-fifth p-[8px]\">\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"20\"\n                  height=\"20\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M18.7011 5.47658C18.639 5.28266 18.5206 5.11156 18.3611 4.98499C18.2015 4.85842 18.008 4.78208 17.805 4.76564L14.8972 4.51955L13.7628 1.87502C13.6828 1.68962 13.5502 1.53172 13.3815 1.42078C13.2127 1.30985 13.0152 1.25073 12.8132 1.25073C12.6113 1.25073 12.4138 1.30985 12.245 1.42078C12.0763 1.53172 11.9437 1.68962 11.8636 1.87502L10.7293 4.52033L7.82066 4.76564C7.61869 4.78187 7.42605 4.85754 7.26704 4.98311C7.10803 5.10869 6.98977 5.27854 6.92718 5.47125C6.86459 5.66396 6.86047 5.87088 6.91534 6.06593C6.97021 6.26097 7.08162 6.4354 7.2355 6.56721L9.4355 8.44221L8.77691 11.2336C8.73061 11.4293 8.74296 11.6342 8.81243 11.8229C8.8819 12.0116 9.00541 12.1756 9.16753 12.2945C9.33158 12.415 9.52749 12.4845 9.73078 12.4942C9.93406 12.5039 10.1357 12.4535 10.3105 12.3492L12.8105 10.8649L15.3105 12.3492C15.4853 12.4535 15.6869 12.5039 15.8902 12.4942C16.0935 12.4845 16.2894 12.415 16.4535 12.2945C16.6157 12.1757 16.7392 12.0117 16.8087 11.823C16.8782 11.6343 16.8905 11.4293 16.8441 11.2336L16.1855 8.44221L18.3847 6.56721C18.5395 6.43716 18.6522 6.26409 18.7085 6.06994C18.7649 5.87578 18.7623 5.66927 18.7011 5.47658ZM15.2746 7.58596C15.1306 7.70792 15.0233 7.86741 14.9645 8.04671C14.9058 8.226 14.8979 8.41807 14.9418 8.60158L15.5128 11.0235L13.3425 9.73361C13.1824 9.63811 12.9993 9.5877 12.8128 9.5877C12.6264 9.5877 12.4433 9.63811 12.2832 9.73361L10.1128 11.0156L10.6839 8.59377C10.7284 8.41035 10.7209 8.21816 10.6623 8.03876C10.6036 7.85937 10.4962 7.69986 10.3519 7.57814L8.4566 5.96721L10.9636 5.75471C11.1497 5.73909 11.328 5.6731 11.4794 5.56382C11.6308 5.45453 11.7496 5.30608 11.823 5.13439L12.8128 2.83127L13.8003 5.13439C13.8739 5.30598 13.9927 5.45434 14.1441 5.56361C14.2955 5.67287 14.4737 5.73893 14.6597 5.75471L17.1668 5.96721L15.2746 7.58596ZM6.69254 9.81721L2.31754 14.1922C2.20026 14.3095 2.0412 14.3754 1.87535 14.3754C1.7095 14.3754 1.55044 14.3095 1.43316 14.1922C1.31588 14.0749 1.25 13.9159 1.25 13.75C1.25 13.5842 1.31588 13.4251 1.43316 13.3078L5.80816 8.93283C5.92544 8.81556 6.0845 8.74967 6.25035 8.74967C6.4162 8.74967 6.57526 8.81556 6.69254 8.93283C6.80981 9.05011 6.87569 9.20917 6.8757 9.37502C6.8757 9.54087 6.80981 9.69993 6.69254 9.81721ZM7.94254 14.1922L3.56753 18.5672C3.50947 18.6253 3.44053 18.6713 3.36466 18.7028C3.28879 18.7342 3.20747 18.7504 3.12535 18.7504C3.04323 18.7504 2.96191 18.7342 2.88604 18.7028C2.81017 18.6713 2.74123 18.6253 2.68316 18.5672C2.62509 18.5091 2.57903 18.4402 2.5476 18.3643C2.51618 18.2885 2.5 18.2071 2.5 18.125C2.5 18.0429 2.51618 17.9616 2.5476 17.8857C2.57903 17.8098 2.62509 17.7409 2.68316 17.6828L7.05816 13.3078C7.17544 13.1906 7.3345 13.1247 7.50035 13.1247C7.6662 13.1247 7.82526 13.1906 7.94254 13.3078C8.05981 13.4251 8.12569 13.5842 8.1257 13.75C8.1257 13.9159 8.05981 14.0749 7.94254 14.1922ZM13.5675 13.3078C13.6256 13.3659 13.6717 13.4348 13.7032 13.5107C13.7347 13.5866 13.7508 13.6679 13.7508 13.75C13.7508 13.8322 13.7347 13.9135 13.7032 13.9894C13.6717 14.0652 13.6256 14.1342 13.5675 14.1922L9.19254 18.5672C9.13447 18.6253 9.06553 18.6713 8.98966 18.7028C8.91379 18.7342 8.83247 18.7504 8.75035 18.7504C8.66823 18.7504 8.58691 18.7342 8.51104 18.7028C8.43517 18.6713 8.36623 18.6253 8.30816 18.5672C8.25009 18.5091 8.20403 18.4402 8.1726 18.3643C8.14117 18.2885 8.125 18.2071 8.125 18.125C8.125 18.0429 8.14117 17.9616 8.1726 17.8857C8.20403 17.8098 8.25009 17.7409 8.30816 17.6828L12.6832 13.3078C12.7412 13.2497 12.8101 13.2036 12.886 13.1722C12.9619 13.1407 13.0432 13.1245 13.1253 13.1245C13.2075 13.1245 13.2888 13.1407 13.3647 13.1722C13.4406 13.2036 13.5095 13.2497 13.5675 13.3078Z\"\n                    fill=\"white\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[20px]\">\n                {item.login\n                  .split('/')[1]\n                  .split('')\n                  .map((char, index) =>\n                    index === 0 ? char.toUpperCase() : char\n                  )\n                  .join('')}\n                {t('stars', 'Stars')}\n              </div>\n            </div>\n            <div className=\"flex-1 relative\">\n              <div className=\"absolute w-full h-full start-0 top-0\">\n                {item.stars.length ? (\n                  <Chart list={item.stars} />\n                ) : (\n                  <div className=\"w-full h-full flex items-center justify-center text-3xl\">\n                    {t('processing_stars', 'Processing stars...')}\n                  </div>\n                )}\n              </div>\n            </div>\n            <div className=\"text-[50px] leading-[60px]\">\n              {item?.stars[item.stars.length - 1]?.totalStars}\n            </div>\n          </div>\n\n          <div className=\"flex-1 bg-secondary py-[10px] px-[16px] flex flex-col\">\n            <div className=\"flex items-center gap-[14px]\">\n              <div className=\"bg-fifth p-[8px]\">\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"20\"\n                  height=\"20\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.7863 14.0719C16.7441 14.1425 16.6883 14.204 16.6222 14.253C16.5561 14.302 16.481 14.3375 16.4011 14.3574C16.3213 14.3773 16.2383 14.3812 16.1569 14.3689C16.0755 14.3567 15.9974 14.3285 15.927 14.2859L10.6254 11.1039V16.875C10.6254 17.0408 10.5596 17.1997 10.4423 17.3169C10.3251 17.4342 10.1662 17.5 10.0004 17.5C9.83464 17.5 9.67567 17.4342 9.55846 17.3169C9.44125 17.1997 9.3754 17.0408 9.3754 16.875V11.1039L4.07228 14.2859C4.00187 14.3292 3.92358 14.3581 3.84195 14.3709C3.76031 14.3837 3.67695 14.3801 3.59668 14.3605C3.51641 14.3409 3.44083 14.3055 3.37431 14.2565C3.30779 14.2075 3.25166 14.1458 3.20915 14.0749C3.16664 14.004 3.13861 13.9255 3.12667 13.8437C3.11474 13.7619 3.11913 13.6786 3.13961 13.5985C3.16008 13.5185 3.19623 13.4433 3.24595 13.3773C3.29568 13.3113 3.358 13.2558 3.42931 13.2141L8.78556 10L3.42931 6.78594C3.358 6.74418 3.29568 6.6887 3.24595 6.62271C3.19623 6.55671 3.16008 6.48151 3.13961 6.40145C3.11913 6.3214 3.11474 6.23808 3.12667 6.15631C3.13861 6.07454 3.16664 5.99595 3.20915 5.92509C3.25166 5.85423 3.30779 5.7925 3.37431 5.74348C3.44083 5.69445 3.51641 5.6591 3.59668 5.63948C3.67695 5.61985 3.76031 5.61634 3.84195 5.62914C3.92358 5.64194 4.00187 5.67081 4.07228 5.71406L9.3754 8.89609V3.125C9.3754 2.95924 9.44125 2.80027 9.55846 2.68306C9.67567 2.56585 9.83464 2.5 10.0004 2.5C10.1662 2.5 10.3251 2.56585 10.4423 2.68306C10.5596 2.80027 10.6254 2.95924 10.6254 3.125V8.89609L15.9285 5.71406C15.9989 5.67081 16.0772 5.64194 16.1589 5.62914C16.2405 5.61634 16.3239 5.61985 16.4041 5.63948C16.4844 5.6591 16.56 5.69445 16.6265 5.74348C16.693 5.7925 16.7491 5.85423 16.7917 5.92509C16.8342 5.99595 16.8622 6.07454 16.8741 6.15631C16.8861 6.23808 16.8817 6.3214 16.8612 6.40145C16.8407 6.48151 16.8046 6.55671 16.7549 6.62271C16.7051 6.6887 16.6428 6.74418 16.5715 6.78594L11.2152 10L16.5715 13.2141C16.642 13.2563 16.7034 13.3119 16.7524 13.3779C16.8013 13.4438 16.8367 13.5188 16.8567 13.5984C16.8767 13.6781 16.8807 13.7609 16.8686 13.8422C16.8566 13.9234 16.8286 14.0015 16.7863 14.0719Z\"\n                    fill=\"white\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[20px]\">\n                {item.login\n                  .split('/')[1]\n                  .split('')\n                  .map((char, index) =>\n                    index === 0 ? char.toUpperCase() : char\n                  )\n                  .join('')}\n                {t('forks', 'Forks')}\n              </div>\n            </div>\n            <div className=\"flex-1 relative\">\n              <div className=\"absolute w-full h-full start-0 top-0\">\n                {item.forks.length ? (\n                  <Chart list={item.forks} />\n                ) : (\n                  <div className=\"w-full h-full flex items-center justify-center text-3xl\">\n                    {t('processing_stars', 'Processing stars...')}\n                  </div>\n                )}\n              </div>\n            </div>\n            <div className=\"text-[50px] leading-[60px]\">\n              {item?.forks[item.forks.length - 1]?.totalForks}\n            </div>\n          </div>\n        </div>\n      ))}\n      <div className=\"flex gap-[24px]\">\n        {[0, 1].map((p) => (\n          <div\n            key={p}\n            className=\"flex-1 bg-secondary py-[24px] px-[16px] gap-[16px] flex flex-col\"\n          >\n            <div className=\"flex items-center gap-[14px]\">\n              <div className=\"p-[8px] bg-fifth\">\n                {p === 0 ? (\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"20\"\n                    height=\"20\"\n                    viewBox=\"0 0 20 20\"\n                    fill=\"none\"\n                  >\n                    <path\n                      d=\"M18.125 16.25C18.125 16.4158 18.0592 16.5747 17.9419 16.6919C17.8247 16.8092 17.6658 16.875 17.5 16.875H2.5C2.33424 16.875 2.17527 16.8092 2.05806 16.6919C1.94085 16.5747 1.875 16.4158 1.875 16.25V3.75C1.875 3.58424 1.94085 3.42527 2.05806 3.30806C2.17527 3.19085 2.33424 3.125 2.5 3.125C2.66576 3.125 2.82473 3.19085 2.94194 3.30806C3.05915 3.42527 3.125 3.58424 3.125 3.75V12.2414L7.05781 8.30781C7.11586 8.2497 7.18479 8.2036 7.26066 8.17215C7.33654 8.1407 7.41787 8.12451 7.5 8.12451C7.58213 8.12451 7.66346 8.1407 7.73934 8.17215C7.81521 8.2036 7.88414 8.2497 7.94219 8.30781L10 10.3664L14.1164 6.25H12.5C12.3342 6.25 12.1753 6.18415 12.0581 6.06694C11.9408 5.94973 11.875 5.79076 11.875 5.625C11.875 5.45924 11.9408 5.30027 12.0581 5.18306C12.1753 5.06585 12.3342 5 12.5 5H15.625C15.7908 5 15.9497 5.06585 16.0669 5.18306C16.1842 5.30027 16.25 5.45924 16.25 5.625V8.75C16.25 8.91576 16.1842 9.07473 16.0669 9.19194C15.9497 9.30915 15.7908 9.375 15.625 9.375C15.4592 9.375 15.3003 9.30915 15.1831 9.19194C15.0658 9.07473 15 8.91576 15 8.75V7.13359L10.4422 11.6922C10.3841 11.7503 10.3152 11.7964 10.2393 11.8279C10.1635 11.8593 10.0821 11.8755 10 11.8755C9.91787 11.8755 9.83654 11.8593 9.76066 11.8279C9.68479 11.7964 9.61586 11.7503 9.55781 11.6922L7.5 9.63359L3.125 14.0086V15.625H17.5C17.6658 15.625 17.8247 15.6908 17.9419 15.8081C18.0592 15.9253 18.125 16.0842 18.125 16.25Z\"\n                      fill=\"white\"\n                    />\n                  </svg>\n                ) : (\n                  <svg\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                    width=\"20\"\n                    height=\"20\"\n                    viewBox=\"0 0 20 20\"\n                    fill=\"none\"\n                  >\n                    <path\n                      d=\"M18.7503 4.375V9.375C18.7503 9.54076 18.6845 9.69973 18.5673 9.81694C18.4501 9.93415 18.2911 10 18.1253 10C17.9596 10 17.8006 9.93415 17.6834 9.81694C17.5662 9.69973 17.5003 9.54076 17.5003 9.375V5.88359L11.0675 12.3172C11.0095 12.3753 10.9406 12.4214 10.8647 12.4529C10.7888 12.4843 10.7075 12.5005 10.6253 12.5005C10.5432 12.5005 10.4619 12.4843 10.386 12.4529C10.3101 12.4214 10.2412 12.3753 10.1832 12.3172L7.50035 9.63359L2.31753 14.8172C2.20026 14.9345 2.0412 15.0003 1.87535 15.0003C1.7095 15.0003 1.55044 14.9345 1.43316 14.8172C1.31588 14.6999 1.25 14.5409 1.25 14.375C1.25 14.2091 1.31588 14.0501 1.43316 13.9328L7.05816 8.30781C7.1162 8.2497 7.18514 8.2036 7.26101 8.17215C7.33688 8.1407 7.41821 8.12451 7.50035 8.12451C7.58248 8.12451 7.66381 8.1407 7.73968 8.17215C7.81556 8.2036 7.88449 8.2497 7.94253 8.30781L10.6253 10.9914L16.6168 5H13.1253C12.9596 5 12.8006 4.93415 12.6834 4.81694C12.5662 4.69973 12.5003 4.54076 12.5003 4.375C12.5003 4.20924 12.5662 4.05027 12.6834 3.93306C12.8006 3.81585 12.9596 3.75 13.1253 3.75H18.1253C18.2911 3.75 18.4501 3.81585 18.5673 3.93306C18.6845 4.05027 18.7503 4.20924 18.7503 4.375Z\"\n                      fill=\"white\"\n                    />\n                  </svg>\n                )}\n              </div>\n              <div className=\"text-[20px]\">\n                {p === 0\n                  ? t('last_github_trending', 'Last Github Trending')\n                  : t('next_predicted_github_trending', 'Next Predicted GitHub Trending')}\n              </div>\n            </div>\n            <div className=\"flex items-center\">\n              <div className=\"w-[2px] h-[30px] bg-customColor11 me-[16px]\"></div>\n              <div className=\"text-[24px] flex-1\">\n                <UtcToLocalDateRender\n                  date={\n                    p === 0 ? props.trending.last : props.trending.predictions\n                  }\n                  format=\"dddd\"\n                />\n              </div>\n              <div\n                className={clsx(\n                  'text-[24px]',\n                  p === 0 ? 'text-customColor12' : 'text-customColor13'\n                )}\n              >\n                <UtcToLocalDateRender\n                  date={\n                    p === 0 ? props.trending.last : props.trending.predictions\n                  }\n                  format=\"DD MMM YYYY\"\n                />\n              </div>\n              <div>\n                <div className=\"rounded-full bg-customColor14 w-[5px] h-[5px] mx-[8px]\" />\n              </div>\n              <div\n                className={clsx(\n                  'text-[24px]',\n                  p === 0 ? 'text-customColor12' : 'text-customColor13'\n                )}\n              >\n                <UtcToLocalDateRender\n                  date={\n                    p === 0 ? props.trending.last : props.trending.predictions\n                  }\n                  format=\"HH:mm\"\n                />\n              </div>\n            </div>\n          </div>\n        ))}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/analytics/stars.table.component.tsx",
    "content": "'use client';\n\nimport {\n  FC,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n  useTransition,\n} from 'react';\nimport { UtcToLocalDateRender } from '@gitroom/react/helpers/utc.date.render';\nimport { Button } from '@gitroom/react/form/button';\nimport dayjs from 'dayjs';\nimport Link from 'next/link';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport useSWR from 'swr';\nimport clsx from 'clsx';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport ReactLoading from 'react-loading';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const UpDown: FC<{\n  name: string;\n  param: string;\n}> = (props) => {\n  const { name, param } = props;\n  const router = useRouter();\n  const searchParams = useSearchParams();\n  const state = useMemo(() => {\n    const newName = searchParams.get('key');\n    const newState = searchParams.get('state');\n    if (newName != param) {\n      return 'none';\n    }\n    return newState as 'asc' | 'desc';\n  }, [searchParams, name, param]);\n  const changeStateUrl = useCallback(\n    (newState: string) => {\n      const query =\n        newState === 'none' ? `` : `?key=${param}&state=${newState}`;\n      router.replace(`/analytics${query}`);\n    },\n    [state, param]\n  );\n  const changeState = useCallback(() => {\n    changeStateUrl(\n      state === 'none' ? 'desc' : state === 'desc' ? 'asc' : 'none'\n    );\n  }, [state, param]);\n  return (\n    <div\n      className=\"flex gap-[5px] items-center select-none\"\n      onClick={changeState}\n    >\n      <div>{name}</div>\n      <div className=\"flex flex-col gap-[3px]\">\n        {['none', 'asc'].indexOf(state) > -1 && (\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"9\"\n            height=\"4\"\n            viewBox=\"0 0 22 12\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M21.9245 11.3823C21.8489 11.5651 21.7207 11.7213 21.5563 11.8312C21.3919 11.9411 21.1986 11.9998 21.0008 11.9998H1.00079C0.802892 12 0.609399 11.9414 0.444805 11.8315C0.280212 11.7217 0.151917 11.5654 0.076165 11.3826C0.000412494 11.1998 -0.0193921 10.9986 0.0192583 10.8045C0.0579087 10.6104 0.153276 10.4322 0.293288 10.2923L10.2933 0.29231C10.3862 0.199333 10.4964 0.125575 10.6178 0.0752506C10.7392 0.0249263 10.8694 -0.000976562 11.0008 -0.000976562C11.1322 -0.000976562 11.2623 0.0249263 11.3837 0.0752506C11.5051 0.125575 11.6154 0.199333 11.7083 0.29231L21.7083 10.2923C21.8481 10.4322 21.9433 10.6105 21.9818 10.8045C22.0202 10.9985 22.0003 11.1996 21.9245 11.3823Z\"\n              fill=\"#94A3B8\"\n            />\n          </svg>\n        )}\n        {['none', 'desc'].indexOf(state) > -1 && (\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"9\"\n            height=\"4\"\n            viewBox=\"0 0 22 12\"\n            fill=\"none\"\n            className=\"rotate-180\"\n          >\n            <path\n              d=\"M21.9245 11.3823C21.8489 11.5651 21.7207 11.7213 21.5563 11.8312C21.3919 11.9411 21.1986 11.9998 21.0008 11.9998H1.00079C0.802892 12 0.609399 11.9414 0.444805 11.8315C0.280212 11.7217 0.151917 11.5654 0.076165 11.3826C0.000412494 11.1998 -0.0193921 10.9986 0.0192583 10.8045C0.0579087 10.6104 0.153276 10.4322 0.293288 10.2923L10.2933 0.29231C10.3862 0.199333 10.4964 0.125575 10.6178 0.0752506C10.7392 0.0249263 10.8694 -0.000976562 11.0008 -0.000976562C11.1322 -0.000976562 11.2623 0.0249263 11.3837 0.0752506C11.5051 0.125575 11.6154 0.199333 11.7083 0.29231L21.7083 10.2923C21.8481 10.4322 21.9433 10.6105 21.9818 10.8045C22.0202 10.9985 22.0003 11.1996 21.9245 11.3823Z\"\n              fill=\"#94A3B8\"\n            />\n          </svg>\n        )}\n      </div>\n    </div>\n  );\n};\nexport const StarsTableComponent = () => {\n  const t = useT();\n  const fetch = useFetch();\n  const router = useRouter();\n  const searchParams = useSearchParams();\n  const page = +(searchParams.get('page') || 1);\n  const key = searchParams.get('key');\n  const state = searchParams.get('state');\n  const [loading, setLoading] = useState(false);\n  const [, startTransition] = useTransition();\n  const starsCallback = useCallback(\n    async (path: string) => {\n      startTransition(() => {\n        setLoading(true);\n      });\n      const data = await (\n        await fetch(path, {\n          body: JSON.stringify({\n            page,\n            ...(key && state\n              ? {\n                  key,\n                  state,\n                }\n              : {}),\n          }),\n          method: 'POST',\n        })\n      ).json();\n      startTransition(() => {\n        setLoading(false);\n      });\n      return data;\n    },\n    [page, key, state]\n  );\n  const {\n    isLoading: isLoadingStars,\n    data: stars,\n    mutate,\n  } = useSWR('/analytics/stars', starsCallback, {\n    revalidateOnMount: false,\n    revalidateOnReconnect: false,\n    revalidateOnFocus: false,\n    refreshWhenHidden: false,\n    revalidateIfStale: false,\n  });\n  useEffect(() => {\n    mutate();\n  }, [searchParams]);\n  const renderMediaLink = useCallback((date: string) => {\n    const local = dayjs.utc(date).local();\n    const weekNumber = local.isoWeek();\n    const year = local.year();\n    return `/launches?week=${weekNumber}&year=${year}`;\n  }, []);\n  const changePage = useCallback(\n    (type: 'increase' | 'decrease') => () => {\n      const newPage = type === 'increase' ? page + 1 : page - 1;\n      const keyAndState = key && state ? `&key=${key}&state=${state}` : '';\n      router.replace(`/analytics?page=${newPage}${keyAndState}`);\n    },\n    [page, key, state]\n  );\n  return (\n    <div className=\"flex flex-1 flex-col gap-[15px] min-h-[426px]\">\n      <div className=\"text-textColor flex gap-[8px] items-center select-none\">\n        <div\n          onClick={changePage('decrease')}\n          className={clsx(\n            (page === 1 || loading) && 'opacity-50 pointer-events-none'\n          )}\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M13.1644 15.5866C13.3405 15.7628 13.4395 16.0016 13.4395 16.2507C13.4395 16.4998 13.3405 16.7387 13.1644 16.9148C12.9883 17.0909 12.7494 17.1898 12.5003 17.1898C12.2513 17.1898 12.0124 17.0909 11.8363 16.9148L5.58629 10.6648C5.49889 10.5777 5.42954 10.4742 5.38222 10.3602C5.3349 10.2463 5.31055 10.1241 5.31055 10.0007C5.31055 9.87732 5.3349 9.75515 5.38222 9.64119C5.42954 9.52724 5.49889 9.42375 5.58629 9.33665L11.8363 3.08665C12.0124 2.91053 12.2513 2.81158 12.5003 2.81158C12.7494 2.81158 12.9883 2.91053 13.1644 3.08665C13.3405 3.26277 13.4395 3.50164 13.4395 3.75071C13.4395 3.99978 13.3405 4.23865 13.1644 4.41477L7.57925 9.99993L13.1644 15.5866Z\"\n              fill=\"#E9E9F1\"\n            />\n          </svg>\n        </div>\n        <h2 className=\"text-[24px]\">{t('stars_per_day', 'Stars per day')}</h2>\n        <div\n          onClick={changePage('increase')}\n          className={clsx(\n            !isLoadingStars &&\n              (loading || stars?.stars?.length < 10) &&\n              'opacity-50 pointer-events-none'\n          )}\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M14.4137 10.6633L8.16374 16.9133C7.98761 17.0894 7.74874 17.1884 7.49967 17.1884C7.2506 17.1884 7.01173 17.0894 6.83561 16.9133C6.65949 16.7372 6.56055 16.4983 6.56055 16.2492C6.56055 16.0002 6.65949 15.7613 6.83561 15.5852L12.4223 10L6.83717 4.41331C6.74997 4.3261 6.68079 4.22257 6.6336 4.10863C6.5864 3.99469 6.56211 3.87257 6.56211 3.74925C6.56211 3.62592 6.5864 3.5038 6.6336 3.38986C6.68079 3.27592 6.74997 3.17239 6.83717 3.08518C6.92438 2.99798 7.02791 2.9288 7.14185 2.88161C7.25579 2.83441 7.37791 2.81012 7.50124 2.81012C7.62456 2.81012 7.74668 2.83441 7.86062 2.88161C7.97456 2.9288 8.07809 2.99798 8.1653 3.08518L14.4153 9.33518C14.5026 9.42238 14.5718 9.52596 14.619 9.63997C14.6662 9.75398 14.6904 9.87618 14.6903 9.99957C14.6901 10.123 14.6656 10.2451 14.6182 10.359C14.5707 10.4729 14.5012 10.5763 14.4137 10.6633Z\"\n              fill=\"#E9E9F1\"\n            />\n          </svg>\n        </div>\n        <div>\n          {loading && (\n            <ReactLoading type=\"spin\" color=\"#fff\" width={20} height={20} />\n          )}\n        </div>\n      </div>\n      <div className=\"flex-1 bg-secondary\">\n        {stars?.stars?.length ? (\n          <table className={`table1`}>\n            <thead>\n              <tr>\n                <th>\n                  <UpDown name={t('repository', 'Repository')} param=\"login\" />\n                </th>\n                <th>\n                  <UpDown name={t('date', 'Date')} param=\"date\" />\n                </th>\n                <th>\n                  <UpDown name={t('total_stars', 'Total Stars')} param=\"totalStars\" />\n                </th>\n                <th>\n                  <UpDown name={t('total_forks', 'Total Forks')} param=\"totalForks\" />\n                </th>\n                <th>\n                  <UpDown name={t('stars', 'Stars')} param=\"stars\" />\n                </th>\n                <th>\n                  <UpDown name={t('forks', 'Forks')} param=\"forks\" />\n                </th>\n                <th>{t('media', 'Media')}</th>\n              </tr>\n            </thead>\n            <tbody>\n              {stars?.stars?.map((p: any) => (\n                <tr key={p.date}>\n                  <td>{p.login}</td>\n                  <td>\n                    <UtcToLocalDateRender date={p.date} format=\"DD/MM/YYYY\" />\n                  </td>\n                  <td>{p.totalStars}</td>\n                  <td>{p.totalForks}</td>\n\n                  <td>{p.stars}</td>\n                  <td>{p.forks}</td>\n                  <td>\n                    <Link href={renderMediaLink(p.date)}>\n                      <Button>{t('check_launch', 'Check Launch')}</Button>\n                    </Link>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        ) : (\n          <div className=\"py-[24px] px-[16px]\">\n            {t(\n              'load_your_github_repository_from_settings_to_see_analytics',\n              'Load your GitHub repository from settings to see analytics'\n            )}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/approved-apps/approved-apps.component.tsx",
    "content": "'use client';\n\nimport { FC, Fragment, useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Button } from '@gitroom/react/form/button';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nconst useApprovedApps = () => {\n  const fetch = useFetch();\n  const load = useCallback(async () => {\n    return (await fetch('/user/approved-apps')).json();\n  }, []);\n  return useSWR('approved-apps', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n  });\n};\n\nexport const ApprovedAppsComponent: FC = () => {\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const t = useT();\n  const { data: apps, mutate } = useApprovedApps();\n\n  const revokeApp = useCallback(\n    (app: any) => async () => {\n      if (\n        await deleteDialog(\n          t(\n            'are_you_sure_revoke_access',\n            `Are you sure you want to revoke access for ${app.oauthApp?.name}?`,\n            { name: app.oauthApp?.name }\n          )\n        )\n      ) {\n        try {\n          await fetch(`/user/approved-apps/${app.id}`, {\n            method: 'DELETE',\n          });\n          toaster.show(\n            t('access_revoked', 'Access revoked successfully'),\n            'success'\n          );\n          mutate();\n        } catch {\n          toaster.show(t('failed_to_revoke', 'Failed to revoke access'), 'warning');\n        }\n      }\n    },\n    []\n  );\n\n  if (apps === undefined) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[20px]\">\n      <div className=\"flex flex-col\">\n        <h3 className=\"text-[20px]\">\n          {t('approved_apps', 'Approved Apps')}\n        </h3>\n        <div className=\"text-customColor18 mt-[4px]\">\n          {t(\n            'apps_you_have_authorized',\n            'Applications you have authorized to access your Postiz account.'\n          )}\n        </div>\n      </div>\n\n      <div className=\"bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n        {!apps?.length ? (\n          <div className=\"text-customColor18\">\n            {t('no_approved_apps', 'No approved apps yet.')}\n          </div>\n        ) : (\n          <div className=\"flex flex-col gap-[16px]\">\n            {apps.map((app: any) => (\n              <div\n                key={app.id}\n                className=\"flex items-center justify-between p-[12px] border border-fifth rounded-[4px]\"\n              >\n                <div className=\"flex items-center gap-[12px]\">\n                  {app.oauthApp?.picture?.path ? (\n                    <img\n                      src={app.oauthApp.picture.path}\n                      alt={app.oauthApp.name}\n                      className=\"w-[40px] h-[40px] rounded-full object-cover\"\n                    />\n                  ) : (\n                    <div className=\"w-[40px] h-[40px] rounded-full bg-fifth flex items-center justify-center text-customColor18\">\n                      {app.oauthApp?.name?.[0]?.toUpperCase() || '?'}\n                    </div>\n                  )}\n                  <div>\n                    <div className=\"text-[14px] font-bold\">\n                      {app.oauthApp?.name}\n                    </div>\n                    {app.oauthApp?.description && (\n                      <div className=\"text-customColor18 text-[12px]\">\n                        {app.oauthApp.description}\n                      </div>\n                    )}\n                    <div className=\"text-customColor18 text-[12px]\">\n                      {t('authorized_on', 'Authorized on')}{' '}\n                      {new Date(app.createdAt).toLocaleDateString()}\n                    </div>\n                  </div>\n                </div>\n                <Button onClick={revokeApp(app)}>\n                  {t('revoke', 'Revoke')}\n                </Button>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/activate.tsx",
    "content": "'use client';\n\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { FormProvider, SubmitHandler, useForm } from 'react-hook-form';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useState, useEffect, useCallback } from 'react';\nimport Link from 'next/link';\n\ntype ResendInputs = {\n  email: string;\n};\n\ntype ResendStatus = 'idle' | 'sent' | 'already_activated';\n\nconst COOLDOWN_SECONDS = 60;\n\nexport function Activate() {\n  const t = useT();\n  const fetch = useFetch();\n  const [loading, setLoading] = useState(false);\n  const [status, setStatus] = useState<ResendStatus>('idle');\n  const [cooldown, setCooldown] = useState(0);\n  const form = useForm<ResendInputs>();\n\n  useEffect(() => {\n    if (cooldown <= 0) return;\n    \n    const timer = setInterval(() => {\n      setCooldown((prev) => prev - 1);\n    }, 1000);\n\n    return () => clearInterval(timer);\n  }, [cooldown]);\n\n  const resetToForm = useCallback(() => {\n    setStatus('idle');\n    setCooldown(COOLDOWN_SECONDS);\n  }, []);\n\n  const onSubmit: SubmitHandler<ResendInputs> = async (data) => {\n    setLoading(true);\n    try {\n      const response = await fetch('/auth/resend-activation', {\n        method: 'POST',\n        body: JSON.stringify(data),\n      });\n      const result = await response.json();\n      if (result.success) {\n        setStatus('sent');\n        setCooldown(COOLDOWN_SECONDS);\n      } else if (result.message === 'Account is already activated') {\n        setStatus('already_activated');\n      } else {\n        form.setError('email', {\n          message: result.message || t('failed_to_resend', 'Failed to resend activation email'),\n        });\n      }\n    } catch (e) {\n      form.setError('email', {\n        message: t('error_occurred', 'An error occurred. Please try again.'),\n      });\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"flex flex-col flex-1\">\n      <div>\n        <h1 className=\"text-3xl font-bold text-start mb-4 cursor-pointer\">\n          {t('activate_your_account', 'Activate your account')}\n        </h1>\n      </div>\n      <div className=\"text-textColor\">\n        {t('thank_you_for_registering', 'Thank you for registering!')}\n        <br />\n        {t(\n          'please_check_your_email_to_activate_your_account',\n          'Please check your email to activate your account.'\n        )}\n      </div>\n\n      <div className=\"mt-8 border-t border-fifth pt-6\">\n        <h2 className=\"text-lg font-semibold mb-4\">\n          {t('didnt_receive_email', \"Didn't receive the email?\")}\n        </h2>\n        {status === 'sent' ? (\n          <div className=\"flex flex-col gap-4\">\n            <div className=\"text-green-400\">\n              {t(\n                'activation_email_sent',\n                'Activation email has been sent! Please check your inbox.'\n              )}\n            </div>\n            {cooldown > 0 ? (\n              <p className=\"text-sm text-textColor\">\n                {t('resend_available_in', 'You can resend in')} {cooldown}s\n              </p>\n            ) : (\n              <Button\n                onClick={resetToForm}\n                className=\"rounded-[10px] !h-[52px]\"\n              >\n                {t('send_again', 'Send Again')}\n              </Button>\n            )}\n          </div>\n        ) : status === 'already_activated' ? (\n          <div className=\"flex flex-col gap-4\">\n            <div className=\"text-green-400\">\n              {t(\n                'account_already_activated',\n                'Great news! Your account is already activated.'\n              )}\n            </div>\n            <Link href=\"/auth/login\">\n              <Button className=\"rounded-[10px] !h-[52px] w-full\">\n                {t('go_to_login', 'Go to Login')}\n              </Button>\n            </Link>\n          </div>\n        ) : (\n          <FormProvider {...form}>\n            <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col gap-4\">\n              <Input\n                label={t('label_email', 'Email')}\n                translationKey=\"label_email\"\n                {...form.register('email', { required: true })}\n                type=\"email\"\n                placeholder={t('email_address', 'Email Address')}\n              />\n              <Button\n                type=\"submit\"\n                className=\"rounded-[10px] !h-[52px]\"\n                loading={loading}\n                disabled={cooldown > 0}\n              >\n                {cooldown > 0\n                  ? `${t('resend_available_in', 'You can resend in')} ${cooldown}s`\n                  : t('resend_activation_email', 'Resend Activation Email')}\n              </Button>\n            </form>\n          </FormProvider>\n        )}\n        {status !== 'already_activated' && (\n          <p className=\"mt-4 text-sm text-textColor\">\n            {t('already_activated', 'Already activated?')}&nbsp;\n            <Link href=\"/auth/login\" className=\"underline cursor-pointer\">\n              {t('sign_in', 'Sign In')}\n            </Link>\n          </p>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/auth/after.activate.tsx",
    "content": "'use client';\n\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useParams } from 'next/navigation';\nimport Link from 'next/link';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport useCookie from 'react-use-cookie';\nexport const AfterActivate = () => {\n  const fetch = useFetch();\n  const params = useParams();\n  const [showLoader, setShowLoader] = useState(true);\n  const run = useRef(false);\n  const t = useT();\n  const [datafast_visitor_id] = useCookie('datafast_visitor_id');\n\n  useEffect(() => {\n    if (!run.current) {\n      run.current = true;\n      loadCode();\n    }\n  }, []);\n  const loadCode = useCallback(async () => {\n    if (params.code) {\n      const { can } = await (\n        await fetch(`/auth/activate`, {\n          method: 'POST',\n          body: JSON.stringify({\n            code: params.code,\n            datafast_visitor_id,\n          }),\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        })\n      ).json();\n      if (!can) {\n        setShowLoader(false);\n      }\n    }\n  }, []);\n  return (\n    <>\n      {showLoader ? (\n        <LoadingComponent />\n      ) : (\n        <>\n          This user is already activated,\n          <br />\n          <Link href=\"/auth/login\" className=\"underline\">\n            {t(\n              'click_here_to_go_back_to_login',\n              'Click here to go back to login'\n            )}\n          </Link>\n        </>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/forgot-return.tsx",
    "content": "'use client';\nimport { useForm, SubmitHandler, FormProvider } from 'react-hook-form';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport Link from 'next/link';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useMemo, useState } from 'react';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\ntype Inputs = {\n  password: string;\n  repeatPassword: string;\n  token: string;\n};\nexport function ForgotReturn({ token }: { token: string }) {\n  const [loading, setLoading] = useState(false);\n  const t = useT();\n  const [state, setState] = useState(false);\n  const resolver = useMemo(() => {\n    return classValidatorResolver(ForgotReturnPasswordDto);\n  }, []);\n  const form = useForm<Inputs>({\n    resolver,\n    mode: 'onChange',\n    defaultValues: {\n      token,\n    },\n  });\n  const fetchData = useFetch();\n  const onSubmit: SubmitHandler<Inputs> = async (data) => {\n    setLoading(true);\n    const { reset } = await (\n      await fetchData('/auth/forgot-return', {\n        method: 'POST',\n        body: JSON.stringify({\n          ...data,\n        }),\n      })\n    ).json();\n    setState(true);\n    if (!reset) {\n      form.setError('password', {\n        type: 'manual',\n        message: t('password_reset_link_expired', 'Your password reset link has expired. Please try again.'),\n      });\n      return false;\n    }\n    setLoading(false);\n  };\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)}>\n        <div>\n          <h1 className=\"text-3xl font-bold text-start mb-4 cursor-pointer\">\n            {t('forgot_password_1', 'Forgot Password')}\n          </h1>\n        </div>\n        {!state ? (\n          <>\n            <div className=\"space-y-4 text-textColor\">\n              <Input\n                label=\"New Password\"\n                translationKey=\"label_new_password\"\n                {...form.register('password')}\n                type=\"password\"\n                placeholder={t('label_password', 'Password')}\n              />\n              <Input\n                label=\"Repeat Password\"\n                translationKey=\"label_repeat_password\"\n                {...form.register('repeatPassword')}\n                type=\"password\"\n                placeholder={t('label_repeat_password', 'Repeat Password')}\n              />\n            </div>\n            <div className=\"text-center mt-6\">\n              <div className=\"w-full flex\">\n                <Button type=\"submit\" className=\"flex-1\" loading={loading}>\n                  {t('change_password', 'Change Password')}\n                </Button>\n              </div>\n              <p className=\"mt-4 text-sm\">\n                <Link href=\"/auth/login\" className=\"underline cursor-pointer\">\n                  {t('go_back_to_login', 'Go back to login')}\n                </Link>\n              </p>\n            </div>\n          </>\n        ) : (\n          <>\n            <div className=\"text-start mt-6\">\n              {t(\n                'we_successfully_reset_your_password_you_can_now_login_with_your',\n                'We successfully reset your password. You can now login with your'\n              )}\n            </div>\n            <p className=\"mt-4 text-sm\">\n              <Link href=\"/auth/login\" className=\"underline cursor-pointer\">\n                {t('go_back_to_login', 'Go back to login')}\n              </Link>\n            </p>\n          </>\n        )}\n      </form>\n    </FormProvider>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/auth/forgot.tsx",
    "content": "'use client';\n\nimport { useForm, SubmitHandler, FormProvider } from 'react-hook-form';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport Link from 'next/link';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useMemo, useState } from 'react';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { ForgotPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot.password.dto';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\ntype Inputs = {\n  email: string;\n};\nexport function Forgot() {\n  const t = useT();\n  const [loading, setLoading] = useState(false);\n  const [state, setState] = useState(false);\n  const resolver = useMemo(() => {\n    return classValidatorResolver(ForgotPasswordDto);\n  }, []);\n  const form = useForm<Inputs>({\n    resolver,\n  });\n  const fetchData = useFetch();\n  const onSubmit: SubmitHandler<Inputs> = async (data) => {\n    setLoading(true);\n    await fetchData('/auth/forgot', {\n      method: 'POST',\n      body: JSON.stringify({\n        ...data,\n        provider: 'LOCAL',\n      }),\n    });\n    setState(true);\n    setLoading(false);\n  };\n  return (\n    <div className=\"flex flex-1 flex-col\">\n      <FormProvider {...form}>\n        <form onSubmit={form.handleSubmit(onSubmit)}>\n          <div>\n            <h1 className=\"text-3xl font-bold text-start mb-4 cursor-pointer\">\n              {t('forgot_password_1', 'Forgot Password')}\n            </h1>\n          </div>\n          {!state ? (\n            <>\n              <div className=\"space-y-4 text-textColor\">\n                <Input\n                  label=\"Email\"\n                  translationKey=\"label_email\"\n                  {...form.register('email')}\n                  type=\"email\"\n                  placeholder={t('email_address', 'Email Address')}\n                />\n              </div>\n              <div className=\"text-center mt-6\">\n                <div className=\"w-full flex\">\n                  <Button type=\"submit\" className=\"flex-1 !h-[52px] !rounded-[10px]\" loading={loading}>\n                    {t(\n                      'send_password_reset_email',\n                      'Send Password Reset Email'\n                    )}\n                  </Button>\n                </div>\n                <p className=\"mt-4 text-sm\">\n                  <Link href=\"/auth/login\" className=\"underline cursor-pointer\">\n                    {t('go_back_to_login', 'Go back to login')}\n                  </Link>\n                </p>\n              </div>\n            </>\n          ) : (\n            <>\n              <div className=\"text-start mt-6\">\n                {t(\n                  'we_have_send_you_an_email_with_a_link_to_reset_your_password',\n                  'We have send you an email with a link to reset your password.'\n                )}\n              </div>\n              <p className=\"mt-4 text-sm\">\n                <Link href=\"/auth/login\" className=\"underline cursor-pointer\">\n                  {t('go_back_to_login', 'Go back to login')}\n                </Link>\n              </p>\n            </>\n          )}\n        </form>\n      </FormProvider>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/auth/login.tsx",
    "content": "'use client';\n\nimport { useForm, SubmitHandler, FormProvider } from 'react-hook-form';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport Link from 'next/link';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useMemo, useState } from 'react';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { LoginUserDto } from '@gitroom/nestjs-libraries/dtos/auth/login.user.dto';\nimport { GithubProvider } from '@gitroom/frontend/components/auth/providers/github.provider';\nimport { OauthProvider } from '@gitroom/frontend/components/auth/providers/oauth.provider';\nimport { GoogleProvider } from '@gitroom/frontend/components/auth/providers/google.provider';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { FarcasterProvider } from '@gitroom/frontend/components/auth/providers/farcaster.provider';\nimport WalletProvider from '@gitroom/frontend/components/auth/providers/wallet.provider';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\ntype Inputs = {\n  email: string;\n  password: string;\n  providerToken: '';\n  provider: 'LOCAL';\n};\nexport function Login() {\n  const t = useT();\n  const [loading, setLoading] = useState(false);\n  const [notActivated, setNotActivated] = useState(false);\n  const { isGeneral, neynarClientId, billingEnabled, genericOauth } =\n    useVariables();\n  const resolver = useMemo(() => {\n    return classValidatorResolver(LoginUserDto);\n  }, []);\n  const form = useForm<Inputs>({\n    resolver,\n    defaultValues: {\n      providerToken: '',\n      provider: 'LOCAL',\n    },\n  });\n  const fetchData = useFetch();\n  const onSubmit: SubmitHandler<Inputs> = async (data) => {\n    setLoading(true);\n    setNotActivated(false);\n    const login = await fetchData('/auth/login', {\n      method: 'POST',\n      body: JSON.stringify({\n        ...data,\n        provider: 'LOCAL',\n      }),\n    });\n    if (login.status === 400) {\n      const errorMessage = await login.text();\n      if (errorMessage === 'User is not activated') {\n        setNotActivated(true);\n      } else {\n        form.setError('email', {\n          message: errorMessage,\n        });\n      }\n      setLoading(false);\n    }\n  };\n  return (\n    <FormProvider {...form}>\n      <form className=\"flex-1 flex\" onSubmit={form.handleSubmit(onSubmit)}>\n        <div className=\"flex flex-col flex-1\">\n          <div>\n            <h1 className=\"text-[40px] font-[500] -tracking-[0.8px] text-start cursor-pointer\">\n              {t('sign_in', 'Sign In')}\n            </h1>\n          </div>\n          <div className=\"text-[14px] mt-[32px] mb-[12px]\">\n            {t('continue_with', 'Continue With')}\n          </div>\n          <div className=\"flex flex-col\">\n            {isGeneral && genericOauth ? (\n              <OauthProvider />\n            ) : !isGeneral ? (\n              <GithubProvider />\n            ) : (\n              <div className=\"gap-[8px] flex\">\n                <GoogleProvider />\n                {!!neynarClientId && <FarcasterProvider />}\n                {billingEnabled && <WalletProvider />}\n              </div>\n            )}\n            <div className=\"h-[20px] mb-[24px] mt-[24px] relative\">\n              <div className=\"absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]\" />\n              <div\n                className={`absolute z-[1] justify-center items-center w-full start-0 -top-[4px] flex`}\n              >\n                <div className=\"px-[16px]\">{t('or', 'or')}</div>\n              </div>\n            </div>\n            <div className=\"flex flex-col gap-[12px]\">\n              <div className=\"text-textColor\">\n                <Input\n                  label=\"Email\"\n                  translationKey=\"label_email\"\n                  {...form.register('email')}\n                  type=\"email\"\n                  placeholder={t('email_address', 'Email Address')}\n                />\n                <Input\n                  label=\"Password\"\n                  translationKey=\"label_password\"\n                  {...form.register('password')}\n                  autoComplete=\"off\"\n                  type=\"password\"\n                  placeholder={t('label_password', 'Password')}\n                />\n              </div>\n              {notActivated && (\n                <div className=\"bg-amber-500/10 border border-amber-500/30 rounded-[10px] p-4 mb-4\">\n                  <p className=\"text-amber-400 text-sm mb-2\">\n                    {t(\n                      'account_not_activated',\n                      'Your account is not activated yet. Please check your email for the activation link.'\n                    )}\n                  </p>\n                  <Link\n                    href=\"/auth/activate\"\n                    className=\"text-amber-400 underline hover:font-bold text-sm\"\n                  >\n                    {t('resend_activation_email', 'Resend Activation Email')}\n                  </Link>\n                </div>\n              )}\n              <div className=\"text-center mt-6\">\n                <div className=\"w-full flex\">\n                  <Button\n                    type=\"submit\"\n                    className=\"flex-1 rounded-[10px] !h-[52px]\"\n                    loading={loading}\n                  >\n                    {t('sign_in_1', 'Sign in')}\n                  </Button>\n                </div>\n                <p className=\"mt-4 text-sm\">\n                  {t('don_t_have_an_account', \"Don't Have An Account?\")}&nbsp;\n                  <Link href=\"/auth\" className=\"underline cursor-pointer\">\n                    {t('sign_up', 'Sign Up')}\n                  </Link>\n                </p>\n                <p className=\"mt-4 text-sm\">\n                  <Link\n                    href=\"/auth/forgot\"\n                    className=\"underline hover:font-bold cursor-pointer\"\n                  >\n                    {t('forgot_password', 'Forgot password')}\n                  </Link>\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </form>\n    </FormProvider>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/auth/login.with.oidc.tsx",
    "content": "'use client';\n\nimport { OauthProvider } from '@gitroom/frontend/components/auth/providers/oauth.provider';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\n\nexport const LoginWithOidc = () => {\n  const { isGeneral, genericOauth } = useVariables();\n  const t = useT();\n\n  if (!(isGeneral && genericOauth)) {\n    return null;\n  }\n\n  return (\n    <>\n      <div>\n        <h1 className=\"text-center text-3xl font-bold text-start mb-4 cursor-pointer\">\n          {t('sign_up', 'Sign Up')}\n        </h1>\n      </div>\n      <OauthProvider />\n      <div className=\"h-[20px] mb-[24px] mt-[24px] relative\">\n        <div className=\"absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]\" />\n        <div\n          className={`absolute z-[1] justify-center items-center w-full start-0 top-0 flex`}\n        />\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/nayner.auth.button.tsx",
    "content": "'use client';\n\nimport React, {\n  useCallback,\n  useEffect,\n  useState,\n  useRef,\n  FC,\n  ReactNode,\n} from 'react';\nimport { useNeynarContext } from '@neynar/react';\nexport const NeynarAuthButton: FC<{\n  children: ReactNode;\n  onLogin: (code: string) => void;\n}> = (props) => {\n  const { children, onLogin } = props;\n  const { client_id } = useNeynarContext();\n  const [showModal, setShowModal] = useState(false);\n  const authWindowRef = useRef<Window | null>(null);\n  const neynarLoginUrl = `${\n    process.env.NEYNAR_LOGIN_URL ?? 'https://app.neynar.com/login'\n  }?client_id=${client_id}`;\n  const authOrigin = new URL(neynarLoginUrl).origin;\n  const modalRef = useRef<HTMLDivElement>(null);\n  const handleMessage = useCallback(\n    async (event: MessageEvent) => {\n      if (\n        event.origin === authOrigin &&\n        event.data &&\n        event.data.is_authenticated\n      ) {\n        authWindowRef.current?.close();\n        window.removeEventListener('message', handleMessage); // Remove listener here\n        delete event.data.user.profile;\n        const _user = {\n          signer_uuid: event.data.signer_uuid,\n          ...event.data.user,\n        };\n        onLogin(Buffer.from(JSON.stringify(_user)).toString('base64'));\n      }\n    },\n    [client_id, onLogin]\n  );\n  const handleSignIn = useCallback(() => {\n    const width = 600,\n      height = 700;\n    const left = window.screen.width / 2 - width / 2;\n    const top = window.screen.height / 2 - height / 2;\n    const windowFeatures = `width=${width},height=${height},top=${top},left=${left}`;\n    authWindowRef.current = window.open(\n      neynarLoginUrl,\n      '_blank',\n      windowFeatures\n    );\n    if (!authWindowRef.current) {\n      console.error(\n        'Failed to open the authentication window. Please check your pop-up blocker settings.'\n      );\n      return;\n    }\n    window.addEventListener('message', handleMessage, false);\n  }, [client_id, handleMessage]);\n  const closeModal = () => setShowModal(false);\n  useEffect(() => {\n    return () => {\n      window.removeEventListener('message', handleMessage); // Cleanup function to remove listener\n    };\n  }, [handleMessage]);\n  const handleOutsideClick = useCallback((event: any) => {\n    if (modalRef.current && !modalRef.current.contains(event.target)) {\n      closeModal();\n    }\n  }, []);\n  useEffect(() => {\n    if (showModal) {\n      document.addEventListener('mousedown', handleOutsideClick);\n    } else {\n      document.removeEventListener('mousedown', handleOutsideClick);\n    }\n    return () => {\n      document.removeEventListener('mousedown', handleOutsideClick);\n    };\n  }, [showModal, handleOutsideClick]);\n  return <div onClick={handleSignIn} className=\"flex-1\">{children}</div>;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/farcaster.provider.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { NeynarContextProvider, Theme, useNeynarContext } from '@neynar/react';\nimport { NeynarAuthButton } from '@gitroom/frontend/components/auth/nayner.auth.button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const FarcasterProvider = () => {\n  const gotoLogin = useCallback(async (code: string) => {\n    window.location.href = `/auth?provider=FARCASTER&code=${code}`;\n  }, []);\n  return <ButtonCaster login={gotoLogin} />;\n};\nexport const ButtonCaster: FC<{\n  login: (code: string) => void;\n}> = (props) => {\n  const { login } = props;\n  const { neynarClientId } = useVariables();\n  const t = useT();\n  return (\n    <NeynarContextProvider\n      settings={{\n        clientId: neynarClientId,\n        defaultTheme: Theme.Dark,\n      }}\n    >\n      <NeynarAuthButton onLogin={login}>\n        <div\n          className={`cursor-pointer bg-white h-[52px] flex-1 rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n          >\n            <g clipPath=\"url(#clip0_2026_31786)\">\n              <path\n                d=\"M18.75 0H5.25C2.35051 0 0 2.35051 0 5.25V18.75C0 21.6495 2.35051 24 5.25 24H18.75C21.6495 24 24 21.6495 24 18.75V5.25C24 2.35051 21.6495 0 18.75 0Z\"\n                fill=\"#7C65C1\"\n              />\n              <path\n                d=\"M17.2139 6.64138H19.9174L19.5312 8.76552H18.8553V16.8759L18.8851 16.8766C19.1912 16.8921 19.4346 17.1452 19.4346 17.4552V17.9379L19.4644 17.9387C19.7705 17.9541 20.0139 18.2073 20.0139 18.5172V19H14.607V18.5172C14.607 18.2073 14.8504 17.9541 15.1566 17.9387L15.1863 17.9379V17.4552C15.1863 17.1452 15.4297 16.8921 15.7359 16.8766L15.7657 16.8759V12.9172C15.7657 10.8376 14.0798 9.15172 12.0001 9.15172C9.92051 9.15172 8.2346 10.8376 8.2346 12.9172V16.8759L8.2644 16.8766C8.5705 16.8921 8.81391 17.1452 8.81391 17.4552V17.9379L8.84371 17.9387C9.14981 17.9541 9.39324 18.2073 9.39324 18.5172V19H3.98633V18.5172C3.98633 18.2073 4.22974 17.9541 4.53584 17.9387L4.56564 17.9379V17.4552C4.56564 17.1452 4.80905 16.8921 5.11515 16.8766L5.14495 16.8759V8.76552H4.46909L4.08288 6.64138H6.78633V5H17.2139V6.64138Z\"\n                fill=\"white\"\n              />\n            </g>\n            <defs>\n              <clipPath id=\"clip0_2026_31786\">\n                <rect width=\"24\" height=\"24\" fill=\"white\" />\n              </clipPath>\n            </defs>\n          </svg>\n          <div className=\"block xs:hidden\">{t('farcaster', 'Farcaster')}</div>\n        </div>\n      </NeynarAuthButton>\n    </NeynarContextProvider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/github.provider.tsx",
    "content": "import { useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const GithubProvider = () => {\n  const fetch = useFetch();\n  const t = useT();\n  const gotoLogin = useCallback(async () => {\n    const link = await (await fetch('/auth/oauth/GITHUB')).text();\n    window.location.href = link;\n  }, []);\n  return (\n    <div\n      onClick={gotoLogin}\n      className={`cursor-pointer bg-white h-[44px] rounded-[4px] flex justify-center items-center text-customColor16 gap-[4px]`}\n    >\n      <div>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"21\"\n          height=\"20\"\n          viewBox=\"0 0 21 20\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M16.7742 5.91251C16.9653 5.29626 17.0265 4.64711 16.9542 4.00599C16.8819 3.36487 16.6775 2.7457 16.3539 2.18751C16.299 2.09248 16.2201 2.01357 16.1251 1.95871C16.03 1.90385 15.9222 1.87499 15.8125 1.87501C15.0845 1.87349 14.3663 2.04225 13.7151 2.36781C13.064 2.69337 12.4981 3.16671 12.0625 3.75001H10.1875C9.75193 3.16671 9.18598 2.69337 8.53485 2.36781C7.88372 2.04225 7.16548 1.87349 6.4375 1.87501C6.32777 1.87499 6.21996 1.90385 6.12492 1.95871C6.02988 2.01357 5.95096 2.09248 5.89609 2.18751C5.57254 2.7457 5.36815 3.36487 5.2958 4.00599C5.22346 4.64711 5.28474 5.29626 5.47578 5.91251C5.08963 6.58651 4.88278 7.34827 4.875 8.12501V8.75001C4.87632 9.80751 5.26021 10.8288 5.9558 11.6254C6.65139 12.4219 7.61169 12.9399 8.65938 13.0836C8.23173 13.6308 7.9996 14.3055 8 15V15.625H6.125C5.62772 15.625 5.15081 15.4275 4.79917 15.0758C4.44754 14.7242 4.25 14.2473 4.25 13.75C4.25 13.3396 4.16917 12.9333 4.01212 12.5541C3.85508 12.175 3.62489 11.8305 3.33471 11.5403C3.04453 11.2501 2.70003 11.0199 2.32089 10.8629C1.94174 10.7058 1.53538 10.625 1.125 10.625C0.95924 10.625 0.800269 10.6909 0.683058 10.8081C0.565848 10.9253 0.5 11.0842 0.5 11.25C0.5 11.4158 0.565848 11.5747 0.683058 11.692C0.800269 11.8092 0.95924 11.875 1.125 11.875C1.62228 11.875 2.09919 12.0726 2.45083 12.4242C2.80246 12.7758 3 13.2527 3 13.75C3 14.5788 3.32924 15.3737 3.91529 15.9597C4.50134 16.5458 5.2962 16.875 6.125 16.875H8V18.125C8 18.2908 8.06585 18.4497 8.18306 18.567C8.30027 18.6842 8.45924 18.75 8.625 18.75C8.79076 18.75 8.94973 18.6842 9.06694 18.567C9.18415 18.4497 9.25 18.2908 9.25 18.125V15C9.25 14.5027 9.44754 14.0258 9.79917 13.6742C10.1508 13.3226 10.6277 13.125 11.125 13.125C11.6223 13.125 12.0992 13.3226 12.4508 13.6742C12.8025 14.0258 13 14.5027 13 15V18.125C13 18.2908 13.0658 18.4497 13.1831 18.567C13.3003 18.6842 13.4592 18.75 13.625 18.75C13.7908 18.75 13.9497 18.6842 14.0669 18.567C14.1842 18.4497 14.25 18.2908 14.25 18.125V15C14.2504 14.3055 14.0183 13.6308 13.5906 13.0836C14.6383 12.9399 15.5986 12.4219 16.2942 11.6254C16.9898 10.8288 17.3737 9.80751 17.375 8.75001V8.12501C17.3672 7.34827 17.1604 6.58651 16.7742 5.91251ZM16.125 8.75001C16.125 9.57881 15.7958 10.3737 15.2097 10.9597C14.6237 11.5458 13.8288 11.875 13 11.875H9.25C8.4212 11.875 7.62634 11.5458 7.04029 10.9597C6.45424 10.3737 6.125 9.57881 6.125 8.75001V8.12501C6.13266 7.50003 6.31978 6.89042 6.66406 6.36876C6.72824 6.28417 6.76981 6.18461 6.78485 6.0795C6.79988 5.97439 6.78789 5.86717 6.75 5.76798C6.5872 5.34813 6.50886 4.90029 6.51945 4.45011C6.53004 3.99993 6.62936 3.55627 6.81172 3.14454C7.32322 3.19957 7.81577 3.36901 8.25287 3.6403C8.68997 3.91159 9.06041 4.27778 9.33672 4.71173C9.39303 4.79978 9.47051 4.8723 9.56209 4.92267C9.65368 4.97303 9.75642 4.99962 9.86094 5.00001H12.3883C12.4932 5.00001 12.5964 4.97361 12.6884 4.92323C12.7805 4.87285 12.8583 4.80011 12.9148 4.71173C13.1911 4.27775 13.5615 3.91153 13.9986 3.64023C14.4358 3.36893 14.9283 3.19951 15.4398 3.14454C15.622 3.55637 15.721 4.00009 15.7313 4.45027C15.7417 4.90044 15.663 5.34823 15.5 5.76798C15.4622 5.86622 15.4496 5.97235 15.4632 6.07672C15.4769 6.18109 15.5164 6.2804 15.5781 6.36563C15.9258 6.8873 16.1157 7.49816 16.125 8.12501V8.75001Z\"\n            fill=\"#121A2D\"\n          />\n        </svg>\n      </div>\n      <div>{t('sign_in_with_github', 'Sign in with GitHub')}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/google.provider.tsx",
    "content": "'use client';\n\nimport { useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const GoogleProvider = () => {\n  const fetch = useFetch();\n  const t = useT();\n  const gotoLogin = useCallback(async () => {\n    const link = await (await fetch('/auth/oauth/GOOGLE')).text();\n    window.location.href = link;\n  }, []);\n  return (\n    <div\n      onClick={gotoLogin}\n      className={`cursor-pointer flex-1 bg-white h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}\n    >\n      <div>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 48 48\"\n          width=\"21px\"\n          height=\"21px\"\n        >\n          <path\n            fill=\"#FFC107\"\n            d=\"M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z\"\n          />\n          <path\n            fill=\"#FF3D00\"\n            d=\"M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z\"\n          />\n          <path\n            fill=\"#4CAF50\"\n            d=\"M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z\"\n          />\n          <path\n            fill=\"#1976D2\"\n            d=\"M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z\"\n          />\n        </svg>\n      </div>\n      <div className=\"block xs:hidden\">{t('google', 'Google')}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/oauth.provider.tsx",
    "content": "'use client';\n\nimport { useCallback } from 'react';\nimport Image from 'next/image';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const OauthProvider = () => {\n  const fetch = useFetch();\n  const { oauthLogoUrl, oauthDisplayName } = useVariables();\n  const t = useT();\n  const gotoLogin = useCallback(async () => {\n    try {\n      const response = await fetch('/auth/oauth/GENERIC');\n      if (!response.ok) {\n        throw new Error(\n          `Login link request failed with status ${response.status}`\n        );\n      }\n      const link = await response.text();\n      window.location.href = link;\n    } catch (error) {\n      console.error('Failed to get generic oauth login link:', error);\n    }\n  }, []);\n  return (\n    <div\n      onClick={gotoLogin}\n      className={`cursor-pointer flex-1 bg-white h-[44px] rounded-[4px] flex justify-center items-center text-customColor16 gap-[4px]`}\n    >\n      <div>\n        <Image\n          src={oauthLogoUrl || '/icons/generic-oauth.svg'}\n          alt=\"genericOauth\"\n          width={40}\n          height={40}\n          className=\"-mt-[7px]\"\n        />\n      </div>\n      <div>\n        {t('sign_in_with', 'Sign in with')}&nbsp;\n        {oauthDisplayName || 'OAuth'}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/placeholder/wallet.ui.provider.tsx",
    "content": "import { FC } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const WalletUiProvider: FC = () => {\n  const t = useT();\n  return (\n    <div\n      className={`cursor-pointer bg-white flex-1 h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M22.0002 10.9702V13.0302C22.0002 13.5802 21.5602 14.0302 21.0002 14.0502H19.0402C17.9602 14.0502 16.9702 13.2602 16.8802 12.1802C16.8202 11.5502 17.0602 10.9602 17.4802 10.5502C17.8502 10.1702 18.3602 9.9502 18.9202 9.9502H21.0002C21.5602 9.9702 22.0002 10.4202 22.0002 10.9702Z\"\n          fill=\"#0E0E0E\"\n        />\n        <path\n          d=\"M20.47 15.55H19.04C17.14 15.55 15.54 14.12 15.38 12.3C15.29 11.26 15.67 10.22 16.43 9.48C17.07 8.82 17.96 8.45 18.92 8.45H20.47C20.76 8.45 21 8.21 20.97 7.92C20.75 5.49 19.14 3.83 16.75 3.55C16.51 3.51 16.26 3.5 16 3.5H7C6.72 3.5 6.45 3.52 6.19 3.56C3.64 3.88 2 5.78 2 8.5V15.5C2 18.26 4.24 20.5 7 20.5H16C18.8 20.5 20.73 18.75 20.97 16.08C21 15.79 20.76 15.55 20.47 15.55ZM13 9.75H7C6.59 9.75 6.25 9.41 6.25 9C6.25 8.59 6.59 8.25 7 8.25H13C13.41 8.25 13.75 8.59 13.75 9C13.75 9.41 13.41 9.75 13 9.75Z\"\n          fill=\"#0E0E0E\"\n        />\n      </svg>\n      <div className=\"block xs:hidden\">Wallet</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/providers/wallet.provider.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n  ConnectionProvider,\n  useWallet,\n  WalletProvider as WalletProviderWrapper,\n} from '@solana/wallet-adapter-react';\nimport { useWalletMultiButton } from '@solana/wallet-adapter-base-ui';\nimport { WalletAdapterNetwork } from '@solana/wallet-adapter-base';\nimport {\n  TorusWalletAdapter,\n  BitgetWalletAdapter,\n  CloverWalletAdapter,\n  Coin98WalletAdapter,\n  FractalWalletAdapter,\n  HyperPayWalletAdapter,\n  KeystoneWalletAdapter,\n  KrystalWalletAdapter,\n  LedgerWalletAdapter,\n  MathWalletAdapter,\n  NightlyWalletAdapter,\n  NufiWalletAdapter,\n  OntoWalletAdapter,\n  ParticleAdapter,\n  PhantomWalletAdapter,\n  SafePalWalletAdapter,\n  SaifuWalletAdapter,\n  SalmonWalletAdapter,\n  SolflareWalletAdapter,\n  TokenaryWalletAdapter,\n  TrustWalletAdapter,\n  XDEFIWalletAdapter,\n  TokenPocketWalletAdapter,\n} from '@postiz/wallets';\nimport {\n  WalletModalProvider,\n  useWalletModal,\n} from '@solana/wallet-adapter-react-ui';\nimport { clusterApiUrl } from '@solana/web3.js';\n\n// Default styles that can be overridden by your app\nimport '@solana/wallet-adapter-react-ui/styles.css';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { WalletUiProvider } from '@gitroom/frontend/components/auth/providers/placeholder/wallet.ui.provider';\nconst WalletProvider = () => {\n  const gotoLogin = useCallback(async (code: string) => {\n    window.location.href = `/auth?provider=FARCASTER&code=${code}`;\n  }, []);\n  return <ButtonCaster login={gotoLogin} />;\n};\nexport const ButtonCaster: FC<{\n  login: (code: string) => void;\n}> = (props) => {\n  const network = WalletAdapterNetwork.Mainnet;\n\n  // You can also provide a custom RPC endpoint.\n  const endpoint = useMemo(() => clusterApiUrl(network), [network]);\n  const wallets = useMemo(\n    () => [\n      new TokenPocketWalletAdapter(),\n      new TorusWalletAdapter(),\n      new BitgetWalletAdapter(),\n      new CloverWalletAdapter(),\n      new Coin98WalletAdapter(),\n      new FractalWalletAdapter(),\n      new HyperPayWalletAdapter(),\n      new KeystoneWalletAdapter(),\n      new KrystalWalletAdapter(),\n      new LedgerWalletAdapter(),\n      new MathWalletAdapter(),\n      new NightlyWalletAdapter(),\n      new NufiWalletAdapter(),\n      new OntoWalletAdapter(),\n      new ParticleAdapter(),\n      new PhantomWalletAdapter(),\n      new SafePalWalletAdapter(),\n      new SaifuWalletAdapter(),\n      new SalmonWalletAdapter(),\n      new SolflareWalletAdapter(),\n      new TokenaryWalletAdapter(),\n      new TrustWalletAdapter(),\n      new XDEFIWalletAdapter(),\n    ],\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [network]\n  );\n  return (\n    <ConnectionProvider endpoint={endpoint}>\n      <WalletProviderWrapper wallets={wallets} autoConnect={false}>\n        <WalletModalProvider>\n          <DisabledAutoConnect />\n        </WalletModalProvider>\n      </WalletProviderWrapper>\n    </ConnectionProvider>\n  );\n};\nconst DisabledAutoConnect = () => {\n  const [connect, setConnect] = useState(false);\n  const wallet = useWallet();\n  const toConnect = useCallback(async () => {\n    try {\n      wallet.select(null);\n    } catch (err) {\n      /** empty */\n    }\n    try {\n      await wallet.disconnect();\n    } catch (err) {\n      /** empty */\n    }\n    setConnect(true);\n  }, []);\n  useEffect(() => {\n    toConnect();\n  }, []);\n  if (connect) {\n    return <InnerWallet />;\n  }\n  return <WalletUiProvider />;\n};\nconst InnerWallet = () => {\n  const walletModal = useWalletModal();\n  const wallet = useWallet();\n  const fetch = useFetch();\n  const { buttonState } = useWalletMultiButton({\n    onSelectWallet: () => {\n      return;\n    },\n  });\n  const connect = useCallback(async () => {\n    if (buttonState !== 'connected') {\n      return;\n    }\n    try {\n      const challenge = await (\n        await fetch(\n          `/auth/oauth/WALLET?publicKey=${wallet?.publicKey?.toString()}`\n        )\n      ).text();\n      const encoded = new TextEncoder().encode(challenge);\n      const signed = await wallet?.signMessage?.(encoded)!;\n      const info = Buffer.from(\n        JSON.stringify({\n          // @ts-ignore\n          signature: Buffer.from(signed).toString('hex'),\n          challenge,\n          publicKey: wallet?.publicKey?.toString(),\n        })\n      ).toString('base64');\n      window.location.href = `/auth?provider=WALLET&code=${info}`;\n    } catch (err) {\n      walletModal.setVisible(false);\n      wallet.select(null);\n      wallet.disconnect().catch(() => {\n        /** empty */\n      });\n    }\n  }, [wallet, buttonState]);\n  useEffect(() => {\n    if (buttonState === 'has-wallet') {\n      wallet\n        .connect()\n        .then(() => {\n          /** empty */\n        })\n        .catch(() => {\n          wallet.select(null);\n          wallet.disconnect();\n        });\n    }\n    if (buttonState === 'connected') {\n      connect();\n    }\n  }, [buttonState]);\n  return (\n    <div onClick={() => walletModal.setVisible(true)} className=\"flex-1\">\n      <WalletUiProvider />\n    </div>\n  );\n};\nexport default WalletProvider;\n"
  },
  {
    "path": "apps/frontend/src/components/auth/register.tsx",
    "content": "'use client';\n\nimport { FormProvider, SubmitHandler, useForm } from 'react-hook-form';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport Link from 'next/link';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';\nimport { GithubProvider } from '@gitroom/frontend/components/auth/providers/github.provider';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport clsx from 'clsx';\nimport { GoogleProvider } from '@gitroom/frontend/components/auth/providers/google.provider';\nimport { OauthProvider } from '@gitroom/frontend/components/auth/providers/oauth.provider';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useTrack } from '@gitroom/react/helpers/use.track';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { FarcasterProvider } from '@gitroom/frontend/components/auth/providers/farcaster.provider';\nimport dynamic from 'next/dynamic';\nimport { WalletUiProvider } from '@gitroom/frontend/components/auth/providers/placeholder/wallet.ui.provider';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport useCookie from 'react-use-cookie';\nconst WalletProvider = dynamic(\n  () => import('@gitroom/frontend/components/auth/providers/wallet.provider'),\n  {\n    ssr: false,\n    loading: () => <WalletUiProvider />,\n  }\n);\ntype Inputs = {\n  email: string;\n  password: string;\n  company: string;\n  providerToken: string;\n  provider: string;\n};\nexport function Register() {\n  const getQuery = useSearchParams();\n  const fetch = useFetch();\n  const [provider] = useState(getQuery?.get('provider')?.toUpperCase());\n  const [code, setCode] = useState(getQuery?.get('code') || '');\n  const [show, setShow] = useState(false);\n  useEffect(() => {\n    if (provider && code) {\n      load();\n    }\n  }, []);\n  const load = useCallback(async () => {\n    const { token } = await (\n      await fetch(`/auth/oauth/${provider?.toUpperCase() || 'LOCAL'}/exists`, {\n        method: 'POST',\n        body: JSON.stringify({\n          code,\n        }),\n      })\n    ).json();\n    if (token) {\n      setCode(token);\n      setShow(true);\n    }\n  }, [provider, code]);\n  if (!code && !provider) {\n    return <RegisterAfter token=\"\" provider=\"LOCAL\" />;\n  }\n  if (!show) {\n    return <LoadingComponent />;\n  }\n  return (\n    <RegisterAfter token={code} provider={provider?.toUpperCase() || 'LOCAL'} />\n  );\n}\nfunction getHelpfulReasonForRegistrationFailure(httpCode: number) {\n  switch (httpCode) {\n    case 400:\n      return 'Email already exists';\n    case 404:\n      return 'Your browser got a 404 when trying to contact the API, the most likely reasons for this are the NEXT_PUBLIC_BACKEND_URL is set incorrectly, or the backend is not running.';\n  }\n  return 'Unhandled error: ' + httpCode;\n}\nexport function RegisterAfter({\n  token,\n  provider,\n}: {\n  token: string;\n  provider: string;\n}) {\n  const t = useT();\n  const { isGeneral, genericOauth, neynarClientId, billingEnabled } =\n    useVariables();\n  const [loading, setLoading] = useState(false);\n  const router = useRouter();\n  const fireEvents = useFireEvents();\n  const track = useTrack();\n  const [datafast_visitor_id] = useCookie('datafast_visitor_id');\n  const isAfterProvider = useMemo(() => {\n    return !!token && !!provider;\n  }, [token, provider]);\n  const resolver = useMemo(() => {\n    return classValidatorResolver(CreateOrgUserDto);\n  }, []);\n  const form = useForm<Inputs>({\n    resolver,\n    defaultValues: {\n      providerToken: token,\n      provider: provider,\n    },\n  });\n  const fetchData = useFetch();\n  const onSubmit: SubmitHandler<Inputs> = async (data) => {\n    setLoading(true);\n    await fetchData('/auth/register', {\n      method: 'POST',\n      body: JSON.stringify({\n        ...data,\n        datafast_visitor_id,\n      }),\n    })\n      .then(async (response) => {\n        setLoading(false);\n        if (response.status === 200) {\n          fireEvents('register');\n          return track(TrackEnum.CompleteRegistration).then(() => {\n            if (response.headers.get('activate') === 'true') {\n              router.push('/auth/activate');\n            } else {\n              router.push('/auth/login');\n            }\n          });\n        } else {\n          form.setError('email', {\n            message: await response.text(),\n          });\n        }\n      })\n      .catch((e) => {\n        form.setError('email', {\n          message:\n            'General error: ' +\n            e.toString() +\n            '. Please check your browser console.',\n        });\n      });\n  };\n  return (\n    <FormProvider {...form}>\n      <form className=\"flex-1 flex\" onSubmit={form.handleSubmit(onSubmit)}>\n        <div className=\"flex flex-col flex-1\">\n          <div>\n            <h1 className=\"text-[40px] font-[500] -tracking-[0.8px] text-start cursor-pointer\">\n              {t('sign_up', 'Sign Up')}\n            </h1>\n          </div>\n          <div className=\"text-[14px] mt-[32px] mb-[12px]\">\n            {t('continue_with', 'Continue With')}\n          </div>\n          <div className=\"flex flex-col\">\n            {!isAfterProvider &&\n              (!isGeneral ? (\n                <GithubProvider />\n              ) : (\n                <div className=\"gap-[8px] flex\">\n                  {genericOauth && isGeneral ? (\n                    <OauthProvider />\n                  ) : (\n                    <GoogleProvider />\n                  )}\n                  {!!neynarClientId && <FarcasterProvider />}\n                  {billingEnabled && <WalletProvider />}\n                </div>\n              ))}\n            {!isAfterProvider && (\n              <div className=\"h-[20px] mb-[24px] mt-[24px] relative\">\n                <div className=\"absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]\" />\n                <div\n                  className={`absolute z-[1] justify-center items-center w-full start-0 -top-[4px] flex`}\n                >\n                  <div className=\"px-[16px]\">{t('or', 'or')}</div>\n                </div>\n              </div>\n            )}\n            <div className=\"flex flex-col gap-[12px]\">\n              <div className=\"text-textColor\">\n                {!isAfterProvider && (\n                  <>\n                    <Input\n                      label=\"Email\"\n                      translationKey=\"label_email\"\n                      {...form.register('email')}\n                      type=\"email\"\n                      placeholder={t('email_address', 'Email Address')}\n                    />\n                    <Input\n                      label=\"Password\"\n                      translationKey=\"label_password\"\n                      {...form.register('password')}\n                      autoComplete=\"off\"\n                      type=\"password\"\n                      placeholder={t('label_password', 'Password')}\n                    />\n                  </>\n                )}\n                <Input\n                  label=\"Company\"\n                  translationKey=\"label_company\"\n                  {...form.register('company')}\n                  autoComplete=\"off\"\n                  type=\"text\"\n                  placeholder={t('label_company', 'Company')}\n                />\n              </div>\n              <div className={clsx('text-[12px]')}>\n                {t(\n                  'by_registering_you_agree_to_our',\n                  'By registering you agree to our'\n                )}\n                &nbsp;\n                <a\n                  href={`https://postiz.com/terms`}\n                  className=\"underline hover:font-bold\"\n                  rel=\"nofollow\"\n                >\n                  {t('terms_of_service', 'Terms of Service')}\n                </a>\n                &nbsp;\n                {t('and', 'and')}&nbsp;\n                <a\n                  href={`https://postiz.com/privacy`}\n                  rel=\"nofollow\"\n                  className=\"underline hover:font-bold\"\n                >\n                  {t('privacy_policy', 'Privacy Policy')}\n                </a>\n                &nbsp;\n              </div>\n              <div className=\"text-center mt-6\">\n                <div className=\"w-full flex\">\n                  <Button\n                    type=\"submit\"\n                    className=\"flex-1 rounded-[10px] !h-[52px]\"\n                    loading={loading}\n                  >\n                    {t('create_account', 'Create Account')}\n                  </Button>\n                </div>\n                <p className=\"mt-4 text-sm\">\n                  {t('already_have_an_account', 'Already Have An Account?')}\n                  &nbsp;\n                  <Link\n                    href=\"/auth/login\"\n                    className=\"underline  cursor-pointer\"\n                  >\n                    {t('sign_in', 'Sign In')}\n                  </Link>\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </form>\n    </FormProvider>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/auth/testimonial.component.tsx",
    "content": "'use client';\n\nimport {\n  testimonials1,\n  testimonials2,\n} from '@gitroom/react/helpers/testomonials';\nimport { Testimonial } from '@gitroom/frontend/components/auth/testimonial';\n\nexport const TestimonialComponent = () => {\n  return (\n    <div className=\"flex-1 relative w-full my-[30px] max-w-[850px]\">\n      <div className=\"absolute w-full h-full left-0 top-0 px-[40px] overflow-hidden\">\n        <div className=\"absolute w-full h-[120px] left-0 top-0 blackGradTopBg z-[100]\" />\n        <div className=\"absolute w-full h-[120px] left-0 bottom-0 blackGradBottomBg z-[100]\" />\n        <div className=\"flex justify-center gap-[12px]\">\n          <div className=\"flex flex-col animate-marqueeUp flex-1 gap-[12px]\">\n            {[1, 2].flatMap((p) =>\n              testimonials1.flatMap((a) => (\n                <div\n                  key={p + '_' + a.name}\n                  className=\"flex flex-col gap-[12px]\"\n                >\n                  <Testimonial {...a} />\n                </div>\n              ))\n            )}\n          </div>\n          <div className=\"flex flex-col animate-marqueeDown flex-1 gap-[12px]\">\n            {[1, 2].flatMap((p) =>\n              testimonials2.flatMap((a) => (\n                <div\n                  key={p + '_' + a.name}\n                  className=\"flex flex-col gap-[12px]\"\n                >\n                  <Testimonial {...a} />\n                </div>\n              ))\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/auth/testimonial.tsx",
    "content": "import { FC } from 'react';\nimport Image from 'next/image';\n\nexport const Testimonial: FC<{\n  picture: string;\n  name: string;\n  description: string;\n  content: any;\n}> = ({ content, description, name, picture }) => {\n  return (\n    <div className=\"rounded-[16px] w-full flex flex-col gap-[16px] p-[20px] bg-[#1A1919] border border-[#2b2a2a]\">\n      {/* Header */}\n      <div className=\"flex gap-[12px] min-w-0\">\n        <div className=\"w-[36px] h-[36px] rounded-full overflow-hidden shrink-0\">\n          <Image src={picture} alt={name} width={36} height={36} />\n        </div>\n\n        <div className=\"flex flex-col -mt-[4px] min-w-0\">\n          <div className=\"text-[16px] font-[700] truncate\">{name}</div>\n          <div className=\"text-[11px] font-[400] text-[#D1D1D1]\">\n            {description}\n          </div>\n        </div>\n      </div>\n\n      {/* Content */}\n      <div className=\"text-[12px] font-[400] text-[#FFF] whitespace-pre-line w-full min-w-0\">\n        {typeof content === 'string' ? content.replace(/\\\\n/g, '\\n') : content}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/autopost/autopost.tsx",
    "content": "'use client';\n\nimport React, { FC, Fragment, useCallback, useMemo, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Button } from '@gitroom/react/form/button';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Input } from '@gitroom/react/form/input';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { array, boolean, object, string } from 'yup';\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport { Select } from '@gitroom/react/form/select';\nimport { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport clsx from 'clsx';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { CopilotTextarea } from '@copilotkit/react-textarea';\nimport { Slider } from '@gitroom/react/form/slider';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const Autopost: FC = () => {\n  const fetch = useFetch();\n  const t = useT();\n  const modal = useModals();\n  const toaster = useToaster();\n  const list = useCallback(async () => {\n    return (await fetch('/autopost')).json();\n  }, []);\n  const { data, mutate } = useSWR('autopost', list);\n  const addWebhook = useCallback(\n    (data?: any) => () => {\n      modal.openModal({\n        title: data ? t('edit_autopost', 'Edit Autopost') : t('add_autopost_title', 'Add Autopost'),\n        withCloseButton: true,\n        children: <AddOrEditWebhook data={data} reload={mutate} />,\n      });\n    },\n    []\n  );\n  const deleteHook = useCallback(\n    (data: any) => async () => {\n      if (\n        await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete',\n            `Are you sure you want to delete ${data.name}?`,\n            { name: data.name }\n          )\n        )\n      ) {\n        await fetch(`/autopost/${data.id}`, {\n          method: 'DELETE',\n        });\n        mutate();\n        toaster.show(t('webhook_deleted_successfully', 'Webhook deleted successfully'), 'success');\n      }\n    },\n    []\n  );\n  const changeActive = useCallback(\n    (data: any) => async (ac: 'on' | 'off') => {\n      await fetch(`/autopost/${data.id}/active`, {\n        body: JSON.stringify({\n          active: ac === 'on',\n        }),\n        method: 'POST',\n      });\n      mutate();\n    },\n    [mutate]\n  );\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">{t('autopost', 'Autopost')}</h3>\n      <div className=\"text-customColor18 mt-[4px]\">\n        {t(\n          'autopost_can_automatically_posts_your_rss_new_items_to_social_media',\n          'Autopost can automatically posts your RSS new items to social media'\n        )}\n      </div>\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n        <div className=\"flex flex-col w-full\">\n          {!!data?.length && (\n            <div className=\"grid grid-cols-[1fr,1fr,1fr,1fr,1fr] w-full gap-y-[10px]\">\n              <div>{t('title', 'Title')}</div>\n              <div>{t('url', 'URL')}</div>\n              <div>{t('edit', 'Edit')}</div>\n              <div>{t('delete', 'Delete')}</div>\n              <div>{t('active', 'Active')}</div>\n              {data?.map((p: any) => (\n                <Fragment key={p.id}>\n                  <div className=\"flex flex-col justify-center\">{p.title}</div>\n                  <div className=\"flex flex-col justify-center\">{p.url}</div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={addWebhook(p)}>\n                        {t('edit', 'Edit')}\n                      </Button>\n                    </div>\n                  </div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={deleteHook(p)}>\n                        {t('delete', 'Delete')}\n                      </Button>\n                    </div>\n                  </div>\n                  <div>\n                    <Slider\n                      value={p.active ? 'on' : 'off'}\n                      onChange={changeActive(p)}\n                      fill={true}\n                    />\n                  </div>\n                </Fragment>\n              ))}\n            </div>\n          )}\n          <div>\n            <Button\n              onClick={addWebhook()}\n              className={clsx((data?.length || 0) > 0 && 'my-[16px]')}\n            >\n              {t('add_an_autopost', 'Add an autopost')}\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\nconst details = object().shape({\n  title: string().required(),\n  content: string(),\n  onSlot: boolean().required(),\n  syncLast: boolean().required(),\n  url: string().url().required(),\n  active: boolean().required(),\n  addPicture: boolean().required(),\n  generateContent: boolean().required(),\n  integrations: array().of(\n    object().shape({\n      id: string().required(),\n    })\n  ),\n});\nconst getOptions = (t: (key: string, fallback: string) => string) => [\n  {\n    label: t('all_integrations', 'All integrations'),\n    value: 'all',\n  },\n  {\n    label: t('specific_integrations', 'Specific integrations'),\n    value: 'specific',\n  },\n];\nconst getOptionsChoose = (t: (key: string, fallback: string) => string) => [\n  {\n    label: t('yes', 'Yes'),\n    value: true,\n  },\n  {\n    label: t('no', 'No'),\n    value: false,\n  },\n];\nconst getPostImmediately = (t: (key: string, fallback: string) => string) => [\n  {\n    label: t('post_on_next_available_slot', 'Post on the next available slot'),\n    value: true,\n  },\n  {\n    label: t('post_immediately', 'Post Immediately'),\n    value: false,\n  },\n];\nexport const AddOrEditWebhook: FC<{\n  data?: any;\n  reload: () => void;\n}> = (props) => {\n  const { data, reload } = props;\n  const fetch = useFetch();\n  const t = useT();\n  const options = getOptions(t);\n  const optionsChoose = getOptionsChoose(t);\n  const postImmediately = getPostImmediately(t);\n  const [allIntegrations, setAllIntegrations] = useState(\n    (JSON.parse(data?.integrations || '[]')?.length || 0) > 0\n      ? options[1]\n      : options[0]\n  );\n  const modal = useModals();\n  const toast = useToaster();\n  const [valid, setValid] = useState(data?.url || '');\n  const [lastUrl, setLastUrl] = useState(data?.lastUrl || '');\n  const form = useForm({\n    resolver: yupResolver(details),\n    values: {\n      title: data?.title || '',\n      content: data?.content || '',\n      onSlot: data?.onSlot || false,\n      syncLast: data?.syncLast || false,\n      url: data?.url || '',\n      // eslint-disable-next-line no-prototype-builtins\n      active: data?.hasOwnProperty?.('active') ? data?.active : true,\n      addPicture: data?.addPicture || false,\n      // eslint-disable-next-line no-prototype-builtins\n      generateContent: data?.hasOwnProperty?.('generateContent')\n        ? data?.generateContent\n        : true,\n      integrations: JSON.parse(data?.integrations || '[]') || [],\n    },\n  });\n  const generateContent = form.watch('generateContent');\n  const content = form.watch('content');\n  const url = form.watch('url');\n  const syncLast = form.watch('syncLast');\n  const integrations = form.watch('integrations');\n  const integration = useCallback(async () => {\n    return (await fetch('/integrations/list')).json();\n  }, []);\n  const changeIntegration = useCallback(\n    (e: React.ChangeEvent<HTMLSelectElement>) => {\n      const findValue = options.find(\n        (option) => option.value === e.target.value\n      )!;\n      setAllIntegrations(findValue);\n      if (findValue.value === 'all') {\n        form.setValue('integrations', []);\n      }\n    },\n    []\n  );\n  const { data: dataList, isLoading } = useSWR('integrations', integration, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n  const callBack = useCallback(\n    async (values: any) => {\n      await fetch(data?.id ? `/autopost/${data?.id}` : '/autopost', {\n        method: data?.id ? 'PUT' : 'POST',\n        body: JSON.stringify({\n          ...(data?.id\n            ? {\n                id: data.id,\n              }\n            : {}),\n          ...values,\n          ...(!syncLast\n            ? {\n                lastUrl,\n              }\n            : {\n                lastUrl: '',\n              }),\n        }),\n      });\n      toast.show(\n        data?.id\n          ? t('autopost_updated_successfully', 'Autopost updated successfully')\n          : t('autopost_added_successfully', 'Autopost added successfully'),\n        'success'\n      );\n      modal.closeAll();\n      reload();\n    },\n    [data, integrations, lastUrl, syncLast]\n  );\n  const sendTest = useCallback(async () => {\n    const url = form.getValues('url');\n    try {\n      const { success, url: newUrl } = await (\n        await fetch(`/autopost/send?url=${encodeURIComponent(url)}`, {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        })\n      ).json();\n      if (!success) {\n        setValid('');\n        toast.show(t('could_not_use_rss_feed', 'Could not use this RSS feed'), 'warning');\n        return;\n      }\n      toast.show(t('rss_valid', 'RSS valid!'), 'success');\n      setValid(url);\n      setLastUrl(newUrl);\n    } catch (e: any) {\n      /** empty **/\n    }\n  }, []);\n\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(callBack)}>\n        <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 pt-0\">\n          <div>\n            <Input\n              label=\"Title\"\n              translationKey=\"label_title\"\n              {...form.register('title')}\n            />\n            <Input\n              label=\"URL\"\n              translationKey=\"label_url\"\n              {...form.register('url')}\n            />\n            <Select\n              label=\"Should we sync the current last post?\"\n              translationKey=\"label_should_sync_last_post\"\n              {...form.register('syncLast', {\n                setValueAs: (value) => {\n                  return value === 'true' || value === true;\n                },\n              })}\n            >\n              {optionsChoose.map((option) => (\n                <option key={String(option.value)} value={String(option.value)}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            <Select\n              label=\"When should we post it?\"\n              translationKey=\"label_when_post\"\n              {...form.register('onSlot', {\n                setValueAs: (value) => value === 'true' || value === true,\n              })}\n            >\n              {postImmediately.map((option) => (\n                <option key={String(option.value)} value={String(option.value)}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            <Select\n              label=\"Autogenerate content\"\n              translationKey=\"label_autogenerate_content\"\n              {...form.register('generateContent', {\n                setValueAs: (value) => value === 'true' || value === true,\n              })}\n            >\n              {optionsChoose.map((option) => (\n                <option key={String(option.value)} value={String(option.value)}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            {!generateContent && (\n              <>\n                <div className={`text-[14px] mb-[6px]`}>\n                  {t('post_content', 'Post content')}\n                </div>\n                <CopilotTextarea\n                  disableBranding={true}\n                  className={clsx(\n                    '!min-h-40 !max-h-80 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-customColor2 outline-none mb-[16px] border-fifth border rounded-[4px]'\n                  )}\n                  value={content}\n                  onChange={(e) => {\n                    form.setValue('content', e.target.value);\n                  }}\n                  placeholder={t('write_your_post_placeholder', 'Write your post...')}\n                  autosuggestionsConfig={{\n                    textareaPurpose: `Assist me in writing social media post`,\n                    chatApiConfigs: {},\n                  }}\n                />\n              </>\n            )}\n            <Select\n              label=\"Generate Picture?\"\n              translationKey=\"label_generate_picture\"\n              {...form.register('addPicture', {\n                setValueAs: (value) => value === 'true' || value === true,\n              })}\n            >\n              {optionsChoose.map((option) => (\n                <option key={String(option.value)} value={String(option.value)}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            <Select\n              value={allIntegrations.value}\n              name=\"integrations\"\n              label=\"Integrations\"\n              translationKey=\"label_integrations\"\n              disableForm={true}\n              onChange={changeIntegration}\n            >\n              {options.map((option) => (\n                <option key={option.value} value={option.value}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            {allIntegrations.value === 'specific' && dataList && !isLoading && (\n              <PickPlatforms\n                integrations={dataList.integrations}\n                selectedIntegrations={integrations as any[]}\n                onChange={(e) => form.setValue('integrations', e)}\n                singleSelect={false}\n                toolTip={true}\n                isMain={true}\n              />\n            )}\n            <div className=\"flex gap-[10px]\">\n              {valid === url && (syncLast || !!lastUrl) && (\n                <Button\n                  type=\"submit\"\n                  className=\"mt-[24px]\"\n                  disabled={\n                    valid !== url ||\n                    !form.formState.isValid ||\n                    (allIntegrations.value === 'specific' &&\n                      !integrations?.length)\n                  }\n                >\n                  {t('save', 'Save')}\n                </Button>\n              )}\n              <Button\n                type=\"button\"\n                className=\"mt-[24px]\"\n                onClick={sendTest}\n                disabled={\n                  !form.formState.isValid ||\n                  (allIntegrations.value === 'specific' &&\n                    !integrations?.length)\n                }\n              >\n                {t('send_test', 'Send Test')}\n              </Button>\n            </div>\n          </div>\n        </div>\n      </form>\n    </FormProvider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/billing.component.tsx",
    "content": "'use client';\n\nimport { useCallback, useEffect } from 'react';\nimport useSWR from 'swr';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { MainBillingComponent } from './main.billing.component';\nexport const BillingComponent = () => {\n  const fetch = useFetch();\n  const load = useCallback(async (path: string) => {\n    return await (await fetch(path)).json();\n  }, []);\n  const { isLoading: isLoadingTier, data: tiers } = useSWR(\n    '/user/subscription/tiers',\n    load\n  );\n  const { isLoading: isLoadingSubscription, data: subscription } = useSWR(\n    '/user/subscription',\n    load\n  );\n  if (isLoadingSubscription || isLoadingTier) {\n    return <LoadingComponent />;\n  }\n  return <MainBillingComponent sub={subscription?.subscription} />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/embedded.billing.tsx",
    "content": "'use client';\n\nimport { Stripe } from '@stripe/stripe-js';\n\nimport { FC, useEffect, useState } from 'react';\nimport {\n  PaymentElement,\n  BillingAddressElement,\n  CheckoutProvider,\n  useCheckout,\n} from '@stripe/react-stripe-js/checkout';\nimport { modeEmitter } from '@gitroom/frontend/components/layout/mode.component';\nimport useCookie from 'react-use-cookie';\nimport { Button } from '@gitroom/react/form/button';\nimport dayjs from 'dayjs';\nimport Image from 'next/image';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const EmbeddedBilling: FC<{\n  stripe: Promise<Stripe>;\n  secret: string;\n  showCoupon?: boolean;\n  autoApplyCoupon?: string;\n}> = ({ stripe, secret, showCoupon = false, autoApplyCoupon }) => {\n  const [saveSecret, setSaveSecret] = useState(secret);\n  const [loading, setLoading] = useState(false);\n  const [mode, setMode] = useCookie('mode', 'dark');\n\n  useEffect(() => {\n    modeEmitter.on('mode', (value) => {\n      setMode(value);\n      setLoading(true);\n    });\n\n    return () => {\n      modeEmitter.removeAllListeners();\n    };\n  }, []);\n\n  useEffect(() => {\n    if (loading) {\n      setLoading(false);\n    }\n  }, [loading]);\n\n  useEffect(() => {\n    if (secret && saveSecret !== secret) {\n      setSaveSecret(secret);\n    }\n  }, [secret, setSaveSecret]);\n\n  if (saveSecret !== secret || loading) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-col w-full pt-[48px] billing-form flex-1 tablet:pt-0\">\n      <CheckoutProvider\n        stripe={stripe}\n        options={{\n          clientSecret: secret,\n          elementsOptions: {\n            appearance: {\n              variables: {\n                colorText: mode === 'dark' ? '#ffffff' : '#0e0e0e',\n                borderRadius: '8px',\n                colorBackground: mode === 'dark' ? '#1E1E1E' : '#FFFFFF',\n              },\n              rules: {\n                '.Label': {\n                  fontSize: '14px',\n                  fontWeight: '600',\n                  marginBottom: '8px',\n                },\n                '.Input': {\n                  height: '44px',\n                  backgroundColor: mode === 'dark' ? '#1E1E1E' : '#FFFFFF',\n                },\n              },\n            },\n          },\n        }}\n      >\n        <FormWrapper\n          showCoupon={showCoupon}\n          autoApplyCoupon={autoApplyCoupon}\n        />\n      </CheckoutProvider>\n    </div>\n  );\n};\n\nconst FormWrapper: FC<{ showCoupon?: boolean; autoApplyCoupon?: string }> = ({\n  showCoupon = false,\n  autoApplyCoupon,\n}) => {\n  const checkoutState = useCheckout();\n  const toaster = useToaster();\n  const [loading, setLoading] = useState(false);\n\n  if (checkoutState.type !== 'success') {\n    return null;\n  }\n\n  const handleSubmit = async (e: any) => {\n    e.preventDefault();\n    setLoading(true);\n\n    const { checkout } = checkoutState;\n\n    const confirmResult = await checkout.confirm();\n\n    if (confirmResult.type === 'error') {\n      toaster.show(confirmResult.error.message, 'warning');\n    }\n\n    setLoading(false);\n  };\n\n  return (\n    <form onSubmit={handleSubmit} className=\"flex flex-col flex-1\">\n      <StripeInputs\n        showCoupon={showCoupon}\n        autoApplyCoupon={autoApplyCoupon}\n        loading={loading}\n      />\n    </form>\n  );\n};\n\nconst StripeInputs: FC<{\n  showCoupon: boolean;\n  autoApplyCoupon?: string;\n  loading: boolean;\n}> = ({ showCoupon, autoApplyCoupon, loading }) => {\n  const checkout = useCheckout();\n  const t = useT();\n  const [ready, setReady] = useState(false);\n  return (\n    <>\n      {/*<div>*/}\n      {/*  <h4 className=\"mb-[32px] text-[24px] font-[700]\">*/}\n      {/*    {checkout.type === 'loading'*/}\n      {/*      ? ''*/}\n      {/*      : t('billing_billing_address', 'Billing Address')}*/}\n      {/*  </h4>*/}\n      {/*  <BillingAddressElement />*/}\n      {/*</div>*/}\n      <div>\n        <h4 className=\"mb-[32px] text-[24px] font-[700]\">\n          {checkout.type === 'loading' ? '' : t('billing_payment', 'Payment')}\n        </h4>\n        <PaymentElement\n          id=\"payment-element\"\n          options={{\n            fields: { billingDetails: { address: 'if_required' } },\n            layout: 'tabs',\n          }}\n          onReady={() => setReady(true)}\n        />\n        {ready && <PriceBreakdown />}\n        {showCoupon && ready && (\n          <CouponInput autoApplyCoupon={autoApplyCoupon} />\n        )}\n        {ready && <SubmitBar loading={loading} />}\n        {checkout.type === 'loading' ? null : (\n          <div className=\"mt-[24px] text-[16px] font-[600] flex gap-[4px] items-center\">\n            <div>\n              {t('billing_powered_by_stripe', 'Secure payments processed by')}\n            </div>\n            <svg\n              className=\"mt-[4px]\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"47\"\n              height=\"20\"\n              viewBox=\"0 0 47 20\"\n              fill=\"none\"\n            >\n              <path\n                fill-rule=\"evenodd\"\n                clip-rule=\"evenodd\"\n                d=\"M45.9725 11.0075H39.7596C39.906 12.4952 40.9929 12.9731 42.2262 12.9731C43.4904 12.9731 44.5079 12.6879 45.3481 12.2408V14.8C44.2819 15.4135 43.0618 15.7078 41.8331 15.6479C38.7421 15.6479 36.5683 13.7208 36.5683 9.88208C36.5683 6.65229 38.4106 4.08542 41.4246 4.08542C44.4463 4.08542 46.0187 6.61375 46.0187 9.86667C46.0187 10.175 45.9879 10.8379 45.9725 11.0075ZM41.4092 6.67542C40.6152 6.67542 39.7365 7.23812 39.7365 8.66417H43.0125C43.0125 7.23812 42.1877 6.67542 41.4092 6.67542ZM31.5656 15.6479C30.4556 15.6479 29.7773 15.1854 29.3302 14.8462L29.3148 18.4152L26.139 19.0858V4.29354H29.0373L29.099 5.07979C29.7712 4.44215 30.6622 4.0863 31.5887 4.08542C33.8242 4.08542 35.9208 6.08958 35.9208 9.78958C35.9208 13.821 33.8396 15.6479 31.5656 15.6479ZM30.8333 6.89896C30.101 6.89896 29.6462 7.16104 29.3148 7.52333L29.3302 12.2408C29.6385 12.58 30.0856 12.8421 30.8333 12.8421C32.005 12.8421 32.7912 11.5702 32.7912 9.85896C32.7912 8.20167 31.9896 6.89896 30.8333 6.89896ZM21.7683 4.29354H24.9519V15.4244H21.7683V4.29354ZM21.7683 0.670625L24.9519 0V2.59L21.7683 3.26833V0.678333V0.670625ZM18.4383 7.87792V15.4244H15.2625V4.29354H18.1146L18.2071 5.23396C18.9779 3.86958 20.5735 4.14708 20.9975 4.29354V7.215C20.5967 7.08396 19.2323 6.88354 18.4383 7.87792ZM11.8477 11.5162C11.8477 13.3894 13.8519 12.8112 14.2527 12.6417V15.2317C13.8287 15.4629 13.0656 15.6479 12.025 15.6479C11.5913 15.6606 11.1595 15.5849 10.756 15.4254C10.3525 15.2659 9.98559 15.026 9.6777 14.7203C9.36981 14.4146 9.12733 14.0494 8.96502 13.647C8.8027 13.2446 8.72395 12.8134 8.73354 12.3796L8.74125 2.22771L11.84 1.56479V4.29354H14.2604V7.01458H11.8477V11.524V11.5162ZM8.06292 12.0558C8.06292 14.3452 6.28229 15.6479 3.64604 15.6479C2.46306 15.647 1.29288 15.403 0.208125 14.931V11.9017C1.27188 12.4798 2.59771 12.9115 3.64604 12.9115C4.35521 12.9115 4.82542 12.7265 4.82542 12.1406C4.82542 10.6144 0 11.1848 0 7.66979C0 5.42667 1.7575 4.08542 4.33208 4.08542C5.38042 4.08542 6.42875 4.23958 7.48479 4.66354V7.65438C6.50888 7.14076 5.42694 6.86103 4.32438 6.83729C3.66146 6.83729 3.21438 7.03 3.21438 7.53104C3.21438 8.95708 8.06292 8.27875 8.06292 12.0635V12.0558Z\"\n                fill=\"#635BFF\"\n              />\n            </svg>\n          </div>\n        )}\n      </div>\n    </>\n  );\n};\n\nconst PriceBreakdown: FC = () => {\n  const checkoutState = useCheckout();\n  const t = useT();\n\n  if (checkoutState.type !== 'success') {\n    return null;\n  }\n\n  const { checkout } = checkoutState;\n  const lineItem = checkout?.lineItems?.[0];\n  const recurring = checkout?.recurring;\n  const discountAmounts = checkout?.discountAmounts;\n  const hasDiscount = discountAmounts && discountAmounts.length > 0;\n\n  // Get values\n  const planName = lineItem?.name || t('billing_subscription', 'Subscription');\n  const unitAmount = lineItem?.unitAmount?.amount || '$0.00';\n  const discountDisplay = hasDiscount ? discountAmounts[0] : null;\n  const dueToday = checkout?.total?.total?.amount || '$0.00';\n  const nextBillingTotal = recurring?.dueNext?.total?.amount;\n  const nextBillingDate = recurring?.trial?.trialEnd\n    ? dayjs(recurring.trial.trialEnd * 1000).format('MMMM D, YYYY')\n    : null;\n  const billingInterval =\n    recurring?.interval === 'month'\n      ? t('billing_monthly', 'Monthly')\n      : t('billing_yearly', 'Yearly');\n\n  return (\n    <div className=\"mt-[40px]\">\n      <h4 className=\"mb-[16px] text-[24px] font-[700]\">\n        {t('billing_order_summary', 'Order Summary')}\n      </h4>\n      <div className=\"rounded-[12px] border border-newColColor p-[20px] flex flex-col gap-[12px]\">\n        {/* Plan */}\n        <div className=\"flex justify-between items-center\">\n          <div className=\"flex flex-col\">\n            <span className=\"font-[600] text-textColor\">{planName}</span>\n            <span className=\"text-[13px] text-textColor/60\">\n              {billingInterval}\n            </span>\n          </div>\n          <span className=\"font-[500] text-textColor\">{unitAmount}</span>\n        </div>\n\n        {/* Discount */}\n        {discountDisplay && (\n          <div className=\"flex justify-between items-center font-[600]\">\n            <div className=\"flex items-center gap-[6px]\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              >\n                <path d=\"M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z\" />\n                <line x1=\"7\" y1=\"7\" x2=\"7.01\" y2=\"7\" />\n              </svg>\n              <span className=\"font-[500]\">\n                {discountDisplay.displayName || discountDisplay.promotionCode}\n                {discountDisplay.percentOff &&\n                  ` (${discountDisplay.percentOff}% off)`}\n              </span>\n            </div>\n            <span className=\"font-[500]\">\n              {discountDisplay.amount !== '$0.00'\n                ? `-${discountDisplay.amount}`\n                : t('billing_applied', 'Applied')}\n            </span>\n          </div>\n        )}\n\n        {/* Divider */}\n        <div className=\"border-t border-newColColor my-[4px]\" />\n\n        {/* Due today */}\n        <div className=\"flex justify-between items-center\">\n          <span className=\"font-[600] text-textColor\">\n            {t('billing_due_today', 'Due today')}\n          </span>\n          <span className=\"font-[700] text-[18px] text-textColor\">\n            {dueToday}\n          </span>\n        </div>\n\n        {/* Next billing info */}\n        {nextBillingTotal && nextBillingDate && (\n          <div className=\"flex justify-between items-center text-[13px] text-textColor/60\">\n            <span>\n              {t('billing_then', 'Then')} {nextBillingTotal}{' '}\n              {t('billing_on', 'on')} {nextBillingDate}\n            </span>\n          </div>\n        )}\n\n        <div className=\"text-[12px]\">\n          <strong>\n            {t(\n              'billing_cancel_notice',\n              'Cancel anytime from settings without talking to a person and never be charged.'\n            )}\n          </strong>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst AppliedCouponDisplay: FC<{\n  appliedCode: string;\n  checkout: any;\n  isApplying: boolean;\n  onRemove: () => void;\n}> = ({ appliedCode, checkout, isApplying, onRemove }) => {\n  const t = useT();\n\n  // Get discount display from checkout state\n  const getDiscountDisplay = (): string | null => {\n    // Try to get percentage from discountAmounts\n    const percentOff = checkout?.discountAmounts?.[0]?.percentOff;\n    if (percentOff && typeof percentOff === 'number' && percentOff > 0) {\n      return `-${percentOff}%`;\n    }\n\n    // Try to get actual discount amount from recurring.dueNext.discount\n    const recurringDiscount =\n      checkout?.recurring?.dueNext?.discount?.minorUnitsAmount;\n    if (\n      recurringDiscount &&\n      typeof recurringDiscount === 'number' &&\n      recurringDiscount > 0\n    ) {\n      return `-$${(recurringDiscount / 100).toFixed(2)}`;\n    }\n\n    // Try lineItems discount\n    const lineItemDiscount =\n      checkout?.lineItems?.[0]?.discountAmounts?.[0]?.percentOff;\n    if (\n      lineItemDiscount &&\n      typeof lineItemDiscount === 'number' &&\n      lineItemDiscount > 0\n    ) {\n      return `-${lineItemDiscount}%`;\n    }\n\n    return null;\n  };\n\n  // Get expiration date from checkout state (if available)\n  const getExpirationDate = (): string | null => {\n    const discount = checkout?.discountAmounts?.[0];\n    const lineItemDiscount = checkout?.lineItems?.[0]?.discountAmounts?.[0];\n\n    // Check for expiresAt in various locations (Unix timestamp)\n    const expiresAt =\n      discount?.expiresAt ||\n      discount?.expires_at ||\n      lineItemDiscount?.expiresAt ||\n      lineItemDiscount?.expires_at ||\n      checkout?.promotionCode?.expiresAt ||\n      checkout?.promotionCode?.expires_at;\n\n    if (expiresAt && typeof expiresAt === 'number') {\n      const date = new Date(expiresAt * 1000);\n      return dayjs(date).format('MMMM D, YYYY');\n    }\n\n    if (expiresAt && typeof expiresAt === 'string') {\n      return dayjs(expiresAt).format('MMMM D, YYYY');\n    }\n\n    return null;\n  };\n\n  const discountDisplay = getDiscountDisplay();\n  const expirationDate = getExpirationDate();\n\n  return (\n    <div className=\"flex flex-col gap-[8px]\">\n      <div className=\"flex items-center gap-[12px] p-[16px] rounded-[12px] border border-[#AA0FA4]/30 bg-[#AA0FA4]/10\">\n        <div className=\"flex-1\">\n          <div className=\"flex items-center gap-[8px] flex-wrap\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"20\"\n              height=\"20\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"#FC69FF\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\" />\n              <polyline points=\"22 4 12 14.01 9 11.01\" />\n            </svg>\n            <span className=\"font-[600] text-[#FC69FF]\">{appliedCode}</span>\n            <span className=\"text-[14px] text-textColor/70\">\n              {t('billing_discount_applied', 'applied')}\n              {discountDisplay && ` (${discountDisplay})`}\n            </span>\n          </div>\n        </div>\n        <button\n          type=\"button\"\n          onClick={onRemove}\n          disabled={isApplying}\n          className=\"text-[14px] text-textColor/50 hover:text-textColor font-[500] disabled:opacity-50\"\n        >\n          {t('billing_remove', 'Remove')}\n        </button>\n      </div>\n      {expirationDate && (\n        <p className=\"text-[13px] text-textColor/50 flex items-center gap-[6px]\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"14\"\n            height=\"14\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            <polyline points=\"12 6 12 12 16 14\" />\n          </svg>\n          {t('billing_coupon_expires', 'Coupon expires on')} {expirationDate}\n        </p>\n      )}\n    </div>\n  );\n};\n\nexport const CouponInput: FC<{ autoApplyCoupon?: string }> = ({\n  autoApplyCoupon,\n}) => {\n  const checkoutState = useCheckout();\n  const t = useT();\n  const toaster = useToaster();\n  const [couponCode, setCouponCode] = useState('');\n  const [isApplying, setIsApplying] = useState(false);\n  const [appliedCode, setAppliedCode] = useState<string | null>(null);\n  const [showInput, setShowInput] = useState(false);\n\n  const { checkout } =\n    checkoutState.type === 'success' ? checkoutState : { checkout: null };\n\n  // Auto-apply coupon from backend when checkout is ready\n  useEffect(() => {\n    if (autoApplyCoupon) {\n      handleApplyCoupon(undefined, autoApplyCoupon);\n    }\n  }, []);\n\n  // Check if a coupon is already pre-applied (e.g., auto-apply coupon from backend)\n  const preAppliedCode = checkout?.discountAmounts?.[0]?.promotionCode;\n  const effectiveAppliedCode = appliedCode || preAppliedCode || null;\n\n  const handleApplyCoupon = async (e?: any, coupon?: string) => {\n    if (!coupon && !couponCode.trim()) return;\n\n    setIsApplying(true);\n    try {\n      const result = await checkout.applyPromotionCode(\n        coupon || couponCode.trim()\n      );\n      if (result.type === 'error') {\n        toaster.show(\n          result.error.message ||\n            t('billing_invalid_coupon', 'Invalid coupon code'),\n          'warning'\n        );\n      } else {\n        setAppliedCode(coupon || couponCode.trim());\n        setCouponCode('');\n        setShowInput(false);\n        toaster.show(\n          t('billing_coupon_applied', 'Coupon applied successfully!'),\n          'success'\n        );\n      }\n    } catch (err: any) {\n      toaster.show(\n        err.message || t('billing_invalid_coupon', 'Invalid coupon code'),\n        'warning'\n      );\n    }\n    setIsApplying(false);\n  };\n\n  const handleRemoveCoupon = async () => {\n    setIsApplying(true);\n    try {\n      await checkout.removePromotionCode();\n      setAppliedCode(null);\n      toaster.show(t('billing_coupon_removed', 'Coupon removed'), 'success');\n    } catch (err: any) {\n      toaster.show(\n        err.message ||\n          t('billing_error_removing_coupon', 'Error removing coupon'),\n        'warning'\n      );\n    }\n    setIsApplying(false);\n  };\n\n  // Show applied coupon (either manually applied or pre-applied from backend)\n  if (effectiveAppliedCode) {\n    return (\n      <div className=\"mt-[40px]\">\n        <AppliedCouponDisplay\n          appliedCode={effectiveAppliedCode}\n          checkout={checkout}\n          isApplying={isApplying}\n          onRemove={handleRemoveCoupon}\n        />\n      </div>\n    );\n  }\n\n  // Show \"Have a promo code?\" link\n  if (!showInput) {\n    return (\n      <div className=\"mt-[40px]\">\n        <button\n          type=\"button\"\n          onClick={() => setShowInput(true)}\n          className=\"text-[16px] text-textColor/60 hover:text-textColor font-[500] flex items-center gap-[8px] transition-colors\"\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"18\"\n            height=\"18\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          >\n            <path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\" />\n          </svg>\n          {t('billing_have_discount_coupon', 'Have a discount coupon?')}\n        </button>\n      </div>\n    );\n  }\n\n  // Show input field\n  return (\n    <div className=\"mt-[40px]\">\n      <div className=\"flex items-center gap-[12px] mb-[12px]\">\n        <h4 className=\"text-[18px] font-[600] text-textColor\">\n          {t('billing_discount_coupon', 'Discount Coupon')}\n        </h4>\n        <button\n          type=\"button\"\n          onClick={() => {\n            setShowInput(false);\n            setCouponCode('');\n          }}\n          className=\"text-[14px] text-textColor/50 hover:text-textColor transition-colors\"\n        >\n          {t('billing_cancel', 'Cancel')}\n        </button>\n      </div>\n      <div className=\"flex gap-[12px]\">\n        <input\n          type=\"text\"\n          value={couponCode}\n          onChange={(e) => setCouponCode(e.target.value)}\n          placeholder={t('billing_enter_coupon_code', 'Enter coupon code')}\n          disabled={isApplying}\n          autoFocus\n          className=\"flex-1 h-[44px] px-[16px] rounded-[8px] border border-newColColor bg-newBgColor text-textColor placeholder:text-textColor/50 focus:outline-none focus:border-boxFocused disabled:opacity-50\"\n          onKeyDown={(e) => {\n            if (e.key === 'Enter') {\n              e.preventDefault();\n              handleApplyCoupon();\n            }\n            if (e.key === 'Escape') {\n              setShowInput(false);\n              setCouponCode('');\n            }\n          }}\n        />\n        <button\n          type=\"button\"\n          onClick={() => handleApplyCoupon()}\n          disabled={isApplying || !couponCode.trim()}\n          className=\"h-[44px] px-[24px] rounded-[8px] bg-boxFocused text-textItemFocused font-[600] hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition-all\"\n        >\n          {isApplying\n            ? t('billing_applying', 'Applying...')\n            : t('billing_apply', 'Apply')}\n        </button>\n      </div>\n    </div>\n  );\n};\n\nconst SubmitBar: FC<{ loading: boolean }> = ({ loading }) => {\n  const checkout = useCheckout();\n  const t = useT();\n  if (checkout.type === 'loading' || checkout.type === 'error') {\n    return null;\n  }\n\n  return (\n    <div className=\"animate-fadeIn h-[92px] mobile:h-auto fixed bottom-0 w-full px-[12px] pb-[12px] left-0 bg-newBgColor z-[100]\">\n      <div className=\"w-full h-full border-t border-newColColor bg-newBgColorInner px-[80px] tablet:px-[33px] mobile:!px-[16px] flex mobile:flex-col gap-[32px] mobile:gap-[16px] justify-end items-center font-[400] text-[14px] text-[#A3A3A3] mobile:py-[16px]\">\n        {checkout.checkout.recurring?.trial?.trialEnd ? (\n          <div>\n            {t('billing_your_7_day_trial_is', 'Your 7-day trial is')}{' '}\n            <span className=\"text-textColor font-[600]\">\n              {t('billing_100_percent_free', '100% free')}\n            </span>{' '}\n            {t('billing_ending', 'ending')}{' '}\n            <br className=\"hidden mobile:block\" />\n            <span className=\"text-textColor font-[600]\">\n              {dayjs(\n                checkout.checkout.recurring?.trial?.trialEnd * 1000\n              ).format('MMMM D, YYYY')}{' '}\n              —{' '}\n            </span>\n            <span className=\"text-textColor font-[600]\">\n              {t(\n                'billing_cancel_anytime_short',\n                'Cancel anytime from settings'\n              )}\n            </span>\n          </div>\n        ) : null}\n        <div>\n          <Button\n            className=\"h-[42px] rounded-[10px] mobile:w-full\"\n            type=\"submit\"\n            loading={loading}\n          >\n            {checkout.checkout.recurring?.trial?.trialEnd\n              ? t(\n                  'billing_pay_0_start_trial',\n                  'Pay $0 Today - Start your free trial!'\n                )\n              : t('billing_pay_now', 'Pay Now')}\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/faq.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst useFaqList = () => {\n  const { isGeneral } = useVariables();\n  const user = useUser();\n  const t = useT();\n  return [\n    ...(user?.allowTrial\n      ? [\n          {\n            title: t(\n              'faq_am_i_going_to_be_charged_by_postiz',\n              'Am I going to be charged by Postiz?'\n            ),\n            description: t(\n              'faq_to_confirm_credit_card_information_postiz_will_hold',\n              'To confirm credit card information Postiz will hold $2 and release it immediately, you can cancel your subscription anytime from settings without talking to a person'\n            ),\n          },\n        ]\n      : []),\n    {\n      title: t(\n        'faq_can_i_trust_postiz_gitroom',\n        `Can I trust ${isGeneral ? 'Postiz' : 'Gitroom'}?`\n      ),\n      description: t(\n        'faq_postiz_gitroom_is_proudly_open_source',\n        `${\n          isGeneral ? 'Postiz' : 'Gitroom'\n        } is proudly open-source! We believe in an ethical and transparent culture, meaning that ${\n          isGeneral ? 'Postiz' : 'Gitroom'\n        } will live forever. You can check out the entire code or use it for personal projects. To view the open-source repository, <a href=\"https://github.com/gitroomhq/postiz-app\" target=\"_blank\" style=\"text-decoration: underline;\">click here</a>.`\n      ),\n    },\n    {\n      title: t('faq_what_are_channels', 'What are channels?'),\n      description: t(\n        'faq_postiz_gitroom_allows_you_to_schedule_posts',\n        `${\n          isGeneral ? 'Postiz' : 'Gitroom'\n        } allows you to schedule your posts between different channels.\nA channel is a publishing platform where you can schedule your posts.\nFor example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest.`\n      ),\n    },\n    {\n      title: t('faq_what_are_team_members', 'What are team members?'),\n      description: t(\n        'faq_if_you_have_a_team_with_multiple_members',\n        'If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels'\n      ),\n    },\n  ];\n};\nexport const FAQSection: FC<{\n  title: string;\n  description: string;\n}> = (props) => {\n  const { title, description } = props;\n  const [show, setShow] = useState(false);\n  const changeShow = useCallback(() => {\n    setShow(!show);\n  }, [show]);\n  return (\n    <div\n      className=\"bg-sixth p-[24px] border border-tableBorder rounded-[8px] flex flex-col\"\n      onClick={changeShow}\n    >\n      <div className={`text-[20px] cursor-pointer flex justify-center`}>\n        <div className=\"flex-1\">{title}</div>\n        <div className=\"flex items-center justify-center w-[32px]\">\n          {!show ? (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M18 12.75H6C5.59 12.75 5.25 12.41 5.25 12C5.25 11.59 5.59 11.25 6 11.25H18C18.41 11.25 18.75 11.59 18.75 12C18.75 12.41 18.41 12.75 18 12.75Z\"\n                fill=\"white\"\n              />\n              <path\n                d=\"M12 18.75C11.59 18.75 11.25 18.41 11.25 18V6C11.25 5.59 11.59 5.25 12 5.25C12.41 5.25 12.75 5.59 12.75 6V18C12.75 18.41 12.41 18.75 12 18.75Z\"\n                fill=\"white\"\n              />\n            </svg>\n          ) : (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"32\"\n              height=\"32\"\n              viewBox=\"0 0 32 32\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M24 17H8C7.45333 17 7 16.5467 7 16C7 15.4533 7.45333 15 8 15H24C24.5467 15 25 15.4533 25 16C25 16.5467 24.5467 17 24 17Z\"\n                fill=\"#ECECEC\"\n              />\n            </svg>\n          )}\n        </div>\n      </div>\n      <div\n        className={clsx(\n          'transition-all duration-500 overflow-hidden',\n          !show ? 'max-h-[0]' : 'max-h-[500px]'\n        )}\n      >\n        <div\n          onClick={(e) => {\n            e.stopPropagation();\n          }}\n          className={`mt-[16px] w-full text-wrap font-[400] text-[16px] text-customColor17 select-text max-w-[450px]`}\n          dangerouslySetInnerHTML={{\n            __html: description,\n          }}\n        />\n      </div>\n    </div>\n  );\n};\nexport const FAQComponent: FC = () => {\n  const t = useT();\n  const list = useFaqList();\n  return (\n    <div>\n      {/*<h3 className=\"text-[24px] mt-[48px] mb-[40px] tablet:mt-[80px]\">*/}\n      {/*  {t('frequently_asked_questions', 'Frequently Asked Questions')}*/}\n      {/*</h3>*/}\n      <div className=\"gap-[24px] flex-col flex select-none  mt-[48px] mb-[40px] tablet:mt-[80px]\">\n        {list.map((item, index) => (\n          <FAQSection key={index} {...item} />\n        ))}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/finish.trial.tsx",
    "content": "import React, { FC, useCallback, useEffect, useState } from 'react';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { Button } from '@gitroom/react/form/button';\n\nexport const FinishTrial: FC<{ close: () => void }> = (props) => {\n  const [finished, setFinished] = useState(false);\n  const fetch = useFetch();\n\n  const finishSubscription = useCallback(async () => {\n    await fetch('/billing/finish-trial', {\n      method: 'POST',\n    });\n    checkFinished();\n  }, []);\n\n  const checkFinished = useCallback(async () => {\n    const {finished} = await (await fetch('/billing/is-trial-finished')).json();\n    if (!finished) {\n      await timer(2000);\n      return checkFinished();\n    }\n\n    setFinished(true);\n  }, []);\n\n  useEffect(() => {\n    finishSubscription();\n  }, []);\n\n  return (\n    <div className=\"text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade justify-center flex bg-black/50\">\n      <div>\n        <div className=\"flex gap-[10px] flex-col w-[500px] h-auto bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative\">\n          <div className=\"flex\">\n            <div className=\"flex-1\">\n              <TopTitle title={'Finishing Trial'} />\n            </div>\n            <button\n              onClick={props.close}\n              className=\"outline-none absolute end-[10px] top-[10px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n              type=\"button\"\n            >\n              <svg\n                viewBox=\"0 0 15 15\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n              >\n                <path\n                  d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                  fill=\"currentColor\"\n                  fillRule=\"evenodd\"\n                  clipRule=\"evenodd\"\n                ></path>\n              </svg>\n            </button>\n          </div>\n          <div className=\"relative h-[400px]\">\n            <div className=\"absolute left-0 top-0 w-full h-full overflow-hidden overflow-y-auto\">\n              <div className=\"mt-[10px] flex w-full justify-center items-center gap-[10px]\">\n                {!finished && <LoadingComponent height={150} width={150} />}\n                {finished && (\n                  <div className=\"flex flex-col\">\n                    <div>\n                      You trial has been successfully finished and you have been charged.\n                    </div>\n                    <div className=\"flex gap-[10px] mt-[20px]\">\n                      <Button className=\"flex-1\" onClick={() => window.close()}>Close window</Button>\n                      <Button className=\"flex-1\" onClick={() => props.close()}>Close dialog</Button>\n                    </div>\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/first.billing.component.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { loadStripe, Stripe } from '@stripe/stripe-js';\nimport { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector';\nimport { LanguageComponent } from '@gitroom/frontend/components/layout/language.component';\nimport { AttachToFeedbackIcon } from '@gitroom/frontend/components/new-layout/sentry.feedback.component';\nimport NotificationComponent from '@gitroom/frontend/components/notifications/notification.component';\nimport dynamic from 'next/dynamic';\nimport { LogoTextComponent } from '@gitroom/frontend/components/ui/logo-text.component';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { capitalize } from 'lodash';\nimport clsx from 'clsx';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { CheckIconComponent } from '@gitroom/frontend/components/ui/check.icon.component';\nimport {\n  FAQComponent,\n  FAQSection,\n} from '@gitroom/frontend/components/billing/faq.component';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useDubClickId } from '@gitroom/frontend/components/layout/dubAnalytics';\nimport Image from 'next/image';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport useCookie from 'react-use-cookie';\nimport { LogoutComponent } from '@gitroom/frontend/components/layout/logout.component';\n\nconst ModeComponent = dynamic(\n  () => import('@gitroom/frontend/components/layout/mode.component'),\n  {\n    ssr: false,\n  }\n);\n\nconst EmbeddedBilling = dynamic(\n  () =>\n    import('@gitroom/frontend/components/billing/embedded.billing').then(\n      (mod) => mod.EmbeddedBilling\n    ),\n  {\n    ssr: false,\n  }\n);\n\nexport const FirstBillingComponent = () => {\n  const { stripeClient } = useVariables();\n  const user = useUser();\n  const dub = useDubClickId();\n  const [stripe, setStripe] = useState<null | Promise<Stripe>>(null);\n  const [tier, setTier] = useState('STANDARD');\n  const [period, setPeriod] = useState('MONTHLY');\n  const fetch = useFetch();\n  const modals = useModals();\n  const t = useT();\n  const [datafast_visitor_id] = useCookie('datafast_visitor_id', '');\n  const [datafast_session_id] = useCookie('datafast_session_id', '');\n\n  useEffect(() => {\n    setStripe(loadStripe(stripeClient));\n  }, []);\n\n  const loadCheckout = useCallback(async () => {\n    return (\n      await fetch('/billing/embedded', {\n        method: 'POST',\n        body: JSON.stringify({\n          billing: tier,\n          period: period,\n          ...(datafast_visitor_id && datafast_session_id\n            ? { datafast_visitor_id, datafast_session_id }\n            : {}),\n          ...(dub ? { dub } : {}),\n        }),\n      })\n    ).json();\n  }, [tier, period]);\n\n  const showYouTube = () => {\n    modals.openModal({\n      title: 'Grow Fast With Postiz (Play the video)',\n      children: (\n        <iframe\n          className=\"h-full aspect-video min-w-[800px]\"\n          src=\"https://www.youtube.com/embed/BdsCVvEYgHU?si=vvhaZJ8I5oXXvVJS?autoplay=1\"\n          title=\"Postiz Tutorial\"\n          allow=\"autoplay\"\n          allowFullScreen\n        />\n      ),\n    });\n  };\n\n  const { data, isLoading } = useSWR(\n    `/billing-${tier}-${period}`,\n    loadCheckout,\n    {\n      revalidateOnFocus: false,\n      revalidateOnReconnect: false,\n      revalidateIfStale: false,\n      refreshWhenOffline: false,\n      refreshWhenHidden: false,\n    }\n  );\n\n  const price = useMemo(\n    () => Object.entries(pricing).filter(([key, value]) => key !== 'FREE'),\n    []\n  );\n\n  const JoinOver = () => {\n    return (\n      <>\n        <div className=\"text-[46px] font-[600] leading-[110%] tablet:text-[36px] mobile:!text-[30px] whitespace-pre-line text-balance\">\n          {t('billing_join_over', 'Join Over')}{' '}\n          <span className=\"text-[#FC69FF]\">\n            {t('billing_entrepreneurs_count', '20,000+ Entrepreneurs')}\n          </span>{' '}\n          {t('billing_who_use', 'who use')}{' '}\n          {t(\n            'billing_postiz_grow_social',\n            'Postiz To Grow Their Social Presence'\n          )}\n        </div>\n\n        <div className=\"flex\" onClick={showYouTube}>\n          <div className=\"tablet:mb-[32px] cursor-pointer mt-[32px] flex gap-[10px] items-center underline hover:font-[700]\">\n            <div>\n              <Image\n                className=\"text-[12px]\"\n                src=\"/icons/platforms/youtube.svg\"\n                width={22.5}\n                height={16}\n                alt=\"YouTube\"\n              />\n            </div>\n            <div>See the power of Postiz (click here)</div>\n          </div>\n        </div>\n\n        {!!user?.allowTrial && (\n          <div className=\"flex mt-[32px] mb-[10px] gap-[15px] tablet:mt-[32px] tablet:mb-[32px] text-[16px] font-[500] mobile:flex-col\">\n            <div className=\"flex gap-[8px]\">\n              <div>\n                <CheckIconComponent />\n              </div>\n              <div>{t('billing_no_risk_trial', '100% No-Risk Free Trial')}</div>\n            </div>\n            <div className=\"flex-1 flex gap-[8px] justify-center mobile:justify-start\">\n              <div>\n                <CheckIconComponent />\n              </div>\n              <div>\n                {t(\n                  'billing_pay_nothing_7_days',\n                  'Pay NOTHING for the first 7-days'\n                )}\n              </div>\n            </div>\n            <div className=\"flex gap-[8px]\">\n              <div>\n                <CheckIconComponent />\n              </div>\n              <div>\n                {t('billing_cancel_anytime', 'Cancel anytime, from settings')}\n              </div>\n            </div>\n          </div>\n        )}\n      </>\n    );\n  };\n\n  return (\n    <div className=\"blurMe flex flex-1 flex-col bg-newBgColorInner pb-[60px] mobile:pb-[100px]\">\n      <div className=\"h-[92px] px-[80px] tablet:px-[32px] mobile:!px-[16px] py-[20px] flex border-b border-newColColor\">\n        <div className=\"flex-1 flex items-center text-textColor\">\n          <LogoTextComponent />\n        </div>\n        <div className=\"flex items-center\">\n          <div className=\"flex gap-[20px] text-textItemBlur\">\n            <OrganizationSelector />\n            <div className=\"hover:text-newTextColor\">\n              <ModeComponent />\n            </div>\n            <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />\n            <LanguageComponent />\n            <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />\n            <AttachToFeedbackIcon />\n            {/*<NotificationComponent />*/}\n            <div className=\"hover:text-newTextColor\">\n              {user?.tier.current === 'FREE' && (\n                <LogoutComponent isIcon={true} />\n              )}\n            </div>\n          </div>\n        </div>\n      </div>\n      <div className=\"flex px-[80px] tablet:px-[32px] mobile:!px-[16px] flex-1 flex-row tablet:flex-none tablet:flex-col-reverse\">\n        <div className=\"flex-1 py-[40px] tablet:pt-[80px] flex flex-col pe-[40px] tablet:pe-0\">\n          <div className=\"block tablet:hidden\">\n            <JoinOver />\n          </div>\n          {!isLoading && data && stripe ? (\n            <EmbeddedBilling\n              stripe={stripe}\n              secret={data.client_secret}\n              showCoupon={period === 'MONTHLY'}\n              autoApplyCoupon={data.auto_apply_coupon}\n            />\n          ) : (\n            <LoadingComponent />\n          )}\n        </div>\n        <div className=\"flex flex-col ps-[40px] tablet:!ps-[0] border-l border-newColColor py-[40px] mobile:!pt-[24px] tablet:border-none tablet:pb-0\">\n          <div className=\"top-[20px] sticky\">\n            <div className=\"hidden tablet:block\">\n              <JoinOver />\n            </div>\n            <div className=\"flex mb-[24px] mobile:flex-col\">\n              <div className=\"flex-1 text-[24px] font-[700]\">\n                {t('billing_choose_plan', 'Choose a Plan')}\n              </div>\n              <div className=\"h-[44px] px-[6px] mobile:px-0 flex items-center justify-center mobile:justify-start gap-[12px] border border-newColColor rounded-[12px] select-none\">\n                <div\n                  className={clsx(\n                    'h-[32px] mobile:flex-1 rounded-[6px] text-[16px] px-[12px] flex justify-center items-center',\n                    period === 'MONTHLY'\n                      ? 'bg-boxFocused text-textItemFocused'\n                      : 'cursor-pointer'\n                  )}\n                  onClick={() => setPeriod('MONTHLY')}\n                >\n                  {t('billing_monthly', 'Monthly')}\n                </div>\n                <div\n                  className={clsx(\n                    'gap-[10px] h-[32px] mobile:flex-1 rounded-[6px] text-[16px] px-[12px] flex justify-center items-center',\n                    period === 'YEARLY'\n                      ? 'bg-boxFocused text-textItemFocused'\n                      : 'cursor-pointer'\n                  )}\n                  onClick={() => setPeriod('YEARLY')}\n                >\n                  <div>{t('billing_yearly', 'Yearly')}</div>\n                  <div className=\"bg-[#AA0FA4] text-[white] px-[8px] rounded-[4px] mobile:hidden\">\n                    {t('billing_20_percent_off', '20% Off')}\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div className=\"grid grid-cols-2 gap-[8px] mobile:!grid-cols-2 tablet:grid-cols-4\">\n              {price.map(\n                ([key, value]) => (\n                  <div\n                    onClick={() => setTier(key)}\n                    key={key}\n                    className={clsx(\n                      'cursor-pointer select-none w-[266px] h-[138px] tablet:w-full tablet:h-[124px] p-[24px] tablet:p-[15px] rounded-[20px] flex flex-col',\n                      key === tier\n                        ? 'border-[1.5px] border-[#618DFF]'\n                        : 'border-[1.5px] border-newColColor'\n                    )}\n                  >\n                    <div className=\"text-[20px] mobile:text-[18px] font-[500]\">\n                      {capitalize(key)}\n                    </div>\n                    <div className=\"text-[24px] mobile:text-[18px] font-[400]\">\n                      <span className=\"text-[44px] mobile:text-[30px] font-[600]\">\n                        $\n                        {\n                          value[\n                            period === 'MONTHLY' ? 'month_price' : 'year_price'\n                          ]\n                        }\n                      </span>{' '}\n                      {period === 'MONTHLY'\n                        ? t('billing_per_month', '/ month')\n                        : t('billing_per_year', '/ year')}\n                    </div>\n                  </div>\n                ),\n                []\n              )}\n            </div>\n            <div className=\"flex flex-col mt-[54px] gap-[24px] tablet:mt-[40px]\">\n              <div className=\"text-[24px] font-[700]\">\n                {t('billing_features', 'Features')}\n              </div>\n              <BillingFeatures tier={tier} />\n            </div>\n            <div className=\"flex flex-col mobile:hidden tablet:hidden\">\n              {/*<div>asd</div>*/}\n              <FAQComponent />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\ntype FeatureItem = {\n  key: string;\n  defaultValue: string;\n  prefix?: string | number;\n};\n\nexport const BillingFeatures: FC<{ tier: string }> = ({ tier }) => {\n  const t = useT();\n  const features = useMemo(() => {\n    const currentPricing = pricing[tier];\n    const channelsOr = currentPricing.channel;\n    const list: FeatureItem[] = [];\n\n    list.push({\n      key: channelsOr === 1 ? 'billing_channel' : 'billing_channels',\n      defaultValue: channelsOr === 1 ? 'channel' : 'channels',\n      prefix: channelsOr,\n    });\n\n    list.push({\n      key: 'billing_posts_per_month',\n      defaultValue: 'posts per month',\n      prefix:\n        currentPricing.posts_per_month > 10000\n          ? 'unlimited'\n          : currentPricing.posts_per_month,\n    });\n\n    if (currentPricing.team_members) {\n      list.push({\n        key: 'billing_unlimited_team_members',\n        defaultValue: 'Unlimited team members',\n      });\n    }\n    if (currentPricing?.ai) {\n      list.push({\n        key: 'billing_ai_auto_complete',\n        defaultValue: 'AI auto-complete',\n      });\n      list.push({ key: 'billing_ai_copilots', defaultValue: 'AI copilots' });\n      list.push({\n        key: 'billing_ai_autocomplete',\n        defaultValue: 'AI Autocomplete',\n      });\n    }\n    list.push({\n      key: 'billing_advanced_picture_editor',\n      defaultValue: 'Advanced Picture Editor',\n    });\n    if (currentPricing?.image_generator) {\n      list.push({\n        key: 'billing_ai_images_per_month',\n        defaultValue: 'AI Images per month',\n        prefix: currentPricing?.image_generation_count,\n      });\n    }\n    if (currentPricing?.generate_videos) {\n      list.push({\n        key: 'billing_ai_videos_per_month',\n        defaultValue: 'AI Videos per month',\n        prefix: currentPricing?.generate_videos,\n      });\n    }\n    return list;\n  }, [tier]);\n\n  const renderFeature = (feature: FeatureItem) => {\n    const translatedText = t(feature.key, feature.defaultValue);\n    if (feature.prefix === 'unlimited') {\n      return `${t('billing_unlimited', 'Unlimited')} ${translatedText}`;\n    }\n    if (feature.prefix !== undefined) {\n      return `${feature.prefix} ${translatedText}`;\n    }\n    return translatedText;\n  };\n\n  return (\n    <div className=\"grid grid-cols-2 mobile:grid-cols-1 gap-y-[8px] gap-x-[32px]\">\n      {features.map((feature) => (\n        <div key={feature.key} className=\"flex items-center gap-[8px]\">\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"17\"\n              height=\"17\"\n              viewBox=\"0 0 17 17\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M11.825 0H4.84167C1.80833 0 0 1.80833 0 4.84167V11.8167C0 14.8583 1.80833 16.6667 4.84167 16.6667H11.8167C14.85 16.6667 16.6583 14.8583 16.6583 11.825V4.84167C16.6667 1.80833 14.8583 0 11.825 0ZM12.3167 6.41667L7.59167 11.1417C7.475 11.2583 7.31667 11.325 7.15 11.325C6.98333 11.325 6.825 11.2583 6.70833 11.1417L4.35 8.78333C4.10833 8.54167 4.10833 8.14167 4.35 7.9C4.59167 7.65833 4.99167 7.65833 5.23333 7.9L7.15 9.81667L11.4333 5.53333C11.675 5.29167 12.075 5.29167 12.3167 5.53333C12.5583 5.775 12.5583 6.16667 12.3167 6.41667Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          </div>\n          <div>{renderFeature(feature)}</div>\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/lifetime.deal.tsx",
    "content": "'use client';\n\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useCallback, useMemo, useState } from 'react';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { Input } from '@gitroom/react/form/input';\nimport { Button } from '@gitroom/react/form/button';\nimport { useSWRConfig } from 'swr';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useRouter } from 'next/navigation';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const LifetimeDeal = () => {\n  const t = useT();\n  const fetch = useFetch();\n  const user = useUser();\n  const [code, setCode] = useState('');\n  const toast = useToaster();\n  const { mutate } = useSWRConfig();\n  const router = useRouter();\n  const fireEvents = useFireEvents();\n  const claim = useCallback(async () => {\n    const { success } = await (\n      await fetch('/billing/lifetime', {\n        body: JSON.stringify({\n          code,\n        }),\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      })\n    ).json();\n    if (success) {\n      mutate('/user/self');\n      toast.show('Successfully claimed the code');\n      fireEvents('lifetime_claimed');\n    } else {\n      toast.show('Code already claimed or invalid code', 'warning');\n    }\n    setCode('');\n  }, [code]);\n  const nextPackage = useMemo(() => {\n    if (user?.tier?.current === 'STANDARD') {\n      return 'PRO';\n    }\n    return 'STANDARD';\n  }, [user?.tier]);\n  const features = useMemo(() => {\n    if (!user?.tier) {\n      return [];\n    }\n    const currentPricing = user?.tier;\n    const channelsOr = currentPricing.channel;\n    const list = [];\n    list.push(\n      `${user.totalChannels} ${\n        user.totalChannels === 1 ? 'channel' : 'channels'\n      }`\n    );\n    list.push(\n      `${\n        currentPricing.posts_per_month > 10000\n          ? 'Unlimited'\n          : currentPricing.posts_per_month\n      } posts per month`\n    );\n    if (currentPricing.team_members) {\n      list.push(`Unlimited team members`);\n    }\n    if (currentPricing?.ai) {\n      list.push(`AI auto-complete`);\n    }\n    return list;\n  }, [user]);\n  const nextFeature = useMemo(() => {\n    if (!user?.tier) {\n      return [];\n    }\n    const currentPricing = pricing[nextPackage];\n    const channelsOr = currentPricing.channel;\n    const list = [];\n    list.push(`${channelsOr} ${channelsOr === 1 ? 'channel' : 'channels'}`);\n    list.push(\n      `${\n        currentPricing.posts_per_month > 10000\n          ? 'Unlimited'\n          : currentPricing.posts_per_month\n      } posts per month`\n    );\n    if (currentPricing.team_members) {\n      list.push(`Unlimited team members`);\n    }\n    if (currentPricing?.ai) {\n      list.push(`AI auto-complete`);\n    }\n    return list;\n  }, [user, nextPackage]);\n  if (!user?.tier) {\n    return null;\n  }\n  if (user?.id && user?.tier?.current !== 'FREE' && !user?.isLifetime) {\n    router.replace('/billing');\n    return null;\n  }\n  return (\n    <div className=\"flex gap-[30px]\">\n      <div className=\"border border-customColor6 bg-sixth p-[24px] flex flex-col gap-[20px] flex-1 rounded-[4px]\">\n        <div className=\"text-[30px]\">\n          {t('current_package', 'Current Package:')}\n          {user?.totalChannels > 8 ? 'EXTRA' : user?.tier?.current}\n        </div>\n\n        <div className=\"flex flex-col gap-[10px] justify-center text-[16px] text-customColor18\">\n          {features.map((feature) => (\n            <div key={feature} className=\"flex gap-[20px]\">\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                    fill=\"#06ff00\"\n                  />\n                </svg>\n              </div>\n              <div>{feature}</div>\n            </div>\n          ))}\n        </div>\n      </div>\n\n      <div className=\"border border-customColor6 bg-sixth p-[24px] flex flex-col gap-[20px] flex-1 rounded-[4px]\">\n        <div className=\"text-[30px]\">\n          {t('next_package', 'Next Package:')}\n          {user?.tier?.current === 'PRO'\n            ? 'EXTRA'\n            : !user?.tier?.current\n            ? 'FREE'\n            : user?.tier?.current === 'STANDARD'\n            ? 'PRO'\n            : 'STANDARD'}\n        </div>\n\n        <div className=\"flex flex-col gap-[10px] justify-center text-[16px] text-customColor18\">\n          {(user?.tier?.current === 'PRO'\n            ? [`${(user?.totalChannels || 0) + 5} channels`]\n            : nextFeature\n          ).map((feature) => (\n            <div key={feature} className=\"flex gap-[20px]\">\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                    fill=\"#06ff00\"\n                  />\n                </svg>\n              </div>\n              <div>{feature}</div>\n            </div>\n          ))}\n\n          <div className=\"mt-[20px] flex items-center gap-[10px]\">\n            <div className=\"flex-1\">\n              <Input\n                label=\"Code\"\n                translationKey=\"label_code\"\n                placeholder=\"Enter your code\"\n                disableForm={true}\n                name=\"code\"\n                value={code}\n                onChange={(e) => setCode(e.target.value)}\n              />\n            </div>\n            <div>\n              <Button disabled={code.length < 4} onClick={claim}>\n                {t('claim', 'Claim')}\n              </Button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/main.billing.component.tsx",
    "content": "'use client';\n\nimport { Slider } from '@gitroom/react/form/slider';\nimport React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { Button } from '@gitroom/react/form/button';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Subscription } from '@prisma/client';\nimport { useDebouncedCallback } from 'use-debounce';\nimport ReactLoading from 'react-loading';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport dayjs from 'dayjs';\nimport clsx from 'clsx';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { FAQComponent } from '@gitroom/frontend/components/billing/faq.component';\nimport { useSWRConfig } from 'swr';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nimport { useUtmUrl } from '@gitroom/helpers/utils/utm.saver';\nimport { useTrack } from '@gitroom/react/helpers/use.track';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { PurchaseCrypto } from '@gitroom/frontend/components/billing/purchase.crypto';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { FinishTrial } from '@gitroom/frontend/components/billing/finish.trial';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { useDubClickId } from '@gitroom/frontend/components/layout/dubAnalytics';\nimport { LogoutComponent } from '@gitroom/frontend/components/layout/logout.component';\n\nexport const Prorate: FC<{\n  period: 'MONTHLY' | 'YEARLY';\n  pack: 'STANDARD' | 'PRO';\n}> = (props) => {\n  const { period, pack } = props;\n  const t = useT();\n  const fetch = useFetch();\n  const [price, setPrice] = useState<number | false>(0);\n  const [loading, setLoading] = useState(false);\n  const calculatePrice = useDebouncedCallback(async () => {\n    setLoading(true);\n    setPrice(\n      (\n        await (\n          await fetch('/billing/prorate', {\n            method: 'POST',\n            body: JSON.stringify({\n              period,\n              billing: pack,\n            }),\n          })\n        ).json()\n      ).price\n    );\n    setLoading(false);\n  }, 500);\n  useEffect(() => {\n    setPrice(false);\n    calculatePrice();\n  }, [period, pack]);\n  if (loading) {\n    return (\n      <div className=\"pt-[12px]\">\n        <ReactLoading type=\"spin\" color=\"#fff\" width={20} height={20} />\n      </div>\n    );\n  }\n  if (price === false) {\n    return null;\n  }\n  return (\n    <div className=\"text-[12px] flex pt-[12px]\">\n      ({t('pay_today', 'Pay Today')} ${(price < 0 ? 0 : price)?.toFixed(1)})\n    </div>\n  );\n};\nexport const Features: FC<{\n  pack: 'FREE' | 'STANDARD' | 'PRO';\n}> = (props) => {\n  const { pack } = props;\n  const features = useMemo(() => {\n    const currentPricing = pricing[pack];\n    const channelsOr = currentPricing.channel;\n    const list = [];\n    list.push(`${channelsOr} ${channelsOr === 1 ? 'channel' : 'channels'}`);\n    list.push(\n      `${\n        currentPricing.posts_per_month > 10000\n          ? 'Unlimited'\n          : currentPricing.posts_per_month\n      } posts per month`\n    );\n    if (currentPricing.team_members) {\n      list.push(`Unlimited team members`);\n    }\n    if (currentPricing?.ai) {\n      list.push(`AI auto-complete`);\n      list.push(`AI copilots`);\n      list.push(`AI Autocomplete`);\n    }\n    list.push(`Advanced Picture Editor`);\n    if (currentPricing?.image_generator) {\n      list.push(\n        `${currentPricing?.image_generation_count} AI Images per month`\n      );\n    }\n    if (currentPricing?.generate_videos) {\n      list.push(`${currentPricing?.generate_videos} AI Videos per month`);\n    }\n    return list;\n  }, [pack]);\n  return (\n    <div className=\"flex flex-col gap-[10px] justify-center text-[16px] text-customColor18\">\n      {features.map((feature) => (\n        <div key={feature} className=\"flex gap-[20px]\">\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                fill=\"#06ff00\"\n              />\n            </svg>\n          </div>\n          <div>{feature}</div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst Accept: FC<{ resolve: (res: boolean) => void }> = ({ resolve }) => {\n  const [loading, setLoading] = useState(false);\n  const fetch = useFetch();\n  const toaster = useToaster();\n\n  const apply = useCallback(async () => {\n    setLoading(true);\n    await fetch('/billing/apply-discount', {\n      method: 'POST',\n    });\n\n    resolve(true);\n    toaster.show('50% discount applied successfully');\n  }, []);\n\n  return (\n    <div>\n      <div className=\"mb-[20px]\">\n        Would you accept 50% discount for 3 months instead? 🙏🏻\n      </div>\n      <div className=\"flex gap-[10px]\">\n        <Button loading={loading} onClick={apply}>\n          Apply 50% discount for 3 months\n        </Button>\n        <Button onClick={() => resolve(false)} className=\"!bg-red-800\">\n          Cancel my subscription\n        </Button>\n      </div>\n    </div>\n  );\n};\nconst Info: FC<{\n  proceed: (feedback: string) => void;\n}> = (props) => {\n  const [feedback, setFeedback] = useState('');\n  const modal = useModals();\n  const events = useFireEvents();\n  const cancel = useCallback(() => {\n    props.proceed(feedback);\n    events('cancel_subscription');\n    modal.closeAll();\n  }, [modal, feedback]);\n\n  const t = useT();\n\n  return (\n    <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px]\">\n      <div>\n        {t(\n          'would_you_mind_shortly_tell_us_what_we_could_have_done_better',\n          'Would you mind shortly tell us what we could have done better?'\n        )}\n      </div>\n      <div>\n        <Textarea\n          className=\"bg-newBgColorInner\"\n          label={'Feedback'}\n          name=\"feedback\"\n          disableForm={true}\n          value={feedback}\n          onChange={(e) => setFeedback(e.target.value)}\n        />\n      </div>\n      <div>\n        <Button disabled={feedback.length < 20} onClick={cancel}>\n          {feedback.length < 20\n            ? t('please_add_at_least', 'Please add at least 20 chars')\n            : t('cancel_subscription', 'Cancel Subscription')}\n        </Button>\n      </div>\n    </div>\n  );\n};\nexport const MainBillingComponent: FC<{\n  sub?: Subscription;\n}> = (props) => {\n  const { sub } = props;\n  const { isGeneral } = useVariables();\n  const { mutate } = useSWRConfig();\n  const fetch = useFetch();\n  const toast = useToaster();\n  const user = useUser();\n  const dub = useDubClickId();\n  const modal = useModals();\n  const router = useRouter();\n  const utm = useUtmUrl();\n  const track = useTrack();\n  const t = useT();\n  const queryParams = useSearchParams();\n  const [finishTrial, setFinishTrial] = useState(\n    !!queryParams.get('finishTrial')\n  );\n\n  const [subscription, setSubscription] = useState<Subscription | undefined>(\n    sub\n  );\n  const [loading, setLoading] = useState<boolean>(false);\n  const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>(\n    subscription?.period || 'MONTHLY'\n  );\n  const [monthlyOrYearly, setMonthlyOrYearly] = useState<'on' | 'off'>(\n    period === 'MONTHLY' ? 'off' : 'on'\n  );\n  const [initialChannels, setInitialChannels] = useState(\n    sub?.totalChannels || 1\n  );\n  useEffect(() => {\n    if (initialChannels !== sub?.totalChannels) {\n      setInitialChannels(sub?.totalChannels || 1);\n    }\n    if (period !== sub?.period) {\n      setPeriod(sub?.period || 'MONTHLY');\n      setMonthlyOrYearly(\n        (sub?.period || 'MONTHLY') === 'MONTHLY' ? 'off' : 'on'\n      );\n    }\n    setSubscription(sub);\n  }, [sub]);\n  const updatePayment = useCallback(async () => {\n    const { portal } = await (await fetch('/billing/portal')).json();\n    window.location.href = portal;\n  }, []);\n  const currentPackage = useMemo(() => {\n    if (!subscription) {\n      return 'FREE';\n    }\n    if (period === 'YEARLY' && monthlyOrYearly === 'off') {\n      return '';\n    }\n    if (period === 'MONTHLY' && monthlyOrYearly === 'on') {\n      return '';\n    }\n    return subscription?.subscriptionTier;\n  }, [subscription, initialChannels, monthlyOrYearly, period]);\n  const moveToCheckout = useCallback(\n    (billing: 'STANDARD' | 'PRO' | 'FREE', reactivate = false) =>\n      async () => {\n        if (reactivate) {\n          setLoading(true);\n          const { cancel_at } = await (\n            await fetch('/billing/cancel', {\n              method: 'POST',\n              body: JSON.stringify({\n                feedback: '',\n              }),\n              headers: {\n                'Content-Type': 'application/json',\n              },\n            })\n          ).json();\n          setSubscription((subs) => ({\n            ...subs!,\n            cancelAt: cancel_at,\n          }));\n\n          toast.show('Subscription reactivated successfully');\n          setLoading(false);\n          return;\n        }\n\n        const messages = [];\n        if (\n          !pricing[billing].team_members &&\n          pricing[subscription?.subscriptionTier!]?.team_members\n        ) {\n          messages.push(\n            `Your team members will be removed from your organization`\n          );\n        }\n        if (billing === 'FREE') {\n          if (\n            subscription?.cancelAt ||\n            (await deleteDialog(\n              `Are you sure you want to cancel your subscription?\n              ${messages.join(', ')}`,\n              'Yes, cancel',\n              'Cancel Subscription'\n            ))\n          ) {\n            const checkDiscount = await (\n              await fetch('/billing/check-discount')\n            ).json();\n            if (checkDiscount.offerCoupon) {\n              const info = await new Promise((res) => {\n                modal.openModal({\n                  title: 'Before you cancel',\n                  withCloseButton: true,\n                  classNames: {\n                    modal: 'bg-transparent text-textColor',\n                  },\n                  children: <Accept resolve={res} />,\n                });\n              });\n\n              modal.closeAll();\n\n              if (info) {\n                return;\n              }\n            }\n\n            const info = await new Promise((res) => {\n              modal.openModal({\n                title: t(\n                  'we_are_sorry_to_see_you_go',\n                  'We are sorry to see you go :('\n                ),\n                withCloseButton: true,\n                classNames: {\n                  modal: 'bg-transparent text-textColor',\n                },\n                children: <Info proceed={(e) => res(e)} />,\n              });\n            });\n\n            setLoading(true);\n            const { cancel_at } = await (\n              await fetch('/billing/cancel', {\n                method: 'POST',\n                body: JSON.stringify({\n                  feedback: info,\n                }),\n                headers: {\n                  'Content-Type': 'application/json',\n                },\n              })\n            ).json();\n            setSubscription((subs) => ({\n              ...subs!,\n              cancelAt: cancel_at,\n            }));\n            if (cancel_at)\n              toast.show('Subscription set to canceled successfully');\n            setLoading(false);\n          }\n          return;\n        }\n        if (\n          messages.length &&\n          !(await deleteDialog(messages.join(', '), 'Yes, continue'))\n        ) {\n          return;\n        }\n        setLoading(true);\n        const { url, portal } = await (\n          await fetch('/billing/subscribe', {\n            method: 'POST',\n            body: JSON.stringify({\n              period: monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY',\n              utm,\n              billing,\n              ...(dub ? { dub } : {}),\n            }),\n          })\n        ).json();\n        if (url) {\n          await track(TrackEnum.InitiateCheckout, {\n            value:\n              pricing[billing][\n                monthlyOrYearly === 'on' ? 'year_price' : 'month_price'\n              ],\n          });\n          window.location.href = url;\n          return;\n        }\n        if (portal) {\n          if (\n            await deleteDialog(\n              'We could not charge your credit card, please update your payment method',\n              'Update',\n              'Payment Method Required'\n            )\n          ) {\n            window.open(portal);\n          }\n        } else {\n          setPeriod(monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY');\n          setSubscription((subs) => ({\n            ...subs!,\n            subscriptionTier: billing,\n            cancelAt: null,\n          }));\n          mutate(\n            '/user/self',\n            {\n              ...user,\n              tier: billing,\n            },\n            {\n              revalidate: false,\n            }\n          );\n          toast.show('Subscription updated successfully');\n        }\n        setLoading(false);\n      },\n    [monthlyOrYearly, subscription, user, utm]\n  );\n  if (user?.isLifetime) {\n    router.replace('/');\n    return null;\n  }\n  return (\n    <div className=\"flex flex-col gap-[16px]\">\n      <div className=\"flex flex-row\">\n        <div className=\"flex-1 text-[20px]\">{t('plans', 'Plans')}</div>\n        <div className=\"flex items-center gap-[16px]\">\n          <div>{t('monthly', 'MONTHLY')}</div>\n          <div>\n            <Slider value={monthlyOrYearly} onChange={setMonthlyOrYearly} />\n          </div>\n          <div>{t('yearly', 'YEARLY')}</div>\n        </div>\n      </div>\n\n      {finishTrial && <FinishTrial close={() => setFinishTrial(false)} />}\n      <div className=\"flex gap-[16px] [@media(max-width:1024px)]:flex-col [@media(max-width:1024px)]:text-center\">\n        {Object.entries(pricing)\n          .filter((f) => !isGeneral || f[0] !== 'FREE')\n          .map(([name, values]) => (\n            <div\n              key={name}\n              className=\"flex-1 bg-sixth border border-customColor6 rounded-[4px] p-[24px] gap-[16px] flex flex-col [@media(max-width:1024px)]:items-center\"\n            >\n              <div className=\"text-[18px]\">{name}</div>\n              <div className=\"text-[38px] flex gap-[2px] items-center\">\n                <div>\n                  $\n                  {monthlyOrYearly === 'on'\n                    ? values.year_price\n                    : values.month_price}\n                </div>\n                <div className={`text-[14px] text-customColor18`}>\n                  {monthlyOrYearly === 'on' ? '/year' : '/month'}\n                </div>\n              </div>\n              <div className=\"text-[14px] flex gap-[10px]\">\n                {currentPackage === name.toUpperCase() &&\n                subscription?.cancelAt ? (\n                  <div className=\"gap-[3px] flex flex-col\">\n                    <div>\n                      <Button\n                        onClick={moveToCheckout('FREE', true)}\n                        loading={loading}\n                      >\n                        {t(\n                          'reactivate_subscription',\n                          'Reactivate subscription'\n                        )}\n                      </Button>\n                    </div>\n                  </div>\n                ) : (\n                  <Button\n                    loading={loading}\n                    disabled={\n                      (!!subscription?.cancelAt &&\n                        name.toUpperCase() === 'FREE') ||\n                      currentPackage === name.toUpperCase()\n                    }\n                    className={clsx(\n                      subscription &&\n                        name.toUpperCase() === 'FREE' &&\n                        '!bg-red-500'\n                    )}\n                    onClick={moveToCheckout(\n                      name.toUpperCase() as 'STANDARD' | 'PRO'\n                    )}\n                  >\n                    {currentPackage === name.toUpperCase()\n                      ? 'Current Plan'\n                      : name.toUpperCase() === 'FREE'\n                      ? subscription?.cancelAt\n                        ? `Downgrade on ${dayjs\n                            .utc(subscription?.cancelAt)\n                            .local()\n                            .format('D MMM, YYYY')}`\n                        : 'Cancel subscription'\n                      : // @ts-ignore\n                      (user?.tier === 'FREE' ||\n                          user?.tier?.current === 'FREE') &&\n                        user.allowTrial\n                      ? t('start_7_days_free_trial', 'Start 7 days free trial')\n                      : 'Purchase'}\n                  </Button>\n                )}\n                {subscription &&\n                  currentPackage !== name.toUpperCase() &&\n                  name !== 'FREE' &&\n                  !!name && (\n                    <Prorate\n                      period={monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY'}\n                      pack={name.toUpperCase() as 'STANDARD' | 'PRO'}\n                    />\n                  )}\n              </div>\n              <Features\n                pack={name.toUpperCase() as 'FREE' | 'STANDARD' | 'PRO'}\n              />\n            </div>\n          ))}\n      </div>\n      {!subscription?.id && <PurchaseCrypto />}\n      {!!subscription?.id && (\n        <div className=\"flex justify-center mt-[20px] gap-[10px]\">\n          <Button onClick={updatePayment}>\n            {t(\n              'update_payment_method_invoices_history',\n              'Update Payment Method / Invoices History'\n            )}\n          </Button>\n          {isGeneral && !subscription?.cancelAt && (\n            <Button\n              className=\"bg-red-500\"\n              loading={loading}\n              onClick={moveToCheckout('FREE')}\n            >\n              {t('cancel_subscription_1', 'Cancel subscription')}\n            </Button>\n          )}\n        </div>\n      )}\n      {subscription?.cancelAt && isGeneral && (\n        <div className=\"text-center\">\n          {t(\n            'your_subscription_will_be_canceled_at',\n            'Your subscription will be canceled at'\n          )}{' '}\n          {newDayjs(subscription.cancelAt).local().format('D MMM, YYYY')}\n          <br />\n          {t(\n            'you_will_never_be_charged_again',\n            'You will never be charged again'\n          )}\n        </div>\n      )}\n      <FAQComponent />\n      <div className=\"flex justify-center mt-[20px]\">\n        <LogoutComponent />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/billing/purchase.crypto.tsx",
    "content": "import { FC, useCallback, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Button } from '@gitroom/react/form/button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const PurchaseCrypto: FC = () => {\n  const fetch = useFetch();\n  const t = useT();\n\n  const [loading, setLoading] = useState(false);\n  const load = useCallback(async () => {\n    setLoading(true);\n    const data = await (await fetch('/billing/crypto')).json();\n    window.location.href = data.invoice_url;\n  }, []);\n  return (\n    <div className=\"flex-1 bg-sixth items-center border border-customColor6 rounded-[4px] p-[24px] gap-[16px] flex [@media(max-width:1024px)]:items-center\">\n      <div>\n        {t(\n          'purchase_a_life_time_pro_account_with_sol_199',\n          'Purchase a Life-time PRO account with SOL ($199)'\n        )}\n      </div>\n      <div>\n        <Button loading={loading} onClick={load}>\n          {t('purchase_now', 'Purchase now')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/developer/developer.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Button } from '@gitroom/react/form/button';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useDecisionModal, useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { MediaBox } from '@gitroom/frontend/components/media/media.component';\nimport copy from 'copy-to-clipboard';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nconst useOAuthApp = () => {\n  const fetch = useFetch();\n  const load = useCallback(async () => {\n    const res = await fetch('/user/oauth-app');\n    const text = await res.text();\n    if (!text || text === 'null' || text === 'false') {\n      return null;\n    }\n    return JSON.parse(text);\n  }, []);\n  return useSWR('oauth-app', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n  });\n};\n\nexport const DeveloperComponent: FC = () => {\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const decision = useDecisionModal();\n  const modals = useModals();\n  const t = useT();\n  const { data: app, mutate } = useOAuthApp();\n  const [plaintextSecret, setPlaintextSecret] = useState<string | null>(null);\n  const [creating, setCreating] = useState(false);\n  const [editing, setEditing] = useState(false);\n\n  const [name, setName] = useState('');\n  const [description, setDescription] = useState('');\n  const [redirectUrl, setRedirectUrl] = useState('');\n  const [pictureId, setPictureId] = useState<string | undefined>(undefined);\n  const [picturePath, setPicturePath] = useState<string | undefined>(undefined);\n\n  const startEditing = useCallback(() => {\n    if (!app) return;\n    setName(app.name || '');\n    setDescription(app.description || '');\n    setRedirectUrl(app.redirectUrl || '');\n    setPictureId(app.pictureId || undefined);\n    setPicturePath(app.picture?.path || undefined);\n    setEditing(true);\n  }, [app]);\n\n  const changeMedia = useCallback((selected: { id: string; path: string }[]) => {\n    const media = Array.isArray(selected) ? selected[0] : selected;\n    if (media) {\n      setPictureId(media.id);\n      setPicturePath(media.path);\n    }\n  }, []);\n\n  const openMedia = useCallback(() => {\n    modals.openModal({\n      title: t('media_library', 'Media Library'),\n      askClose: false,\n      closeOnEscape: true,\n      fullScreen: true,\n      size: 'calc(100% - 80px)',\n      height: 'calc(100% - 80px)',\n      children: (close: () => void) => (\n        <MediaBox\n          setMedia={changeMedia}\n          closeModal={close}\n        />\n      ),\n    });\n  }, [modals, t, changeMedia]);\n\n  const createApp = useCallback(async () => {\n    if (!name || !redirectUrl) {\n      toaster.show('Name and Redirect URL are required', 'warning');\n      return;\n    }\n    try {\n      const result = await (\n        await fetch('/user/oauth-app', {\n          method: 'POST',\n          body: JSON.stringify({\n            name,\n            description,\n            redirectUrl,\n            pictureId,\n          }),\n        })\n      ).json();\n\n      if (result.clientSecret) {\n        setPlaintextSecret(result.clientSecret);\n        toaster.show(\n          'App created! Copy your client secret now - it will only be shown once.',\n          'success'\n        );\n      }\n      setCreating(false);\n      mutate();\n    } catch {\n      toaster.show('Failed to create app', 'warning');\n    }\n  }, [name, description, redirectUrl, pictureId]);\n\n  const updateApp = useCallback(async () => {\n    try {\n      await fetch('/user/oauth-app', {\n        method: 'PUT',\n        body: JSON.stringify({\n          name,\n          description,\n          redirectUrl,\n          pictureId,\n        }),\n      });\n      toaster.show('App updated', 'success');\n      setEditing(false);\n      mutate();\n    } catch {\n      toaster.show('Failed to update app', 'warning');\n    }\n  }, [name, description, redirectUrl, pictureId]);\n\n  const rotateSecret = useCallback(async () => {\n    const approved = await decision.open({\n      title: 'Rotate Client Secret?',\n      description:\n        'This will generate a new client secret and invalidate the current one. Any integrations using the old secret will stop working.',\n      approveLabel: 'Rotate',\n      cancelLabel: 'Cancel',\n    });\n    if (!approved) return;\n    try {\n      const result = await (\n        await fetch('/user/oauth-app/rotate-secret', { method: 'POST' })\n      ).json();\n      if (result.clientSecret) {\n        setPlaintextSecret(result.clientSecret);\n        toaster.show(\n          'Secret rotated! Copy your new client secret now.',\n          'success'\n        );\n        mutate();\n      }\n    } catch {\n      toaster.show('Failed to rotate secret', 'warning');\n    }\n  }, [decision]);\n\n  const deleteApp = useCallback(async () => {\n    const approved = await decision.open({\n      title: 'Delete OAuth App?',\n      description:\n        'This will delete the OAuth application and revoke all user authorizations. This action cannot be undone.',\n      approveLabel: 'Delete',\n      cancelLabel: 'Cancel',\n    });\n    if (!approved) return;\n    try {\n      await fetch('/user/oauth-app', { method: 'DELETE' });\n      toaster.show('OAuth app deleted', 'success');\n      setPlaintextSecret(null);\n      mutate();\n    } catch {\n      toaster.show('Failed to delete app', 'warning');\n    }\n  }, [decision]);\n\n  const copyToClipboard = useCallback(\n    (text: string, label: string) => {\n      copy(text);\n      toaster.show(`${label} copied to clipboard`, 'success');\n    },\n    []\n  );\n\n  if (app === undefined) {\n    return null;\n  }\n\n  if (!app && !creating) {\n    return (\n      <div className=\"flex flex-col gap-[20px]\">\n        <div className=\"flex flex-col\">\n          <h3 className=\"text-[20px]\">{t('developer', 'Developer')}</h3>\n          <div className=\"text-customColor18 mt-[4px]\">\n            {t(\n              'create_an_oauth_application',\n              'Create an OAuth application to allow third-party integrations with Postiz on behalf of your users.'\n            )}\n            <br />\n            <a\n              className=\"underline hover:font-bold hover:underline\"\n              href=\"https://docs.postiz.com/public-api/oauth\"\n              target=\"_blank\"\n            >\n              {t(\n                'read_the_oauth_documentation',\n                'Read the OAuth documentation.'\n              )}\n            </a>\n          </div>\n          <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n            <Button onClick={() => setCreating(true)}>\n              {t('create_oauth_app', 'Create OAuth App')}\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (creating && !app) {\n    return (\n      <div className=\"flex flex-col gap-[20px]\">\n        <div className=\"flex flex-col\">\n          <h3 className=\"text-[20px]\">\n            {t('create_oauth_app', 'Create OAuth App')}\n          </h3>\n          <div className=\"text-customColor18 mt-[4px]\">\n            {t(\n              'fill_in_the_details_for_your_oauth_application',\n              'Fill in the details for your OAuth application.'\n            )}\n          </div>\n        </div>\n        <div className=\"bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[16px]\">\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">{t('app_name', 'App Name')} *</label>\n            <input\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n              placeholder=\"My Application\"\n              maxLength={100}\n            />\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('description', 'Description')}\n            </label>\n            <textarea\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none min-h-[80px]\"\n              value={description}\n              onChange={(e) => setDescription(e.target.value)}\n              placeholder=\"Describe what your app does\"\n              maxLength={500}\n            />\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('profile_picture', 'Profile Picture')}\n            </label>\n            <div className=\"flex items-center gap-[12px]\">\n              {picturePath ? (\n                <img\n                  src={picturePath}\n                  alt=\"App picture\"\n                  className=\"w-[48px] h-[48px] rounded-full object-cover\"\n                />\n              ) : (\n                <div className=\"w-[48px] h-[48px] rounded-full bg-fifth flex items-center justify-center text-customColor18\">\n                  ?\n                </div>\n              )}\n              <Button onClick={openMedia}>\n                {t('choose_image', 'Choose Image')}\n              </Button>\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('redirect_url', 'Redirect URL')} *\n            </label>\n            <input\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none\"\n              value={redirectUrl}\n              onChange={(e) => setRedirectUrl(e.target.value)}\n              placeholder=\"https://yourapp.com/callback\"\n            />\n          </div>\n          <div className=\"flex gap-[10px]\">\n            <Button onClick={createApp}>\n              {t('create', 'Create')}\n            </Button>\n            <Button onClick={() => setCreating(false)}>\n              {t('cancel', 'Cancel')}\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[20px]\">\n      <div className=\"flex flex-col\">\n        <h3 className=\"text-[20px]\">{t('developer', 'Developer')}</h3>\n        <div className=\"text-customColor18 mt-[4px]\">\n          {t(\n            'manage_your_oauth_application',\n            'Manage your OAuth application for third-party integrations.'\n          )}\n          <br />\n          <a\n            className=\"underline hover:font-bold hover:underline\"\n            href=\"https://docs.postiz.com/public-api/oauth\"\n            target=\"_blank\"\n          >\n            {t(\n              'read_the_oauth_documentation',\n              'Read the OAuth documentation.'\n            )}\n          </a>\n        </div>\n      </div>\n\n      {editing ? (\n        <div className=\"bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[16px]\">\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">{t('app_name', 'App Name')} *</label>\n            <input\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n              placeholder=\"My Application\"\n              maxLength={100}\n            />\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('description', 'Description')}\n            </label>\n            <textarea\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none min-h-[80px]\"\n              value={description}\n              onChange={(e) => setDescription(e.target.value)}\n              placeholder=\"Describe what your app does\"\n              maxLength={500}\n            />\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('profile_picture', 'Profile Picture')}\n            </label>\n            <div className=\"flex items-center gap-[12px]\">\n              {picturePath ? (\n                <img\n                  src={picturePath}\n                  alt=\"App picture\"\n                  className=\"w-[48px] h-[48px] rounded-full object-cover\"\n                />\n              ) : (\n                <div className=\"w-[48px] h-[48px] rounded-full bg-fifth flex items-center justify-center text-customColor18\">\n                  ?\n                </div>\n              )}\n              <Button onClick={openMedia}>\n                {t('choose_image', 'Choose Image')}\n              </Button>\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px]\">\n              {t('redirect_url', 'Redirect URL')} *\n            </label>\n            <input\n              className=\"bg-input border border-fifth rounded-[4px] p-[8px] text-textColor outline-none\"\n              value={redirectUrl}\n              onChange={(e) => setRedirectUrl(e.target.value)}\n              placeholder=\"https://yourapp.com/callback\"\n            />\n          </div>\n          <div className=\"flex gap-[10px]\">\n            <Button onClick={updateApp}>\n              {t('save', 'Save')}\n            </Button>\n            <Button onClick={() => setEditing(false)}>\n              {t('cancel', 'Cancel')}\n            </Button>\n          </div>\n        </div>\n      ) : (\n        <div className=\"bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[16px]\">\n          <div className=\"flex items-center gap-[12px]\">\n            {app.picture?.path ? (\n              <img\n                src={app.picture.path}\n                alt={app.name}\n                className=\"w-[48px] h-[48px] rounded-full object-cover\"\n              />\n            ) : (\n              <div className=\"w-[48px] h-[48px] rounded-full bg-fifth flex items-center justify-center text-customColor18\">\n                {app.name?.[0]?.toUpperCase() || '?'}\n              </div>\n            )}\n            <div>\n              <div className=\"text-[16px] font-bold\">{app.name}</div>\n              {app.description && (\n                <div className=\"text-customColor18 text-[14px]\">\n                  {app.description}\n                </div>\n              )}\n            </div>\n          </div>\n\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px] text-customColor18\">\n              {t('redirect_url', 'Redirect URL')}\n            </label>\n            <div className=\"text-[14px]\">{app.redirectUrl}</div>\n          </div>\n\n          <div>\n            <Button onClick={startEditing}>\n              {t('edit_app', 'Edit App')}\n            </Button>\n          </div>\n        </div>\n      )}\n\n      <div className=\"flex flex-col gap-[12px]\">\n        <h4 className=\"text-[16px]\">{t('credentials', 'Credentials')}</h4>\n\n        <div className=\"bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[16px]\">\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px] text-customColor18\">\n              {t('client_id', 'Client ID')}\n            </label>\n            <div className=\"flex items-center gap-[12px]\">\n              <code className=\"text-[14px] break-all\">{app.clientId}</code>\n              <Button onClick={() => copyToClipboard(app.clientId, 'Client ID')}>\n                {t('copy', 'Copy')}\n              </Button>\n            </div>\n          </div>\n\n          <div className=\"flex flex-col gap-[4px]\">\n            <label className=\"text-[14px] text-customColor18\">\n              {t('client_secret', 'Client Secret')}\n            </label>\n            <div className=\"flex items-center gap-[12px]\">\n              {plaintextSecret ? (\n                <code className=\"text-[14px] break-all\">\n                  {plaintextSecret}\n                </code>\n              ) : (\n                <span className=\"text-customColor18 text-[14px]\">\n                  {t(\n                    'secret_only_shown_on_creation',\n                    'Secret is only shown on creation or rotation'\n                  )}\n                </span>\n              )}\n              {plaintextSecret && (\n                <Button\n                  onClick={() =>\n                    copyToClipboard(plaintextSecret, 'Client Secret')\n                  }\n                >\n                  {t('copy', 'Copy')}\n                </Button>\n              )}\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex gap-[10px]\">\n        <Button onClick={rotateSecret}>\n          {t('rotate_secret', 'Rotate Secret')}\n        </Button>\n        <Button onClick={deleteApp}>\n          {t('delete_app', 'Delete App')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/add.provider.component.tsx",
    "content": "'use client';\n\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport React, { FC, useCallback, useMemo } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Input } from '@gitroom/react/form/input';\nimport { FieldValues, FormProvider, useForm } from 'react-hook-form';\nimport { Button } from '@gitroom/react/form/button';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto';\nimport { useRouter } from 'next/navigation';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { object, string } from 'yup';\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport { web3List } from '@gitroom/frontend/components/launches/web3/web3.list';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport clsx from 'clsx';\nimport copy from 'copy-to-clipboard';\nimport { capitalize } from 'lodash';\nconst resolver = classValidatorResolver(ApiKeyDto);\n\nexport const useAddProvider = (update?: () => void, invite?: boolean) => {\n  const modal = useModals();\n  const fetch = useFetch();\n  return useCallback(async () => {\n    const data = await (await fetch('/integrations')).json();\n    modal.openModal({\n      title: 'Add Channel',\n      withCloseButton: true,\n      children: (\n        <AddProviderComponent invite={!!invite} update={update} {...data} />\n      ),\n    });\n  }, []);\n};\nexport const AddProviderButton: FC<{\n  update?: () => void;\n}> = (props) => {\n  const { update } = props;\n  const add = useAddProvider(update);\n  const invite = useAddProvider(update, true);\n  const t = useT();\n\n  return (\n    <div className=\"flex group-[.sidebar]:block gap-[8px]\">\n      <button\n        className=\"flex-1 group-[.sidebar]:w-[100%] group-[.sidebar]:flex-none text-btnText bg-btnSimple h-[44px] pt-[12px] pb-[14px] ps-[16px] pe-[20px] justify-center items-center flex rounded-[8px] gap-[8px]\"\n        onClick={add}\n      >\n        <div>\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M1.66675 10.0417C3.35907 10.2299 4.93698 10.9884 6.14101 12.1924C7.34504 13.3964 8.10353 14.9743 8.29175 16.6667M1.66675 13.4167C2.46749 13.58 3.20253 13.9751 3.7804 14.553C4.35827 15.1309 4.75344 15.8659 4.91675 16.6667M1.66675 16.6667H1.67508M11.6667 17.5H14.3334C15.7335 17.5 16.4336 17.5 16.9684 17.2275C17.4388 16.9878 17.8212 16.6054 18.0609 16.135C18.3334 15.6002 18.3334 14.9001 18.3334 13.5V6.5C18.3334 5.09987 18.3334 4.3998 18.0609 3.86502C17.8212 3.39462 17.4388 3.01217 16.9684 2.77248C16.4336 2.5 15.7335 2.5 14.3334 2.5H5.66675C4.26662 2.5 3.56655 2.5 3.03177 2.77248C2.56137 3.01217 2.17892 3.39462 1.93923 3.86502C1.66675 4.3998 1.66675 5.09987 1.66675 6.5V6.66667\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n        <div className=\"text-start text-[14px] group-[.sidebar]:hidden\">\n          {t('add_channel', 'Add Channel')}\n        </div>\n      </button>\n      <button\n        onClick={invite}\n        data-tooltip-id=\"tooltip\"\n        data-tooltip-content={t(\n          'invite_link',\n          'Send Invite Link to a customer to add channel'\n        )}\n        className=\"group-[.sidebar]:hidden min-h-[44px] min-w-[44px] bg-btnSimple justify-center items-center flex rounded-[8px] cursor-pointer\"\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"20\"\n          viewBox=\"0 0 16 16\"\n          fill=\"none\"\n        >\n          <g clip-path=\"url(#clip0_2452_193804)\">\n            <path\n              d=\"M6.6668 8.66599C6.9531 9.04875 7.31837 9.36545 7.73783 9.59462C8.1573 9.82379 8.62114 9.96007 9.0979 9.99422C9.57466 10.0284 10.0532 9.95957 10.501 9.79251C10.9489 9.62546 11.3555 9.36404 11.6935 9.02599L13.6935 7.02599C14.3007 6.39732 14.6366 5.55531 14.629 4.68132C14.6215 3.80733 14.2709 2.97129 13.6529 2.35326C13.0348 1.73524 12.1988 1.38467 11.3248 1.37708C10.4508 1.36948 9.60881 1.70547 8.98013 2.31266L7.83347 3.45266M9.33347 7.33266C9.04716 6.94991 8.68189 6.6332 8.26243 6.40403C7.84297 6.17486 7.37913 6.03858 6.90237 6.00444C6.4256 5.97029 5.94708 6.03908 5.49924 6.20614C5.0514 6.3732 4.64472 6.63461 4.3068 6.97266L2.3068 8.97266C1.69961 9.60133 1.36363 10.4433 1.37122 11.3173C1.37881 12.1913 1.72938 13.0274 2.3474 13.6454C2.96543 14.2634 3.80147 14.614 4.67546 14.6216C5.54945 14.6292 6.39146 14.2932 7.02013 13.686L8.16013 12.546\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            ></path>\n          </g>\n          <defs>\n            <clipPath id=\"clip0_2452_193804\">\n              <rect width=\"16\" height=\"16\" fill=\"textColor\"></rect>\n            </clipPath>\n          </defs>\n        </svg>\n      </button>\n    </div>\n  );\n};\n\nexport const UrlModal: FC<{\n  gotoUrl(url: string): void;\n}> = (props) => {\n  const { gotoUrl } = props;\n  const methods = useForm({\n    mode: 'onChange',\n  });\n\n  const t = useT();\n\n  const submit = useCallback(async (data: FieldValues) => {\n    gotoUrl(data.url);\n  }, []);\n  return (\n    <div className=\"rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative\">\n      <TopTitle title={`Instance URL`} />\n      <button\n        onClick={close}\n        className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n        type=\"button\"\n      >\n        <svg\n          viewBox=\"0 0 15 15\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n        >\n          <path\n            d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n            fill=\"currentColor\"\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n          ></path>\n        </svg>\n      </button>\n      <FormProvider {...methods}>\n        <form\n          className=\"gap-[8px] flex flex-col\"\n          onSubmit={methods.handleSubmit(submit)}\n        >\n          <div className=\"pt-[10px]\">\n            <Input label=\"URL\" name=\"url\" />\n          </div>\n          <div>\n            <Button type=\"submit\">{t('connect', 'Connect')}</Button>\n          </div>\n        </form>\n      </FormProvider>\n    </div>\n  );\n};\nexport const CustomVariables: FC<{\n  variables: Array<{\n    key: string;\n    label: string;\n    defaultValue?: string;\n    validation: string;\n    type: 'text' | 'password';\n  }>;\n  close?: () => void;\n  identifier: string;\n  gotoUrl(url: string): void;\n  onboarding?: boolean;\n}> = (props) => {\n  const { close, gotoUrl, identifier, variables, onboarding } = props;\n  const fetch = useFetch();\n  const modals = useModals();\n  const schema = useMemo(() => {\n    return object({\n      ...variables.reduce((aIcc, item) => {\n        const splitter = item.validation.split('/');\n        const regex = new RegExp(\n          splitter.slice(1, -1).join('/'),\n          splitter.pop()\n        );\n        return {\n          ...aIcc,\n          [item.key]: string()\n            .matches(regex, `${item.label} is invalid`)\n            .required(),\n        };\n      }, {}),\n    });\n  }, [variables]);\n  const methods = useForm({\n    mode: 'onChange',\n    resolver: yupResolver(schema),\n    values: variables.reduce(\n      (acc, item) => ({\n        ...acc,\n        ...(item.defaultValue\n          ? {\n              [item.key]: item.defaultValue,\n            }\n          : {}),\n      }),\n      {}\n    ),\n  });\n  const submit = useCallback(\n    async (data: FieldValues) => {\n      const { url } = await (\n        await fetch(\n          `/integrations/social/${identifier}${\n            onboarding ? '?onboarding=true' : ''\n          }`\n        )\n      ).json();\n      modals.closeAll();\n      gotoUrl(\n        `/integrations/social/${identifier}?state=${url}&code=${Buffer.from(\n          JSON.stringify(data)\n        ).toString('base64')}${onboarding ? '&onboarding=true' : ''}`\n      );\n    },\n    [variables, onboarding]\n  );\n\n  const t = useT();\n\n  return (\n    <div className=\"rounded-[4px] relative\">\n      <FormProvider {...methods}>\n        <form\n          className=\"gap-[8px] flex flex-col pt-[10px]\"\n          onSubmit={methods.handleSubmit(submit)}\n        >\n          {variables.map((variable) => (\n            <div key={variable.key}>\n              <Input\n                label={variable.label}\n                name={variable.key}\n                type={variable.type == 'text' ? 'text' : 'password'}\n              />\n            </div>\n          ))}\n          <div>\n            <Button type=\"submit\">{t('connect', 'Connect')}</Button>\n          </div>\n        </form>\n      </FormProvider>\n    </div>\n  );\n};\nconst ExtensionNotFound: FC = () => {\n  const modals = useModals();\n  const t = useT();\n  return (\n    <div className=\"flex flex-col gap-[16px] pt-[8px]\">\n      <p className=\"text-[14px] text-textColor/80\">\n        {t(\n          'extension_not_available',\n          'The Postiz browser extension is not installed. You need to install it before connecting this channel.'\n        )}\n      </p>\n      <div className=\"flex gap-[10px]\">\n        <Button\n          type=\"button\"\n          className=\"flex-1\"\n          onClick={() => {\n            window.open(\n              'https://chromewebstore.google.com/detail/postiz/cidhffagahknaeodkplfbcpfeielnkjl?hl=en',\n              '_blank'\n            );\n            modals.closeCurrent();\n          }}\n        >\n          {t('install_extension', 'Install Extension')}\n        </Button>\n        <Button\n          type=\"button\"\n          className=\"flex-1 !bg-transparent border border-tableBorder text-textColor\"\n          onClick={() => modals.closeCurrent()}\n        >\n          {t('cancel', 'Cancel')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nconst ChromeExtensionWarning: FC<{\n  onConfirm: () => void;\n  onCancel: () => void;\n}> = ({ onConfirm, onCancel }) => {\n  const modals = useModals();\n  const t = useT();\n  return (\n    <div className=\"flex flex-col gap-[16px] pt-[8px]\">\n      <p className=\"text-[14px] text-textColor/80\">\n        {t(\n          'chrome_extension_warning_intro',\n          'This channel connects via the browser extension. Please be aware of the following:'\n        )}\n      </p>\n      <ul className=\"flex flex-col gap-[8px] list-disc ps-[20px] text-[14px] text-textColor/80\">\n        <li>\n          {t(\n            'chrome_extension_warning_tos',\n            'Using a browser extension to interact with a platform may violate its terms of service and could result in your account being suspended or banned.'\n          )}\n        </li>\n        <li>\n          {t(\n            'chrome_extension_warning_unstable',\n            'This method is not as reliable as native integrations and may experience random disconnections.'\n          )}\n        </li>\n        <li>\n          {t(\n            'chrome_extension_warning_reconnect',\n            'You may need to reconnect periodically if the session expires.'\n          )}\n        </li>\n        <li>\n          We will store your cookies securely to facilitate the connection.\n        </li>\n        <li>\n          Postiz does not take responsibility for any issues arising or account termination due to the use of this method.\n        </li>\n      </ul>\n      <div className=\"flex gap-[10px] mt-[8px]\">\n        <Button\n          type=\"button\"\n          className=\"flex-1\"\n          onClick={() => {\n            modals.closeCurrent();\n            onConfirm();\n          }}\n        >\n          {t('i_understand_continue', 'I understand, continue')}\n        </Button>\n        <Button\n          type=\"button\"\n          className=\"flex-1 !bg-transparent border border-tableBorder text-textColor\"\n          onClick={() => {\n            modals.closeCurrent();\n            onCancel();\n          }}\n        >\n          {t('cancel', 'Cancel')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport const AddProviderComponent: FC<{\n  social: Array<{\n    identifier: string;\n    name: string;\n    toolTip?: string;\n    isExternal: boolean;\n    isWeb3: boolean;\n    isChromeExtension?: boolean;\n    extensionCookies?: Array<{\n      name: string;\n      domain: string;\n    }>;\n    customFields?: Array<{\n      key: string;\n      label: string;\n      validation: string;\n      type: 'text' | 'password';\n    }>;\n  }>;\n  article: Array<{\n    identifier: string;\n    name: string;\n  }>;\n  invite: boolean;\n  update?: () => void;\n  onboarding?: boolean;\n}> = (props) => {\n  const { update, social, article, onboarding } = props;\n  const { isGeneral, extensionId } = useVariables();\n  const toaster = useToaster();\n  const router = useRouter();\n  const fetch = useFetch();\n  const modal = useModals();\n  const getSocialLink = useCallback(\n    (\n        invite: boolean,\n        identifier: string,\n        isExternal: boolean,\n        isWeb3: boolean,\n        isChromeExtension?: boolean,\n        customFields?: Array<{\n          key: string;\n          label: string;\n          validation: string;\n          defaultValue?: string;\n          type: 'text' | 'password';\n        }>\n      ) =>\n      async () => {\n        const onboardingParam = onboarding ? 'onboarding=true' : '';\n        const openWeb3 = async () => {\n          const { component: Web3Providers } = web3List.find(\n            (item) => item.identifier === identifier\n          )!;\n          const { url } = await (\n            await fetch(\n              `/integrations/social/${identifier}${\n                onboarding ? '?onboarding=true' : ''\n              }`\n            )\n          ).json();\n          modal.openModal({\n            title: `Add ${capitalize(identifier)}`,\n            withCloseButton: true,\n            classNames: {\n              modal: 'bg-transparent text-textColor',\n            },\n            children: (\n              <Web3Providers\n                onComplete={(code, newState) => {\n                  window.location.href = `/integrations/social/${identifier}?code=${code}&state=${newState}${\n                    onboarding ? '&onboarding=true' : ''\n                  }`;\n                }}\n                nonce={url}\n              />\n            ),\n          });\n          return;\n        };\n        const gotoIntegration = async (externalUrl?: string) => {\n          const params = [\n            externalUrl ? `externalUrl=${externalUrl}` : '',\n            onboardingParam,\n          ]\n            .filter(Boolean)\n            .join('&');\n          const { url, err } = await (\n            await fetch(\n              `/integrations/social/${identifier}${params ? `?${params}` : ''}`\n            )\n          ).json();\n          if (err) {\n            toaster.show(\n              t(\n                'could_not_connect_to_platform',\n                'Could not connect to the platform'\n              ),\n              'warning'\n            );\n            return;\n          }\n\n          if (invite) {\n            toaster.show(\n              'Invite link copied to clipboard, link will be available for 1 hour',\n              'success'\n            );\n            modal.closeAll();\n            copy(url);\n            return;\n          }\n\n          window.location.href = url;\n        };\n        if (isWeb3) {\n          openWeb3();\n          return;\n        }\n        if (isChromeExtension) {\n          const confirmed = await new Promise<boolean>((resolve) => {\n            modal.openModal({\n              title: t('chrome_extension_notice', 'Browser Extension Notice'),\n              withCloseButton: true,\n              onClose: () => resolve(false),\n              children: (\n                <ChromeExtensionWarning\n                  onConfirm={() => {\n                    resolve(true);\n                  }}\n                  onCancel={() => {\n                    resolve(false);\n                  }}\n                />\n              ),\n            });\n          });\n          if (!confirmed) {\n            return;\n          }\n          if (!extensionId || !chrome?.runtime?.sendMessage) {\n            modal.openModal({\n              title: t('extension_not_available_title', 'Extension Not Found'),\n              withCloseButton: true,\n              children: <ExtensionNotFound />,\n            });\n            return;\n          }\n          try {\n            await new Promise<void>((resolve, reject) => {\n              chrome.runtime.sendMessage(\n                extensionId,\n                { type: 'PING' },\n                (response: any) => {\n                  if (chrome.runtime.lastError || !response?.status) {\n                    reject(new Error('Extension not reachable'));\n                  } else {\n                    resolve();\n                  }\n                }\n              );\n            });\n          } catch {\n            toaster.show(\n              t(\n                'extension_not_installed',\n                'Postiz browser extension is not installed or not reachable.'\n              ),\n              'warning'\n            );\n            return;\n          }\n          try {\n            const cookieResponse = await new Promise<any>((resolve, reject) => {\n              chrome.runtime.sendMessage(\n                extensionId,\n                { type: 'GET_COOKIES', provider: identifier },\n                (response: any) => {\n                  if (chrome.runtime.lastError) {\n                    reject(new Error(chrome.runtime.lastError.message));\n                  } else {\n                    resolve(response);\n                  }\n                }\n              );\n            });\n            if (!cookieResponse.success) {\n              toaster.show(\n                cookieResponse.error ||\n                  t(\n                    'extension_cookies_missing',\n                    'Could not get cookies. Please log in to the platform first.'\n                  ),\n                'warning'\n              );\n              return;\n            }\n            const { url } = await (\n              await fetch(\n                `/integrations/social/${identifier}${\n                  onboarding ? '?onboarding=true' : ''\n                }`\n              )\n            ).json();\n            modal.closeAll();\n            window.location.href = `/integrations/social/${identifier}?state=${url}&code=${Buffer.from(\n              JSON.stringify(cookieResponse.cookies)\n            ).toString('base64')}${onboarding ? '&onboarding=true' : ''}`;\n          } catch {\n            toaster.show(\n              t(\n                'extension_communication_error',\n                'Failed to communicate with the browser extension.'\n              ),\n              'warning'\n            );\n          }\n          return;\n        }\n        if (isExternal) {\n          modal.openModal({\n            title: 'URL',\n            withCloseButton: true,\n            classNames: {\n              modal: 'bg-transparent text-textColor',\n            },\n            children: <UrlModal gotoUrl={gotoIntegration} />,\n          });\n          return;\n        }\n        if (customFields) {\n          modal.openModal({\n            title: t('add_provider_title', 'Add Provider'),\n            withCloseButton: true,\n            classNames: {\n              modal: 'bg-transparent text-textColor',\n            },\n            children: (\n              <CustomVariables\n                identifier={identifier}\n                gotoUrl={(url: string) => router.push(url)}\n                variables={customFields}\n                onboarding={onboarding}\n              />\n            ),\n          });\n          return;\n        }\n        await gotoIntegration();\n      },\n    [onboarding]\n  );\n\n  const t = useT();\n\n  return (\n    <div className=\"w-full flex flex-col gap-[20px] rounded-[4px] relative]\">\n      <div className=\"flex flex-col\">\n        <div\n          className={clsx(\n            'grid grid-cols-5 gap-[10px] justify-items-center justify-center',\n            onboarding ? 'grid-cols-9' : 'grid-cols-5'\n          )}\n        >\n          {social\n            .filter((item) => {\n              if (!props.invite) {\n                return true;\n              }\n\n              return (\n                !item.isExternal &&\n                !item.isWeb3 &&\n                !item.isChromeExtension &&\n                !item.customFields\n              );\n            })\n            .map((item) => (\n              <div\n                key={item.identifier}\n                onClick={getSocialLink(\n                  props.invite,\n                  item.identifier,\n                  item.isExternal,\n                  item.isWeb3,\n                  item.isChromeExtension,\n                  item.customFields\n                )}\n                {...(!!item.toolTip\n                  ? {\n                      'data-tooltip-id': 'tooltip',\n                      'data-tooltip-content': item.toolTip,\n                    }\n                  : {})}\n                className={\n                  'w-full h-[100px] text-[14px] p-[10px] rounded-[8px] bg-newTableHeader text-textColor relative justify-center items-center flex flex-col gap-[10px] cursor-pointer'\n                }\n              >\n                <div>\n                  {item.identifier === 'youtube' ? (\n                    <img src={`/icons/platforms/youtube.svg`} />\n                  ) : (\n                    <img\n                      className={clsx(\n                        'w-[32px] h-[32px]',\n                        item.identifier !== 'google_my_business' &&\n                          'rounded-full'\n                      )}\n                      src={`/icons/platforms/${item.identifier}.png`}\n                    />\n                  )}\n                </div>\n                <div className=\"whitespace-pre-wrap text-center\">\n                  {item.name}\n                  {!!item.toolTip && (\n                    <svg\n                      width=\"15\"\n                      height=\"15\"\n                      viewBox=\"0 0 26 26\"\n                      fill=\"none\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      className=\"absolute top-[10px] end-[10px]\"\n                    >\n                      <path\n                        d=\"M13 0C10.4288 0 7.91543 0.762437 5.77759 2.1909C3.63975 3.61935 1.97351 5.64968 0.989572 8.02512C0.0056327 10.4006 -0.251811 13.0144 0.249797 15.5362C0.751405 18.0579 1.98953 20.3743 3.80762 22.1924C5.6257 24.0105 7.94208 25.2486 10.4638 25.7502C12.9856 26.2518 15.5995 25.9944 17.9749 25.0104C20.3503 24.0265 22.3807 22.3603 23.8091 20.2224C25.2376 18.0846 26 15.5712 26 13C25.9964 9.5533 24.6256 6.24882 22.1884 3.81163C19.7512 1.37445 16.4467 0.00363977 13 0ZM13 21C12.7033 21 12.4133 20.912 12.1667 20.7472C11.92 20.5824 11.7277 20.3481 11.6142 20.074C11.5007 19.7999 11.471 19.4983 11.5288 19.2074C11.5867 18.9164 11.7296 18.6491 11.9393 18.4393C12.1491 18.2296 12.4164 18.0867 12.7074 18.0288C12.9983 17.9709 13.2999 18.0007 13.574 18.1142C13.8481 18.2277 14.0824 18.42 14.2472 18.6666C14.412 18.9133 14.5 19.2033 14.5 19.5C14.5 19.8978 14.342 20.2794 14.0607 20.5607C13.7794 20.842 13.3978 21 13 21ZM14 14.91V15C14 15.2652 13.8946 15.5196 13.7071 15.7071C13.5196 15.8946 13.2652 16 13 16C12.7348 16 12.4804 15.8946 12.2929 15.7071C12.1054 15.5196 12 15.2652 12 15V14C12 13.7348 12.1054 13.4804 12.2929 13.2929C12.4804 13.1054 12.7348 13 13 13C14.6538 13 16 11.875 16 10.5C16 9.125 14.6538 8 13 8C11.3463 8 10 9.125 10 10.5V11C10 11.2652 9.89465 11.5196 9.70711 11.7071C9.51958 11.8946 9.26522 12 9.00001 12C8.73479 12 8.48044 11.8946 8.2929 11.7071C8.10536 11.5196 8.00001 11.2652 8.00001 11V10.5C8.00001 8.01875 10.2425 6 13 6C15.7575 6 18 8.01875 18 10.5C18 12.6725 16.28 14.4913 14 14.91Z\"\n                        fill=\"currentColor\"\n                      />\n                    </svg>\n                  )}\n                </div>\n              </div>\n            ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/ai.image.tsx",
    "content": "import { Button } from '@gitroom/react/form/button';\nimport { FC, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport Loading from 'react-loading';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nconst list = [\n  'Realistic',\n  'Cartoon',\n  'Anime',\n  'Fantasy',\n  'Abstract',\n  'Pixel Art',\n  'Sketch',\n  'Watercolor',\n  'Minimalist',\n  'Cyberpunk',\n  'Monochromatic',\n  'Surreal',\n  'Pop Art',\n  'Fantasy Realism',\n];\nexport const AiImage: FC<{\n  value: string;\n  onChange: (params: { id: string; path: string }) => void;\n}> = (props) => {\n  const t = useT();\n  const { value, onChange } = props;\n  const [loading, setLoading] = useState(false);\n  const setLocked = useLaunchStore((p) => p.setLocked);\n  const fetch = useFetch();\n  const generateImage = useCallback(\n    (type: string) => async () => {\n      setLoading(true);\n      setLocked(true);\n      const image = await (\n        await fetch('/media/generate-image-with-prompt', {\n          method: 'POST',\n          body: JSON.stringify({\n            prompt: `\n<!-- description -->\n${value}\n<!-- /description -->\n\n<!-- style -->\n${type}\n<!-- /style -->\n  \n`,\n          }),\n        })\n      ).json();\n      setLoading(false);\n      setLocked(false);\n      onChange(image);\n    },\n    [value, onChange]\n  );\n  return (\n    <div className=\"relative group\">\n      <div\n        {...(value.length < 30\n          ? {\n              'data-tooltip-id': 'tooltip',\n              'data-tooltip-content':\n                'Please add at least 30 characters to generate AI image',\n            }\n          : {})}\n        className={clsx(\n          'cursor-pointer h-[30px] rounded-[6px] justify-center items-center flex bg-newColColor px-[8px]',\n          value.length < 30 && 'opacity-50'\n        )}\n      >\n        {loading && (\n          <div className=\"absolute start-[50%] -translate-x-[50%]\">\n            <Loading height={15} width={15} type=\"spin\" color=\"#fff\" />\n          </div>\n        )}\n        <div\n          className={clsx(\n            'flex gap-[5px] items-center',\n            loading && 'invisible'\n          )}\n        >\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n            >\n              <g clip-path=\"url(#clip0_2352_53053)\">\n                <path\n                  d=\"M8.33333 2.00033H5.2C4.07989 2.00033 3.51984 2.00033 3.09202 2.21831C2.71569 2.41006 2.40973 2.71602 2.21799 3.09234C2 3.52017 2 4.08022 2 5.20032V10.8003C2 11.9204 2 12.4805 2.21799 12.9083C2.40973 13.2846 2.71569 13.5906 3.09202 13.7823C3.51984 14.0003 4.07989 14.0003 5.2 14.0003H11.3333C11.9533 14.0003 12.2633 14.0003 12.5176 13.9322C13.2078 13.7472 13.7469 13.2081 13.9319 12.518C14 12.2636 14 11.9536 14 11.3337M7 5.66699C7 6.40337 6.40305 7.00033 5.66667 7.00033C4.93029 7.00033 4.33333 6.40337 4.33333 5.66699C4.33333 4.93061 4.93029 4.33366 5.66667 4.33366C6.40305 4.33366 7 4.93061 7 5.66699ZM9.99336 7.94576L4.3541 13.0724C4.03691 13.3607 3.87831 13.5049 3.86429 13.6298C3.85213 13.738 3.89364 13.8454 3.97546 13.9173C4.06985 14.0003 4.28419 14.0003 4.71286 14.0003H10.9707C11.9301 14.0003 12.4098 14.0003 12.7866 13.8391C13.2596 13.6368 13.6365 13.2599 13.8388 12.7869C14 12.4101 14 11.9304 14 10.971C14 10.6482 14 10.4867 13.9647 10.3364C13.9204 10.1475 13.8353 9.97056 13.7155 9.81792C13.6202 9.69646 13.4941 9.59562 13.242 9.39396L11.3772 7.9021C11.1249 7.70026 10.9988 7.59935 10.8599 7.56373C10.7374 7.53234 10.6086 7.53641 10.4884 7.57545C10.352 7.61975 10.2324 7.72842 9.99336 7.94576ZM13 1.01074L12.5932 1.82425C12.4556 2.09958 12.3868 2.23724 12.2948 2.35653C12.2132 2.46238 12.1183 2.55728 12.0125 2.63887C11.8932 2.73083 11.7555 2.79966 11.4802 2.93732L10.6667 3.34408L11.4802 3.75083C11.7555 3.88849 11.8932 3.95732 12.0125 4.04928C12.1183 4.13087 12.2132 4.22577 12.2948 4.33162C12.3868 4.45091 12.4556 4.58857 12.5932 4.8639L13 5.67741L13.4068 4.8639C13.5444 4.58857 13.6132 4.45091 13.7052 4.33162C13.7868 4.22577 13.8817 4.13087 13.9875 4.04928C14.1068 3.95732 14.2445 3.88849 14.5198 3.75083L15.3333 3.34408L14.5198 2.93732C14.2445 2.79966 14.1068 2.73083 13.9875 2.63887C13.8817 2.55728 13.7868 2.46238 13.7052 2.35653C13.6132 2.23724 13.5444 2.09958 13.4068 1.82425L13 1.01074Z\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.2\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </g>\n              <defs>\n                <clipPath id=\"clip0_2352_53053\">\n                  <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n                </clipPath>\n              </defs>\n            </svg>\n          </div>\n          <div className=\"text-[10px] font-[600] iconBreak:hidden block\">{t('ai', 'AI')} Image</div>\n        </div>\n      </div>\n      {value.length >= 30 && !loading && (\n        <div className=\"text-[12px] -mt-[10px] w-[200px] absolute bottom-[100%] z-[500] start-0 hidden group-hover:block\">\n          <ul className=\"cursor-pointer rounded-[4px] border border-dashed mt-[3px] p-[5px] border-newBgLineColor bg-newColColor\">\n            {list.map((p) => (\n              <li onClick={generateImage(p)} key={p} className=\"hover:bg-sixth\">\n                {p}\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/ai.video.tsx",
    "content": "import { Button } from '@gitroom/react/form/button';\nimport React, { FC, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport Loading from 'react-loading';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport useSWR from 'swr';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { VideoWrapper } from '@gitroom/frontend/components/videos/video.render.component';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { VideoContextWrapper } from '@gitroom/frontend/components/videos/video.context.wrapper';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\n\nexport const Modal: FC<{\n  close: () => void;\n  value: string;\n  type: any;\n  setLoading: (loading: boolean) => void;\n  onChange: (params: { id: string; path: string }) => void;\n}> = (props) => {\n  const { type, value, onChange, close, setLoading } = props;\n  const fetch = useFetch();\n  const setLocked = useLaunchStore((state) => state.setLocked);\n  const form = useForm();\n  const [position, setPosition] = useState('vertical');\n  const toaster = useToaster();\n\n  const loadCredits = useCallback(async () => {\n    return (\n      await fetch(`/copilot/credits?type=ai_videos`, {\n        method: 'GET',\n      })\n    ).json();\n  }, []);\n\n  const { data, mutate } = useSWR('copilot-credits', loadCredits);\n\n  const generate = useCallback(async () => {\n    await fetch(`/media/generate-video/${type.identifier}/allowed`);\n    setLoading(true);\n    close();\n    setLocked(true);\n\n    const customParams = form.getValues();\n    if (!(await form.trigger())) {\n      toaster.show('Please fill all required fields', 'warning');\n      return;\n    }\n    try {\n      const image = await fetch(`/media/generate-video`, {\n        method: 'POST',\n        body: JSON.stringify({\n          type: type.identifier,\n          output: position,\n          customParams,\n        }),\n      });\n\n      if (image.status == 200 || image.status == 201) {\n        onChange(await image.json());\n      }\n    } catch (e) {}\n\n    setLocked(false);\n    setLoading(false);\n  }, [type, value, position]);\n\n  return (\n    <VideoContextWrapper.Provider value={{ value: value }}>\n      <form\n        onSubmit={form.handleSubmit(generate)}\n        className=\"flex flex-col gap-[10px]\"\n      >\n        <FormProvider {...form}>\n          <div className=\"text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade justify-center flex bg-black/50\">\n            <div>\n              <div className=\"flex gap-[10px] flex-col w-[500px] h-auto bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative\">\n                <div className=\"flex\">\n                  <div className=\"flex-1\">\n                    <TopTitle title={'Video Type'}>\n                      <div className=\"mr-[25px]\">\n                        {data?.credits || 0} credits left\n                      </div>\n                    </TopTitle>\n                  </div>\n                  <button\n                    onClick={props.close}\n                    className=\"outline-none absolute end-[10px] top-[10px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n                    type=\"button\"\n                  >\n                    <svg\n                      viewBox=\"0 0 15 15\"\n                      fill=\"none\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      width=\"16\"\n                      height=\"16\"\n                    >\n                      <path\n                        d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                        fill=\"currentColor\"\n                        fillRule=\"evenodd\"\n                        clipRule=\"evenodd\"\n                      ></path>\n                    </svg>\n                  </button>\n                </div>\n                <div className=\"relative h-[400px]\">\n                  <div className=\"absolute left-0 top-0 w-full h-full overflow-hidden overflow-y-auto\">\n                    <div className=\"mt-[10px] flex w-full justify-center items-center gap-[10px]\">\n                      <div className=\"flex-1 flex\">\n                        <Button\n                          className=\"!flex-1\"\n                          onClick={() => setPosition('vertical')}\n                          secondary={position === 'horizontal'}\n                        >\n                          Vertical (Stories, Reels)\n                        </Button>\n                      </div>\n                      <div className=\"flex-1 flex mt-[10px]\">\n                        <Button\n                          className=\"!flex-1\"\n                          onClick={() => setPosition('horizontal')}\n                          secondary={position === 'vertical'}\n                        >\n                          Horizontal (Normal Post)\n                        </Button>\n                      </div>\n                    </div>\n                    <VideoWrapper identifier={type.identifier} />\n                  </div>\n                </div>\n                <div className=\"flex\">\n                  <Button type=\"submit\" className=\"flex-1\">\n                    Generate\n                  </Button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </FormProvider>\n      </form>\n    </VideoContextWrapper.Provider>\n  );\n};\n\nexport const AiVideo: FC<{\n  value: string;\n  onChange: (params: { id: string; path: string }) => void;\n}> = (props) => {\n  const t = useT();\n  const { value, onChange } = props;\n  const [loading, setLoading] = useState(false);\n  const [type, setType] = useState<any | null>(null);\n  const [modal, setModal] = useState(false);\n  const fetch = useFetch();\n  const { isTrailing } = useUser();\n\n  const loadVideoList = useCallback(async () => {\n    return (await (await fetch('/media/video-options')).json()).filter(\n      (f: any) => f.placement === 'text-to-image'\n    );\n  }, []);\n\n  const { isLoading, data } = useSWR('load-videos-ai', loadVideoList, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    refreshWhenHidden: false,\n    revalidateIfStale: false,\n    refreshWhenOffline: false,\n    keepPreviousData: true,\n  });\n\n  const generateVideo = useCallback(\n    (type: { identifier: string }) => async () => {\n      setType(type);\n      setModal(true);\n    },\n    [value, onChange]\n  );\n\n  if (isLoading || data?.length === 0) {\n    return null;\n  }\n\n  return (\n    <>\n      {modal && (\n        <Modal\n          onChange={onChange}\n          setLoading={setLoading}\n          close={() => {\n            setModal(false);\n            setType(null);\n          }}\n          type={type}\n          value={props.value}\n        />\n      )}\n      <div className=\"relative group\">\n        <div\n          {...(value.length < 30\n            ? {\n                'data-tooltip-id': 'tooltip',\n                'data-tooltip-content':\n                  'Please add at least 30 characters to generate AI video',\n              }\n            : {})}\n          className={clsx(\n            'cursor-pointer h-[30px] rounded-[6px] justify-center items-center flex bg-newColColor px-[8px]',\n            value.length < 30 && 'opacity-50'\n          )}\n        >\n          {loading && (\n            <div className=\"absolute start-[50%] -translate-x-[50%]\">\n              <Loading height={30} width={30} type=\"spin\" color=\"#fff\" />\n            </div>\n          )}\n          <div\n            className={clsx(\n              'flex gap-[5px] items-center',\n              loading && 'invisible'\n            )}\n          >\n            <div>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 16 16\"\n                fill=\"none\"\n              >\n                <g clip-path=\"url(#clip0_2352_53058)\">\n                  <path\n                    d=\"M8.06916 14.1663V2.04134M4.97208 14.1663V11.1351M4.97208 5.07259V2.04134M11.1662 14.1663V11.1351M9.09973 2.02152L4.8482 2.04134C3.80748 2.04134 3.28712 2.04134 2.88962 2.23957C2.53997 2.41394 2.25569 2.69218 2.07754 3.0344C1.875 3.42345 1.875 3.93275 1.875 4.95134L1.875 11.2563C1.875 12.2749 1.875 12.7842 2.07754 13.1733C2.25569 13.5155 2.53997 13.7937 2.88962 13.9681C3.28712 14.1663 3.80748 14.1663 4.8482 14.1663H11.2901C12.3308 14.1663 12.8512 14.1663 13.2487 13.9681C13.5984 13.7937 13.8826 13.5155 14.0608 13.1733C14.2633 12.7842 14.2633 12.2749 14.2633 11.2563V7.61426M1.875 5.07259L9.09973 5.06116M1.875 11.1351H14.2633M12.8141 1.20801L12.3949 2.02152C12.253 2.29684 12.1821 2.4345 12.0873 2.55379C12.0032 2.65965 11.9054 2.75455 11.7963 2.83614C11.6734 2.92809 11.5315 2.99692 11.2478 3.13458L10.4094 3.54134L11.2478 3.9481C11.5315 4.08576 11.6734 4.15459 11.7963 4.24654C11.9054 4.32814 12.0032 4.42303 12.0873 4.52889C12.1821 4.64818 12.253 4.78584 12.3949 5.06116L12.8141 5.87467L13.2333 5.06116C13.3751 4.78584 13.4461 4.64818 13.5408 4.52889C13.6249 4.42303 13.7227 4.32814 13.8318 4.24654C13.9548 4.15459 14.0966 4.08576 14.3804 3.9481L15.2188 3.54134L14.3804 3.13458C14.0966 2.99692 13.9548 2.92809 13.8318 2.83614C13.7227 2.75455 13.6249 2.65965 13.5408 2.55379C13.4461 2.4345 13.3751 2.29684 13.2333 2.02152L12.8141 1.20801Z\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"1.2\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                  />\n                </g>\n                <defs>\n                  <clipPath id=\"clip0_2352_53058\">\n                    <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n                  </clipPath>\n                </defs>\n              </svg>\n            </div>\n            <div className=\"text-[10px] font-[600] iconBreak:hidden block\">{t('ai', 'AI')} Video</div>\n          </div>\n        </div>\n        {value.length >= 30 && !loading && (\n          <div className=\"text-[12px] -mt-[10px] w-[200px] absolute bottom-[100%] z-[500] start-0 hidden group-hover:block\">\n            <ul className=\"cursor-pointer rounded-[4px] border border-dashed border-newBgLineColor bg-newColColor mt-[3px] p-[5px]\">\n              {data.map((p: any) => (\n                <li\n                  onClick={generateVideo(p)}\n                  key={p.identifier}\n                  className=\"hover:bg-sixth\"\n                >\n                  {p.title}\n                </li>\n              ))}\n            </ul>\n          </div>\n        )}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/bot.picture.tsx",
    "content": "'use client';\n\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport React, { FC, FormEventHandler, useCallback, useState } from 'react';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Input } from '@gitroom/react/form/input';\nimport { Button } from '@gitroom/react/form/button';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { showMediaBox } from '@gitroom/frontend/components/media/media.component';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const BotPicture: FC<{\n  integration: Integrations;\n  canChangeProfilePicture: boolean;\n  canChangeNickName: boolean;\n  mutate: () => void;\n}> = (props) => {\n  const t = useT();\n  const modal = useModals();\n  const toast = useToaster();\n  const [nick, setNickname] = useState(props.integration.name);\n  const [picture, setPicture] = useState(\n    props.integration.picture || '/no-picture.jpg'\n  );\n  const fetch = useFetch();\n  const submitForm: FormEventHandler<HTMLFormElement> = useCallback(\n    async (e) => {\n      e.preventDefault();\n      await fetch(`/integrations/${props.integration.id}/nickname`, {\n        method: 'POST',\n        body: JSON.stringify({\n          name: nick,\n          picture,\n        }),\n      });\n      props.mutate();\n      toast.show(t('updated', 'Updated'), 'success');\n      modal.closeAll();\n    },\n    [nick, picture, props.mutate]\n  );\n  const openMedia = useCallback(() => {\n    showMediaBox((values) => {\n      setPicture(values.path);\n    });\n  }, []);\n  return (\n    <div className=\"rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative w-full\">\n      <TopTitle title={t('change_bot_picture_title', 'Change Bot Picture')} />\n      <button\n        className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n        type=\"button\"\n        onClick={() => modal.closeAll()}\n      >\n        <svg\n          viewBox=\"0 0 15 15\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n        >\n          <path\n            d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n            fill=\"currentColor\"\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n          ></path>\n        </svg>\n      </button>\n\n      <div className=\"mt-[16px]\">\n        <form onSubmit={submitForm} className=\"gap-[50px] flex flex-col\">\n          {props.canChangeProfilePicture && (\n            <div className=\"flex items-center gap-[20px]\">\n              <img\n                src={picture}\n                alt=\"Bot Picture\"\n                className=\"w-[100px] h-[100px] rounded-full\"\n              />\n              <Button type=\"button\" onClick={openMedia}>\n                {t('upload', 'Upload')}\n              </Button>\n            </div>\n          )}\n          {props.canChangeNickName && (\n            <Input\n              value={nick}\n              onChange={(e) => setNickname(e.target.value)}\n              name=\"Nickname\"\n              label={t('label_nickname', 'Nickname')}\n              placeholder=\"\"\n              disableForm={true}\n            />\n          )}\n\n          <div className=\"mt-[50px]\">\n            <Button type=\"submit\">{t('save', 'Save')}</Button>\n          </div>\n        </form>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/calendar.context.tsx",
    "content": "'use client';\n\nimport 'reflect-metadata';\nimport {\n  createContext,\n  FC,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport dayjs from 'dayjs';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Post, Integration, Tags } from '@prisma/client';\nimport { useSearchParams } from 'next/navigation';\nimport isoWeek from 'dayjs/plugin/isoWeek';\nimport weekOfYear from 'dayjs/plugin/weekOfYear';\nimport { extend } from 'dayjs';\nimport useCookie from 'react-use-cookie';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { expandPostsList, expandPosts } from '@gitroom/helpers/utils/posts.list.minify';\nextend(isoWeek);\nextend(weekOfYear);\n\nexport const CalendarContext = createContext({\n  startDate: newDayjs().startOf('isoWeek').format('YYYY-MM-DD'),\n  endDate: newDayjs().endOf('isoWeek').format('YYYY-MM-DD'),\n  customer: null as string | null,\n  loading: true,\n  sets: [] as { name: string; id: string; content: string[] }[],\n  signature: undefined as any,\n  comments: [] as Array<{\n    date: string;\n    total: number;\n  }>,\n  integrations: [] as (Integrations & {\n    refreshNeeded?: boolean;\n  })[],\n  trendings: [] as string[],\n  posts: [] as Array<\n    Post & {\n      integration: Integration;\n      tags: {\n        tag: Tags;\n      }[];\n    }\n  >,\n  reloadCalendarView: () => {\n    /** empty **/\n  },\n  display: 'week',\n  setFilters: (filters: {\n    startDate: string;\n    endDate: string;\n    display: 'week' | 'month' | 'day' | 'list';\n    customer: string | null;\n  }) => {\n    /** empty **/\n  },\n  changeDate: (id: string, date: dayjs.Dayjs) => {\n    /** empty **/\n  },\n  // List view specific\n  listPosts: [] as Array<\n    Post & {\n      integration: Integration;\n      tags: {\n        tag: Tags;\n      }[];\n    }\n  >,\n  listPage: 0,\n  listTotalPages: 0,\n  setListPage: (page: number) => {\n    /** empty **/\n  },\n});\n\nexport interface Integrations {\n  name: string;\n  id: string;\n  disabled?: boolean;\n  inBetweenSteps: boolean;\n  editor: 'none' | 'normal' | 'markdown' | 'html';\n  display: string;\n  identifier: string;\n  type: string;\n  picture: string;\n  changeProfilePicture: boolean;\n  additionalSettings: string;\n  changeNickName: boolean;\n  time: {\n    time: number;\n  }[];\n  customer?: {\n    name?: string;\n    id?: string;\n  };\n}\n\n// Helper function to get start and end dates based on display type\nfunction getDateRange(display: string, referenceDate?: string) {\n  const date = referenceDate ? newDayjs(referenceDate) : newDayjs();\n\n  switch (display) {\n    case 'day':\n      return {\n        startDate: date.format('YYYY-MM-DD'),\n        endDate: date.format('YYYY-MM-DD'),\n      };\n    case 'week':\n      return {\n        startDate: date.startOf('isoWeek').format('YYYY-MM-DD'),\n        endDate: date.endOf('isoWeek').format('YYYY-MM-DD'),\n      };\n    case 'month':\n      return {\n        startDate: date.startOf('month').format('YYYY-MM-DD'),\n        endDate: date.endOf('month').format('YYYY-MM-DD'),\n      };\n    default:\n      return {\n        startDate: date.startOf('isoWeek').format('YYYY-MM-DD'),\n        endDate: date.endOf('isoWeek').format('YYYY-MM-DD'),\n      };\n  }\n}\n\nexport const CalendarWeekProvider: FC<{\n  children: ReactNode;\n  integrations: Integrations[];\n}> = ({ children, integrations }) => {\n  const fetch = useFetch();\n  const [internalData, setInternalData] = useState([] as any[]);\n  const [trendings] = useState<string[]>([]);\n  const searchParams = useSearchParams();\n  const [displaySaved, setDisplaySaved] = useCookie('calendar-display', 'week');\n  const display = searchParams.get('display') || displaySaved;\n\n  // List view state\n  const [listPage, setListPage] = useState(0);\n\n  // Initialize with current date range based on URL params or defaults\n  const initStartDate = searchParams.get('startDate');\n  const initEndDate = searchParams.get('endDate');\n  const initCustomer = searchParams.get('customer');\n\n  const initialRange =\n    initStartDate && initEndDate\n      ? { startDate: initStartDate, endDate: initEndDate }\n      : getDateRange(display);\n\n  const [filters, setFilters] = useState({\n    startDate: initialRange.startDate,\n    endDate: initialRange.endDate,\n    customer: initCustomer || null,\n    display,\n  });\n\n  const params = useMemo(() => {\n    return new URLSearchParams({\n      display: filters.display,\n      startDate: filters.startDate,\n      endDate: filters.endDate,\n      customer: filters?.customer?.toString() || '',\n    }).toString();\n  }, [filters]);\n\n  // Calendar view data fetcher\n  const loadData = useCallback(async () => {\n    const modifiedParams = new URLSearchParams({\n      display: filters.display,\n      customer: filters?.customer?.toString() || '',\n      startDate: newDayjs(filters.startDate).startOf('day').utc().format(),\n      endDate: newDayjs(filters.endDate).endOf('day').utc().format(),\n    }).toString();\n\n    const data = await (await fetch(`/posts?${modifiedParams}`)).json();\n    return expandPosts(data);\n  }, [filters, params]);\n\n  // List view data fetcher\n  const listParams = useMemo(() => {\n    return new URLSearchParams({\n      page: listPage.toString(),\n      limit: '100',\n      customer: filters?.customer?.toString() || '',\n    }).toString();\n  }, [listPage, filters.customer]);\n\n  const loadListData = useCallback(async () => {\n    const response = await fetch(`/posts/list?${listParams}`);\n    return expandPostsList(await response.json());\n  }, [listParams]);\n\n  // SWR for calendar view\n  const {\n    data: calendarData,\n    isLoading: calendarIsLoading,\n    mutate: mutateCalendar,\n  } = useSWR(\n    filters.display !== 'list' ? `/posts-${params}` : null,\n    loadData,\n    {\n      refreshInterval: 3600000,\n      refreshWhenOffline: false,\n      refreshWhenHidden: false,\n      revalidateOnFocus: false,\n    }\n  );\n\n  // SWR for list view\n  const {\n    data: listData,\n    isLoading: listIsLoading,\n    mutate: mutateList,\n  } = useSWR(\n    filters.display === 'list' ? `/posts-list-${listParams}` : null,\n    loadListData,\n    {\n      refreshInterval: 3600000,\n      refreshWhenOffline: false,\n      refreshWhenHidden: false,\n      revalidateOnFocus: false,\n    }\n  );\n\n  const defaultSign = useCallback(async () => {\n    return await (await fetch('/signatures/default')).json();\n  }, []);\n\n  const setList = useCallback(async () => {\n    return (await fetch('/sets')).json();\n  }, []);\n\n  const { data: sets, mutate } = useSWR('sets', setList, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n  const { data: sign } = useSWR('default-sign', defaultSign, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n\n  const setFiltersWrapper = useCallback(\n    (newFilters: {\n      startDate: string;\n      endDate: string;\n      display: 'week' | 'month' | 'day' | 'list';\n      customer: string | null;\n    }) => {\n      setDisplaySaved(newFilters.display);\n      setFilters(newFilters);\n      setInternalData([]);\n\n      // Reset page when switching to list view\n      if (newFilters.display === 'list') {\n        setListPage(0);\n      }\n\n      const path = [\n        `startDate=${newFilters.startDate}`,\n        `endDate=${newFilters.endDate}`,\n        `display=${newFilters.display}`,\n        newFilters.customer ? `customer=${newFilters.customer}` : ``,\n      ].filter((f) => f);\n      window.history.replaceState(null, '', `/launches?${path.join('&')}`);\n    },\n    []\n  );\n\n  const posts = useMemo(() => calendarData?.posts || [], [calendarData?.posts]);\n  const comments = useMemo(() => calendarData?.comments || [], [calendarData?.comments]);\n\n  // List view data\n  const listPosts = useMemo(() => listData?.posts || [], [listData?.posts]);\n  const listTotal = listData?.total || 0;\n  const listTotalPages = Math.ceil(listTotal / 100);\n\n  const changeDate = useCallback(\n    (id: string, date: dayjs.Dayjs) => {\n      setInternalData((d) =>\n        d.map((post: Post) => {\n          if (post.id === id) {\n            return {\n              ...post,\n              publishDate: date.utc().format('YYYY-MM-DDTHH:mm:ss'),\n            };\n          }\n          return post;\n        })\n      );\n    },\n    [posts, internalData]\n  );\n\n  useEffect(() => {\n    if (posts) {\n      setInternalData(posts);\n    }\n  }, [posts]);\n\n  // Combined reload function that handles both calendar and list views\n  const reloadCalendarView = useCallback(() => {\n    mutateCalendar();\n    mutateList();\n  }, [mutateCalendar, mutateList]);\n\n  // Determine loading state based on current view\n  const loading = filters.display === 'list' ? listIsLoading : calendarIsLoading;\n\n  return (\n    <CalendarContext.Provider\n      value={{\n        trendings,\n        reloadCalendarView,\n        ...filters,\n        posts: calendarIsLoading ? [] : internalData,\n        loading,\n        integrations,\n        setFilters: setFiltersWrapper,\n        changeDate,\n        comments,\n        sets: sets || [],\n        signature: sign,\n        // List view specific\n        listPosts,\n        listPage,\n        listTotalPages,\n        setListPage,\n      }}\n    >\n      {children}\n    </CalendarContext.Provider>\n  );\n};\n\nexport const useCalendar = () => useContext(CalendarContext);\n"
  },
  {
    "path": "apps/frontend/src/components/launches/calendar.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  Fragment,\n  memo,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport {\n  CalendarContext,\n  Integrations,\n  useCalendar,\n} from '@gitroom/frontend/components/launches/calendar.context';\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/en';\nimport 'dayjs/locale/he';\nimport 'dayjs/locale/ru';\nimport 'dayjs/locale/zh';\nimport 'dayjs/locale/fr';\nimport 'dayjs/locale/es';\nimport 'dayjs/locale/pt';\nimport 'dayjs/locale/de';\nimport 'dayjs/locale/it';\nimport 'dayjs/locale/ja';\nimport 'dayjs/locale/ko';\nimport 'dayjs/locale/ar';\nimport 'dayjs/locale/tr';\nimport 'dayjs/locale/vi';\nimport localizedFormat from 'dayjs/plugin/localizedFormat';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport clsx from 'clsx';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { useDrag, useDrop } from 'react-dnd';\nimport { Integration, Post, State, Tags } from '@prisma/client';\nimport { useAddProvider } from '@gitroom/frontend/components/launches/add.provider.component';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport isSameOrAfter from 'dayjs/plugin/isSameOrAfter';\nimport isSameOrBefore from 'dayjs/plugin/isSameOrBefore';\nimport { groupBy, random, sortBy } from 'lodash';\nimport Image from 'next/image';\nimport { extend } from 'dayjs';\nimport { isUSCitizen } from './helpers/isuscitizen.utils';\nimport { useInterval } from '@mantine/hooks';\nimport { StatisticsModal } from '@gitroom/frontend/components/launches/statistics';\nimport { MissingReleaseModal } from '@gitroom/frontend/components/launches/missing-release.modal';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport i18next from 'i18next';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { Button } from '@gitroom/react/form/button';\n\n// Extend dayjs with necessary plugins\nextend(isSameOrAfter);\nextend(isSameOrBefore);\nextend(localizedFormat);\n\n// Initialize language\nconst updateDayjsLocale = () => {\n  const currentLanguage = i18next.resolvedLanguage || 'en';\n  dayjs.locale(currentLanguage);\n};\n\n// Set dayjs locale whenever i18next language changes\ni18next.on('languageChanged', () => {\n  updateDayjsLocale();\n});\n\n// Initial setup\nupdateDayjsLocale();\n\nconst convertTimeFormatBasedOnLocality = (time: number) => {\n  if (isUSCitizen()) {\n    return `${time === 12 ? 12 : time % 12}:00 ${time >= 12 ? 'PM' : 'AM'}`;\n  } else {\n    return `${time}:00`;\n  }\n};\n\nexport const hours = Array.from(\n  {\n    length: 24,\n  },\n  (_, i) => i\n);\n\n// Shared hook for post actions (edit, delete, statistics)\nconst usePostActions = (onMutate?: () => void) => {\n  const t = useT();\n  const fetch = useFetch();\n  const modal = useModals();\n  const toaster = useToaster();\n  const { integrations, reloadCalendarView } = useCalendar();\n\n  const mutate = useCallback(() => {\n    reloadCalendarView();\n    onMutate?.();\n  }, [reloadCalendarView, onMutate]);\n\n  const editPost = useCallback(\n    (loadPost: any, isDuplicate?: boolean) => async () => {\n      const post = {\n        ...loadPost,\n        publishDate: loadPost.actualDate || loadPost.publishDate,\n      };\n\n      const data = await (await fetch(`/posts/group/${post.group}`)).json();\n      const date = !isDuplicate\n        ? null\n        : (await (await fetch('/posts/find-slot')).json()).date;\n      const publishDate = dayjs\n        .utc(date || data.posts[0].publishDate)\n        .local();\n      const ExistingData = !isDuplicate\n        ? ExistingDataContextProvider\n        : Fragment;\n      modal.openModal({\n        id: 'add-edit-modal',\n        closeOnClickOutside: false,\n        removeLayout: true,\n        closeOnEscape: false,\n        withCloseButton: false,\n        askClose: true,\n        fullScreen: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px] text-textColor',\n        },\n        children: (\n          <ExistingData value={data}>\n            <AddEditModal\n              {...(isDuplicate\n                ? {\n                    onlyValues: data.posts.map(\n                      ({ image, settings, content }: any) => ({\n                        image,\n                        settings,\n                        content,\n                      })\n                    ),\n                  }\n                : {})}\n              allIntegrations={integrations.map((p) => ({ ...p }))}\n              reopenModal={editPost(post)}\n              mutate={mutate}\n              integrations={\n                isDuplicate\n                  ? integrations\n                  : integrations\n                      .slice(0)\n                      .filter((f) => f.id === data.integration)\n                      .map((p) => ({\n                        ...p,\n                        picture: data.integrationPicture,\n                      }))\n              }\n              date={publishDate}\n            />\n          </ExistingData>\n        ),\n        size: '80%',\n        title: ``,\n      });\n    },\n    [integrations, fetch, modal, mutate]\n  );\n\n  const deletePost = useCallback(\n    (post: any) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_post',\n            'Are you sure you want to delete post?'\n          )\n        ))\n      ) {\n        return;\n      }\n\n      await fetch(`/posts/${post.group}`, {\n        method: 'DELETE',\n      });\n\n      toaster.show(\n        t('post_deleted_successfully', 'Post deleted successfully'),\n        'success'\n      );\n\n      mutate();\n    },\n    [toaster, t, fetch, mutate]\n  );\n\n  const openStatistics = useCallback(\n    (id: string) => () => {\n      modal.openModal({\n        title: t('statistics', 'Statistics'),\n        closeOnClickOutside: true,\n        closeOnEscape: true,\n        withCloseButton: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px]',\n        },\n        children: <StatisticsModal postId={id} />,\n        size: '80%',\n      });\n    },\n    [modal, t]\n  );\n\n  const openMissingRelease = useCallback(\n    (id: string) => () => {\n      modal.openModal({\n        title: t('connect_post', 'Connect Post'),\n        closeOnClickOutside: true,\n        closeOnEscape: true,\n        withCloseButton: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[800px]',\n        },\n        children: (\n          <MissingReleaseModal postId={id} onSuccess={mutate} />\n        ),\n        size: '60%',\n      });\n    },\n    [modal, t, mutate]\n  );\n\n  return { editPost, deletePost, openStatistics, openMissingRelease };\n};\n\nexport const DayView = () => {\n  const calendar = useCalendar();\n  const { integrations, posts, startDate } = calendar;\n\n  // Set dayjs locale based on current language\n  const currentLanguage = i18next.resolvedLanguage || 'en';\n  dayjs.locale(currentLanguage);\n\n  const currentDay = dayjs.utc(startDate);\n\n  const options = useMemo(() => {\n    const createdPosts = posts.map((post) => ({\n      integration: [integrations.find((i) => i.id === post.integration.id)!],\n      image: post?.integration?.picture || '',\n      identifier: post?.integration?.providerIdentifier || '',\n      id: post?.integration?.id || '',\n      name: post?.integration?.name || '',\n      time: dayjs\n        .utc(post.publishDate)\n        .diff(dayjs.utc(post.publishDate).startOf('day'), 'minute'),\n    }));\n    return sortBy(\n      Object.values(\n        groupBy(\n          [\n            ...createdPosts,\n            ...integrations.flatMap((p) =>\n              p.time.flatMap((t) => ({\n                integration: p,\n                identifier: p?.identifier,\n                name: p?.name,\n                id: p?.id,\n                image: p?.picture,\n                time: t?.time,\n              }))\n            ),\n          ],\n          (p: any) => p.time\n        )\n      ),\n      (p) => p[0].time\n    );\n  }, [integrations, posts]);\n\n  return (\n    <div className=\"flex flex-col gap-[10px] flex-1 relative\">\n      <div className=\"absolute start-0 top-0 w-full h-full flex flex-col overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor\">\n        {options.map((option) => (\n          <Fragment key={option[0].time}>\n            <div className=\"text-center text-[14px] min-h-[21px]\">\n              {newDayjs()\n                .utc()\n                .startOf('day')\n                .add(option[0].time, 'minute')\n                .local()\n                .format(isUSCitizen() ? 'hh:mm A' : 'LT')}\n            </div>\n            <div\n              key={option[0].time}\n              className=\"min-h-[60px] rounded-[10px] flex justify-center items-center gap-[10px] mb-[20px]\"\n            >\n              <CalendarContext.Provider\n                value={{\n                  ...calendar,\n                  integrations: option.flatMap((p) => p.integration),\n                }}\n              >\n                <CalendarColumn\n                  getDate={currentDay\n                    .startOf('day')\n                    .add(option[0].time, 'minute')\n                    .local()}\n                />\n              </CalendarContext.Provider>\n            </div>\n          </Fragment>\n        ))}\n      </div>\n    </div>\n  );\n};\nexport const WeekView = () => {\n  const { startDate, endDate } = useCalendar();\n  const t = useT();\n\n  // Use dayjs to get localized day names\n  const localizedDays = useMemo(() => {\n    const currentLanguage = i18next.resolvedLanguage || 'en';\n    dayjs.locale(currentLanguage);\n\n    const days = [];\n    const weekStart = newDayjs(startDate);\n    for (let i = 0; i < 7; i++) {\n      const day = weekStart.add(i, 'day');\n      days.push({\n        name: day.format('dddd'),\n        day: day.format('L'),\n        date: day,\n      });\n    }\n    return days;\n  }, [i18next.resolvedLanguage, startDate]);\n\n  return (\n    <div className=\"flex flex-col text-textColor flex-1\">\n      <div className=\"flex-1 relative\">\n        <div className=\"grid [grid-template-columns:136px_repeat(7,_minmax(0,_1fr))] gap-[4px] rounded-[10px] absolute h-full start-0 top-0 w-full overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor\">\n          <div className=\"z-10 bg-newTableHeader flex justify-center items-center flex-col h-[62px] rounded-[8px] sticky top-0\"></div>\n          {localizedDays.map((day, index) => (\n            <div\n              key={day.name}\n              className=\"p-2 text-center bg-newTableHeader flex justify-center items-center flex-col h-[62px] rounded-[8px] sticky top-0 z-[20]\"\n            >\n              <div className=\"text-[14px] font-[500] text-newTableText\">\n                {day.name}\n              </div>\n              <div\n                className={clsx(\n                  'text-[14px] font-[600] flex items-center justify-center gap-[6px]',\n                  day.day === newDayjs().format('L') &&\n                    'text-newTableTextFocused'\n                )}\n              >\n                {day.day === newDayjs().format('L') && (\n                  <div className=\"w-[6px] h-[6px] bg-newTableTextFocused rounded-full\" />\n                )}\n                {day.day}\n              </div>\n            </div>\n          ))}\n          {hours.map((hour) => (\n            <Fragment key={hour}>\n              <div className=\"p-2 pe-4 text-center items-center justify-center flex text-[14px] text-newTableText\">\n                {convertTimeFormatBasedOnLocality(hour)}\n              </div>\n              {localizedDays.map((day, indexDay) => (\n                <Fragment\n                  key={`${startDate}-${day.date.format('YYYY-MM-DD')}-${hour}`}\n                >\n                  <div className=\"relative\">\n                    <CalendarColumn\n                      getDate={day.date.hour(hour).startOf('hour')}\n                    />\n                  </div>\n                </Fragment>\n              ))}\n            </Fragment>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\nexport const MonthView = () => {\n  const { startDate } = useCalendar();\n  const t = useT();\n\n  // Use dayjs to get localized day names\n  const localizedDays = useMemo(() => {\n    const currentLanguage = i18next.resolvedLanguage || 'en';\n    dayjs.locale(currentLanguage);\n\n    const days = [];\n    // Starting from Monday (1) to Sunday (7)\n    for (let i = 1; i <= 7; i++) {\n      days.push(newDayjs().day(i).format('dddd'));\n    }\n    return days;\n  }, [i18next.resolvedLanguage]);\n\n  const calendarDays = useMemo(() => {\n    const monthStart = newDayjs(startDate);\n    const currentMonth = monthStart.month();\n    const currentYear = monthStart.year();\n\n    const startOfMonth = newDayjs(new Date(currentYear, currentMonth, 1));\n\n    // Calculate the day offset for Monday (isoWeekday() returns 1 for Monday)\n    const startDayOfWeek = startOfMonth.isoWeekday(); // 1 for Monday, 7 for Sunday\n    const daysBeforeMonth = startDayOfWeek - 1; // Days to show from the previous month\n\n    // Get the start date (Monday of the first week that includes this month)\n    const calendarStartDate = startOfMonth.subtract(daysBeforeMonth, 'day');\n\n    // Create an array to hold the calendar days (6 weeks * 7 days = 42 days max)\n    const calendarDays = [];\n    let currentDay = calendarStartDate;\n    for (let i = 0; i < 42; i++) {\n      let label = 'current-month';\n      if (currentDay.month() < currentMonth) label = 'previous-month';\n      if (currentDay.month() > currentMonth) label = 'next-month';\n      calendarDays.push({\n        day: currentDay,\n        label,\n      });\n\n      // Move to the next day\n      currentDay = currentDay.add(1, 'day');\n    }\n    return calendarDays;\n  }, [startDate]);\n\n  return (\n    <div className=\"flex flex-col text-textColor flex-1\">\n      <div className=\"flex-1 flex relative\">\n        <div className=\"grid grid-cols-7 grid-rows-[62px_auto] gap-[4px] rounded-[10px] absolute start-0 top-0 overflow-auto w-full h-full scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary\">\n          {localizedDays.map((day) => (\n            <div\n              key={day}\n              className=\"z-[20] p-2 bg-newTableHeader flex justify-center items-center flex-col h-[62px] rounded-[8px] sticky top-0\"\n            >\n              <div>{day}</div>\n            </div>\n          ))}\n          {calendarDays.map((date, index) => (\n            <div\n              key={index}\n              className=\"text-center items-center justify-center flex\"\n            >\n              <CalendarColumn\n                getDate={newDayjs(date.day).endOf('day')}\n                randomHour={true}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\nexport const ListView = () => {\n  const t = useT();\n  const { integrations, loading, listPosts } = useCalendar();\n\n  // Use shared post actions hook\n  const { editPost, deletePost, openStatistics, openMissingRelease } = usePostActions();\n\n  // Group posts by date\n  const groupedPosts = useMemo(() => {\n    const groups: { [key: string]: any[] } = {};\n    listPosts.forEach((post) => {\n      const dateKey = newDayjs(post.publishDate).local().format('YYYY-MM-DD');\n      if (!groups[dateKey]) {\n        groups[dateKey] = [];\n      }\n      groups[dateKey].push(post);\n    });\n    return Object.entries(groups).sort(([a], [b]) => a.localeCompare(b));\n  }, [listPosts]);\n\n  if (loading) {\n    return (\n      <div className=\"flex flex-col flex-1 items-center justify-center\">\n        <div className=\"text-textColor\">{t('loading', 'Loading...')}</div>\n      </div>\n    );\n  }\n\n  if (listPosts.length === 0) {\n    return (\n      <div className=\"flex flex-col flex-1 items-center justify-center\">\n        <div className=\"text-textColor text-[16px]\">\n          {t('no_upcoming_posts', 'No upcoming posts scheduled')}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[10px] flex-1 relative\">\n      <div className=\"absolute start-0 top-0 w-full h-full flex flex-col overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor\">\n        {groupedPosts.map(([dateKey, datePosts]) => (\n          <Fragment key={dateKey}>\n            <div className=\"text-center text-[14px] min-h-[21px] text-textColor font-[500] mt-[10px]\">\n              {newDayjs(dateKey).format(isUSCitizen() ? 'dddd, MMMM D, YYYY' : 'dddd, D MMMM YYYY')}\n            </div>\n            <div className=\"flex flex-col gap-[10px] mb-[20px] px-[10px]\">\n              {datePosts.map((post) => (\n                <CalendarItem\n                  key={post.id}\n                  display=\"day\"\n                  isBeforeNow={false}\n                  date={newDayjs(post.publishDate)}\n                  state={post.state}\n                  statistics={openStatistics(post.id)}\n                  missingRelease={openMissingRelease(post.id)}\n                  editPost={editPost(post, false)}\n                  duplicatePost={editPost(post, true)}\n                  post={post}\n                  integrations={integrations}\n                  deletePost={deletePost(post)}\n                  showTime={true}\n                />\n              ))}\n            </div>\n          </Fragment>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport const Calendar = () => {\n  const { display } = useCalendar();\n  return (\n    <>\n      {display === 'list' ? (\n        <ListView />\n      ) : display === 'day' ? (\n        <DayView />\n      ) : display === 'week' ? (\n        <WeekView />\n      ) : (\n        <MonthView />\n      )}\n    </>\n  );\n};\nexport const CalendarColumn: FC<{\n  getDate: dayjs.Dayjs;\n  randomHour?: boolean;\n}> = memo((props) => {\n  const t = useT();\n\n  const { getDate, randomHour } = props;\n  const [num, setNum] = useState(0);\n  const user = useUser();\n  const {\n    integrations,\n    posts,\n    changeDate,\n    display,\n    reloadCalendarView,\n    sets,\n    signature,\n    loading,\n  } = useCalendar();\n  const modal = useModals();\n  const fetch = useFetch();\n\n  // Use shared post actions hook\n  const { editPost, deletePost, openStatistics, openMissingRelease } = usePostActions();\n  const postList = useMemo(() => {\n    return posts.filter((post) => {\n      const pList = dayjs.utc(post.publishDate).local();\n      const check =\n        display === 'day'\n          ? pList.format('YYYY-MM-DD HH:mm') ===\n            getDate.format('YYYY-MM-DD HH:mm')\n          : display === 'week'\n          ? pList.isSameOrAfter(getDate.startOf('hour')) &&\n            pList.isBefore(getDate.endOf('hour'))\n          : pList.format('DD/MM/YYYY') === getDate.format('DD/MM/YYYY');\n      return check;\n    });\n  }, [posts, display, getDate]);\n  const [showAll, setShowAll] = useState(false);\n  const showAllFunc = useCallback(() => {\n    setShowAll(true);\n  }, []);\n  const showLessFunc = useCallback(() => {\n    setShowAll(false);\n  }, []);\n  const list = useMemo(() => {\n    if (showAll) {\n      return postList;\n    }\n    return postList.slice(0, 3);\n  }, [postList, showAll]);\n\n  const isBeforeNow = useMemo(() => {\n    const originalUtc = getDate.startOf('hour');\n    return originalUtc\n      .startOf('hour')\n      .isBefore(newDayjs().startOf('hour').utc());\n  }, [getDate, num]);\n\n  const { start, stop } = useInterval(\n    useCallback(() => {\n      if (isBeforeNow) {\n        return;\n      }\n      setNum(num + 1);\n    }, [isBeforeNow]),\n    random(120000, 150000)\n  );\n\n  useEffect(() => {\n    start();\n    return () => {\n      stop();\n    };\n  }, []);\n  const [{ canDrop }, drop] = useDrop(() => ({\n    accept: 'post',\n    drop: async (item: any) => {\n      if (isBeforeNow) return;\n\n      // Find the post to check its state\n      const post = posts.find((p) => p.id === item.id);\n      let action: 'schedule' | 'update' = 'schedule';\n\n      // Check if post is already published or queued in the past\n      if (\n        post &&\n        (post.state === 'PUBLISHED' ||\n          (post.state === 'QUEUE' && dayjs().isAfter(dayjs.utc(post.publishDate))))\n      ) {\n        const whatToDo = await new Promise<'schedule' | 'update' | 'cancel'>(\n          (resolve) => {\n            modal.openModal({\n              title: t('what_do_you_want_to_do', 'What do you want to do?'),\n              children: (\n                <div className=\"flex flex-col\">\n                  <div className=\"text-[20px] mb-[20px]\">\n                    {t(\n                      'post_already_published_drag',\n                      'This post was already published, what do you want to do?'\n                    )}\n                  </div>\n                  <div className=\"flex w-full gap-[10px]\">\n                    <div className=\"flex-1 flex\">\n                      <Button\n                        type=\"button\"\n                        className=\"flex-1\"\n                        onClick={() => {\n                          modal.closeAll();\n                          resolve('update');\n                        }}\n                      >\n                        {t('just_update_post_details', 'Just update the post details')}\n                      </Button>\n                    </div>\n                    <div className=\"flex-1 flex\">\n                      <Button\n                        type=\"button\"\n                        className=\"flex-1\"\n                        onClick={() => {\n                          modal.closeAll();\n                          resolve('schedule');\n                        }}\n                      >\n                        {t('reschedule_post', 'Reschedule the post')}\n                      </Button>\n                    </div>\n                  </div>\n                </div>\n              ),\n              onClose: () => resolve('cancel'),\n            });\n          }\n        );\n\n        if (whatToDo === 'cancel') {\n          return;\n        }\n        action = whatToDo;\n      }\n\n      if (!item.interval) {\n        changeDate(item.id, getDate);\n      }\n      const { status } = await fetch(`/posts/${item.id}/date`, {\n        method: 'PUT',\n        body: JSON.stringify({\n          date: getDate.utc().format('YYYY-MM-DDTHH:mm:ss'),\n          action,\n        }),\n      });\n      if (status !== 500) {\n        if (item.interval || action === 'schedule') {\n          reloadCalendarView();\n          return;\n        }\n        return;\n      }\n    },\n    collect: (monitor) => ({\n      canDrop: isBeforeNow ? false : !!monitor.canDrop() && !!monitor.isOver(),\n    }),\n  }), [posts]);\n\n  const addModal = useCallback(async () => {\n    const set: any = !sets.length\n      ? undefined\n      : await new Promise((resolve) => {\n          modal.openModal({\n            title: t('select_set', 'Select a Set'),\n            closeOnClickOutside: true,\n            askClose: false,\n            closeOnEscape: true,\n            withCloseButton: true,\n            onClose: () => resolve('exit'),\n            children: (\n              <SetSelectionModal\n                sets={sets}\n                onSelect={(selectedSet) => {\n                  resolve(selectedSet);\n                  modal.closeAll();\n                }}\n                onContinueWithoutSet={() => {\n                  resolve(undefined);\n                  modal.closeAll();\n                }}\n              />\n            ),\n          });\n        });\n\n    if (set === 'exit') return;\n\n    modal.openModal({\n      id: 'add-edit-modal',\n      closeOnClickOutside: false,\n      removeLayout: true,\n      closeOnEscape: false,\n      withCloseButton: false,\n      askClose: true,\n      fullScreen: true,\n      classNames: {\n        modal: 'w-[100%] max-w-[1400px] text-textColor',\n      },\n      children: (\n        <AddEditModal\n          allIntegrations={integrations.map((p) => ({\n            ...p,\n          }))}\n          integrations={integrations.slice(0).map((p) => ({\n            ...p,\n          }))}\n          mutate={reloadCalendarView}\n          {...(signature?.id && !set\n            ? {\n                onlyValues: [\n                  {\n                    content: '\\n' + signature.content,\n                  },\n                ],\n              }\n            : {})}\n          date={\n            randomHour\n              ? getDate.hour(Math.floor(Math.random() * 24))\n              : getDate.format('YYYY-MM-DDTHH:mm:ss') ===\n                newDayjs().startOf('hour').format('YYYY-MM-DDTHH:mm:ss')\n              ? newDayjs().add(10, 'minute')\n              : getDate\n          }\n          {...(set?.content ? { set: JSON.parse(set.content) } : {})}\n          reopenModal={() => ({})}\n        />\n      ),\n      size: '80%',\n    });\n  }, [integrations, getDate, sets, signature]);\n\n  const addProvider = useAddProvider();\n  return (\n    <div\n      className={clsx(\n        'flex flex-col w-full min-h-full relative',\n        isBeforeNow && 'repeated-strip',\n        loading && 'animate-pulse',\n        isBeforeNow\n          ? 'cursor-not-allowed'\n          : 'border border-newTextColor/5 rounded-[8px]'\n      )}\n      ref={drop as any}\n    >\n      {display === 'month' && (\n        <div className={clsx('pt-[6px] text-[14px]')}>{getDate.date()}</div>\n      )}\n      <div\n        className={clsx(\n          'relative flex flex-col flex-1 text-white rounded-[8px] min-h-[70px]',\n          canDrop && 'border border-[#612BD3]'\n        )}\n      >\n        <div\n          className={clsx(\n            'flex-col text-[12px] pointer w-full flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',\n            isBeforeNow ? 'flex-1' : 'cursor-pointer',\n            isBeforeNow && postList.length === 0 && 'col-calendar'\n          )}\n        >\n          {loading && (\n            <div className=\"h-full w-full p-[5px] animate-pulse absolute left-0 top-0 z-[50]\">\n              <div className=\"h-full w-full bg-newSettings rounded-[10px]\" />\n            </div>\n          )}\n          {list.map((post) => (\n            <div\n              key={post.id}\n              className={clsx(\n                'text-textColor p-[2.5px] relative flex flex-col justify-center items-center'\n              )}\n            >\n              <div className=\"relative w-full flex flex-col items-center p-[2.5px]\">\n                <CalendarItem\n                  display={display as 'day' | 'week' | 'month'}\n                  isBeforeNow={isBeforeNow}\n                  date={getDate}\n                  state={post.state}\n                  statistics={openStatistics(post.id)}\n                  missingRelease={openMissingRelease(post.id)}\n                  editPost={editPost(post, false)}\n                  duplicatePost={editPost(post, true)}\n                  post={post}\n                  integrations={integrations}\n                  deletePost={deletePost(post)}\n                />\n              </div>\n            </div>\n          ))}\n          {!showAll && postList.length > 3 && (\n            <div\n              className=\"text-center hover:underline py-[5px] text-textColor\"\n              onClick={showAllFunc}\n            >\n              {t('show_more', '+ Show more')} ({postList.length - 3})\n            </div>\n          )}\n          {showAll && postList.length > 3 && (\n            <div\n              className=\"text-center hover:underline py-[5px]\"\n              onClick={showLessFunc}\n            >\n              {t('show_less', '- Show less')}\n            </div>\n          )}\n        </div>\n        {!isBeforeNow && (\n          <div\n            className=\"pb-[2.5px] px-[5px] flex-1 flex\"\n            onClick={integrations.length ? addModal : addProvider}\n          >\n            <div\n              className={clsx(\n                display === ('month' as any)\n                  ? 'flex-1 min-h-[40px] w-full'\n                  : !postList.length\n                  ? 'min-h-full w-full p-[5px]'\n                  : 'min-h-[40px] w-full',\n                'flex items-center justify-center cursor-pointer pb-[2.5px]'\n              )}\n            >\n              {display !== 'day' && (\n                <div\n                  className={clsx(\n                    'group hover:before:h-[30px] w-full h-full rounded-[10px] flex justify-center items-center text-white'\n                  )}\n                >\n                  <div\n                    className={`group-hover:before:content-[\"+\"] pb-[5px] flex justify-center items-center rounded-[8px] transition-all group-hover:bg-btnPrimary w-full h-full max-w-[40px] max-h-[40px]`}\n                  />\n                </div>\n              )}\n              {display === 'day' && (\n                <div\n                  className={`w-full h-full rounded-[10px] py-[10px] flex-wrap hover:border hover:border-seventh flex justify-center items-center gap-[20px] opacity-30 grayscale hover:grayscale-0 hover:opacity-100`}\n                >\n                  {integrations.map((selectedIntegrations) => (\n                    <div\n                      className=\"relative\"\n                      key={selectedIntegrations.identifier}\n                    >\n                      <div\n                        className={clsx(\n                          'relative w-[34px] h-[34px] rounded-[8px] flex justify-center items-center filter transition-all duration-500'\n                        )}\n                      >\n                        <Image\n                          src={\n                            selectedIntegrations.picture || '/no-picture.jpg'\n                          }\n                          className=\"rounded-[8px]\"\n                          alt={selectedIntegrations.identifier}\n                          width={32}\n                          height={32}\n                        />\n                        {selectedIntegrations.identifier === 'youtube' ? (\n                          <img\n                            src=\"/icons/platforms/youtube.svg\"\n                            className=\"absolute z-10 -bottom-[5px] -end-[5px]\"\n                            width={20}\n                          />\n                        ) : (\n                          <Image\n                            src={`/icons/platforms/${selectedIntegrations.identifier}.png`}\n                            className=\"rounded-[8px] absolute z-10 -bottom-[5px] -end-[5px] border border-fifth\"\n                            alt={selectedIntegrations.identifier}\n                            width={20}\n                            height={20}\n                          />\n                        )}\n                      </div>\n                    </div>\n                  ))}\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n});\nconst CalendarItem: FC<{\n  date: dayjs.Dayjs;\n  isBeforeNow: boolean;\n  editPost: () => void;\n  duplicatePost: () => void;\n  deletePost: () => void;\n  statistics: () => void;\n  missingRelease?: () => void;\n  integrations: Integrations[];\n  state: State;\n  display: 'day' | 'week' | 'month';\n  showTime?: boolean;\n  post: Post & {\n    integration: Integration;\n    tags: {\n      tag: Tags;\n    }[];\n  };\n}> = memo((props) => {\n  const t = useT();\n  const {\n    editPost,\n    statistics,\n    duplicatePost,\n    post,\n    date,\n    isBeforeNow,\n    state,\n    display,\n    deletePost,\n    showTime,\n    missingRelease,\n  } = props;\n  const { disableXAnalytics } = useVariables();\n  const preview = useCallback(() => {\n    window.open(`/p/` + post.id + '?share=true', '_blank');\n  }, [post]);\n  const [{ opacity }, dragRef] = useDrag(\n    () => ({\n      type: 'post',\n      item: {\n        id: post.id,\n        interval: !!post.intervalInDays,\n        date,\n      },\n      collect: (monitor) => ({\n        opacity: monitor.isDragging() ? 0 : 1,\n      }),\n    }),\n    []\n  );\n  return (\n    <div\n      // @ts-ignore\n      ref={dragRef}\n      className={clsx('w-full flex h-full flex-1 flex-col group', 'relative')}\n      style={{\n        opacity,\n      }}\n    >\n      <div\n        className={clsx(\n          'text-white text-[11px] max-h-[24px] h-[24px] min-h-[24px] w-full rounded-tr-[10px] rounded-tl-[10px] flex items-center justify-center gap-[10px] px-[5px] bg-btnPrimary'\n        )}\n        style={{\n          backgroundColor: post?.tags?.[0]?.tag?.color,\n        }}\n      >\n        <div\n          className={clsx(\n            post?.tags?.[0]?.tag?.color ? 'mix-blend-difference' : '',\n            'group-hover:hidden cursor-pointer'\n          )}\n        >\n          {post.tags.map((p) => p.tag.name).join(', ')}\n        </div>\n        <div\n          className={clsx(\n            'hidden group-hover:block hover:underline cursor-pointer',\n            post?.tags?.[0]?.tag?.color && 'mix-blend-difference'\n          )}\n          onClick={duplicatePost}\n        >\n          <Duplicate />\n        </div>\n        <div\n          className={clsx(\n            'hidden group-hover:block hover:underline cursor-pointer',\n            post?.tags?.[0]?.tag?.color && 'mix-blend-difference'\n          )}\n          onClick={preview}\n        >\n          <Preview />\n        </div>{' '}\n        {((post.integration.providerIdentifier === 'x' && disableXAnalytics) || !post.releaseId) ? (\n          <></>\n        ) : post.releaseId === 'missing' && missingRelease ? (\n          <div\n            className={clsx(\n              'hidden group-hover:block hover:underline cursor-pointer',\n              post?.tags?.[0]?.tag?.color && 'mix-blend-difference'\n            )}\n            onClick={missingRelease}\n          >\n            <Statistics />\n          </div>\n        ) : post.releaseId !== 'missing' ? (\n          <div\n            className={clsx(\n              'hidden group-hover:block hover:underline cursor-pointer',\n              post?.tags?.[0]?.tag?.color && 'mix-blend-difference'\n            )}\n            onClick={statistics}\n          >\n            <Statistics />\n          </div>\n        ) : (\n          <></>\n        )}{' '}\n        <div\n          className={clsx(\n            'hidden group-hover:block hover:underline cursor-pointer',\n            post?.tags?.[0]?.tag?.color && 'mix-blend-difference'\n          )}\n          onClick={deletePost}\n        >\n          <DeletePost />\n        </div>\n      </div>\n      <div\n        onClick={editPost}\n        className={clsx(\n          'gap-[5px] w-full flex h-full flex-1 rounded-br-[10px] rounded-bl-[10px] p-[8px] text-[14px] bg-newColColor',\n          'relative',\n          isBeforeNow && '!grayscale'\n        )}\n      >\n        <div className={clsx('relative min-w-[20px]')}>\n          <img\n            className=\"w-[20px] h-[20px] rounded-[8px]\"\n            src={post.integration.picture! || '/no-picture.jpg'}\n          />\n          <img\n            className=\"w-[12px] h-[12px] rounded-[8px] absolute z-10 top-[10px] end-0 border border-fifth\"\n            src={`/icons/platforms/${post.integration?.providerIdentifier}.png`}\n          />\n        </div>\n        <div className=\"w-full flex-1 flex flex-col min-h-[40px]\">\n          <div className=\"text-start\">\n            {state === 'DRAFT' ? t('draft', 'Draft') + ': ' : ''}\n          </div>\n            <div className=\"w-full relative\">\n              <div className=\"absolute top-0 start-0 w-full text-ellipsis break-words line-clamp-1 text-start\">\n                {stripHtmlValidation('none', post.content, false, true, false) ||\n                  t('no_content', 'no content')}\n              </div>\n            </div>\n        </div>\n        {showTime && (\n          <div className=\"text-textColor/50 text-[12px] whitespace-nowrap flex items-center\">\n            {newDayjs(post.publishDate).local().format(isUSCitizen() ? 'hh:mm A' : 'HH:mm')}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n});\nconst Duplicate = () => {\n  const t = useT();\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={t('duplicate_post', 'Duplicate Post')}\n    >\n      <path\n        d=\"M27 5H9C8.46957 5 7.96086 5.21071 7.58579 5.58579C7.21071 5.96086 7 6.46957 7 7V9H5C4.46957 9 3.96086 9.21071 3.58579 9.58579C3.21071 9.96086 3 10.4696 3 11V25C3 25.5304 3.21071 26.0391 3.58579 26.4142C3.96086 26.7893 4.46957 27 5 27H23C23.5304 27 24.0391 26.7893 24.4142 26.4142C24.7893 26.0391 25 25.5304 25 25V23H27C27.5304 23 28.0391 22.7893 28.4142 22.4142C28.7893 22.0391 29 21.5304 29 21V7C29 6.46957 28.7893 5.96086 28.4142 5.58579C28.0391 5.21071 27.5304 5 27 5ZM23 11V13H5V11H23ZM23 25H5V15H23V25ZM27 21H25V11C25 10.4696 24.7893 9.96086 24.4142 9.58579C24.0391 9.21071 23.5304 9 23 9H9V7H27V21Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\nconst Preview = () => {\n  const t = useT();\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={t('preview_post', 'Preview Post')}\n    >\n      <path\n        d=\"M30.9137 15.595C30.87 15.4963 29.8112 13.1475 27.4575 10.7937C24.3212 7.6575 20.36 6 16 6C11.64 6 7.67874 7.6575 4.54249 10.7937C2.18874 13.1475 1.12499 15.5 1.08624 15.595C1.02938 15.7229 1 15.8613 1 16.0012C1 16.1412 1.02938 16.2796 1.08624 16.4075C1.12999 16.5062 2.18874 18.8538 4.54249 21.2075C7.67874 24.3425 11.64 26 16 26C20.36 26 24.3212 24.3425 27.4575 21.2075C29.8112 18.8538 30.87 16.5062 30.9137 16.4075C30.9706 16.2796 31 16.1412 31 16.0012C31 15.8613 30.9706 15.7229 30.9137 15.595ZM16 24C12.1525 24 8.79124 22.6012 6.00874 19.8438C4.86704 18.7084 3.89572 17.4137 3.12499 16C3.89551 14.5862 4.86686 13.2915 6.00874 12.1562C8.79124 9.39875 12.1525 8 16 8C19.8475 8 23.2087 9.39875 25.9912 12.1562C27.1352 13.2912 28.1086 14.5859 28.8812 16C27.98 17.6825 24.0537 24 16 24ZM16 10C14.8133 10 13.6533 10.3519 12.6666 11.0112C11.6799 11.6705 10.9108 12.6075 10.4567 13.7039C10.0026 14.8003 9.88377 16.0067 10.1153 17.1705C10.3468 18.3344 10.9182 19.4035 11.7573 20.2426C12.5965 21.0818 13.6656 21.6532 14.8294 21.8847C15.9933 22.1162 17.1997 21.9974 18.2961 21.5433C19.3924 21.0892 20.3295 20.3201 20.9888 19.3334C21.6481 18.3467 22 17.1867 22 16C21.9983 14.4092 21.3657 12.884 20.2408 11.7592C19.1159 10.6343 17.5908 10.0017 16 10ZM16 20C15.2089 20 14.4355 19.7654 13.7777 19.3259C13.1199 18.8864 12.6072 18.2616 12.3045 17.5307C12.0017 16.7998 11.9225 15.9956 12.0768 15.2196C12.2312 14.4437 12.6122 13.731 13.1716 13.1716C13.731 12.6122 14.4437 12.2312 15.2196 12.0769C15.9956 11.9225 16.7998 12.0017 17.5307 12.3045C18.2616 12.6072 18.8863 13.1199 19.3259 13.7777C19.7654 14.4355 20 15.2089 20 16C20 17.0609 19.5786 18.0783 18.8284 18.8284C18.0783 19.5786 17.0609 20 16 20Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\nexport const Statistics = () => {\n  const t = useT();\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={t('post_statistics', 'Post Statistics')}\n    >\n      <path\n        d=\"M28 25H27V5C27 4.73478 26.8946 4.48043 26.7071 4.29289C26.5196 4.10536 26.2652 4 26 4H19C18.7348 4 18.4804 4.10536 18.2929 4.29289C18.1054 4.48043 18 4.73478 18 5V10H12C11.7348 10 11.4804 10.1054 11.2929 10.2929C11.1054 10.4804 11 10.7348 11 11V16H6C5.73478 16 5.48043 16.1054 5.29289 16.2929C5.10536 16.4804 5 16.7348 5 17V25H4C3.73478 25 3.48043 25.1054 3.29289 25.2929C3.10536 25.4804 3 25.7348 3 26C3 26.2652 3.10536 26.5196 3.29289 26.7071C3.48043 26.8946 3.73478 27 4 27H28C28.2652 27 28.5196 26.8946 28.7071 26.7071C28.8946 26.5196 29 26.2652 29 26C29 25.7348 28.8946 25.4804 28.7071 25.2929C28.5196 25.1054 28.2652 25 28 25ZM20 6H25V25H20V6ZM13 12H18V25H13V12ZM7 18H11V25H7V18Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\n\nexport const DeletePost = () => {\n  const t = useT();\n  return (\n    <svg\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={t('delete_post', 'Delete Post')}\n    >\n      <path\n        d=\"M15 10V18H9V10H15ZM14 4H9.9L8.9 5H6V7H18V5H15L14 4ZM17 8H7V18C7 19.1 7.9 20 9 20H15C16.1 20 17 19.1 17 18V8Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\n\nexport const SetSelectionModal: FC<{\n  sets: any[];\n  onSelect: (set: any) => void;\n  onContinueWithoutSet: () => void;\n}> = ({ sets, onSelect, onContinueWithoutSet }) => {\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <div className=\"text-lg font-medium\">\n        {t('choose_set_or_continue', 'Choose a set or continue without one')}\n      </div>\n\n      <div className=\"flex flex-col gap-2 max-h-60 overflow-y-auto\">\n        {sets.map((set) => (\n          <div\n            key={set.id}\n            onClick={() => onSelect(set)}\n            className=\"p-3 border border-tableBorder rounded-lg cursor-pointer hover:transition-colors\"\n          >\n            <div className=\"font-medium\">{set.name}</div>\n            {set.description && (\n              <div className=\"text-sm text-gray-400 mt-1\">\n                {set.description}\n              </div>\n            )}\n          </div>\n        ))}\n      </div>\n\n      <div className=\"flex gap-2 pt-2 border-t border-tableBorder\">\n        <button\n          onClick={onContinueWithoutSet}\n          className=\"flex-1 px-4 py-2 text-textColor rounded-lg hover:transition-colors\"\n        >\n          {t('continue_without_set', 'Continue without set')}\n        </button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/comments/comment.component.tsx",
    "content": "import { FC, useCallback, useEffect, useState } from 'react';\nimport dayjs from 'dayjs';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { Button } from '@gitroom/react/form/button';\nimport clsx from 'clsx';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { Input } from '@gitroom/react/form/input';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nexport const CommentBox: FC<{\n  value?: string;\n  type: 'textarea' | 'input';\n  onChange: (comment: string) => void;\n}> = (props) => {\n  const { value, onChange, type } = props;\n  const Component = type === 'textarea' ? Textarea : Input;\n  const [newComment, setNewComment] = useState(value || '');\n  const newCommentFunc = useCallback(\n    (event: {\n      target: {\n        value: string;\n      };\n    }) => {\n      setNewComment(event.target.value);\n    },\n    [newComment]\n  );\n  const changeIt = useCallback(() => {\n    onChange(newComment);\n    setNewComment('');\n  }, [newComment]);\n  return (\n    <div\n      className={clsx(\n        'flex',\n        type === 'textarea' ? 'flex-col' : 'flex-row flex items-end gap-[10px]'\n      )}\n    >\n      <div className={clsx(type === 'input' && 'flex-1')}>\n        <Component\n          label={type === 'textarea' ? 'Add comment' : ''}\n          placeholder={type === 'input' ? 'Add comment' : ''}\n          name=\"comment\"\n          disableForm={true}\n          value={newComment}\n          onChange={newCommentFunc}\n        />\n      </div>\n      <Button\n        disabled={newComment.length < 2}\n        onClick={changeIt}\n        className={clsx(type === 'input' && 'mb-[27px]')}\n      >\n        {value ? 'Update' : 'Add comment'}\n      </Button>\n    </div>\n  );\n};\ninterface Comments {\n  id: string;\n  content: string;\n  user: {\n    email: string;\n    id: string;\n  };\n  childrenComment: Comments[];\n}\nexport const EditableCommentComponent: FC<{\n  comment: Comments;\n  onEdit: (content: string) => void;\n  onDelete: () => void;\n}> = (props) => {\n  const { comment, onEdit, onDelete } = props;\n  const [commentContent, setCommentContent] = useState(comment.content);\n  const [editMode, setEditMode] = useState(false);\n  const user = useUser();\n  const updateComment = useCallback((commentValue: string) => {\n    if (commentValue !== comment.content) {\n      setCommentContent(commentValue);\n      onEdit(commentValue);\n    }\n    setEditMode(false);\n  }, []);\n  const deleteCommentFunction = useCallback(async () => {\n    if (\n      await deleteDialog(\n        'Are you sure you want to delete this comment?',\n        'Yes, Delete'\n      )\n    ) {\n      onDelete();\n    }\n  }, []);\n  if (editMode) {\n    return (\n      <CommentBox\n        type=\"input\"\n        value={commentContent}\n        onChange={updateComment}\n      />\n    );\n  }\n  return (\n    <div className=\"flex gap-[5px]\">\n      <pre className=\"text-wrap\">{commentContent}</pre>\n      {user?.id === comment.user.id && (\n        <>\n          <svg\n            onClick={() => setEditMode(!editMode)}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 32 32\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M28.415 9.17119L22.8288 3.58619C22.643 3.40043 22.4225 3.25307 22.1799 3.15253C21.9372 3.05199 21.6771 3.00024 21.4144 3.00024C21.1517 3.00024 20.8916 3.05199 20.6489 3.15253C20.4062 3.25307 20.1857 3.40043 20 3.58619L4.58626 18.9999C4.39973 19.185 4.25185 19.4053 4.15121 19.648C4.05057 19.8907 3.99917 20.151 4.00001 20.4137V25.9999C4.00001 26.5304 4.21072 27.0391 4.5858 27.4142C4.96087 27.7892 5.46958 27.9999 6.00001 27.9999H27C27.2652 27.9999 27.5196 27.8946 27.7071 27.7071C27.8947 27.5195 28 27.2652 28 26.9999C28 26.7347 27.8947 26.4804 27.7071 26.2928C27.5196 26.1053 27.2652 25.9999 27 25.9999H14.415L28.415 11.9999C28.6008 11.8142 28.7481 11.5937 28.8487 11.351C28.9492 11.1084 29.001 10.8482 29.001 10.5856C29.001 10.3229 28.9492 10.0628 28.8487 9.82009C28.7481 9.57741 28.6008 9.35692 28.415 9.17119ZM11.5863 25.9999H6.00001V20.4137L17 9.41369L22.5863 14.9999L11.5863 25.9999ZM24 13.5862L18.415 7.99994L21.415 4.99994L27 10.5862L24 13.5862Z\"\n              fill=\"#fff\"\n            />\n          </svg>\n\n          <svg\n            onClick={deleteCommentFunction}\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 32 32\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M27 6H22V5C22 4.20435 21.6839 3.44129 21.1213 2.87868C20.5587 2.31607 19.7956 2 19 2H13C12.2044 2 11.4413 2.31607 10.8787 2.87868C10.3161 3.44129 10 4.20435 10 5V6H5C4.73478 6 4.48043 6.10536 4.29289 6.29289C4.10536 6.48043 4 6.73478 4 7C4 7.26522 4.10536 7.51957 4.29289 7.70711C4.48043 7.89464 4.73478 8 5 8H6V26C6 26.5304 6.21071 27.0391 6.58579 27.4142C6.96086 27.7893 7.46957 28 8 28H24C24.5304 28 25.0391 27.7893 25.4142 27.4142C25.7893 27.0391 26 26.5304 26 26V8H27C27.2652 8 27.5196 7.89464 27.7071 7.70711C27.8946 7.51957 28 7.26522 28 7C28 6.73478 27.8946 6.48043 27.7071 6.29289C27.5196 6.10536 27.2652 6 27 6ZM12 5C12 4.73478 12.1054 4.48043 12.2929 4.29289C12.4804 4.10536 12.7348 4 13 4H19C19.2652 4 19.5196 4.10536 19.7071 4.29289C19.8946 4.48043 20 4.73478 20 5V6H12V5ZM24 26H8V8H24V26ZM14 13V21C14 21.2652 13.8946 21.5196 13.7071 21.7071C13.5196 21.8946 13.2652 22 13 22C12.7348 22 12.4804 21.8946 12.2929 21.7071C12.1054 21.5196 12 21.2652 12 21V13C12 12.7348 12.1054 12.4804 12.2929 12.2929C12.4804 12.1054 12.7348 12 13 12C13.2652 12 13.5196 12.1054 13.7071 12.2929C13.8946 12.4804 14 12.7348 14 13ZM20 13V21C20 21.2652 19.8946 21.5196 19.7071 21.7071C19.5196 21.8946 19.2652 22 19 22C18.7348 22 18.4804 21.8946 18.2929 21.7071C18.1054 21.5196 18 21.2652 18 21V13C18 12.7348 18.1054 12.4804 18.2929 12.2929C18.4804 12.1054 18.7348 12 19 12C19.2652 12 19.5196 12.1054 19.7071 12.2929C19.8946 12.4804 20 12.7348 20 13Z\"\n              fill=\"#fff\"\n            />\n          </svg>\n        </>\n      )}\n    </div>\n  );\n};\nexport const CommentComponent: FC<{\n  date: dayjs.Dayjs;\n}> = (props) => {\n  const { date } = props;\n  const { closeAll } = useModals();\n  const [commentsList, setCommentsList] = useState<Comments[]>([]);\n  const user = useUser();\n  const fetch = useFetch();\n  const load = useCallback(async () => {\n    const data = await (\n      await fetch(`/comments/${date.utc().format('YYYY-MM-DDTHH:mm:00')}`)\n    ).json();\n    setCommentsList(data);\n  }, []);\n  useEffect(() => {\n    load();\n  }, []);\n  const editComment = useCallback(\n    (comment: Comments) => async (content: string) => {\n      fetch(`/comments/${comment.id}`, {\n        method: 'PUT',\n        body: JSON.stringify({\n          content,\n          date: date.utc().format('YYYY-MM-DDTHH:mm:00'),\n        }),\n      });\n    },\n    []\n  );\n  const addComment = useCallback(\n    async (content: string) => {\n      const { id } = await (\n        await fetch('/comments', {\n          method: 'POST',\n          body: JSON.stringify({\n            content,\n            date: date.utc().format('YYYY-MM-DDTHH:mm:00'),\n          }),\n        })\n      ).json();\n      setCommentsList((list) => [\n        {\n          id,\n          user: {\n            email: user?.email!,\n            id: user?.id!,\n          },\n          content,\n          childrenComment: [],\n        },\n        ...list,\n      ]);\n    },\n    [commentsList, setCommentsList]\n  );\n  const deleteComment = useCallback(\n    (comment: Comments) => async () => {\n      await fetch(`/comments/${comment.id}`, {\n        method: 'DELETE',\n      });\n      setCommentsList((list) => list.filter((item) => item.id !== comment.id));\n    },\n    [commentsList, setCommentsList]\n  );\n  const deleteChildrenComment = useCallback(\n    (parent: Comments, children: Comments) => async () => {\n      await fetch(`/comments/${children.id}`, {\n        method: 'DELETE',\n      });\n      setCommentsList((list) =>\n        list.map((item) => {\n          if (item.id === parent.id) {\n            return {\n              ...item,\n              childrenComment: item.childrenComment.filter(\n                (child) => child.id !== children.id\n              ),\n            };\n          }\n          return item;\n        })\n      );\n    },\n    [commentsList, setCommentsList]\n  );\n  const addChildrenComment = useCallback(\n    (comment: Comments) => async (content: string) => {\n      const { id } = await (\n        await fetch(`/comments/${comment.id}`, {\n          method: 'POST',\n          body: JSON.stringify({\n            content,\n            date: date.utc().format('YYYY-MM-DDTHH:mm:00'),\n          }),\n        })\n      ).json();\n      setCommentsList((list) =>\n        list.map((item) => {\n          if (item.id === comment.id) {\n            return {\n              ...item,\n              childrenComment: [\n                ...item.childrenComment,\n                {\n                  id,\n                  user: {\n                    email: user?.email!,\n                    id: user?.id!,\n                  },\n                  content,\n                  childrenComment: [],\n                },\n              ],\n            };\n          }\n          return item;\n        })\n      );\n    },\n    [commentsList]\n  );\n  const extractNameFromEmailAndCapitalize = useCallback((email: string) => {\n    return (\n      email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1)\n    );\n  }, []);\n  return (\n    <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0\">\n      <TopTitle title={`Comments for ${date.format('DD/MM/YYYY HH:mm')}`} />\n      <button\n        onClick={closeAll}\n        className=\"outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n        type=\"button\"\n      >\n        <svg\n          viewBox=\"0 0 15 15\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n        >\n          <path\n            d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n            fill=\"currentColor\"\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n          ></path>\n        </svg>\n      </button>\n\n      <div>\n        {commentsList.map((comment, index) => (\n          <>\n            <div\n              key={`comment_${index}_${comment.content}`}\n              className={clsx(\n                `flex relative flex-col`,\n                comment?.childrenComment?.length && 'gap-[10px]'\n              )}\n            >\n              <div className=\"flex gap-[8px]\">\n                <div className=\"w-[40px] flex flex-col items-center\">\n                  <div\n                    className={`rounded-full relative z-[2] text-blue-500 font-bold flex justify-center items-center w-[40px] h-[40px] bg-white border-tableBorder border`}\n                  >\n                    {comment.user.email[0].toUpperCase()}\n                  </div>\n                  <div className=\"flex-1 w-[2px] h-[calc(100%-10px)] bg-customColor25 absolute top-[10px] z-[1]\" />\n                </div>\n                <div className=\"flex-1 flex flex-col gap-[4px]\">\n                  <div className=\"flex\">\n                    <div className=\"h-[22px] text-[15px] font-[700]\">\n                      {extractNameFromEmailAndCapitalize(comment.user.email)}\n                    </div>\n                  </div>\n                  <EditableCommentComponent\n                    onDelete={deleteComment(comment)}\n                    onEdit={editComment(comment)}\n                    comment={comment}\n                  />\n                </div>\n              </div>\n\n              <div className=\"flex flex-col gap-[10px]\">\n                {comment?.childrenComment?.map((childComment, index2) => (\n                  <div\n                    key={`comment2_${index2}_${childComment.content}`}\n                    className={clsx(`flex gap-[8px] relative`)}\n                  >\n                    <div className=\"w-[40px] flex flex-col items-center\">\n                      <div\n                        className={`rounded-full relative z-[2] text-blue-500 font-bold flex justify-center items-center w-[40px] h-[40px] bg-white border-tableBorder border`}\n                      >\n                        {childComment.user.email[0].toUpperCase()}\n                      </div>\n                    </div>\n                    <div className=\"flex-1 flex flex-col gap-[4px]\">\n                      <div className=\"flex\">\n                        <div className=\"h-[22px] text-[15px] font-[700]\">\n                          {extractNameFromEmailAndCapitalize(\n                            childComment.user.email\n                          )}\n                        </div>\n                      </div>\n                      <EditableCommentComponent\n                        onDelete={deleteChildrenComment(comment, childComment)}\n                        onEdit={editComment(childComment)}\n                        comment={childComment}\n                      />\n                    </div>\n                  </div>\n                ))}\n              </div>\n            </div>\n            <div className=\"flex\">\n              <div className=\"relative w-[40px] flex flex-col items-center\">\n                <div className=\"h-[30px] w-[2px] bg-customColor25 absolute top-0 z-[1]\" />\n                <div className=\"h-[2px] w-[21px] bg-customColor25 absolute top-[30px] end-0 z-[1]\" />\n              </div>\n              <div className=\"flex-1\">\n                <CommentBox\n                  type=\"input\"\n                  onChange={addChildrenComment(comment)}\n                />\n              </div>\n            </div>\n          </>\n        ))}\n        <CommentBox type=\"textarea\" onChange={addComment} />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/continue.integration.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { HttpStatusCode } from 'axios';\nimport { useRouter } from 'next/navigation';\nimport { Redirect } from '@gitroom/frontend/components/layout/redirect';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport dayjs from 'dayjs';\nimport { continueProviderList } from '@gitroom/frontend/components/new-launch/providers/continue-provider/list';\nimport { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\n\ninterface TwoStepState {\n  integrationId: string;\n  onboarding: boolean;\n  pages: any[];\n  returnURL?: string;\n}\n\ninterface SuccessState {\n  message: string;\n}\n\nexport const ContinueIntegration: FC<{\n  provider: string;\n  searchParams: any;\n  logged: boolean;\n}> = (props) => {\n  const { provider, searchParams, logged } = props;\n  const { push } = useRouter();\n  const t = useT();\n  const fetch = useFetch();\n  const { extensionId, backendUrl } = useVariables();\n  const [error, setError] = useState(false);\n  const [errorMessage, setErrorMessage] = useState<string | null>(null);\n  const [twoStepState, setTwoStepState] = useState<TwoStepState | null>(null);\n  const [successState, setSuccessState] = useState<SuccessState | null>(null);\n  const [isSaving, setIsSaving] = useState(false);\n\n  // Helper to handle navigation - redirects if logged or returnURL exists, otherwise shows inline\n  const navigateOrShow = useCallback(\n    (path: string, returnURL: string | undefined, successMessage: string) => {\n      if (returnURL) {\n        // If returnURL exists, always redirect to it with the path params\n        const params = path.includes('?') ? path.split('?')[1] : '';\n        push(params ? `${returnURL}?${params}` : returnURL);\n      } else if (logged) {\n        // If logged in without returnURL, use normal navigation\n        push(path);\n      } else {\n        // If not logged in without returnURL, show success inline\n        setSuccessState({ message: successMessage });\n      }\n    },\n    [logged, push]\n  );\n  const modifiedParams = useMemo(() => {\n    if (provider === 'mewe') {\n      return {\n        state: searchParams.state || '',\n        code: searchParams.loginRequestToken || '',\n        refresh: searchParams.refresh || '',\n      };\n    }\n    if (provider === 'x') {\n      return {\n        state: searchParams.oauth_token || '',\n        code: searchParams.oauth_verifier || '',\n        refresh: searchParams.refresh || '',\n      };\n    }\n\n    if (provider === 'vk') {\n      return {\n        ...searchParams,\n        state: searchParams.state || '',\n        code: searchParams.code + '&&&&' + searchParams.device_id,\n      };\n    }\n\n    if (provider === 'mewe') {\n      const hash =\n        typeof window !== 'undefined' ? window.location.hash.substring(1) : '';\n      const hashParams = new URLSearchParams(hash);\n      return {\n        state: hashParams.get('state') || searchParams.state || '',\n        code: hashParams.get('loginRequestToken') || '',\n        refresh: searchParams.refresh || '',\n      };\n    }\n\n    return searchParams;\n  }, []);\n\n  useEffect(() => {\n    (async () => {\n      const timezone = String(dayjs.tz().utcOffset());\n\n      // Try public endpoint first (handles both public and fallback scenarios)\n      let data = await fetch(`/integrations/social-connect/${provider}`, {\n        method: 'POST',\n        body: JSON.stringify({ ...modifiedParams, timezone }),\n      });\n\n      // If public endpoint fails with specific errors, try authenticated endpoint\n      if (data.status === HttpStatusCode.BadRequest) {\n        const errorData = await data.json().catch(() => ({}));\n        // \"Invalid connection type\" means this wasn't started as a public flow\n        if (\n          errorData.message?.includes('Invalid connection type') ||\n          errorData.message?.includes('Invalid or expired state')\n        ) {\n          data = await fetch(`/integrations/social-connect/${provider}`, {\n            method: 'POST',\n            body: JSON.stringify({ ...modifiedParams, timezone }),\n          });\n        }\n      }\n\n      if (data.status === HttpStatusCode.PreconditionFailed) {\n        const { returnURL } = await data.json().catch(() => ({}));\n        navigateOrShow(\n          `/launches?precondition=true`,\n          returnURL,\n          'Precondition failed'\n        );\n        return;\n      }\n\n      if (data.status === HttpStatusCode.NotAcceptable) {\n        const { msg, returnURL } = await data.json();\n        navigateOrShow(`/launches?msg=${msg}`, returnURL, msg);\n        return;\n      }\n\n      if (\n        data.status !== HttpStatusCode.Ok &&\n        data.status !== HttpStatusCode.Created\n      ) {\n        const errorData = await data.json().catch(() => ({}));\n        setErrorMessage(\n          errorData.message || errorData.msg || 'Could not add provider'\n        );\n        setError(true);\n        return;\n      }\n\n      const {\n        inBetweenSteps,\n        id,\n        onboarding: resOnboarding,\n        pages,\n        returnURL,\n        extensionToken,\n      } = await data.json();\n      const onboarding = resOnboarding || searchParams.onboarding === 'true';\n\n      // Store refresh token in extension for background cookie refresh\n      if (\n        extensionToken &&\n        extensionId &&\n        typeof chrome !== 'undefined' &&\n        chrome?.runtime?.sendMessage\n      ) {\n        try {\n          chrome.runtime.sendMessage(\n            extensionId,\n            {\n              type: 'STORE_REFRESH_TOKEN',\n              provider,\n              integrationId: id,\n              jwt: extensionToken,\n              backendUrl,\n            },\n            () => {}\n          );\n        } catch {\n          // Silently ignore — extension may not be available\n        }\n      }\n\n      // If it's a two-step provider, show the selection UI inline\n      if (inBetweenSteps && !searchParams.refresh) {\n        setTwoStepState({\n          integrationId: id,\n          onboarding,\n          pages: pages || [],\n          returnURL,\n        });\n        return;\n      }\n\n      navigateOrShow(\n        `/launches?added=${provider}&msg=Channel Updated${\n          onboarding ? '&onboarding=true' : ''\n        }`,\n        returnURL,\n        'Channel Updated'\n      );\n    })();\n  }, []);\n\n  const onSave = useCallback(\n    async (data: any) => {\n      if (!twoStepState) return;\n\n      setIsSaving(true);\n\n      try {\n        // Use public or authenticated endpoint based on the flow\n        const endpoint = logged\n          ? `/integrations/provider/${twoStepState.integrationId}/connect`\n          : `/integrations/public/provider/${twoStepState.integrationId}/connect`;\n\n        const response = await fetch(endpoint, {\n          method: 'POST',\n          body: JSON.stringify({ ...modifiedParams, ...data }),\n        });\n\n        if (\n          response.status !== HttpStatusCode.Ok &&\n          response.status !== HttpStatusCode.Created\n        ) {\n          const errorData = await response.json().catch(() => ({}));\n          setErrorMessage(\n            errorData.message || 'Failed to save channel configuration'\n          );\n          setError(true);\n          return;\n        }\n\n        navigateOrShow(\n          `/launches?added=${provider}&msg=Channel Added${\n            twoStepState.onboarding ? '&onboarding=true' : ''\n          }`,\n          twoStepState.returnURL,\n          'Channel Added'\n        );\n      } finally {\n        setIsSaving(false);\n      }\n    },\n    [twoStepState, fetch, modifiedParams, provider, navigateOrShow]\n  );\n\n  const Provider = useMemo(() => {\n    return (\n      continueProviderList[provider as keyof typeof continueProviderList] ||\n      null\n    );\n  }, [provider]);\n\n  const providerDisplayName = useMemo(() => {\n    const names: Record<string, string> = {\n      facebook: 'Facebook',\n      instagram: 'Instagram',\n      'linkedin-page': 'LinkedIn',\n      youtube: 'YouTube',\n      gmb: 'Google Business',\n    };\n    return names[provider] || provider;\n  }, [provider]);\n\n  // Success state for non-logged users without returnURL\n  if (successState) {\n    return (\n      <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n        {/* Background gradient decoration */}\n        <div className=\"absolute inset-0 opacity-30\">\n          <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n          <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n        </div>\n\n        <div className=\"relative z-10 text-center\">\n          <div className=\"w-[80px] h-[80px] mx-auto mb-[24px] rounded-full bg-green-500/20 flex items-center justify-center\">\n            <svg\n              className=\"w-[40px] h-[40px] text-green-500\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 20 20\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n          </div>\n          <div className=\"text-[28px] font-semibold mb-[12px]\">\n            {t('channel_connected', 'Channel Connected!')}\n          </div>\n          <div className=\"text-[16px] text-gray-400 max-w-[400px]\">\n            {successState.message ||\n              t(\n                'channel_connected_description',\n                `Your ${providerDisplayName} channel has been successfully connected. You can close this window now.`\n              )}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // Show the two-step selection UI\n  if (twoStepState && Provider) {\n    return (\n      <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n        {/* Background gradient decoration */}\n        <div className=\"absolute inset-0 opacity-30\">\n          <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n          <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n        </div>\n\n        {/* Content */}\n        <div className=\"relative z-10 w-full max-w-[550px] mx-auto px-[20px]\">\n          <div className=\"bg-[#1A1919] rounded-[16px] p-[32px] flex flex-col gap-[24px]\">\n            <div className=\"flex flex-col gap-[8px] text-center\">\n              <h1 className=\"text-[24px] font-semibold\">\n                {t('configure_your_channel', 'Configure Your Channel')}\n              </h1>\n              <p className=\"text-[14px] text-gray-400\">\n                {t(\n                  'select_the_page_or_account',\n                  `Select the ${providerDisplayName} page or account you want to connect.`\n                )}\n              </p>\n            </div>\n\n            <IntegrationContext.Provider\n              value={{\n                date: newDayjs(),\n                value: [],\n                allIntegrations: [],\n                integration: {\n                  editor: 'normal',\n                  additionalSettings: '',\n                  display: '',\n                  time: [{ time: 0 }],\n                  id: twoStepState.integrationId,\n                  type: '',\n                  name: '',\n                  picture: '',\n                  inBetweenSteps: true,\n                  changeNickName: false,\n                  changeProfilePicture: false,\n                  identifier: provider,\n                },\n              }}\n            >\n              <Provider\n                onSave={onSave}\n                existingId={[]}\n                initialData={twoStepState.pages}\n                isSaving={isSaving}\n              />\n            </IntegrationContext.Provider>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (error) {\n    return (\n      <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n        {/* Background gradient decoration */}\n        <div className=\"absolute inset-0 opacity-30\">\n          <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n          <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n        </div>\n\n        <div className=\"relative z-10 text-center\">\n          <div className=\"w-[80px] h-[80px] mx-auto mb-[24px] rounded-full bg-red-500/20 flex items-center justify-center\">\n            <svg\n              className=\"w-[40px] h-[40px] text-red-500\"\n              fill=\"currentColor\"\n              viewBox=\"0 0 20 20\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n          </div>\n          <div className=\"text-[28px] font-semibold mb-[12px]\">\n            {t('could_not_add_provider', 'Could not add provider')}\n          </div>\n          <div className=\"text-[16px] text-gray-400 max-w-[400px]\">\n            {errorMessage ||\n              t(\n                'you_are_being_redirected_back',\n                'An error occurred. Please try again.'\n              )}\n          </div>\n          {logged && <Redirect url=\"/launches\" delay={3000} />}\n        </div>\n      </div>\n    );\n  }\n\n  // Loading state\n  return (\n    <div className=\"flex flex-1 items-center justify-center text-white relative overflow-hidden\">\n      {/* Background gradient decoration */}\n      <div className=\"absolute inset-0 opacity-30\">\n        <div className=\"absolute top-[20%] left-[10%] w-[300px] h-[300px] bg-[#612BD3] rounded-full blur-[120px]\" />\n        <div className=\"absolute bottom-[20%] right-[10%] w-[250px] h-[250px] bg-[#FC69FF] rounded-full blur-[120px]\" />\n      </div>\n\n      <div className=\"relative z-10 text-center\">\n        <div className=\"text-[28px] font-semibold mb-[12px]\">\n          {t('adding_channel', 'Adding Channel')}\n        </div>\n        <div className=\"text-[16px] text-gray-400\">\n          {t('please_wait', 'Please wait while we connect your account...')}\n        </div>\n        {/* Loading spinner */}\n        <div className=\"mt-[32px] flex justify-center\">\n          <div className=\"w-[48px] h-[48px] border-[3px] border-[#612BD3] border-t-transparent rounded-full animate-spin\" />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/customer.modal.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useEffect, useState } from 'react';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Integration } from '@prisma/client';\nimport { Autocomplete } from '@mantine/core';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Button } from '@gitroom/react/form/button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const CustomerModal: FC<{\n  integration: Integration & {\n    customer?: {\n      id: string;\n      name: string;\n    };\n  };\n  onClose: () => void;\n}> = (props) => {\n  const t = useT();\n\n  const fetch = useFetch();\n  const { onClose, integration } = props;\n  const [customer, setCustomer] = useState(\n    integration.customer?.name || undefined\n  );\n  const modal = useModals();\n  const loadCustomers = useCallback(async () => {\n    return (await fetch('/integrations/customers')).json();\n  }, []);\n  const removeFromCustomer = useCallback(async () => {\n    saveCustomer(true);\n  }, []);\n  const saveCustomer = useCallback(\n    async (removeCustomer?: boolean) => {\n      if (!customer) {\n        return;\n      }\n      await fetch(`/integrations/${integration.id}/customer-name`, {\n        method: 'PUT',\n        body: JSON.stringify({\n          name: removeCustomer ? '' : customer,\n        }),\n      });\n      modal.closeAll();\n      onClose();\n    },\n    [customer]\n  );\n  const { data } = useSWR('/customers', loadCustomers);\n  return (\n    <div className=\"relative w-full\">\n      <div className=\"mb-[80px]\">\n        <Autocomplete\n          value={customer}\n          onChange={setCustomer}\n          classNames={{\n            label: 'text-white',\n          }}\n          label={t('select_customer_label', 'Select Customer')}\n          placeholder={t('start_typing', 'Start typing...')}\n          data={data?.map((p: any) => p.name) || []}\n        />\n      </div>\n\n      <div className=\"my-[16px] flex gap-[10px]\">\n        <Button onClick={() => saveCustomer()}>{t('save', 'Save')}</Button>\n        {!!integration?.customer?.name && (\n          <Button className=\"bg-red-700\" onClick={removeFromCustomer}>\n            {t('remove_from_customer', 'Remove from customer')}\n          </Button>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/filters.tsx",
    "content": "'use client';\n\nimport { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';\nimport clsx from 'clsx';\nimport dayjs from 'dayjs';\nimport { useCallback } from 'react';\nimport { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport i18next from 'i18next';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\n\n// Helper function to get start and end dates based on display type\nfunction getDateRange(\n  display: 'day' | 'week' | 'month' | 'list',\n  referenceDate?: string\n) {\n  const date = referenceDate ? newDayjs(referenceDate) : newDayjs();\n\n  switch (display) {\n    case 'day':\n      return {\n        startDate: date.format('YYYY-MM-DD'),\n        endDate: date.format('YYYY-MM-DD'),\n      };\n    case 'week':\n      return {\n        startDate: date.startOf('isoWeek').format('YYYY-MM-DD'),\n        endDate: date.endOf('isoWeek').format('YYYY-MM-DD'),\n      };\n    case 'month':\n      return {\n        startDate: date.startOf('month').format('YYYY-MM-DD'),\n        endDate: date.endOf('month').format('YYYY-MM-DD'),\n      };\n    case 'list':\n      return {\n        startDate: date.format('YYYY-MM-DD'),\n        endDate: date.format('YYYY-MM-DD'),\n      };\n  }\n}\n\nexport const Filters = () => {\n  const calendar = useCalendar();\n  const t = useT();\n\n  // Set dayjs locale based on current language\n  const currentLanguage = i18next.resolvedLanguage || 'en';\n  dayjs.locale();\n\n  // Calculate display date range text\n  const getDisplayText = () => {\n    const startDate = newDayjs(calendar.startDate);\n    const endDate = newDayjs(calendar.endDate);\n\n    switch (calendar.display) {\n      case 'day':\n        return startDate.format('dddd (L)');\n      case 'week':\n        return `${startDate.format('L')} - ${endDate.format('L')}`;\n      case 'month':\n        return startDate.format('MMMM YYYY');\n      default:\n        return '';\n    }\n  };\n\n  const setToday = useCallback(() => {\n    const today = newDayjs();\n    const currentRange = getDateRange(\n      calendar.display as 'day' | 'week' | 'month'\n    );\n\n    // Check if we're already showing today's range\n    if (\n      calendar.startDate === currentRange.startDate &&\n      calendar.endDate === currentRange.endDate\n    ) {\n      return; // No need to set the same range\n    }\n\n    calendar.setFilters({\n      startDate: currentRange.startDate,\n      endDate: currentRange.endDate,\n      display: calendar.display as 'day' | 'week' | 'month',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setDay = useCallback(() => {\n    // If already in day view and showing today, don't change\n    if (calendar.display === 'day') {\n      const todayRange = getDateRange('day');\n      if (calendar.startDate === todayRange.startDate) {\n        return;\n      }\n    }\n\n    const range = getDateRange('day');\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: 'day',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setWeek = useCallback(() => {\n    // If already in week view and showing current week, don't change\n    if (calendar.display === 'week') {\n      const currentWeekRange = getDateRange('week');\n      if (calendar.startDate === currentWeekRange.startDate) {\n        return;\n      }\n    }\n\n    const range = getDateRange('week');\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: 'week',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setMonth = useCallback(() => {\n    // If already in month view and showing current month, don't change\n    if (calendar.display === 'month') {\n      const currentMonthRange = getDateRange('month');\n      if (calendar.startDate === currentMonthRange.startDate) {\n        return;\n      }\n    }\n\n    const range = getDateRange('month');\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: 'month',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setList = useCallback(() => {\n    if (calendar.display === 'list') {\n      return;\n    }\n\n    const range = getDateRange('list');\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: 'list',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setCalendarView = useCallback(() => {\n    if (calendar.display !== 'list') {\n      return;\n    }\n\n    const range = getDateRange('week');\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: 'week',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setCustomer = useCallback(\n    (customer: string) => {\n      if (calendar.customer === customer) {\n        return; // No need to set the same customer\n      }\n      calendar.setFilters({\n        startDate: calendar.startDate,\n        endDate: calendar.endDate,\n        display: calendar.display as 'day' | 'week' | 'month',\n        customer: customer,\n      });\n    },\n    [calendar]\n  );\n\n  const next = useCallback(() => {\n    const currentStart = newDayjs(calendar.startDate);\n    let nextStart: dayjs.Dayjs;\n\n    switch (calendar.display) {\n      case 'day':\n        nextStart = currentStart.add(1, 'day');\n        break;\n      case 'week':\n        nextStart = currentStart.add(1, 'week');\n        break;\n      case 'month':\n        nextStart = currentStart.add(1, 'month');\n        break;\n      default:\n        nextStart = currentStart.add(1, 'week');\n    }\n\n    const range = getDateRange(\n      calendar.display as 'day' | 'week' | 'month',\n      nextStart.format('YYYY-MM-DD')\n    );\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: calendar.display as 'day' | 'week' | 'month',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const previous = useCallback(() => {\n    const currentStart = newDayjs(calendar.startDate);\n    let prevStart: dayjs.Dayjs;\n\n    switch (calendar.display) {\n      case 'day':\n        prevStart = currentStart.subtract(1, 'day');\n        break;\n      case 'week':\n        prevStart = currentStart.subtract(1, 'week');\n        break;\n      case 'month':\n        prevStart = currentStart.subtract(1, 'month');\n        break;\n      default:\n        prevStart = currentStart.subtract(1, 'week');\n    }\n\n    const range = getDateRange(\n      calendar.display as 'day' | 'week' | 'month',\n      prevStart.format('YYYY-MM-DD')\n    );\n    calendar.setFilters({\n      startDate: range.startDate,\n      endDate: range.endDate,\n      display: calendar.display as 'day' | 'week' | 'month',\n      customer: calendar.customer,\n    });\n  }, [calendar]);\n\n  const setCurrent = useCallback(\n    (type: 'day' | 'week' | 'month') => () => {\n      if (type === 'day') {\n        setDay();\n      } else if (type === 'week') {\n        setWeek();\n      } else if (type === 'month') {\n        setMonth();\n      }\n    },\n    [setDay, setWeek, setMonth]\n  );\n\n  const isListView = calendar.display === 'list';\n\n  const previousPage = useCallback(() => {\n    if (calendar.listPage > 0) {\n      calendar.setListPage(calendar.listPage - 1);\n    }\n  }, [calendar]);\n\n  const nextPage = useCallback(() => {\n    if (calendar.listPage < calendar.listTotalPages - 1) {\n      calendar.setListPage(calendar.listPage + 1);\n    }\n  }, [calendar]);\n\n  return (\n    <div className=\"text-textColor flex flex-col md:flex-row gap-[8px] items-center select-none\">\n      {!isListView && (\n        <div className=\"flex flex-grow flex-row items-center gap-[10px]\">\n          <div className=\"border h-[42px] border-newTableBorder bg-newTableBorder gap-[1px] flex items-center rounded-[8px] overflow-hidden\">\n            <div\n              onClick={previous}\n              className=\"cursor-pointer text-textColor rtl:rotate-180 px-[9px] bg-newBgColorInner h-full flex items-center justify-center hover:text-textItemFocused hover:bg-boxFocused\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"8\"\n                height=\"12\"\n                viewBox=\"0 0 8 12\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M6.5 11L1.5 6L6.5 1\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n            <div className=\"min-w-[200px] text-center bg-newBgColorInner h-full flex items-center justify-center\">\n              <div className=\"py-[3px] px-[9px] rounded-[5px] transition-all text-[14px]\">\n                {getDisplayText()}\n              </div>\n            </div>\n            <div\n              onClick={next}\n              className=\"cursor-pointer text-textColor rtl:rotate-180 px-[9px] bg-newBgColorInner h-full flex items-center justify-center hover:text-textItemFocused hover:bg-boxFocused\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"8\"\n                height=\"12\"\n                viewBox=\"0 0 8 12\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M1.5 11L6.5 6L1.5 1\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div className=\"flex-1 text-[14px] font-[500]\">\n            <div className=\"text-center flex h-[42px]\">\n              <div\n                onClick={setToday}\n                className=\"hover:text-textItemFocused hover:bg-boxFocused py-[3px] px-[9px] flex justify-center items-center rounded-[8px] transition-all cursor-pointer text-[14px] bg-newBgColorInner border border-newTableBorder\"\n              >\n                {t('today', 'Today')}\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n      {isListView && (\n        <div className=\"flex flex-grow flex-row items-center gap-[10px]\">\n          <div className=\"border h-[42px] border-newTableBorder bg-newTableBorder gap-[1px] flex items-center rounded-[8px] overflow-hidden\">\n            <div\n              onClick={previousPage}\n              className={clsx(\n                'text-textColor rtl:rotate-180 px-[9px] bg-newBgColorInner h-full flex items-center justify-center',\n                calendar.listPage > 0\n                  ? 'cursor-pointer hover:text-textItemFocused hover:bg-boxFocused'\n                  : 'opacity-50 cursor-not-allowed'\n              )}\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"8\"\n                height=\"12\"\n                viewBox=\"0 0 8 12\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M6.5 11L1.5 6L6.5 1\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n            <div className=\"min-w-[200px] text-center bg-newBgColorInner h-full flex items-center justify-center\">\n              <div className=\"py-[3px] px-[9px] rounded-[5px] transition-all text-[14px]\">\n                {t('page', 'Page')} {calendar.listPage + 1} {t('of', 'of')} {Math.max(1, calendar.listTotalPages)}\n              </div>\n            </div>\n            <div\n              onClick={nextPage}\n              className={clsx(\n                'text-textColor rtl:rotate-180 px-[9px] bg-newBgColorInner h-full flex items-center justify-center',\n                calendar.listPage < calendar.listTotalPages - 1\n                  ? 'cursor-pointer hover:text-textItemFocused hover:bg-boxFocused'\n                  : 'opacity-50 cursor-not-allowed'\n              )}\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"8\"\n                height=\"12\"\n                viewBox=\"0 0 8 12\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M1.5 11L6.5 6L1.5 1\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div className=\"flex-1\" />\n        </div>\n      )}\n      <SelectCustomer\n        customer={calendar.customer as string}\n        onChange={(customer: string) => setCustomer(customer)}\n        integrations={calendar.integrations}\n      />\n      {!isListView && (\n        <div className=\"flex flex-row p-[4px] border border-newTableBorder rounded-[8px] text-[14px] font-[500]\">\n          <div\n            className={clsx(\n              'pt-[6px] pb-[5px] cursor-pointer w-[74px] text-center rounded-[6px]',\n              calendar.display === 'day' && 'text-textItemFocused bg-boxFocused'\n            )}\n            onClick={setDay}\n          >\n            {t('day', 'Day')}\n          </div>\n          <div\n            className={clsx(\n              'pt-[6px] pb-[5px] cursor-pointer w-[74px] text-center rounded-[6px]',\n              calendar.display === 'week' && 'text-textItemFocused bg-boxFocused'\n            )}\n            onClick={setWeek}\n          >\n            {t('week', 'Week')}\n          </div>\n          <div\n            className={clsx(\n              'pt-[6px] pb-[5px] cursor-pointer w-[74px] text-center rounded-[6px]',\n              calendar.display === 'month' && 'text-textItemFocused bg-boxFocused'\n            )}\n            onClick={setMonth}\n          >\n            {t('month', 'Month')}\n          </div>\n        </div>\n      )}\n      <div className=\"flex flex-row p-[4px] border border-newTableBorder rounded-[8px] text-[14px] font-[500]\">\n        <div\n          onClick={setCalendarView}\n          className={clsx(\n            'pt-[6px] pb-[5px] cursor-pointer flex justify-center items-center w-[34px] text-center rounded-[6px]',\n            !isListView && 'text-textItemFocused bg-boxFocused'\n          )}\n        >\n          {/*calendar*/}\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"17\"\n            height=\"19\"\n            viewBox=\"0 0 17 19\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M15.75 7.41667H0.75M11.5833 0.75V4.08333M4.91667 0.75V4.08333M4.75 17.4167H11.75C13.1501 17.4167 13.8502 17.4167 14.385 17.1442C14.8554 16.9045 15.2378 16.522 15.4775 16.0516C15.75 15.5169 15.75 14.8168 15.75 13.4167V6.41667C15.75 5.01654 15.75 4.31647 15.4775 3.78169C15.2378 3.31129 14.8554 2.92883 14.385 2.68915C13.8502 2.41667 13.1501 2.41667 11.75 2.41667H4.75C3.34987 2.41667 2.6498 2.41667 2.11502 2.68915C1.64462 2.92883 1.26217 3.31129 1.02248 3.78169C0.75 4.31647 0.75 5.01654 0.75 6.41667V13.4167C0.75 14.8168 0.75 15.5169 1.02248 16.0516C1.26217 16.522 1.64462 16.9045 2.11502 17.1442C2.6498 17.4167 3.34987 17.4167 4.75 17.4167Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n        <div\n          onClick={setList}\n          className={clsx(\n            'pt-[6px] pb-[5px] flex justify-center items-center cursor-pointer w-[34px] text-center rounded-[6px]',\n            isListView && 'text-textItemFocused bg-boxFocused'\n          )}\n        >\n          {/*list*/}\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 20 20\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M17.5 10L7.5 10M17.5 5.00002L7.5 5.00002M17.5 15L7.5 15M4.16667 10C4.16667 10.4603 3.79357 10.8334 3.33333 10.8334C2.8731 10.8334 2.5 10.4603 2.5 10C2.5 9.53978 2.8731 9.16669 3.33333 9.16669C3.79357 9.16669 4.16667 9.53978 4.16667 10ZM4.16667 5.00002C4.16667 5.46026 3.79357 5.83335 3.33333 5.83335C2.8731 5.83335 2.5 5.46026 2.5 5.00002C2.5 4.53978 2.8731 4.16669 3.33333 4.16669C3.79357 4.16669 4.16667 4.53978 4.16667 5.00002ZM4.16667 15C4.16667 15.4603 3.79357 15.8334 3.33333 15.8334C2.8731 15.8334 2.5 15.4603 2.5 15C2.5 14.5398 2.8731 14.1667 3.33333 14.1667C3.79357 14.1667 4.16667 14.5398 4.16667 15Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/general.preview.component.tsx",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport clsx from 'clsx';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\nimport { FC } from 'react';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport Image from 'next/image';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\n\nexport const GeneralPreviewComponent: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n\n  return (\n    <div className={clsx('w-full p-[15px]')}>\n      <div className=\"w-full h-full relative flex flex-col\">\n        {renderContent.map((value, index) => (\n          <div\n            key={`tweet_${index}`}\n            style={{}}\n            className={clsx(\n              `flex gap-[8px] relative`,\n              index === renderContent.length - 1 ? 'pb-[12px]' : 'pb-[24px]'\n            )}\n          >\n            <div className=\"min-w-[40px] h-[40px] min-h-[40px] w-[40px] flex flex-col items-center\">\n              <div className=\"relative\">\n                <img\n                  src={\n                    current === 'global'\n                      ? '/no-picture.jpg'\n                      : integration?.picture || '/no-picture.jpg'\n                  }\n                  alt=\"x\"\n                  className=\"rounded-full relative z-[2]\"\n                />\n\n                {current !== 'global' && (\n                  <Image\n                    src={`/icons/platforms/${integration?.identifier}.png`}\n                    className=\"min-w-[20px] min-h-[20px] rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth\"\n                    alt={integration.identifier}\n                    width={20}\n                    height={20}\n                  />\n                )}\n              </div>\n              {index !== topValue.length - 1 && (\n                <div className=\"flex-1 w-[2px] h-[calc(100%-10px)] bg-customColor25 absolute top-[10px] z-[1]\" />\n              )}\n            </div>\n            <div className=\"flex-1 flex flex-col gap-[4px]\">\n              <div className=\"flex\">\n                <div className=\"h-[22px] text-[15px] font-[700]\">\n                  {current === 'global' ? 'Global Edit' : integration?.name}\n                </div>\n                <div className=\"text-[15px] text-customColor26 mt-[1px] ms-[2px]\">\n                  <svg\n                    viewBox=\"0 0 22 22\"\n                    aria-label=\"Verified account\"\n                    role=\"img\"\n                    className=\"max-w-[20px] max-h-[20px] fill-current h-[1.25em]\"\n                    data-testid=\"icon-verified\"\n                  >\n                    <g>\n                      <path d=\"M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.902-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.144 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.567s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.907.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z\"></path>\n                    </g>\n                  </svg>\n                </div>\n                <div className=\"text-[15px] font-[400] text-customColor27 ms-[4px]\">\n                  {current === 'global'\n                    ? ''\n                    : integration?.display || '@username'}\n                </div>\n              </div>\n              <div\n                className={clsx('text-wrap whitespace-pre', 'preview')}\n                dangerouslySetInnerHTML={{\n                  __html: value.text,\n                }}\n              />\n              {!!value?.images?.length && (\n                <div\n                  className={clsx(\n                    'w-full rounded-[16px] overflow-hidden mt-[12px]',\n                    value?.images?.length > 3\n                      ? 'grid grid-cols-2 gap-[4px]'\n                      : 'flex gap-[4px]'\n                  )}\n                >\n                  {value.images.map((image, index) => (\n                    <a\n                      key={`image_${index}`}\n                      className=\"flex-1\"\n                      href={mediaDir.set(image.path)}\n                      target=\"_blank\"\n                    >\n                      <VideoOrImage\n                        autoplay={true}\n                        src={mediaDir.set(image.path)}\n                      />\n                    </a>\n                  ))}\n                </div>\n              )}\n            </div>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/generator/generator.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useMemo, useState } from 'react';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useRouter } from 'next/navigation';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { FormProvider, SubmitHandler, useForm } from 'react-hook-form';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';\nimport { Button } from '@gitroom/react/form/button';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { Checkbox } from '@gitroom/react/form/checkbox';\nimport clsx from 'clsx';\nimport {\n  CalendarWeekProvider,\n  useCalendar,\n} from '@gitroom/frontend/components/launches/calendar.context';\nimport dayjs from 'dayjs';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\n\nconst FirstStep: FC = (props) => {\n  const { integrations, reloadCalendarView } = useCalendar();\n  const modal = useModals();\n  const fetch = useFetch();\n  const [loading, setLoading] = useState(false);\n  const [showStep, setShowStep] = useState('');\n  const t = useT();\n  const resolver = useMemo(() => {\n    return classValidatorResolver(GeneratorDto);\n  }, []);\n  const form = useForm({\n    mode: 'all',\n    resolver,\n    values: {\n      research: '',\n      isPicture: false,\n      format: 'one_short',\n      tone: 'personal',\n    },\n  });\n  const [research] = form.watch(['research']);\n  const generateStep = useCallback(\n    async (reader: ReadableStreamDefaultReader) => {\n      const decoder = new TextDecoder('utf-8');\n      let lastResponse = {} as any;\n      // eslint-disable-next-line no-constant-condition\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) return lastResponse.data.output;\n\n        // Convert chunked binary data to string\n        const chunkStr = decoder.decode(value, {\n          stream: true,\n        });\n        for (const chunk of chunkStr\n          .split('\\n')\n          .filter((f) => f && f.indexOf('{') > -1)) {\n          try {\n            const data = JSON.parse(chunk);\n            switch (data.name) {\n              case 'agent':\n                setShowStep(t('agent_starting', 'Agent starting'));\n                break;\n              case 'research':\n                setShowStep(\n                  t('researching_your_content', 'Researching your content...')\n                );\n                break;\n              case 'find-category':\n                setShowStep(\n                  t(\n                    'understanding_the_category',\n                    'Understanding the category...'\n                  )\n                );\n                break;\n              case 'find-topic':\n                setShowStep(t('finding_the_topic', 'Finding the topic...'));\n                break;\n              case 'find-popular-posts':\n                setShowStep(\n                  t(\n                    'finding_popular_posts_to_match_with',\n                    'Finding popular posts to match with...'\n                  )\n                );\n                break;\n              case 'generate-hook':\n                setShowStep(t('generating_hook', 'Generating hook...'));\n                break;\n              case 'generate-content':\n                setShowStep(t('generating_content', 'Generating content...'));\n                break;\n              case 'generate-picture':\n                setShowStep(t('generating_pictures', 'Generating pictures...'));\n                break;\n              case 'upload-pictures':\n                setShowStep(t('uploading_pictures', 'Uploading pictures...'));\n                break;\n              case 'post-time':\n                setShowStep(\n                  t('finding_time_to_post', 'Finding time to post...')\n                );\n                break;\n            }\n            lastResponse = data;\n          } catch (e) {\n            /** don't do anything **/\n          }\n        }\n      }\n    },\n    [t]\n  );\n  const onSubmit: SubmitHandler<{\n    research: string;\n  }> = useCallback(\n    async (value) => {\n      setLoading(true);\n      const response = await fetch('/posts/generator', {\n        method: 'POST',\n        body: JSON.stringify(value),\n      });\n      if (!response.body) {\n        return;\n      }\n      const reader = response.body.getReader();\n      const load = await generateStep(reader);\n      const messages = load.content.map((p: any, index: number) => {\n        if (index === 0) {\n          return {\n            content: load.hook + '\\n' + p.content,\n            ...(p?.image?.path\n              ? {\n                  image: [p.image],\n                }\n              : {}),\n          };\n        }\n        return {\n          content: p.content,\n          ...(p?.image?.path\n            ? {\n                image: [p.image],\n              }\n            : {}),\n        };\n      });\n      setShowStep('');\n      modal.openModal({\n        id: 'add-edit-modal',\n        closeOnClickOutside: false,\n        removeLayout: true,\n        closeOnEscape: false,\n        withCloseButton: false,\n        askClose: true,\n        fullScreen: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px] text-textColor',\n        },\n        children: (\n          <AddEditModal\n            allIntegrations={integrations.map((p) => ({\n              ...p,\n            }))}\n            integrations={integrations.slice(0).map((p) => ({\n              ...p,\n            }))}\n            mutate={reloadCalendarView}\n            date={dayjs.utc(load.date).local()}\n            reopenModal={() => ({})}\n            onlyValues={messages}\n          />\n        ),\n        size: '80%',\n      });\n      setLoading(false);\n    },\n    [integrations, reloadCalendarView]\n  );\n  return (\n    <form\n      onSubmit={form.handleSubmit(onSubmit)}\n      className={loading ? 'pointer-events-none select-none opacity-75' : ''}\n    >\n      <FormProvider {...form}>\n        <div className=\"flex flex-col\">\n          <div className=\"pb-[10px] rounded-[4px]\">\n            <div className=\"flex\">\n              <div className=\"flex-1\">\n                {!showStep ? (\n                  <div className=\"loading-shimmer pb-[10px]\">&nbsp;</div>\n                ) : (\n                  <div\n                    className=\"loading-shimmer pb-[10px]\"\n                    data-text={showStep}\n                  >\n                    {showStep}\n                  </div>\n                )}\n                <Textarea\n                  label={t('write_anything', 'Write anything')}\n                  disabled={loading}\n                  placeholder={t(\n                    'you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you',\n                    'You can write anything you want, and also add links, we will do the research for you...'\n                  )}\n                  {...form.register('research')}\n                />\n                <Select\n                  label={t('output_format', 'Output format')}\n                  {...form.register('format')}\n                >\n                  <option value=\"one_short\">\n                    {t('short_post', 'Short post')}\n                  </option>\n                  <option value=\"one_long\">\n                    {t('long_post', 'Long post')}\n                  </option>\n                  <option value=\"thread_short\">\n                    {t(\n                      'a_thread_with_short_posts',\n                      'A thread with short posts'\n                    )}\n                  </option>\n                  <option value=\"thread_long\">\n                    {t('a_thread_with_long_posts', 'A thread with long posts')}\n                  </option>\n                </Select>\n                <Select\n                  label={t('output_format', 'Output format')}\n                  {...form.register('tone')}\n                >\n                  <option value=\"personal\">\n                    {t(\n                      'personal_voice_i_am_happy_to_announce',\n                      'Personal voice (\"I am happy to announce\")'\n                    )}\n                  </option>\n                  <option value=\"company\">\n                    {t(\n                      'company_voice_we_are_happy_to_announce',\n                      'Company voice (\"We are happy to announce\")'\n                    )}\n                  </option>\n                </Select>\n                <div\n                  className={clsx('flex items-center', loading && 'opacity-50')}\n                >\n                  <Checkbox\n                    disabled={loading}\n                    {...form.register('isPicture')}\n                    label={t('add_pictures', 'Add pictures?')}\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div className=\"mt-[20px] flex justify-end\">\n          <Button\n            type=\"submit\"\n            disabled={research.length < 10}\n            loading={loading}\n          >\n            {t('generate', 'Generate')}\n          </Button>\n        </div>\n      </FormProvider>\n    </form>\n  );\n};\nexport const GeneratorPopup = () => {\n  const t = useT();\n\n  const modals = useModals();\n  const closeAll = useCallback(() => {\n    modals.closeAll();\n  }, []);\n  return (\n    <div className=\"w-full flex flex-col rounded-[4px] relative\">\n      <FirstStep />\n    </div>\n  );\n};\nexport const GeneratorComponent = () => {\n  const t = useT();\n  const user = useUser();\n  const router = useRouter();\n  const modal = useModals();\n  const all = useCalendar();\n  const generate = useCallback(async () => {\n    if (!user?.tier?.ai) {\n      if (\n        await deleteDialog(\n          t('upgrade_required', 'You need to upgrade to use this feature'),\n          t('move_to_billing', 'Move to billing'),\n          t('payment_required', 'Payment Required')\n        )\n      ) {\n        router.push('/billing');\n      }\n      return;\n    }\n    modal.openModal({\n      title: t('generate_posts', 'Generate Posts'),\n      withCloseButton: false,\n      classNames: {\n        modal: 'bg-transparent text-textColor',\n      },\n      size: 'xl',\n      children: (\n        <CalendarWeekProvider {...all}>\n          <GeneratorPopup />\n        </CalendarWeekProvider>\n      ),\n    });\n  }, [user, all]);\n  return (\n    <div\n      className=\"h-[44px] w-[44px] group-[.sidebar]:w-full bg-ai justify-center items-center flex rounded-[8px] cursor-pointer\"\n      onClick={generate}\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"20\"\n        height=\"20\"\n        viewBox=\"0 0 20 20\"\n        fill=\"none\"\n      >\n        <g clipPath=\"url(#clip0_1930_7370)\">\n          <path\n            d=\"M5.41675 10.8337L6.07046 12.1411C6.2917 12.5836 6.40232 12.8048 6.55011 12.9965C6.68124 13.1667 6.83375 13.3192 7.00388 13.4503C7.19559 13.5981 7.41684 13.7087 7.85932 13.9299L9.16675 14.5837L7.85932 15.2374C7.41684 15.4586 7.19559 15.5692 7.00388 15.717C6.83375 15.8482 6.68124 16.0007 6.55011 16.1708C6.40232 16.3625 6.2917 16.5837 6.07046 17.0262L5.41675 18.3337L4.76303 17.0262C4.54179 16.5837 4.43117 16.3625 4.28339 16.1708C4.15225 16.0007 3.99974 15.8482 3.82962 15.717C3.6379 15.5692 3.41666 15.4586 2.97418 15.2374L1.66675 14.5837L2.97418 13.9299C3.41666 13.7087 3.6379 13.5981 3.82962 13.4503C3.99974 13.3192 4.15225 13.1667 4.28339 12.9965C4.43117 12.8048 4.54179 12.5836 4.76303 12.1411L5.41675 10.8337Z\"\n            stroke=\"white\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M12.5001 1.66699L13.4823 4.22067C13.7173 4.8317 13.8348 5.13721 14.0175 5.39419C14.1795 5.62195 14.3785 5.82095 14.6062 5.9829C14.8632 6.16563 15.1687 6.28313 15.7797 6.51814L18.3334 7.50033L15.7797 8.48251C15.1687 8.71752 14.8632 8.83502 14.6062 9.01775C14.3785 9.1797 14.1795 9.3787 14.0175 9.60646C13.8348 9.86344 13.7173 10.169 13.4823 10.78L12.5001 13.3337L11.5179 10.78C11.2829 10.169 11.1654 9.86344 10.9827 9.60646C10.8207 9.3787 10.6217 9.1797 10.3939 9.01775C10.137 8.83503 9.83145 8.71752 9.22043 8.48251L6.66675 7.50033L9.22043 6.51814C9.83145 6.28313 10.137 6.16563 10.3939 5.9829C10.6217 5.82095 10.8207 5.62195 10.9827 5.39419C11.1654 5.13721 11.2829 4.8317 11.5179 4.22067L12.5001 1.66699Z\"\n            stroke=\"white\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </g>\n        <defs>\n          <clipPath id=\"clip0_1930_7370\">\n            <rect width=\"20\" height=\"20\" fill=\"white\" />\n          </clipPath>\n        </defs>\n      </svg>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/date.picker.tsx",
    "content": "import { FC, useCallback, useState } from 'react';\nimport dayjs from 'dayjs';\nimport { Calendar, TimeInput } from '@mantine/dates';\nimport { useClickOutside } from '@mantine/hooks';\nimport { Button } from '@gitroom/react/form/button';\nimport { isUSCitizen } from './isuscitizen.utils';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { CalendarIcon } from '@gitroom/frontend/components/ui/icons';\nexport const DatePicker: FC<{\n  date: dayjs.Dayjs;\n  onChange: (day: dayjs.Dayjs) => void;\n}> = (props) => {\n  const { date, onChange } = props;\n  const [open, setOpen] = useState(false);\n  const t = useT();\n\n  const changeShow = useCallback(() => {\n    setOpen((prev) => !prev);\n  }, []);\n  const ref = useClickOutside<HTMLDivElement>(() => {\n    setOpen(false);\n  });\n  const changeDate = useCallback(\n    (type: 'date' | 'time') => (day: Date) => {\n      onChange(\n        newDayjs(\n          type === 'time'\n            ? date.format('YYYY-MM-DD') + ' ' + newDayjs(day).format('HH:mm:ss')\n            : newDayjs(day).format('YYYY-MM-DD') + ' ' + date.format('HH:mm:ss')\n        )\n      );\n    },\n    [date]\n  );\n  return (\n    <div\n      className=\"px-[16px] border border-newTextColor/10 rounded-[8px] justify-center flex gap-[8px] items-center relative h-[44px] text-[15px] font-[600] ml-[7px] select-none flex-1\"\n      onClick={changeShow}\n      ref={ref}\n    >\n      <div className=\"cursor-pointer\">\n        <CalendarIcon />\n      </div>\n      <div className=\"cursor-pointer\">\n        {date.format(isUSCitizen() ? 'MM/DD/YYYY hh:mm A' : 'DD/MM/YYYY HH:mm')}\n      </div>\n      {open && (\n        <div\n          onClick={(e) => e.stopPropagation()}\n          className=\"animate-fadeIn absolute bottom-[100%] mb-[16px] start-[50%] -translate-x-[50%] bg-sixth border border-tableBorder text-textColor rounded-[16px] z-[300] p-[16px] flex flex-col\"\n        >\n          <Calendar\n            onChange={changeDate('date')}\n            value={date.toDate()}\n            dayClassName={(date, modifiers) => {\n              if (modifiers.weekend) {\n                return '!text-customColor28';\n              }\n              if (modifiers.outside) {\n                return '!text-gray';\n              }\n              if (modifiers.selected) {\n                return '!text-white !bg-seventh !outline-none';\n              }\n              return '!text-textColor';\n            }}\n            classNames={{\n              day: 'hover:bg-seventh',\n              calendarHeaderControl: 'text-textColor hover:bg-third',\n              calendarHeaderLevel: 'text-textColor hover:bg-third', // cell: 'child:!text-textColor'\n            }}\n          />\n          <TimeInput\n            onChange={changeDate('time')}\n            label=\"Pick time\"\n            classNames={{\n              label: 'text-textColor py-[12px]',\n              input:\n                'bg-sixth h-[40px] border border-tableBorder text-textColor rounded-[4px] outline-none',\n            }}\n            defaultValue={date.toDate()}\n          />\n          <Button className=\"mt-[12px]\" onClick={changeShow}>\n            {t('close', 'Close')}\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/dnd.provider.tsx",
    "content": "'use client';\n\nimport { FC, ReactNode } from 'react';\nimport { HTML5Backend } from 'react-dnd-html5-backend';\nimport { DndProvider } from 'react-dnd';\nexport const DNDProvider: FC<{\n  children: ReactNode;\n}> = ({ children }) => {\n  return <DndProvider backend={HTML5Backend}>{children}</DndProvider>;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/isuscitizen.utils.tsx",
    "content": "export const isUSCitizen = () => {\n  const userLanguage = localStorage.getItem('isUS') || ((navigator.language || navigator.languages[0]).startsWith('en-US') ? 'US' : 'GLOBAL');\n  return userLanguage === 'US';\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/linkedin.component.tsx",
    "content": "'use client';\n\nimport { EventEmitter } from 'events';\nimport React, { FC, useCallback, useEffect, useState } from 'react';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Input } from '@gitroom/react/form/input';\nimport { Button } from '@gitroom/react/form/button';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nconst postUrlEmitter = new EventEmitter();\nexport const ShowLinkedinCompany = () => {\n  const [showPostSelector, setShowPostSelector] = useState(false);\n  const [id, setId] = useState('');\n  const [callback, setCallback] = useState<{\n    callback: (tag: string) => void;\n    // eslint-disable-next-line @typescript-eslint/no-empty-function\n  } | null>({\n    callback: (tag: string) => {},\n  } as any);\n  useEffect(() => {\n    postUrlEmitter.on(\n      'show',\n      (params: { id: string; callback: (url: string) => void }) => {\n        setCallback(params);\n        setId(params.id);\n        setShowPostSelector(true);\n      }\n    );\n    return () => {\n      setShowPostSelector(false);\n      setCallback(null);\n      setId('');\n      postUrlEmitter.removeAllListeners();\n    };\n  }, []);\n  const close = useCallback(() => {\n    setShowPostSelector(false);\n    setCallback(null);\n    setId('');\n  }, []);\n  if (!showPostSelector) {\n    return <></>;\n  }\n  return (\n    <LinkedinCompany id={id} onClose={close} onSelect={callback?.callback!} />\n  );\n};\n\nexport const LinkedinCompanyPop: FC<{\n  addText: (value: any) => void;\n}> = (props) => {\n  const current = useLaunchStore((state) => state.current);\n  return (\n    <svg\n      onClick={() => {\n        postUrlEmitter.emit('show', {\n          id: current,\n          callback: (value: any) => {\n            props.addText(value);\n          },\n        });\n      }}\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content=\"Add a LinkedIn Company\"\n      className=\"mx-[10px] cursor-pointer\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 26 26\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M24 0H2C1.46957 0 0.960859 0.210714 0.585786 0.585786C0.210714 0.960859 0 1.46957 0 2V24C0 24.5304 0.210714 25.0391 0.585786 25.4142C0.960859 25.7893 1.46957 26 2 26H24C24.5304 26 25.0391 25.7893 25.4142 25.4142C25.7893 25.0391 26 24.5304 26 24V2C26 1.46957 25.7893 0.960859 25.4142 0.585786C25.0391 0.210714 24.5304 0 24 0ZM9 19C9 19.2652 8.89464 19.5196 8.70711 19.7071C8.51957 19.8946 8.26522 20 8 20C7.73478 20 7.48043 19.8946 7.29289 19.7071C7.10536 19.5196 7 19.2652 7 19V11C7 10.7348 7.10536 10.4804 7.29289 10.2929C7.48043 10.1054 7.73478 10 8 10C8.26522 10 8.51957 10.1054 8.70711 10.2929C8.89464 10.4804 9 10.7348 9 11V19ZM8 9C7.70333 9 7.41332 8.91203 7.16665 8.7472C6.91997 8.58238 6.72771 8.34811 6.61418 8.07403C6.50065 7.79994 6.47094 7.49834 6.52882 7.20736C6.5867 6.91639 6.72956 6.64912 6.93934 6.43934C7.14912 6.22956 7.41639 6.0867 7.70736 6.02882C7.99834 5.97094 8.29994 6.00065 8.57403 6.11418C8.84811 6.22771 9.08238 6.41997 9.2472 6.66665C9.41203 6.91332 9.5 7.20333 9.5 7.5C9.5 7.89782 9.34196 8.27936 9.06066 8.56066C8.77936 8.84196 8.39782 9 8 9ZM20 19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20C18.7348 20 18.4804 19.8946 18.2929 19.7071C18.1054 19.5196 18 19.2652 18 19V14.5C18 13.837 17.7366 13.2011 17.2678 12.7322C16.7989 12.2634 16.163 12 15.5 12C14.837 12 14.2011 12.2634 13.7322 12.7322C13.2634 13.2011 13 13.837 13 14.5V19C13 19.2652 12.8946 19.5196 12.7071 19.7071C12.5196 19.8946 12.2652 20 12 20C11.7348 20 11.4804 19.8946 11.2929 19.7071C11.1054 19.5196 11 19.2652 11 19V11C11.0012 10.7551 11.0923 10.5191 11.256 10.3369C11.4197 10.1546 11.6446 10.0388 11.888 10.0114C12.1314 9.98392 12.3764 10.0468 12.5765 10.188C12.7767 10.3292 12.918 10.539 12.9738 10.7775C13.6502 10.3186 14.4389 10.0526 15.2552 10.0081C16.0714 9.96368 16.8844 10.1424 17.6067 10.5251C18.329 10.9078 18.9335 11.48 19.3551 12.1803C19.7768 12.8806 19.9997 13.6825 20 14.5V19Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\nexport const showPostSelector = (id: string) => {\n  return new Promise<string>((resolve) => {\n    postUrlEmitter.emit('show', {\n      id,\n      callback: (tag: string) => {\n        resolve(tag);\n      },\n    });\n  });\n};\nexport const LinkedinCompany: FC<{\n  onClose: () => void;\n  onSelect: (tag: string) => void;\n  id: string;\n}> = (props) => {\n  const { onClose, onSelect, id } = props;\n  const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton);\n  useEffect(() => {\n    setActivateExitButton(false);\n    return () => {\n      setActivateExitButton(true);\n    };\n  }, []);\n  const fetch = useFetch();\n  const [company, setCompany] = useState<any>(null);\n  const toast = useToaster();\n  const t = useT();\n  const getCompany = async () => {\n    if (!company) {\n      return;\n    }\n    try {\n      const { options } = await (\n        await fetch('/integrations/function', {\n          method: 'POST',\n          body: JSON.stringify({\n            id,\n            name: 'company',\n            data: {\n              url: company,\n            },\n          }),\n        })\n      ).json();\n      onSelect(options.value);\n      onClose();\n    } catch (e) {\n      toast.show('Failed to load profile', 'warning');\n    }\n  };\n  return (\n    <div className=\"text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade justify-center flex\">\n      <div className=\"flex flex-col w-[500px] h-[250px] bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative\">\n        <div className=\"flex\">\n          <div className=\"flex-1\">\n            <TopTitle title={'Select Company'} />\n          </div>\n          <button\n            onClick={onClose}\n            className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n            type=\"button\"\n          >\n            <svg\n              viewBox=\"0 0 15 15\"\n              fill=\"none\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n            >\n              <path\n                d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                fill=\"currentColor\"\n                fillRule=\"evenodd\"\n                clipRule=\"evenodd\"\n              ></path>\n            </svg>\n          </button>\n        </div>\n        <div className=\"mt-[10px]\">\n          <Input\n            name=\"url\"\n            disableForm={true}\n            label=\"URL\"\n            value={company}\n            onChange={(e) => setCompany(e.target.value)}\n            placeholder=\"https://www.linkedin.com/company/gitroom\"\n          />\n          <Button onClick={getCompany}>{t('add', 'Add')}</Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/media.settings.component.tsx",
    "content": "'use client';\n\nimport { EventEmitter } from 'events';\nimport React, { FC, useCallback, useEffect, useRef, useState } from 'react';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nconst postUrlEmitter = new EventEmitter();\n\nexport const MediaSettingsLayout = () => {\n  const [showPostSelector, setShowPostSelector] = useState(false);\n  const [media, setMedia] = useState(undefined);\n  const [callback, setCallback] = useState<{\n    callback: (tag: {\n      id: string;\n      name: string;\n      path: string;\n      thumbnail: string;\n      alt: string;\n    }) => void;\n    // eslint-disable-next-line @typescript-eslint/no-empty-function\n  } | null>({\n    callback: (params: {\n      id: string;\n      name: string;\n      path: string;\n      thumbnail: string;\n      alt: string;\n    }) => {},\n  } as any);\n  useEffect(() => {\n    postUrlEmitter.on(\n      'show',\n      (params: {\n        media: any;\n        callback: (url: {\n          id: string;\n          name: string;\n          path: string;\n          thumbnail: string;\n          alt: string;\n        }) => void;\n      }) => {\n        setCallback(params);\n        setMedia(params.media);\n        setShowPostSelector(true);\n      }\n    );\n    return () => {\n      setShowPostSelector(false);\n      setCallback(null);\n      setMedia(undefined);\n      postUrlEmitter.removeAllListeners();\n    };\n  }, []);\n  const close = useCallback(() => {\n    setShowPostSelector(false);\n    setCallback(null);\n    setMedia(undefined);\n  }, []);\n  if (!showPostSelector) {\n    return <></>;\n  }\n  return (\n    <MediaComponentInner\n      media={media}\n      onClose={close}\n      onSelect={callback?.callback!}\n    />\n  );\n};\n\nexport const useMediaSettings = () => {\n  return useCallback((media: any) => {\n    return new Promise((resolve) => {\n      postUrlEmitter.emit('show', {\n        media,\n        callback: (value: any) => {\n          resolve(value);\n        },\n      });\n    });\n  }, []);\n};\n\nexport const CreateThumbnail: FC<{\n  onSelect: (blob: Blob, timestampMs: number) => void;\n  media:\n    | {\n        id: string;\n        name: string;\n        path: string;\n        thumbnail?: string;\n        alt?: string;\n      }\n    | undefined;\n  altText?: string;\n  onAltTextChange?: (altText: string) => void;\n}> = (props) => {\n  const { onSelect, media } = props;\n  const { backendUrl } = useVariables();\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const [currentTime, setCurrentTime] = useState(0);\n  const [duration, setDuration] = useState(0);\n  const [isLoaded, setIsLoaded] = useState(false);\n  const [isCapturing, setIsCapturing] = useState(false);\n\n  const handleLoadedMetadata = useCallback(() => {\n    setDuration(videoRef?.current?.duration);\n    setIsLoaded(true);\n  }, []);\n\n  const handleTimeUpdate = useCallback(() => {\n    setCurrentTime(videoRef?.current?.currentTime);\n  }, []);\n\n  const handleSeek = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n    const time = parseFloat(e.target.value);\n    if (videoRef.current) {\n      videoRef.current.currentTime = time;\n      setCurrentTime(time);\n    }\n  }, []);\n\n  const captureFrame = useCallback(async () => {\n    setIsCapturing(true);\n\n    try {\n      const video = videoRef.current;\n      const canvas = canvasRef.current;\n      const ctx = canvas.getContext('2d');\n\n      if (!ctx) {\n        setIsCapturing(false);\n        return;\n      }\n\n      // Set canvas dimensions to match video\n      canvas.width = video.videoWidth;\n      canvas.height = video.videoHeight;\n\n      // Draw current frame to canvas\n      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n\n      // Get timestamp in milliseconds\n      const timestampMs = Math.round(currentTime * 1000);\n\n      // Convert canvas to blob\n      canvas.toBlob(\n        (blob: Blob | null) => {\n          if (blob) {\n            onSelect(blob, timestampMs);\n          }\n          setIsCapturing(false);\n        },\n        'image/jpeg',\n        0.8\n      );\n    } catch (error) {\n      console.error('Error capturing frame:', error);\n      setIsCapturing(false);\n\n      // Fallback: try to capture using a different approach\n      try {\n        const video = videoRef.current;\n        if (video) {\n          // Create a temporary canvas element\n          const tempCanvas = document.createElement('canvas');\n          const tempCtx = tempCanvas.getContext('2d');\n\n          if (tempCtx) {\n            tempCanvas.width = video.videoWidth;\n            tempCanvas.height = video.videoHeight;\n            tempCtx.drawImage(video, 0, 0);\n\n            // Get timestamp in milliseconds\n            const timestampMs = Math.round(currentTime * 1000);\n\n            tempCanvas.toBlob(\n              (blob: Blob | null) => {\n                if (blob) {\n                  onSelect(blob, timestampMs);\n                }\n                setIsCapturing(false);\n              },\n              'image/jpeg',\n              0.8\n            );\n          }\n        }\n      } catch (fallbackError) {\n        console.error('Fallback capture also failed:', fallbackError);\n        alert(\n          'Unable to capture frame. This might be due to CORS restrictions on the video source.'\n        );\n        setIsCapturing(false);\n      }\n    }\n  }, [onSelect, currentTime]);\n\n  const formatTime = useCallback((seconds: number) => {\n    const mins = Math.floor(seconds / 60);\n    const secs = Math.floor(seconds % 60);\n    return `${mins}:${secs.toString().padStart(2, '0')}`;\n  }, []);\n\n  if (!media) return null;\n\n  return (\n    <div className=\"flex flex-col space-y-4\">\n      <div className=\"relative bg-black rounded-lg overflow-hidden\">\n        <video\n          ref={videoRef}\n          src={\n            backendUrl + '/public/stream?url=' + encodeURIComponent(media.path)\n          }\n          className=\"w-full h-[200px] object-contain\"\n          onLoadedMetadata={handleLoadedMetadata}\n          onTimeUpdate={handleTimeUpdate}\n          muted\n          preload=\"metadata\"\n          crossOrigin=\"anonymous\"\n        />\n        <canvas ref={canvasRef} className=\"hidden\" />\n      </div>\n\n      {isLoaded && (\n        <>\n          <div className=\"flex flex-col space-y-2\">\n            <input\n              type=\"range\"\n              min=\"0\"\n              max={duration}\n              step=\"0.1\"\n              value={currentTime}\n              onChange={handleSeek}\n              className=\"w-full h-2 bg-fifth rounded-lg appearance-none cursor-pointer slider\"\n              style={{\n                background: `linear-gradient(to right, #4f46e5 0%, #4f46e5 ${\n                  (currentTime / duration) * 100\n                }%, #374151 ${(currentTime / duration) * 100}%, #374151 100%)`,\n              }}\n            />\n            <div className=\"flex justify-between text-sm text-textColor\">\n              <span>{formatTime(currentTime)}</span>\n              <span>{formatTime(duration)}</span>\n            </div>\n          </div>\n\n          <div className=\"flex justify-center\">\n            <button\n              onClick={captureFrame}\n              disabled={isCapturing}\n              className=\"bg-forth text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all disabled:opacity-50 disabled:cursor-not-allowed\"\n            >\n              {isCapturing ? 'Capturing...' : 'Select This Frame'}\n            </button>\n          </div>\n        </>\n      )}\n\n      <style jsx>{`\n        .slider::-webkit-slider-thumb {\n          appearance: none;\n          width: 20px;\n          height: 20px;\n          border-radius: 50%;\n          background: #4f46e5;\n          cursor: pointer;\n          border: 2px solid #ffffff;\n          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n        }\n\n        .slider::-moz-range-thumb {\n          width: 20px;\n          height: 20px;\n          border-radius: 50%;\n          background: #4f46e5;\n          cursor: pointer;\n          border: 2px solid #ffffff;\n          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n        }\n      `}</style>\n    </div>\n  );\n};\n\nexport const MediaComponentInner: FC<{\n  onClose: () => void;\n  onSelect: (media: {\n    id: string;\n    name: string;\n    path: string;\n    thumbnail: string;\n    alt: string;\n  }) => void;\n  media:\n    | {\n        id: string;\n        name: string;\n        path: string;\n        thumbnail: string;\n        alt: string;\n        thumbnailTimestamp?: number;\n      }\n    | undefined;\n}> = (props) => {\n  const { onClose, onSelect, media } = props;\n  const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton);\n  const newFetch = useFetch();\n  const [newThumbnail, setNewThumbnail] = useState<string | null>(null);\n  const [isEditingThumbnail, setIsEditingThumbnail] = useState(false);\n  const [altText, setAltText] = useState<string>(media?.alt || '');\n  const [loading, setLoading] = useState(false);\n  const [thumbnail, setThumbnail] = useState<string | null>(\n    props.media?.thumbnail || null\n  );\n  const [thumbnailTimestamp, setThumbnailTimestamp] = useState<number | null>(\n    props.media?.thumbnailTimestamp || null\n  );\n\n  useEffect(() => {\n    setActivateExitButton(false);\n    return () => {\n      setActivateExitButton(true);\n    };\n  }, []);\n\n  const save = useCallback(async () => {\n    setLoading(true);\n    let path = thumbnail || '';\n    if (newThumbnail) {\n      const blob = await (await fetch(newThumbnail)).blob();\n      const formData = new FormData();\n      formData.append('file', blob, 'media.jpg');\n      formData.append('preventSave', 'true');\n      const data = await (\n        await newFetch('/media/upload-simple', {\n          method: 'POST',\n          body: formData,\n        })\n      ).json();\n      path = data.path;\n    }\n\n    const media = await (\n      await newFetch('/media/information', {\n        method: 'POST',\n        body: JSON.stringify({\n          id: props.media.id,\n          alt: altText,\n          thumbnail: path,\n          thumbnailTimestamp: thumbnailTimestamp,\n        }),\n      })\n    ).json();\n\n    onSelect(media);\n    onClose();\n  }, [altText, newThumbnail, thumbnail, thumbnailTimestamp]);\n\n  return (\n    <div className=\"mt-[10px] flex flex-col gap-[20px]\">\n      <div className=\"flex flex-col space-y-2\">\n        <label className=\"text-sm text-textColor font-medium\">\n          Alt Text (for accessibility)\n        </label>\n        <input\n          type=\"text\"\n          value={altText}\n          onChange={(e) => setAltText(e.target.value)}\n          placeholder=\"Describe the image/video content...\"\n          className=\"w-full px-3 py-2 bg-fifth border border-tableBorder rounded-lg text-textColor placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-forth focus:border-transparent\"\n        />\n      </div>\n      {media?.path.indexOf('mp4') > -1 && (\n        <>\n          {/* Alt Text Input */}\n          <div>\n            {!isEditingThumbnail ? (\n              <div className=\"flex flex-col\">\n                {/* Show existing thumbnail if it exists */}\n                {(newThumbnail || thumbnail) && (\n                  <div className=\"flex flex-col space-y-2\">\n                    <span className=\"text-sm text-textColor\">\n                      Current Thumbnail:\n                    </span>\n                    <img\n                      src={newThumbnail || thumbnail}\n                      alt=\"Current thumbnail\"\n                      className=\"max-w-full max-h-[500px] object-contain rounded-lg border border-tableBorder\"\n                    />\n                  </div>\n                )}\n\n                {/* Action Buttons */}\n                <div className=\"flex space-x-2\">\n                  <button\n                    disabled={loading}\n                    onClick={() => setIsEditingThumbnail(true)}\n                    className=\"bg-third text-textColor px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-tableBorder\"\n                  >\n                    {media.thumbnail || newThumbnail\n                      ? 'Edit Thumbnail'\n                      : 'Create Thumbnail'}\n                  </button>\n                  {(thumbnail || newThumbnail) && (\n                    <button\n                      disabled={loading}\n                      onClick={() => {\n                        setNewThumbnail(null);\n                        setThumbnail(null);\n                      }}\n                      className=\"bg-red-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all flex-1 border border-red-700\"\n                    >\n                      Clear Thumbnail\n                    </button>\n                  )}\n                </div>\n              </div>\n            ) : (\n              <div>\n                {/* Back button */}\n                <div className=\"flex justify-start\">\n                  <button\n                    onClick={() => setIsEditingThumbnail(false)}\n                    className=\"text-textColor hover:text-white transition-colors flex items-center space-x-2\"\n                  >\n                    <svg\n                      width=\"16\"\n                      height=\"16\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"none\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                    >\n                      <path\n                        d=\"M19 12H5M12 19L5 12L12 5\"\n                        stroke=\"currentColor\"\n                        strokeWidth=\"2\"\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                      />\n                    </svg>\n                    <span>Back</span>\n                  </button>\n                </div>\n\n                {/* Thumbnail Editor */}\n                <CreateThumbnail\n                  onSelect={(blob: Blob, timestampMs: number) => {\n                    // Convert blob to base64 or handle as needed\n                    const reader = new FileReader();\n                    reader.onload = () => {\n                      // You can handle the result here - for now just call onSelect with the blob URL\n                      const url = URL.createObjectURL(blob);\n                      setNewThumbnail(url);\n                      setThumbnailTimestamp(timestampMs);\n                      setIsEditingThumbnail(false);\n                    };\n                    reader.readAsDataURL(blob);\n                  }}\n                  media={media}\n                  altText={altText}\n                  onAltTextChange={setAltText}\n                />\n              </div>\n            )}\n          </div>\n        </>\n      )}\n\n      {!isEditingThumbnail && (\n        <div className=\"flex space-x-2 !mt-[20px]\">\n          <button\n            disabled={loading}\n            onClick={onClose}\n            className=\"flex-1 bg-gray-600 text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all\"\n          >\n            Cancel\n          </button>\n          <button\n            onClick={save}\n            className=\"flex-1 bg-forth text-white px-6 py-2 rounded-lg hover:bg-opacity-80 transition-all\"\n          >\n            Save Changes\n          </button>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/pick.platform.component.tsx",
    "content": "import { FC, useCallback, useEffect, useRef, useState } from 'react';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport { useMoveToIntegrationListener } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport clsx from 'clsx';\nimport Image from 'next/image';\nimport { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';\nimport { useStateCallback } from '@gitroom/react/helpers/use.state.callback';\nimport { timer } from '@gitroom/helpers/utils/timer';\nexport const PickPlatforms: FC<{\n  integrations: Integrations[];\n  selectedIntegrations: Integrations[];\n  onChange: (integrations: Integrations[], callback: () => void) => void;\n  singleSelect: boolean;\n  hide?: boolean;\n  isMain: boolean;\n  toolTip?: boolean;\n}> = (props) => {\n  const { hide, isMain, integrations, selectedIntegrations, onChange } = props;\n  const ref = useRef<HTMLDivElement>(null);\n  const [isLeft, setIsLeft] = useState(false);\n  const [isRight, setIsRight] = useState(false);\n  const [selectedAccounts, setSelectedAccounts] = useStateCallback<\n    Integrations[]\n  >(\n    selectedIntegrations.slice(0).map((p) => ({\n      ...p,\n    }))\n  );\n  useEffect(() => {\n    if (\n      props.singleSelect &&\n      selectedAccounts.length &&\n      integrations.indexOf(selectedAccounts?.[0]) === -1\n    ) {\n      addPlatform(integrations[0])();\n    }\n  }, [integrations, selectedAccounts]);\n  const checkLeftRight = (test = true) => {\n    const scrollWidth = ref.current?.scrollWidth;\n    const offsetWidth = +(ref.current?.offsetWidth || 0);\n    const scrollOffset = ref.current?.scrollLeft || 0;\n    const totalScroll = scrollOffset + offsetWidth + 100;\n    setIsLeft(!!scrollOffset);\n    setIsRight(!!scrollWidth && !!offsetWidth && scrollWidth > totalScroll);\n  };\n  const changeOffset = useCallback(\n    (offset: number) => () => {\n      if (ref.current) {\n        ref.current.scrollLeft += offset;\n        checkLeftRight();\n      }\n    },\n    [selectedIntegrations, integrations, selectedAccounts]\n  );\n  useEffect(() => {\n    checkLeftRight();\n  }, [selectedIntegrations, integrations]);\n  useMoveToIntegrationListener(\n    [integrations],\n    props.singleSelect,\n    ({ identifier, toPreview }: { identifier: string; toPreview: boolean }) => {\n      const findIntegration = integrations.find((p) => p.id === identifier);\n      if (findIntegration) {\n        addPlatform(findIntegration)();\n      }\n    }\n  );\n  const addPlatform = useCallback(\n    (integration: Integrations) => async () => {\n      const promises = [];\n      if (props.singleSelect) {\n        promises.push(\n          new Promise((res) => {\n            onChange([integration], () => {\n              res('');\n            });\n          })\n        );\n        promises.push(\n          new Promise((res) => {\n            setSelectedAccounts([integration], () => {\n              res('');\n            });\n          })\n        );\n        return;\n      }\n      if (selectedAccounts.some((account) => account.id === integration.id)) {\n        const changedIntegrations = selectedAccounts.filter(\n          ({ id }) => id !== integration.id\n        );\n        if (\n          !props.singleSelect &&\n          !(await deleteDialog(\n            'Are you sure you want to remove this platform?'\n          ))\n        ) {\n          return;\n        }\n        promises.push(\n          new Promise((res) => {\n            onChange(changedIntegrations, () => {\n              res('');\n            });\n          })\n        );\n        promises.push(\n          new Promise((res) => {\n            setSelectedAccounts(changedIntegrations, () => {\n              res('');\n            });\n          })\n        );\n      } else {\n        const changedIntegrations = [...selectedAccounts, integration];\n        promises.push(\n          new Promise((res) => {\n            onChange(changedIntegrations, () => {\n              res('');\n            });\n          })\n        );\n        promises.push(\n          new Promise((res) => {\n            setSelectedAccounts(changedIntegrations, () => {\n              res('');\n            });\n          })\n        );\n      }\n      await timer(500);\n      await Promise.all(promises);\n    },\n    [onChange, props.singleSelect, selectedAccounts, setSelectedAccounts]\n  );\n  const handler = async ({ integrationsId }: { integrationsId: string[] }) => {\n    const selected = selectedIntegrations.map((p) => p.id);\n    const notToRemove = selected.filter((p) => integrationsId.includes(p));\n    const toAdd = integrationsId.filter((p) => !selected.includes(p));\n    const newIntegrations = [...notToRemove, ...toAdd]\n      .map((id) => integrations.find((p) => p.id === id)!)\n      .filter((p) => p);\n    setSelectedAccounts(newIntegrations, () => {\n      console.log('changed');\n    });\n    onChange(newIntegrations, () => {\n      console.log('changed');\n    });\n  };\n  useCopilotReadable({\n    description: isMain\n      ? 'All available platforms channels'\n      : 'Possible platforms channels to edit',\n    value: JSON.stringify(integrations),\n  });\n  useCopilotAction(\n    {\n      name: isMain ? `addOrRemovePlatform` : 'setSelectedIntegration',\n      description: isMain\n        ? `Add or remove channels to schedule your post to, pass all the ids as array`\n        : 'Set selected integrations',\n      parameters: [\n        {\n          name: 'integrationsId',\n          type: 'string[]',\n          description: 'List of integrations id to set as selected',\n          required: true,\n        },\n      ],\n      handler,\n    },\n    [\n      addPlatform,\n      selectedAccounts,\n      integrations,\n      onChange,\n      props.singleSelect,\n      setSelectedAccounts,\n    ]\n  );\n  if (hide) {\n    return null;\n  }\n  return (\n    <div\n      className={clsx('flex select-none', props.singleSelect && 'gap-[10px]')}\n    >\n      {props.singleSelect && isLeft && (\n        <div className=\"flex items-center\">\n          {isLeft && (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n              onClick={changeOffset(-200)}\n            >\n              <path\n                d=\"M10.3541 12.6463C10.4006 12.6927 10.4374 12.7479 10.4626 12.8086C10.4877 12.8693 10.5007 12.9343 10.5007 13C10.5007 13.0657 10.4877 13.1308 10.4626 13.1915C10.4374 13.2522 10.4006 13.3073 10.3541 13.3538C10.3077 13.4002 10.2525 13.4371 10.1918 13.4622C10.1311 13.4874 10.0661 13.5003 10.0004 13.5003C9.9347 13.5003 9.86964 13.4874 9.80894 13.4622C9.74825 13.4371 9.6931 13.4002 9.64664 13.3538L4.64664 8.35378C4.60015 8.30735 4.56328 8.2522 4.53811 8.1915C4.51295 8.13081 4.5 8.06574 4.5 8.00003C4.5 7.93433 4.51295 7.86926 4.53811 7.80856C4.56328 7.74786 4.60015 7.69272 4.64664 7.64628L9.64664 2.64628C9.74046 2.55246 9.86771 2.49976 10.0004 2.49976C10.1331 2.49976 10.2603 2.55246 10.3541 2.64628C10.448 2.7401 10.5007 2.86735 10.5007 3.00003C10.5007 3.13272 10.448 3.25996 10.3541 3.35378L5.70727 8.00003L10.3541 12.6463Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          )}\n        </div>\n      )}\n      <div\n        className={clsx(\n          'flex-1 flex',\n          props.singleSelect && 'relative h-[40px]'\n        )}\n      >\n        <div\n          className={clsx(\n            props.singleSelect\n              ? 'absolute w-full h-[40px] flex flex-nowrap overflow-hidden transition-all'\n              : 'flex-1 flex'\n          )}\n          ref={ref}\n        >\n          <div className=\"innerComponent\">\n            <div className=\"flex gap-[10px] flex-wrap\">\n              {integrations\n                .filter((f) => !f.inBetweenSteps)\n                .map((integration) =>\n                  !props.singleSelect ? (\n                    <div\n                      key={integration.id}\n                      className=\"flex gap-[8px] items-center\"\n                      {...(props.toolTip && {\n                        'data-tooltip-id': 'tooltip',\n                        'data-tooltip-content': integration.name,\n                      })}\n                    >\n                      <div\n                        onClick={addPlatform(integration)}\n                        className={clsx(\n                          'cursor-pointer relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth filter transition-all duration-500',\n                          selectedAccounts.findIndex(\n                            (p) => p.id === integration.id\n                          ) === -1\n                            ? 'opacity-40'\n                            : ''\n                        )}\n                      >\n                        <Image\n                          src={integration.picture || '/no-picture.jpg'}\n                          className=\"rounded-full\"\n                          alt={integration.identifier}\n                          width={32}\n                          height={32}\n                        />\n                        {integration.identifier === 'youtube' ? (\n                          <img\n                            src=\"/icons/platforms/youtube.svg\"\n                            className=\"absolute z-10 bottom-0 -end-[5px]\"\n                            width={20}\n                          />\n                        ) : (\n                          <Image\n                            src={`/icons/platforms/${integration.identifier}.png`}\n                            className=\"rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth\"\n                            alt={integration.identifier}\n                            width={20}\n                            height={20}\n                          />\n                        )}\n                      </div>\n                    </div>\n                  ) : (\n                    <div key={integration.id} className=\"\">\n                      <div\n                        onClick={addPlatform(integration)}\n                        className={clsx(\n                          'cursor-pointer rounded-[50px] w-[200px] relative h-[40px] flex justify-center items-center bg-fifth filter transition-all duration-500',\n                          selectedAccounts.findIndex(\n                            (p) => p.id === integration.id\n                          ) === -1\n                            ? 'bg-third border border-third'\n                            : 'bg-customColor29 border border-customColor30'\n                        )}\n                      >\n                        <div className=\"flex items-center justify-center gap-[10px]\">\n                          <div className=\"relative\">\n                            <img\n                              src={integration.picture || '/no-picture.jpg'}\n                              className=\"rounded-full\"\n                              alt={integration.identifier}\n                              width={24}\n                              height={24}\n                            />\n                            <Image\n                              src={`/icons/platforms/${integration.identifier}.png`}\n                              className=\"rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth\"\n                              alt={integration.identifier}\n                              width={15}\n                              height={15}\n                            />\n                          </div>\n                          <div>\n                            {integration.name.slice(0, 10)}\n                            {integration.name.length > 10 ? '...' : ''}\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  )\n                )}\n            </div>\n          </div>\n        </div>\n      </div>\n      {props.singleSelect && isRight && (\n        <div className=\"flex items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n            viewBox=\"0 0 16 16\"\n            fill=\"none\"\n            className={!isRight ? 'pointer-events-none invisible' : ' '}\n            onClick={changeOffset(200)}\n          >\n            <path\n              d=\"M5.64586 12.6463C5.5994 12.6927 5.56255 12.7479 5.53741 12.8086C5.51227 12.8693 5.49933 12.9343 5.49933 13C5.49933 13.0657 5.51227 13.1308 5.53741 13.1915C5.56255 13.2522 5.5994 13.3073 5.64586 13.3538C5.69231 13.4002 5.74746 13.4371 5.80816 13.4622C5.86886 13.4874 5.93391 13.5003 5.99961 13.5003C6.0653 13.5003 6.13036 13.4874 6.19106 13.4622C6.25175 13.4371 6.3069 13.4002 6.35336 13.3538L11.3534 8.35378C11.3998 8.30735 11.4367 8.2522 11.4619 8.1915C11.487 8.13081 11.5 8.06574 11.5 8.00003C11.5 7.93433 11.487 7.86926 11.4619 7.80856C11.4367 7.74786 11.3998 7.69272 11.3534 7.64628L6.35336 2.64628C6.25954 2.55246 6.13229 2.49976 5.99961 2.49976C5.86692 2.49976 5.73968 2.55246 5.64586 2.64628C5.55204 2.7401 5.49933 2.86735 5.49933 3.00003C5.49933 3.13272 5.55204 3.25996 5.64586 3.35378L10.2927 8.00003L5.64586 12.6463Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/top.title.component.tsx",
    "content": "import { FC, ReactNode } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport clsx from 'clsx';\nimport { ExpandIcon, CollapseIcon } from '@gitroom/frontend/components/ui/icons';\n\nexport const TopTitle: FC<{\n  title: string;\n  shouldExpend?: boolean;\n  removeTitle?: boolean;\n  extraClass?: string;\n  expend?: () => void;\n  collapse?: () => void;\n  children?: ReactNode;\n  titleSize?: string;\n}> = (props) => {\n  const { title, removeTitle, children, shouldExpend, expend, collapse } =\n    props;\n  const t = useT();\n\n  // Translate the title using a key derived from the title itself\n  // This creates a consistent key pattern for each title\n  const translatedTitle = t(\n    // Convert to lowercase, replace spaces with underscores\n    `top_title_${title\n      .toLowerCase()\n      .replace(/\\s+/g, '_')\n      .replace(/[^\\w]/g, '')}`,\n    title\n  );\n\n  return (\n    <div\n      className={clsx(\n        'border-b flex items-center border-newBgLineColor -mx-[24px]',\n        props.extraClass ? props.extraClass : 'h-[57px]'\n      )}\n    >\n      <div className=\"px-[24px] flex flex-1 items-center\">\n        {!removeTitle && (\n          <div className={clsx('flex-1', props.titleSize)}>\n            {translatedTitle}\n          </div>\n        )}\n        {children}\n        {shouldExpend !== undefined && (\n          <div className=\"cursor-pointer\">\n            {!shouldExpend ? (\n              <ExpandIcon onClick={expend} className=\"text-white\" />\n            ) : (\n              <CollapseIcon onClick={collapse} className=\"text-white\" />\n            )}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.custom.provider.function.ts",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nexport const useCustomProviderFunction = () => {\n  const { integration } = useIntegration();\n  const fetch = useFetch();\n  const get = useCallback(\n    async (funcName: string, customData?: any) => {\n      const load = await fetch('/integrations/function', {\n        method: 'POST',\n        body: JSON.stringify({\n          name: funcName,\n          id: integration?.id!,\n          data: customData,\n        }),\n      });\n      if (load.status > 299 && load.status < 200) {\n        throw new Error('Failed to fetch');\n      }\n      return load.json();\n    },\n    [integration]\n  );\n  return {\n    get,\n  };\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.existing.data.tsx",
    "content": "import { createContext, FC, ReactNode, useContext } from 'react';\nimport { Post } from '@prisma/client';\nconst ExistingDataContext = createContext({\n  integration: '',\n  group: undefined as undefined | string,\n  posts: [] as Post[],\n  settings: {} as any,\n});\nexport const ExistingDataContextProvider: FC<{\n  children: ReactNode;\n  value: any;\n}> = ({ children, value }) => {\n  return (\n    <ExistingDataContext.Provider value={value}>\n      {children}\n    </ExistingDataContext.Provider>\n  );\n};\nexport const useExistingData = () => useContext(ExistingDataContext);\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.expend.tsx",
    "content": "'use client';\n\nimport EventEmitter from 'events';\nimport { useEffect, useState } from 'react';\nconst emitter = new EventEmitter();\nexport const useExpend = () => {\n  const [expend, setExpend] = useState(false);\n  useEffect(() => {\n    const hide = () => {\n      setExpend(false);\n    };\n    const show = () => {\n      setExpend(true);\n    };\n    emitter.on('hide', hide);\n    emitter.on('show', show);\n    return () => {\n      emitter.off('hide', hide);\n      emitter.off('show', show);\n    };\n  }, []);\n  return {\n    expend,\n    hide: () => {\n      emitter.emit('hide');\n    },\n    show: () => {\n      emitter.emit('show');\n    },\n  };\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.formatting.ts",
    "content": "import { useMemo } from 'react';\nexport const useFormatting = (\n  text: Array<{\n    content: string;\n    image?: Array<{\n      id: string;\n      path: string;\n    }>;\n    id?: string;\n  }>,\n  params: {\n    removeMarkdown?: boolean;\n    saveBreaklines?: boolean;\n    specialFunc?: (text: string) => any;\n    beforeSpecialFunc?: (text: string) => string;\n  }\n) => {\n  return useMemo(() => {\n    return text.map((value) => {\n      let newText = value.content;\n      if (params.beforeSpecialFunc) {\n        newText = params.beforeSpecialFunc(newText);\n      }\n      if (params.saveBreaklines) {\n        newText = newText.replace('\\n', '𝔫𝔢𝔴𝔩𝔦𝔫𝔢');\n      }\n      newText = newText.replace(/@\\w{1,15}/g, function (match) {\n        return `<strong>${match}</strong>`;\n      });\n      if (params.saveBreaklines) {\n        newText = newText.replace('𝔫𝔢𝔴𝔩𝔦𝔫𝔢', '\\n');\n      }\n      if (params.specialFunc) {\n        newText = params.specialFunc(newText);\n      }\n      return {\n        id: value.id,\n        text: newText,\n        images: value.image,\n        count:\n          params.removeMarkdown && params.saveBreaklines\n            ? newText.replace(/\\n/g, ' ').length\n            : newText.length,\n      };\n    });\n  }, [text]);\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.hide.top.editor.tsx",
    "content": "'use client';\n\nimport EventEmitter from 'events';\nimport { useEffect, useState } from 'react';\nconst emitter = new EventEmitter();\nexport const useHideTopEditor = () => {\n  const [hideTopEditor, setHideTopEditor] = useState(false);\n  useEffect(() => {\n    const hide = () => {\n      setHideTopEditor(true);\n    };\n    const show = () => {\n      setHideTopEditor(false);\n    };\n    emitter.on('hide', hide);\n    emitter.on('show', show);\n    return () => {\n      emitter.off('hide', hide);\n      emitter.off('show', show);\n    };\n  }, []);\n  return {\n    hideTopEditor,\n    hide: () => {\n      emitter.emit('hide');\n    },\n    show: () => {\n      emitter.emit('show');\n    },\n  };\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.integration.list.tsx",
    "content": "'use client';\n\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useCallback } from 'react';\nimport useSWR from 'swr';\n\nexport const useIntegrationList = () => {\n  const fetch = useFetch();\n\n  const load = useCallback(async (path: string) => {\n    return (await (await fetch(path)).json()).integrations;\n  }, []);\n\n  return useSWR('/integrations/list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n};"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.integration.ts",
    "content": "'use client';\n\nimport { createContext, useContext } from 'react';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport dayjs from 'dayjs';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nexport const IntegrationContext = createContext<{\n  date: dayjs.Dayjs;\n  integration: Integrations | undefined;\n  allIntegrations: Integrations[];\n  value: Array<{\n    content: string;\n    id?: string;\n    image?: Array<{\n      path: string;\n      id: string;\n    }>;\n  }>;\n}>({\n  integration: undefined,\n  value: [],\n  date: newDayjs(),\n  allIntegrations: [],\n});\nexport const useIntegration = () => useContext(IntegrationContext);\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.move.to.integration.tsx",
    "content": "'use client';\n\nimport EventEmitter from 'events';\nimport { useCallback, useEffect } from 'react';\nconst emitter = new EventEmitter();\nexport const useMoveToIntegration = () => {\n  return useCallback(\n    ({\n      identifier,\n      toPreview,\n    }: {\n      identifier: string;\n      toPreview?: boolean;\n    }) => {\n      emitter.emit('moveToIntegration', {\n        identifier,\n        toPreview,\n      });\n    },\n    []\n  );\n};\nexport const useMoveToIntegrationListener = (\n  useEffectParams: any[],\n  enabled: boolean,\n  callback: ({\n    identifier,\n    toPreview,\n  }: {\n    identifier: string;\n    toPreview: boolean;\n  }) => void\n) => {\n  useEffect(() => {\n    if (!enabled) {\n      return;\n    }\n    return load();\n  }, useEffectParams);\n  const load = useCallback(() => {\n    emitter.off('moveToIntegration', callback);\n    emitter.on('moveToIntegration', callback);\n    return () => {\n      emitter.off('moveToIntegration', callback);\n    };\n  }, useEffectParams);\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/helpers/use.values.ts",
    "content": "import { useEffect, useMemo } from 'react';\nimport { useForm, useFormContext } from 'react-hook-form';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { IsOptional } from 'class-validator';\n\nclass Empty {\n  @IsOptional()\n  empty: string;\n}\n\nconst finalInformation = {} as {\n  [key: string]: {\n    posts: Array<{\n      id?: string;\n      content: string;\n      media?: Array<string>;\n    }>;\n    settings: () => object;\n    trigger: () => Promise<boolean>;\n    isValid: boolean;\n    checkValidity?: (\n      value: Array<\n        Array<{\n          path: string;\n        }>\n      >,\n      settings: any,\n      additionalSettings: any,\n    ) => Promise<string | true>;\n    maximumCharacters?: number;\n  };\n};\nexport const useValues = (\n  initialValues: object,\n  integration: string,\n  identifier: string,\n  value: Array<{\n    id?: string;\n    content: string;\n    media?: Array<string>;\n  }>,\n  dto: any,\n  checkValidity?: (\n    value: Array<\n      Array<{\n        path: string;\n      }>\n    >,\n    settings: any,\n    additionalSettings: any,\n  ) => Promise<string | true>,\n  maximumCharacters?: number\n) => {\n\n  const form = useForm({\n    resolver: classValidatorResolver(dto || Empty),\n    values: initialValues,\n    mode: 'onChange',\n    criteriaMode: 'all',\n  });\n\n  const getValues = useMemo(() => {\n    return () => ({\n      ...form.getValues(),\n      __type: identifier,\n    });\n  }, [form, integration]);\n\n  // @ts-ignore\n  finalInformation[integration] = finalInformation[integration] || {};\n  finalInformation[integration].posts = value;\n  finalInformation[integration].isValid = form.formState.isValid;\n  finalInformation[integration].settings = getValues;\n  finalInformation[integration].trigger = form.trigger;\n  if (checkValidity) {\n    finalInformation[integration].checkValidity = checkValidity;\n  }\n  if (maximumCharacters) {\n    finalInformation[integration].maximumCharacters = maximumCharacters;\n  }\n  useEffect(() => {\n    return () => {\n      delete finalInformation[integration];\n    };\n  }, []);\n  return form;\n};\nexport const useSettings = () => useFormContext();\nexport const getValues = () => finalInformation;\nexport const resetValues = () => {\n  Object.keys(finalInformation).forEach((key) => {\n    delete finalInformation[key];\n  });\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/information.component.tsx",
    "content": "'use client';\n\nimport React, { FC, Fragment, useMemo } from 'react';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport clsx from 'clsx';\nimport Image from 'next/image';\nimport { capitalize } from 'lodash';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nconst Valid: FC = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M6 7.33333L8 9.33333L14.6667 2.66667M10.6667 2H5.2C4.0799 2 3.51984 2 3.09202 2.21799C2.71569 2.40973 2.40973 2.71569 2.21799 3.09202C2 3.51984 2 4.07989 2 5.2V10.8C2 11.9201 2 12.4802 2.21799 12.908C2.40973 13.2843 2.71569 13.5903 3.09202 13.782C3.51984 14 4.07989 14 5.2 14H10.8C11.9201 14 12.4802 14 12.908 13.782C13.2843 13.5903 13.5903 13.2843 13.782 12.908C14 12.4802 14 11.9201 14 10.8V8\"\n        stroke=\"#00EB75\"\n        strokeWidth=\"1.2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n};\n\nconst Invalid: FC = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <g clip-path=\"url(#clip0_2482_97670)\">\n        <path\n          d=\"M8.00049 6.00015V8.66682M8.00049 11.3335H8.00715M7.07737 2.59464L1.59411 12.0657C1.28997 12.591 1.1379 12.8537 1.16038 13.0693C1.17998 13.2573 1.2785 13.4282 1.4314 13.5394C1.60671 13.6668 1.91022 13.6668 2.51723 13.6668H13.4837C14.0908 13.6668 14.3943 13.6668 14.5696 13.5394C14.7225 13.4282 14.821 13.2573 14.8406 13.0693C14.8631 12.8537 14.711 12.591 14.4069 12.0657L8.92361 2.59463C8.62056 2.07119 8.46904 1.80947 8.27135 1.72157C8.09892 1.64489 7.90206 1.64489 7.72962 1.72157C7.53193 1.80947 7.38041 2.07119 7.07737 2.59464Z\"\n          stroke=\"white\"\n          strokeWidth=\"1.2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0_2482_97670\">\n          <rect width=\"16\" height=\"16\" fill=\"white\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n};\nexport const InformationComponent: FC<{\n  chars: Record<string, number>;\n  totalChars: number;\n  totalAllowedChars: number;\n  isPicture: boolean;\n}> = ({ totalChars, totalAllowedChars, chars, isPicture }) => {\n  const t = useT();\n  const { isGlobal, selectedIntegrations, internal } = useLaunchStore(\n    useShallow((state) => ({\n      isGlobal: state.current === 'global',\n      selectedIntegrations: state.selectedIntegrations,\n      internal: state.internal,\n    }))\n  );\n\n  const isInternal = useMemo(() => {\n    if (!isGlobal) {\n      return [];\n    }\n    return selectedIntegrations.map((p) => {\n      const findIt = internal.find(\n        (a) => a.integration.id === p.integration.id\n      );\n\n      return !!findIt;\n    });\n  }, [isGlobal, internal, selectedIntegrations]);\n\n  const isValid = useMemo(() => {\n    if (!isPicture && !totalChars) {\n      return false;\n    }\n\n    if (totalChars > totalAllowedChars && !isGlobal) {\n      return false;\n    }\n\n    if (totalChars <= totalAllowedChars && !isGlobal) {\n      return true;\n    }\n\n    if (\n      selectedIntegrations.some((p, index) => {\n        if (isInternal[index]) {\n          return false;\n        }\n\n        return totalChars > (chars?.[p.integration.id] || 0);\n      })\n    ) {\n      return false;\n    }\n\n    return true;\n  }, [totalAllowedChars, totalChars, isInternal, isPicture, chars]);\n\n  const globalDisplayLimit = useMemo(() => {\n    if (!isGlobal || !selectedIntegrations.length) {\n      return null;\n    }\n\n    // Get all limits from non-internal integrations, sorted ascending\n    const limits = selectedIntegrations\n      .map((p, index) => ({\n        limit: chars?.[p.integration.id] || 0,\n        isInternal: isInternal[index],\n      }))\n      .filter((item) => !item.isInternal && item.limit > 0)\n      .map((item) => item.limit)\n      .sort((a, b) => a - b);\n\n    if (!limits.length) {\n      return null;\n    }\n\n    // Find the smallest limit that hasn't been exceeded yet\n    // If all are exceeded, show the smallest one\n    const validLimit = limits.find((limit) => totalChars <= limit);\n    return validLimit ?? limits[0];\n  }, [isGlobal, selectedIntegrations, chars, isInternal, totalChars]);\n\n  return (\n    <div\n      className={clsx(\n        'group rounded-[6px] gap-[4px] h-[30px] px-[6px] flex justify-center items-center relative',\n        isValid ? 'border border-newColColor' : 'bg-[#FF3F3F]'\n      )}\n    >\n      {isValid ? <Valid /> : <Invalid />}\n\n      {!isGlobal && (\n        <div className={clsx(\"text-[10px] font-[600] flex justify-center items-center\", !isValid && 'text-white')}>\n          {totalChars}/{totalAllowedChars}\n        </div>\n      )}\n      {isGlobal && globalDisplayLimit !== null && (\n        <div className={clsx(\"text-[10px] font-[600] flex justify-center items-center\", !isValid && 'text-white')}>\n          {totalChars}/{globalDisplayLimit}\n        </div>\n      )}\n      {((isGlobal && selectedIntegrations.length) || !isValid) && (\n        <svg\n          className={clsx('group-hover:rotate-180', !isValid && 'text-white')}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M5.4563 6L10.5437 6C10.9494 6 11.1526 6.56798 10.8657 6.90016L8.32201 9.84556C8.14417 10.0515 7.85583 10.0515 7.67799 9.84556L5.13429 6.90016C4.84741 6.56798 5.05059 6 5.4563 6Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      )}\n      {((isGlobal && selectedIntegrations.length) || !isValid) && (\n        <div\n          className={clsx(\n            'z-[300] hidden rounded-[12px] bg-newBgColorInner group-hover:flex absolute end-0 bottom-[100%] mb-[5px] p-[12px] flex-col',\n            isValid ? 'border border-newColColor' : 'border border-[#FF3F3F]'\n          )}\n        >\n          {!isPicture && !totalChars && (\n            <div\n              className={clsx(\n                'text-sm text-[#FF3F3F] whitespace-nowrap',\n                isGlobal && selectedIntegrations.length && 'mb-[12px]'\n              )}\n            >\n              {t('your_post_should_have_at_least_one_character_or_one_image', 'Your post should have at least one character or one image.')}\n            </div>\n          )}\n          {isGlobal && (\n            <div className=\"grid grid-cols-[auto_auto_auto] text-[14px] font-[500] gap-[8px] items-center\">\n              {selectedIntegrations.map((p, index) => (\n                <Fragment key={p.integration.id}>\n                  <div>\n                    <Image\n                      src={`/icons/platforms/${p.integration.identifier}.png`}\n                      alt={p.integration.name}\n                      className=\"rounded-[4px] w-[16px] h-[16px] min-w-[16px] min-h-[16px]\"\n                      width={16}\n                      height={16}\n                    />\n                  </div>\n                  <div\n                    className={clsx(\n                      'whitespace-nowrap',\n                      isInternal?.[index]\n                        ? ''\n                        : totalChars > (chars?.[p.integration.id] || 0)\n                        ? 'text-[#FF3F3F]'\n                        : ''\n                    )}\n                  >\n                    {p.integration.name} (\n                    {capitalize(p.integration.identifier.split('-')[0])}):\n                  </div>\n                  <div\n                    className={clsx(\n                      'whitespace-nowrap',\n                      isInternal?.[index]\n                        ? ''\n                        : totalChars > (chars?.[p.integration.id] || 0)\n                        ? 'text-[#FF3F3F]'\n                        : ''\n                    )}\n                  >\n                    {isInternal?.[index]\n                      ? t('internal_edit', 'Internal Edit')\n                      : `${totalChars}/${chars?.[p.integration.id] || 0}`}\n                  </div>\n                </Fragment>\n              ))}\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/integration.redirect.component.tsx",
    "content": "'use client';\n\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport timezone from 'dayjs/plugin/timezone';\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation';\nimport { FC, useEffect } from 'react';\ndayjs.extend(utc);\ndayjs.extend(timezone);\nexport const IntegrationRedirectComponent: FC = () => {\n  const offset = dayjs.tz().utcOffset();\n  const pathname = usePathname();\n  const searchParams = useSearchParams();\n  const router = useRouter();\n  const newUrl = `${pathname}/continue?${searchParams.toString()}&timezone=${offset}`;\n  useEffect(() => {\n    router.push(newUrl);\n  }, [newUrl]);\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/internal.channels.tsx",
    "content": "import { FC, useEffect, useState } from 'react';\nimport {\n  Integrations,\n  useCalendar,\n} from '@gitroom/frontend/components/launches/calendar.context';\nimport { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { Select } from '@gitroom/react/form/select';\nimport { Slider } from '@gitroom/react/form/slider';\nimport { Input } from '@gitroom/react/form/input';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst delayOptions = [\n  {\n    name: 'Immediately',\n    value: 0,\n  },\n  {\n    name: '1 hour',\n    value: 3600000,\n  },\n  {\n    name: '2 hours',\n    value: 7200000,\n  },\n  {\n    name: '3 hours',\n    value: 10800000,\n  },\n  {\n    name: '8 hours',\n    value: 28800000,\n  },\n  {\n    name: '12 hours',\n    value: 43200000,\n  },\n  {\n    name: '15 hours',\n    value: 54000000,\n  },\n  {\n    name: '24 hours',\n    value: 86400000,\n  },\n];\nexport const InternalChannels: FC<{\n  plugs: {\n    identifier: string;\n    title: string;\n    description: string;\n    pickIntegration: string[];\n    fields: {\n      name: string;\n      description: string;\n      type: string;\n      placeholder: string;\n      validation?: RegExp;\n    }[];\n  }[];\n}> = (props) => {\n  const { plugs } = props;\n  return (\n    <div>\n      {plugs.map((plug, index) => (\n        <Plug plug={plug} key={index} />\n      ))}\n    </div>\n  );\n};\nconst PlugField: FC<{\n  plugIdentifier: string;\n  field: {\n    name: string;\n    description: string;\n    type: string;\n    placeholder: string;\n    validation?: RegExp;\n  };\n}> = ({ plugIdentifier, field }) => {\n  const fieldName = `plug--${plugIdentifier}--${field.name}`;\n\n  if (field.type === 'textarea') {\n    return (\n      <Textarea\n        label={field.description}\n        name={fieldName}\n        placeholder={field.placeholder}\n      />\n    );\n  }\n\n  return (\n    <Input\n      label={field.description}\n      name={fieldName}\n      placeholder={field.placeholder}\n    />\n  );\n};\n\nconst Plug: FC<{\n  plug: {\n    identifier: string;\n    title: string;\n    description: string;\n    pickIntegration: string[];\n    fields: {\n      name: string;\n      description: string;\n      type: string;\n      placeholder: string;\n      validation?: RegExp;\n    }[];\n  };\n}> = ({ plug }) => {\n  const { allIntegrations, integration } = useIntegration();\n  const t = useT();\n\n  const { watch, setValue, control, register } = useSettings();\n  const [load, setLoad] = useState(false);\n  const val = watch(`plug--${plug.identifier}--integrations`);\n  const active = watch(`plug--${plug.identifier}--active`);\n  useEffect(() => {\n    setTimeout(() => {\n      setLoad(true);\n    }, 20);\n  }, []);\n  const [localValue, setLocalValue] = useState<Integrations[]>(\n    (val || []).map((p: any) => ({\n      ...p,\n    }))\n  );\n  useEffect(() => {\n    setValue(`plug--${plug.identifier}--integrations`, [...localValue]);\n  }, [localValue, plug, setValue]);\n  const [allowedIntegrations] = useState(\n    allIntegrations.filter(\n      (i) =>\n        plug.pickIntegration.includes(i.identifier) && integration?.id !== i.id\n    )\n  );\n  if (!load) {\n    return null;\n  }\n  return (\n    <div\n      key={plug.title}\n      className=\"flex flex-col gap-[10px] border-tableBorder border p-[15px] rounded-lg\"\n    >\n      <div className=\"flex items-center\">\n        <div className=\"flex-1\">{plug.title}</div>\n        <div>\n          <Slider\n            value={active ? 'on' : 'off'}\n            onChange={(p) =>\n              setValue(`plug--${plug.identifier}--active`, p === 'on')\n            }\n            fill={true}\n          />\n        </div>\n      </div>\n      <div className=\"w-full max-w-[600px] overflow-y-auto pb-[10px] text-[12px] flex flex-col gap-[10px]\">\n        {!allowedIntegrations.length ? (\n          'No available accounts'\n        ) : (\n          <div\n            className={clsx(\n              'flex flex-col gap-[10px]',\n              !active && 'opacity-25 pointer-events-none'\n            )}\n          >\n            <div>{plug.description}</div>\n            <Select\n              label=\"Delay\"\n              hideErrors={true}\n              {...register(`plug--${plug.identifier}--delay`)}\n            >\n              {delayOptions.map((p) => (\n                <option key={p.name} value={p.value}>\n                  {p.name}\n                </option>\n              ))}\n            </Select>\n            {plug.fields.length > 0 && (\n              <div className=\"flex flex-col gap-[10px]\">\n                {plug.fields.map((field) => (\n                  <PlugField\n                    key={field.name}\n                    plugIdentifier={plug.identifier}\n                    field={field}\n                  />\n                ))}\n              </div>\n            )}\n            <div>\n              {t('accounts_that_will_engage', 'Accounts that will engage:')}\n            </div>\n            <PickPlatforms\n              hide={false}\n              integrations={allowedIntegrations}\n              selectedIntegrations={localValue}\n              singleSelect={false}\n              isMain={true}\n              onChange={setLocalValue}\n            />\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/launches.component.tsx",
    "content": "'use client';\n\nimport { AddProviderButton } from '@gitroom/frontend/components/launches/add.provider.component';\nimport { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport Image from 'next/image';\nimport { capitalize, groupBy, orderBy } from 'lodash';\nimport { CalendarWeekProvider } from '@gitroom/frontend/components/launches/calendar.context';\nimport { Filters } from '@gitroom/frontend/components/launches/filters';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport clsx from 'clsx';\nimport { useUser } from '../layout/user.context';\nimport { Menu } from '@gitroom/frontend/components/launches/menu/menu';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { Integration } from '@prisma/client';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nimport { Calendar } from './calendar';\nimport { useDrag, useDrop } from 'react-dnd';\nimport { DNDProvider } from '@gitroom/frontend/components/launches/helpers/dnd.provider';\nimport { GeneratorComponent } from './generator/generator';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { NewPost } from '@gitroom/frontend/components/launches/new.post';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useIntegrationList } from '@gitroom/frontend/components/launches/helpers/use.integration.list';\nimport useCookie from 'react-use-cookie';\nimport { Onboarding } from '@gitroom/frontend/components/onboarding/onboarding';\n\nexport const SVGLine = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"5\"\n      height=\"52\"\n      viewBox=\"0 0 5 52\"\n      fill=\"none\"\n      className=\"rtl:rotate-180\"\n    >\n      <path\n        d=\"M0.5 4C0.5 1.79086 2.29086 0 4.5 0V52C2.29086 52 0.5 50.2091 0.5 48V4Z\"\n        fill=\"url(#paint0_linear_1930_1119)\"\n      />\n      <path\n        d=\"M0.5 4C0.5 1.79086 2.29086 0 4.5 0V52C2.29086 52 0.5 50.2091 0.5 48V4Z\"\n        fill=\"url(#paint1_radial_1930_1119)\"\n      />\n      <defs>\n        <linearGradient\n          id=\"paint0_linear_1930_1119\"\n          x1=\"-7\"\n          y1=\"-27.7727\"\n          x2=\"-2.58929\"\n          y2=\"-28.6843\"\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"#662FDA\" />\n          <stop offset=\"1\" stopColor=\"#5720CB\" />\n        </linearGradient>\n        <radialGradient\n          id=\"paint1_radial_1930_1119\"\n          cx=\"0\"\n          cy=\"0\"\n          r=\"1\"\n          gradientUnits=\"userSpaceOnUse\"\n          gradientTransform=\"translate(1.19333 7.45342) rotate(21.2064) scale(16.1503 188.627)\"\n        >\n          <stop stopColor=\"#8C66FF\" />\n          <stop offset=\"1\" stopColor=\"#8C66FF\" stopOpacity=\"0\" />\n        </radialGradient>\n      </defs>\n    </svg>\n  );\n};\ninterface MenuComponentInterface {\n  refreshChannel: (\n    integration: Integration & {\n      identifier: string;\n    }\n  ) => () => void;\n  collapsed: boolean;\n  continueIntegration: (integration: Integration) => () => void;\n  totalNonDisabledChannels: number;\n  mutate: (shouldReload?: boolean) => void;\n  update: (shouldReload: boolean) => void;\n}\nexport const OpenClose: FC<{\n  isOpen: boolean;\n}> = (props) => {\n  const { isOpen } = props;\n  return (\n    <svg\n      width=\"11\"\n      height=\"6\"\n      viewBox=\"0 0 22 12\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={clsx(\n        'rotate-180 transition-all',\n        isOpen ? 'rotate-180' : 'rotate-90'\n      )}\n    >\n      <path\n        d=\"M21.9245 11.3823C21.8489 11.5651 21.7207 11.7213 21.5563 11.8312C21.3919 11.9411 21.1986 11.9998 21.0008 11.9998H1.00079C0.802892 12 0.609399 11.9414 0.444805 11.8315C0.280212 11.7217 0.151917 11.5654 0.076165 11.3826C0.000412494 11.1998 -0.0193921 10.9986 0.0192583 10.8045C0.0579087 10.6104 0.153276 10.4322 0.293288 10.2923L10.2933 0.29231C10.3862 0.199333 10.4964 0.125575 10.6178 0.0752506C10.7392 0.0249263 10.8694 -0.000976562 11.0008 -0.000976562C11.1322 -0.000976562 11.2623 0.0249263 11.3837 0.0752506C11.5051 0.125575 11.6154 0.199333 11.7083 0.29231L21.7083 10.2923C21.8481 10.4322 21.9433 10.6105 21.9818 10.8045C22.0202 10.9985 22.0003 11.1996 21.9245 11.3823Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n};\nexport const MenuGroupComponent: FC<\n  MenuComponentInterface & {\n    changeItemGroup: (id: string, group: string) => void;\n    group: {\n      id: string;\n      name: string;\n      values: Array<\n        Integration & {\n          identifier: string;\n          changeProfilePicture: boolean;\n          changeNickName: boolean;\n        }\n      >;\n    };\n  }\n> = (props) => {\n  const {\n    group,\n    mutate,\n    update,\n    continueIntegration,\n    totalNonDisabledChannels,\n    refreshChannel,\n    changeItemGroup,\n    collapsed,\n  } = props;\n  const [isOpen, setIsOpen] = useState(\n    !!+(localStorage.getItem(group.name + '_isOpen') || '1')\n  );\n  const changeOpenClose = useCallback(\n    (e: any) => {\n      setIsOpen(!isOpen);\n      localStorage.setItem(group.name + '_isOpen', isOpen ? '0' : '1');\n      e.stopPropagation();\n    },\n    [isOpen]\n  );\n  const [collectedProps, drop] = useDrop(() => ({\n    accept: 'menu',\n    drop: (\n      item: {\n        id: string;\n      },\n      monitor\n    ) => {\n      changeItemGroup(item.id, group.id);\n    },\n    collect: (monitor) => ({\n      isOver: !!monitor.isOver(),\n    }),\n  }));\n  return (\n    <div\n      className=\"gap-[16px] flex flex-col relative\"\n      // @ts-ignore\n      ref={drop}\n    >\n      {collectedProps.isOver && (\n        <div className=\"absolute start-0 top-0 w-full h-full pointer-events-none\">\n          <div className=\"w-full h-full start-0 top-0 relative\">\n            <div className=\"bg-white/30 w-full h-full p-[8px] box-content rounded-md\" />\n          </div>\n        </div>\n      )}\n      {!!group.name && (\n        <div\n          className=\"flex items-center gap-[5px] cursor-pointer\"\n          onClick={changeOpenClose}\n        >\n          <div>\n            <OpenClose isOpen={isOpen} />\n          </div>\n          <div\n            className=\"line-clamp-1\"\n            {...(collapsed\n              ? {\n                  'data-tooltip-id': 'tooltip',\n                  'data-tooltip-content': group.name,\n                }\n              : {})}\n          >\n            {group.name}\n          </div>\n        </div>\n      )}\n      <div\n        className={clsx(\n          'gap-[12px] flex flex-col relative',\n          !isOpen && 'hidden'\n        )}\n      >\n        {group.values.map((integration) => (\n          <MenuComponent\n            collapsed={collapsed}\n            key={integration.id}\n            integration={integration}\n            mutate={mutate}\n            continueIntegration={continueIntegration}\n            update={update}\n            refreshChannel={refreshChannel}\n            totalNonDisabledChannels={totalNonDisabledChannels}\n          />\n        ))}\n      </div>\n    </div>\n  );\n};\nexport const MenuComponent: FC<\n  MenuComponentInterface & {\n    integration: Integration & {\n      identifier: string;\n      changeProfilePicture: boolean;\n      changeNickName: boolean;\n      refreshNeeded?: boolean;\n    };\n  }\n> = (props) => {\n  const {\n    totalNonDisabledChannels,\n    continueIntegration,\n    refreshChannel,\n    mutate,\n    update,\n    integration,\n    collapsed,\n  } = props;\n  const user = useUser();\n  const t = useT();\n  const [collected, drag, dragPreview] = useDrag(() => ({\n    type: 'menu',\n    item: {\n      id: integration.id,\n    },\n  }));\n  return (\n    <div\n      // @ts-ignore\n      ref={dragPreview}\n      {...(integration.refreshNeeded && {\n        onClick: refreshChannel(integration),\n        'data-tooltip-id': 'tooltip',\n        'data-tooltip-content': t(\n          'channel_disconnected_click_to_reconnect',\n          'Channel disconnected, click to reconnect.'\n        ),\n      })}\n      {...(collapsed\n        ? {\n            'data-tooltip-id': 'tooltip',\n            'data-tooltip-content': integration.name,\n          }\n        : {})}\n      key={integration.id}\n      className={clsx(\n        'flex gap-[12px] items-center bg-newBgColorInner hover:bg-boxHover group/profile transition-all rounded-e-[8px]',\n        integration.refreshNeeded && 'cursor-pointer'\n      )}\n    >\n      <div\n        className={clsx(\n          'relative gap-[6px] flex justify-center items-center',\n          integration.disabled && 'opacity-50'\n        )}\n      >\n        <div className=\"h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity\">\n          <SVGLine />\n        </div>\n        {(integration.inBetweenSteps || integration.refreshNeeded) && (\n          <div\n            className=\"absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer\"\n            onClick={\n              integration.refreshNeeded\n                ? refreshChannel(integration)\n                : continueIntegration(integration)\n            }\n          >\n            <div className=\"bg-red-500 w-[15px] h-[15px] rounded-full start-[5px] top-[5px] absolute z-[200] text-[10px] flex justify-center items-center\">\n              !\n            </div>\n            <div className=\"bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]\" />\n          </div>\n        )}\n        <ImageWithFallback\n          fallbackSrc={'/no-picture.jpg'}\n          src={integration.picture || '/no-picture.jpg'}\n          className=\"rounded-[8px] min-w-[36px] min-h-[36px]\"\n          alt={integration.identifier}\n          width={36}\n          height={36}\n        />\n        {integration.identifier === 'youtube' ? (\n          <img\n            src=\"/icons/platforms/youtube.svg\"\n            className=\"absolute z-10 bottom-[5px] -end-[5px]\"\n            width={20}\n          />\n        ) : (\n          <Image\n            src={`/icons/platforms/${integration.identifier}.png`}\n            className=\"rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth\"\n            alt={integration.identifier}\n            width={18.41}\n            height={18.41}\n          />\n        )}\n      </div>\n      <div\n        // @ts-ignore\n        ref={drag}\n        {...(integration.disabled &&\n        totalNonDisabledChannels === user?.totalChannels\n          ? {\n              'data-tooltip-id': 'tooltip',\n              'data-tooltip-content': t(\n                'channel_disabled_upgrade_plan',\n                'This channel is disabled, please upgrade your plan to enable it.'\n              ),\n            }\n          : {})}\n        role=\"Handle\"\n        className={clsx(\n          'group-[.sidebar]:hidden flex-1 whitespace-nowrap text-ellipsis overflow-hidden cursor-move',\n          integration.disabled && 'opacity-50'\n        )}\n      >\n        {integration.name}\n      </div>\n      <Menu\n        canChangeProfilePicture={integration.changeProfilePicture}\n        canChangeNickName={integration.changeNickName}\n        refreshChannel={refreshChannel}\n        mutate={mutate}\n        onChange={update}\n        id={integration.id}\n        canEnable={\n          user?.totalChannels! > totalNonDisabledChannels &&\n          integration.disabled\n        }\n        canDisable={!integration.disabled}\n      />\n    </div>\n  );\n};\nexport const LaunchesComponent = () => {\n  const fetch = useFetch();\n  const user = useUser();\n  const { billingEnabled } = useVariables();\n  const router = useRouter();\n  const search = useSearchParams();\n  const toast = useToaster();\n  const fireEvents = useFireEvents();\n  const t = useT();\n  const [reload, setReload] = useState(false);\n  const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0');\n  const [mode] = useCookie('mode', 'dark');\n  const { isLoading, data: integrations, mutate } = useIntegrationList();\n\n  const totalNonDisabledChannels = useMemo(() => {\n    return (\n      integrations?.filter((integration: any) => !integration.disabled)\n        ?.length || 0\n    );\n  }, [integrations]);\n  const changeItemGroup = useCallback(\n    async (id: string, group: string) => {\n      mutate(\n        integrations.map((integration: any) => {\n          if (integration.id === id) {\n            return {\n              ...integration,\n              customer: {\n                id: group,\n              },\n            };\n          }\n          return integration;\n        }),\n        false\n      );\n      await fetch(`/integrations/${id}/group`, {\n        method: 'PUT',\n        body: JSON.stringify({\n          group,\n        }),\n      });\n      mutate();\n    },\n    [integrations]\n  );\n  const sortedIntegrations = useMemo(() => {\n    return orderBy(\n      integrations,\n      ['type', 'disabled', 'identifier'],\n      ['desc', 'asc', 'asc']\n    );\n  }, [integrations]);\n  const menuIntegrations = useMemo(() => {\n    return orderBy(\n      Object.values(\n        groupBy(sortedIntegrations, (o) => o?.customer?.id || '')\n      ).map((p) => ({\n        name: (p[0].customer?.name || '') as string,\n        id: (p[0].customer?.id || '') as string,\n        isEmpty: p.length === 0,\n        values: orderBy(\n          p,\n          ['type', 'disabled', 'identifier'],\n          ['desc', 'asc', 'asc']\n        ),\n      })),\n      ['isEmpty', 'name'],\n      ['desc', 'asc']\n    );\n  }, [sortedIntegrations]);\n  const update = useCallback(async (shouldReload: boolean) => {\n    if (shouldReload) {\n      setReload(true);\n    }\n    await mutate();\n    if (shouldReload) {\n      setReload(false);\n    }\n  }, []);\n  const continueIntegration = useCallback(\n    (integration: any) => async () => {\n      router.push(\n        `/launches?added=${integration.identifier}&continue=${integration.id}`\n      );\n    },\n    []\n  );\n  const refreshChannel = useCallback(\n    (\n        integration: Integration & {\n          identifier: string;\n        }\n      ) =>\n      async () => {\n        const { url } = await (\n          await fetch(\n            `/integrations/social/${integration.identifier}?refresh=${integration.internalId}`,\n            {\n              method: 'GET',\n            }\n          )\n        ).json();\n        window.location.href = url;\n      },\n    []\n  );\n  useEffect(() => {\n    if (typeof window === 'undefined') {\n      return;\n    }\n    if (search.get('msg')) {\n      toast.show(search.get('msg')!, 'success');\n      window?.opener?.postMessage(\n        {\n          msg: search.get('msg')!,\n          success: false,\n        },\n        '*'\n      );\n    }\n    if (search.get('added')) {\n      fireEvents('channel_added');\n      window?.opener?.postMessage(\n        {\n          msg: t('channel_added', 'Channel added'),\n          success: true,\n        },\n        '*'\n      );\n    }\n    if (window.opener) {\n      window.close();\n    }\n  }, []);\n  if (isLoading || reload) {\n    return (\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-1 flex-col gap-[15px] transition-all items-center justify-center\">\n        <LoadingComponent />\n      </div>\n    );\n  }\n\n  // @ts-ignore\n  return (\n    <DNDProvider>\n      <Onboarding />\n      <CalendarWeekProvider integrations={sortedIntegrations}>\n        <div\n          className={clsx(\n            'flex relative flex-col',\n            collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]'\n          )}\n        >\n          <div\n            className={clsx(\n              'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all absolute start-0 top-0 w-full h-full overflow-x-hidden overflow-y-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor'\n            )}\n          >\n            <div className=\"flex items-center\">\n              <h2 className=\"group-[.sidebar]:hidden flex-1 text-[20px] font-[500]\">\n                {t('channels')}\n              </h2>\n              <div\n                onClick={() =>\n                  setCollapseMenu(collapseMenu === '1' ? '0' : '1')\n                }\n                className=\"group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none\"\n              >\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"7\"\n                  height=\"13\"\n                  viewBox=\"0 0 7 13\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M6 11.5L1 6.5L6 1.5\"\n                    stroke=\"currentColor\"\n                    strokeWidth=\"1.5\"\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                  />\n                </svg>\n              </div>\n            </div>\n            <div className=\"flex flex-col gap-[8px] group-[.sidebar]:mx-auto group-[.sidebar]:w-[44px]\">\n              <AddProviderButton update={() => update(true)} />\n              <div className=\"flex gap-[8px] group-[.sidebar]:flex-col\">\n                {sortedIntegrations?.length > 0 && <NewPost />}\n                {sortedIntegrations?.length > 0 &&\n                  user?.tier?.ai &&\n                  billingEnabled && <GeneratorComponent />}\n              </div>\n            </div>\n            <div className=\"gap-[32px] flex flex-col select-none flex-1\">\n              {sortedIntegrations.length === 0 && collapseMenu === '0' && (\n                <div className=\"flex-1 max-h-[500px] justify-center items-center flex\">\n                  <div className=\"flex flex-col gap-[12px] text-center\">\n                    <img\n                      src={\n                        mode === 'dark'\n                          ? '/no-channels.svg'\n                          : '/no-channels-colors.svg'\n                      }\n                      alt=\"No channels\"\n                      className=\"mx-auto min-w-[100%]\"\n                    />\n                    <div className=\"font-[600] text-[20px]\">\n                      {t('no_channels', 'No channels yet')}\n                    </div>\n                    <div className=\"text-[14px]\">\n                      {t('connect_your_accounts')}\n                    </div>\n                  </div>\n                </div>\n              )}\n              {menuIntegrations.map((menu) => (\n                <MenuGroupComponent\n                  collapsed={collapseMenu === '1'}\n                  changeItemGroup={changeItemGroup}\n                  key={menu.name}\n                  group={menu}\n                  mutate={mutate}\n                  continueIntegration={continueIntegration}\n                  update={update}\n                  refreshChannel={refreshChannel}\n                  totalNonDisabledChannels={totalNonDisabledChannels}\n                />\n              ))}\n            </div>\n            <div className=\"mt-[5px] text-center flex flex-col\">\n              {billingEnabled && user?.isLifetime && (\n                <div>{capitalize(user?.tier?.current || '')} tier</div>\n              )}\n              <div>\n                {process.env.NEXT_PUBLIC_VERSION\n                  ? process.env.NEXT_PUBLIC_VERSION\n                  : ''}\n              </div>\n            </div>\n          </div>\n        </div>\n        <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n          <Filters />\n          <div className=\"flex-1 flex\">\n            <Calendar />\n          </div>\n        </div>\n      </CalendarWeekProvider>\n    </DNDProvider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/layout.standalone.tsx",
    "content": "'use client';\n\nimport { ReactNode, useMemo } from 'react';\nimport { PreviewWrapper } from '@gitroom/frontend/components/preview/preview.wrapper';\nimport { usePathname } from 'next/navigation';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\ndayjs.extend(utc);\nexport const AppLayout = ({ children }: { children: ReactNode }) => {\n  const params = usePathname();\n  const style = useMemo(() => {\n    const all = params.split('/');\n    all.pop();\n    return all.pop();\n  }, [params]);\n  return (\n    <div\n      className={`hideCopilot ${style} h-[100vh] w-full text-textColor flex flex-1 flex-col !bg-none`}\n    >\n      <style>\n        {`\n          #add-edit-modal, .hideCopilot {\n            background: transparent !important;\n          }\n          html body.dark, html {\n            background: transparent !important;\n          }\n        `}\n      </style>\n      <PreviewWrapper>{children}</PreviewWrapper>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/menu/menu.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  MouseEventHandler,\n  useCallback,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { useClickOutside } from '@mantine/hooks';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { TimeTable } from '@gitroom/frontend/components/launches/time.table';\nimport {\n  Integrations,\n  useCalendar,\n} from '@gitroom/frontend/components/launches/calendar.context';\nimport { BotPicture } from '@gitroom/frontend/components/launches/bot.picture';\nimport { CustomerModal } from '@gitroom/frontend/components/launches/customer.modal';\nimport { Integration } from '@prisma/client';\nimport { SettingsModal } from '@gitroom/frontend/components/launches/settings.modal';\nimport { CustomVariables } from '@gitroom/frontend/components/launches/add.provider.component';\nimport { useRouter } from 'next/navigation';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport dayjs from 'dayjs';\nimport { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';\nimport copy from 'copy-to-clipboard';\n\nexport const Menu: FC<{\n  canEnable: boolean;\n  canDisable: boolean;\n  canChangeProfilePicture: boolean;\n  canChangeNickName: boolean;\n  refreshChannel: (\n    integration: Integration & {\n      identifier: string;\n    }\n  ) => () => void;\n  id: string;\n  mutate: () => void;\n  onChange: (shouldReload: boolean) => void;\n}> = (props) => {\n  const {\n    canEnable,\n    canDisable,\n    id,\n    onChange,\n    mutate,\n    canChangeProfilePicture,\n    canChangeNickName,\n    refreshChannel,\n  } = props;\n  const t = useT();\n\n  const fetch = useFetch();\n  const router = useRouter();\n  const { extensionId } = useVariables();\n  const { integrations, reloadCalendarView } = useCalendar();\n  const toast = useToaster();\n  const modal = useModals();\n  const [show, setShow] = useState<false | { x: number; y: number }>(false);\n  const menuRef = useRef<HTMLDivElement>(null);\n  const ref = useClickOutside<HTMLDivElement>(() => {\n    setShow(false);\n  });\n  const showRef = useRef();\n\n  // Adjust menu position if it would overflow viewport\n  useLayoutEffect(() => {\n    if (show && menuRef.current) {\n      const menuRect = menuRef.current.getBoundingClientRect();\n      const viewportHeight = window.innerHeight;\n      const padding = 10;\n\n      // Check if menu overflows bottom of viewport\n      if (menuRect.bottom > viewportHeight - padding) {\n        const newY = Math.max(\n          padding,\n          viewportHeight - menuRect.height - padding\n        );\n        // Only update if position actually changed significantly to avoid infinite loop\n        if (Math.abs(show.y - newY) > 1) {\n          setShow((prev) => (prev ? { ...prev, y: newY } : false));\n        }\n      }\n    }\n  }, [show]);\n  const findIntegration: any = useMemo(() => {\n    return integrations.find((integration) => integration.id === id);\n  }, [integrations, id]);\n  const changeShow: MouseEventHandler<HTMLDivElement> = useCallback(\n    (e) => {\n      e.stopPropagation();\n      // @ts-ignore\n      const boundBox = showRef?.current?.getBoundingClientRect();\n      setShow(\n        show\n          ? false\n          : { x: boundBox?.left, y: boundBox?.top + boundBox?.height }\n      );\n    },\n    [show]\n  );\n  const disableChannel = useCallback(async () => {\n    if (\n      !(await deleteDialog(\n        t('are_you_sure_disable_channel', 'Are you sure you want to disable this channel?'),\n        t('disable_channel_title', 'Disable Channel')\n      ))\n    ) {\n      return;\n    }\n    await fetch('/integrations/disable', {\n      method: 'POST',\n      body: JSON.stringify({\n        id,\n      }),\n    });\n    toast.show(t('channel_disabled', 'Channel Disabled'), 'success');\n    setShow(false);\n    onChange(false);\n  }, [t]);\n  const deleteChannel = useCallback(async () => {\n    if (\n      !(await deleteDialog(\n        t('are_you_sure_delete_channel', 'Are you sure you want to delete this channel?'),\n        t('delete_channel_title', 'Delete Channel')\n      ))\n    ) {\n      return;\n    }\n    const deleteIntegration = await fetch('/integrations', {\n      method: 'DELETE',\n      body: JSON.stringify({\n        id,\n      }),\n    });\n    if (deleteIntegration.status === 406) {\n      toast.show(\n        t('delete_posts_before_channel', 'You have to delete all the posts associated with this channel before deleting it'),\n        'warning'\n      );\n      return;\n    }\n    // Clean up extension refresh token if applicable\n    if (\n      extensionId &&\n      typeof chrome !== 'undefined' &&\n      chrome?.runtime?.sendMessage\n    ) {\n      try {\n        chrome.runtime.sendMessage(\n          extensionId,\n          { type: 'REMOVE_REFRESH_TOKEN', integrationId: id },\n          () => {}\n        );\n      } catch {\n        // Silently ignore\n      }\n    }\n    toast.show(t('channel_deleted', 'Channel Deleted'), 'success');\n    setShow(false);\n    onChange(true);\n  }, [t, extensionId, id]);\n\n  const enableChannel = useCallback(async () => {\n    await fetch('/integrations/enable', {\n      method: 'POST',\n      body: JSON.stringify({\n        id,\n      }),\n    });\n    toast.show(t('channel_enabled', 'Channel Enabled'), 'success');\n    setShow(false);\n    onChange(false);\n  }, [t]);\n\n  const editTimeTable = useCallback(() => {\n    const findIntegration = integrations.find(\n      (integration) => integration.id === id\n    );\n    modal.openModal({\n      withCloseButton: true,\n      closeOnEscape: false,\n      closeOnClickOutside: false,\n      askClose: true,\n      title: t('time_table_slots', 'Time Table Slots'),\n      children: <TimeTable integration={findIntegration!} mutate={mutate} />,\n    });\n    setShow(false);\n  }, [integrations, t]);\n\n  const copyChannelId = useCallback(\n    (integration: Integrations) => async () => {\n      setShow(false);\n      const channelId = integration.id;\n      copy(channelId);\n      toast.show(t('channel_id_copied', 'Channel ID copied to clipboard'), 'success');\n    },\n    [t]\n  );\n\n  const createPost = useCallback(\n    (integration: Integrations) => async () => {\n      setShow(false);\n\n      const { date } = await (\n        await fetch(`/posts/find-slot/${integration.id}`)\n      ).json();\n\n      modal.openModal({\n        id: 'add-edit-modal',\n        closeOnClickOutside: false,\n        removeLayout: true,\n        closeOnEscape: false,\n        withCloseButton: false,\n        askClose: true,\n        fullScreen: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px] text-textColor',\n        },\n        children: (\n          <AddEditModal\n            allIntegrations={integrations.map((p) => ({\n              ...p,\n            }))}\n            reopenModal={createPost(integration)}\n            mutate={reloadCalendarView}\n            integrations={integrations}\n            selectedChannels={[integration.id]}\n            // focusedChannel={integration.id}\n            date={dayjs.utc(date).local()}\n          />\n        ),\n        size: '80%',\n        title: ``,\n      });\n    },\n    [integrations]\n  );\n\n  const changeBotPicture = useCallback(() => {\n    const findIntegration = integrations.find(\n      (integration) => integration.id === id\n    );\n    modal.openModal({\n      classNames: {\n        modal: 'w-[100%] max-w-[600px] bg-transparent text-textColor',\n      },\n      size: '100%',\n      withCloseButton: false,\n      closeOnEscape: true,\n      closeOnClickOutside: true,\n      children: (\n        <BotPicture\n          canChangeProfilePicture={canChangeProfilePicture}\n          canChangeNickName={canChangeNickName}\n          integration={findIntegration!}\n          mutate={mutate}\n        />\n      ),\n    });\n    setShow(false);\n  }, [integrations]);\n  const additionalSettings = useCallback(() => {\n    const findIntegration = integrations.find(\n      (integration) => integration.id === id\n    );\n    modal.openModal({\n      title: t('additional_settings', 'Additional Settings'),\n      children: (\n        <SettingsModal\n          // @ts-ignore\n          integration={findIntegration}\n          onClose={() => {\n            mutate();\n            toast.show(t('settings_updated', 'Settings Updated'), 'success');\n          }}\n        />\n      ),\n    });\n    setShow(false);\n  }, [integrations, t]);\n  const addToCustomer = useCallback(() => {\n    const findIntegration = integrations.find(\n      (integration) => integration.id === id\n    );\n    modal.openModal({\n      classNames: {\n        modal: 'md',\n      },\n      title: t('move_add_to_customer', 'Move / Add to customer'),\n      withCloseButton: false,\n      closeOnEscape: true,\n      closeOnClickOutside: true,\n      children: (\n        <CustomerModal\n          // @ts-ignore\n          integration={findIntegration}\n          onClose={() => {\n            mutate();\n            toast.show(t('customer_updated', 'Customer Updated'), 'success');\n          }}\n        />\n      ),\n    });\n    setShow(false);\n  }, [integrations, t]);\n  const updateCredentials = useCallback(() => {\n    modal.openModal({\n      title: t('custom_url', 'Custom URL'),\n      withCloseButton: false,\n      classNames: {\n        modal: 'md',\n      },\n      children: (\n        <CustomVariables\n          identifier={findIntegration.identifier}\n          gotoUrl={(url: string) => router.push(url)}\n          variables={findIntegration.customFields}\n        />\n      ),\n    });\n  }, [t]);\n\n  return (\n    <div\n      className=\"cursor-pointer relative select-none flex\"\n      onClick={changeShow}\n      ref={ref}\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        className=\"text-menuDots group-hover/profile:text-menuDotsHover\"\n      >\n        <path\n          d=\"M13.125 12C13.125 12.2225 13.059 12.44 12.9354 12.625C12.8118 12.81 12.6361 12.9542 12.4305 13.0394C12.225 13.1245 11.9988 13.1468 11.7805 13.1034C11.5623 13.06 11.3618 12.9528 11.2045 12.7955C11.0472 12.6382 10.94 12.4377 10.8966 12.2195C10.8532 12.0012 10.8755 11.775 10.9606 11.5695C11.0458 11.3639 11.19 11.1882 11.375 11.0646C11.56 10.941 11.7775 10.875 12 10.875C12.2984 10.875 12.5845 10.9935 12.7955 11.2045C13.0065 11.4155 13.125 11.7016 13.125 12ZM12 6.75C12.2225 6.75 12.44 6.68402 12.625 6.5604C12.81 6.43679 12.9542 6.26109 13.0394 6.05552C13.1245 5.84995 13.1468 5.62375 13.1034 5.40552C13.06 5.1873 12.9528 4.98684 12.7955 4.82951C12.6382 4.67217 12.4377 4.56503 12.2195 4.52162C12.0012 4.47821 11.775 4.50049 11.5695 4.58564C11.3639 4.67078 11.1882 4.81498 11.0646 4.99998C10.941 5.18499 10.875 5.4025 10.875 5.625C10.875 5.92337 10.9935 6.20952 11.2045 6.4205C11.4155 6.63147 11.7016 6.75 12 6.75ZM12 17.25C11.7775 17.25 11.56 17.316 11.375 17.4396C11.19 17.5632 11.0458 17.7389 10.9606 17.9445C10.8755 18.15 10.8532 18.3762 10.8966 18.5945C10.94 18.8127 11.0472 19.0132 11.2045 19.1705C11.3618 19.3278 11.5623 19.435 11.7805 19.4784C11.9988 19.5218 12.225 19.4995 12.4305 19.4144C12.6361 19.3292 12.8118 19.185 12.9354 19C13.059 18.815 13.125 18.5975 13.125 18.375C13.125 18.0766 13.0065 17.7905 12.7955 17.5795C12.5845 17.3685 12.2984 17.25 12 17.25Z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n      <div>\n        <div ref={showRef} />\n      </div>\n      {show && (\n        <div\n          ref={menuRef}\n          onClick={(e) => e.stopPropagation()}\n          style={{ left: show.x, top: show.y }}\n          className={`fixed p-[12px] bg-newBgColorInner shadow-menu flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder text-nowrap`}\n        >\n          {canDisable && !findIntegration?.refreshNeeded && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={createPost(findIntegration!)}\n            >\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"18\"\n                  height=\"18\"\n                  viewBox=\"0 0 32 32\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M21 4H11C9.14409 4.00199 7.36477 4.74012 6.05245 6.05245C4.74012 7.36477 4.00199 9.14409 4 11V21C4.00199 22.8559 4.74012 24.6352 6.05245 25.9476C7.36477 27.2599 9.14409 27.998 11 28H17C17.1075 27.9999 17.2142 27.9826 17.3162 27.9487C20.595 26.855 26.855 20.595 27.9487 17.3162C27.9826 17.2142 27.9999 17.1075 28 17V11C27.998 9.14409 27.2599 7.36477 25.9476 6.05245C24.6352 4.74012 22.8559 4.00199 21 4ZM17 25.9275V22C17 20.6739 17.5268 19.4021 18.4645 18.4645C19.4021 17.5268 20.6739 17 22 17H25.9275C24.77 19.6938 19.6938 24.77 17 25.9275Z\"\n                    fill=\"green\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('create_new_post', 'Create a new post')}\n              </div>\n            </div>\n          )}\n          <div\n            className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n            onClick={copyChannelId(findIntegration)}\n          >\n            <div>\n              <svg\n                width=\"18\"\n                height=\"18\"\n                viewBox=\"0 0 26 28\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M13 0.749756C13.2093 0.749756 13.4155 0.802887 13.5986 0.904053L13.5996 0.905029L24.5996 6.92749C24.7962 7.03506 24.9608 7.19374 25.0752 7.38647C25.1609 7.53099 25.2159 7.69083 25.2383 7.8562L25.25 8.02319V19.9773C25.25 20.2016 25.1896 20.4221 25.0752 20.615C24.9608 20.8078 24.7963 20.9664 24.5996 21.074L13.5996 27.0955H13.5986C13.4153 27.1965 13.2093 27.2498 13 27.2498L12.8438 27.24C12.689 27.2203 12.5388 27.1712 12.4014 27.0955H12.4004L1.40039 21.074C1.20372 20.9664 1.03916 20.8078 0.924805 20.615C0.810442 20.4221 0.750028 20.2016 0.75 19.9773V8.02222L0.761719 7.85522C0.784096 7.68984 0.839135 7.53004 0.924805 7.3855C1.03917 7.19259 1.20366 7.03416 1.40039 6.92651L12.4004 0.905029L12.4014 0.904053C12.5845 0.802887 12.7907 0.749756 13 0.749756ZM24.0098 8.25659L13.5098 14.0066L13.25 14.1492V26.7195L13.9902 26.3132L18.4902 23.8484L18.75 23.7058V16.9998L18.7588 16.9333C18.7647 16.9119 18.7737 16.8911 18.7852 16.8718C18.808 16.8333 18.8406 16.8015 18.8799 16.78L24.4902 13.7087L24.75 13.5662V7.85132L24.0098 8.25659ZM24.0098 14.5427L19.5098 17.0056L19.25 17.1472V23.4333L19.9902 23.0291L24.3652 20.6365L24.625 20.4939V20.3914C24.6326 20.3798 24.6415 20.3691 24.6484 20.3572C24.6978 20.2723 24.7299 20.1784 24.7432 20.0818L24.75 19.9841V14.1375L24.0098 14.5427ZM18.3721 4.34741L13.1221 7.22241C13.0855 7.2424 13.0446 7.25359 13.0029 7.25366C12.961 7.25366 12.9196 7.2425 12.8828 7.22241L7.63281 4.34741L7.39258 4.21655L7.15234 4.34741L2.32129 6.99097L1.51953 7.43042L2.32129 7.8689L12.7598 13.5837L13 13.7146L13.2402 13.5837L23.6836 7.8689L24.4854 7.43042L23.6836 6.99097L18.8525 4.34741L18.6123 4.21655L18.3721 4.34741ZM12.9951 1.25073C12.8693 1.25073 12.7451 1.28213 12.6348 1.34253L8.71289 3.49292L7.91309 3.9314L8.71387 4.36987L12.7598 6.58374L13 6.71558L13.2402 6.58374L17.2803 4.36987L18.0811 3.9314L17.2803 3.49292L13.3555 1.34253L13.2695 1.30249C13.2115 1.27967 13.1507 1.2644 13.0889 1.25659L12.9951 1.25073ZM6.75 17.1433L6.49023 17.0017L1.99023 14.5388L1.25 14.1335V19.9734C1.24887 20.1061 1.2828 20.2371 1.34863 20.3523C1.39806 20.4387 1.46456 20.5137 1.54297 20.574L1.625 20.6296L1.63574 20.6355L6.01074 23.0242L6.75 23.4275V17.1433ZM12.75 14.1501L12.4902 14.0076L1.99023 8.25757L1.25 7.85229V13.5671L1.50977 13.7097L7.12012 16.781C7.15938 16.8025 7.19198 16.8343 7.21484 16.8728C7.22629 16.8921 7.23533 16.9129 7.24121 16.9343L7.25 17.0007V23.7058L7.50977 23.8484L12.0098 26.3132L12.75 26.7195V14.1501Z\"\n                  fill=\"#343330\"\n                />\n                <path\n                  d=\"M13 0.749756C13.2093 0.749756 13.4155 0.802887 13.5986 0.904053L13.5996 0.905029L24.5996 6.92749C24.7962 7.03506 24.9608 7.19374 25.0752 7.38647C25.1609 7.53099 25.2159 7.69083 25.2383 7.8562L25.25 8.02319V19.9773C25.25 20.2016 25.1896 20.4221 25.0752 20.615C24.9608 20.8078 24.7963 20.9664 24.5996 21.074L13.5996 27.0955H13.5986C13.4153 27.1965 13.2093 27.2498 13 27.2498L12.8438 27.24C12.689 27.2203 12.5388 27.1712 12.4014 27.0955H12.4004L1.40039 21.074C1.20372 20.9664 1.03916 20.8078 0.924805 20.615C0.810442 20.4221 0.750028 20.2016 0.75 19.9773V8.02222L0.761719 7.85522C0.784096 7.68984 0.839135 7.53004 0.924805 7.3855C1.03917 7.19259 1.20366 7.03416 1.40039 6.92651L12.4004 0.905029L12.4014 0.904053C12.5845 0.802887 12.7907 0.749756 13 0.749756ZM24.0098 8.25659L13.5098 14.0066L13.25 14.1492V26.7195L13.9902 26.3132L18.4902 23.8484L18.75 23.7058V16.9998L18.7588 16.9333C18.7647 16.9119 18.7737 16.8911 18.7852 16.8718C18.808 16.8333 18.8406 16.8015 18.8799 16.78L24.4902 13.7087L24.75 13.5662V7.85132L24.0098 8.25659ZM24.0098 14.5427L19.5098 17.0056L19.25 17.1472V23.4333L19.9902 23.0291L24.3652 20.6365L24.625 20.4939V20.3914C24.6326 20.3798 24.6415 20.3691 24.6484 20.3572C24.6978 20.2723 24.7299 20.1784 24.7432 20.0818L24.75 19.9841V14.1375L24.0098 14.5427ZM18.3721 4.34741L13.1221 7.22241C13.0855 7.2424 13.0446 7.25359 13.0029 7.25366C12.961 7.25366 12.9196 7.2425 12.8828 7.22241L7.63281 4.34741L7.39258 4.21655L7.15234 4.34741L2.32129 6.99097L1.51953 7.43042L2.32129 7.8689L12.7598 13.5837L13 13.7146L13.2402 13.5837L23.6836 7.8689L24.4854 7.43042L23.6836 6.99097L18.8525 4.34741L18.6123 4.21655L18.3721 4.34741ZM12.9951 1.25073C12.8693 1.25073 12.7451 1.28213 12.6348 1.34253L8.71289 3.49292L7.91309 3.9314L8.71387 4.36987L12.7598 6.58374L13 6.71558L13.2402 6.58374L17.2803 4.36987L18.0811 3.9314L17.2803 3.49292L13.3555 1.34253L13.2695 1.30249C13.2115 1.27967 13.1507 1.2644 13.0889 1.25659L12.9951 1.25073ZM6.75 17.1433L6.49023 17.0017L1.99023 14.5388L1.25 14.1335V19.9734C1.24887 20.1061 1.2828 20.2371 1.34863 20.3523C1.39806 20.4387 1.46456 20.5137 1.54297 20.574L1.625 20.6296L1.63574 20.6355L6.01074 23.0242L6.75 23.4275V17.1433ZM12.75 14.1501L12.4902 14.0076L1.99023 8.25757L1.25 7.85229V13.5671L1.50977 13.7097L7.12012 16.781C7.15938 16.8025 7.19198 16.8343 7.21484 16.8728C7.22629 16.8921 7.23533 16.9129 7.24121 16.9343L7.25 17.0007V23.7058L7.50977 23.8484L12.0098 26.3132L12.75 26.7195V14.1501Z\"\n                  stroke=\"currentColor\"\n                />\n                <path\n                  d=\"M13 0.749756C13.2093 0.749756 13.4155 0.802887 13.5986 0.904053L13.5996 0.905029L24.5996 6.92749C24.7962 7.03506 24.9608 7.19374 25.0752 7.38647C25.1609 7.53099 25.2159 7.69083 25.2383 7.8562L25.25 8.02319V19.9773C25.25 20.2016 25.1896 20.4221 25.0752 20.615C24.9608 20.8078 24.7963 20.9664 24.5996 21.074L13.5996 27.0955H13.5986C13.4153 27.1965 13.2093 27.2498 13 27.2498L12.8438 27.24C12.689 27.2203 12.5388 27.1712 12.4014 27.0955H12.4004L1.40039 21.074C1.20372 20.9664 1.03916 20.8078 0.924805 20.615C0.810442 20.4221 0.750028 20.2016 0.75 19.9773V8.02222L0.761719 7.85522C0.784096 7.68984 0.839135 7.53004 0.924805 7.3855C1.03917 7.19259 1.20366 7.03416 1.40039 6.92651L12.4004 0.905029L12.4014 0.904053C12.5845 0.802887 12.7907 0.749756 13 0.749756ZM24.0098 8.25659L13.5098 14.0066L13.25 14.1492V26.7195L13.9902 26.3132L18.4902 23.8484L18.75 23.7058V16.9998L18.7588 16.9333C18.7647 16.9119 18.7737 16.8911 18.7852 16.8718C18.808 16.8333 18.8406 16.8015 18.8799 16.78L24.4902 13.7087L24.75 13.5662V7.85132L24.0098 8.25659ZM24.0098 14.5427L19.5098 17.0056L19.25 17.1472V23.4333L19.9902 23.0291L24.3652 20.6365L24.625 20.4939V20.3914C24.6326 20.3798 24.6415 20.3691 24.6484 20.3572C24.6978 20.2723 24.7299 20.1784 24.7432 20.0818L24.75 19.9841V14.1375L24.0098 14.5427ZM18.3721 4.34741L13.1221 7.22241C13.0855 7.2424 13.0446 7.25359 13.0029 7.25366C12.961 7.25366 12.9196 7.2425 12.8828 7.22241L7.63281 4.34741L7.39258 4.21655L7.15234 4.34741L2.32129 6.99097L1.51953 7.43042L2.32129 7.8689L12.7598 13.5837L13 13.7146L13.2402 13.5837L23.6836 7.8689L24.4854 7.43042L23.6836 6.99097L18.8525 4.34741L18.6123 4.21655L18.3721 4.34741ZM12.9951 1.25073C12.8693 1.25073 12.7451 1.28213 12.6348 1.34253L8.71289 3.49292L7.91309 3.9314L8.71387 4.36987L12.7598 6.58374L13 6.71558L13.2402 6.58374L17.2803 4.36987L18.0811 3.9314L17.2803 3.49292L13.3555 1.34253L13.2695 1.30249C13.2115 1.27967 13.1507 1.2644 13.0889 1.25659L12.9951 1.25073ZM6.75 17.1433L6.49023 17.0017L1.99023 14.5388L1.25 14.1335V19.9734C1.24887 20.1061 1.2828 20.2371 1.34863 20.3523C1.39806 20.4387 1.46456 20.5137 1.54297 20.574L1.625 20.6296L1.63574 20.6355L6.01074 23.0242L6.75 23.4275V17.1433ZM12.75 14.1501L12.4902 14.0076L1.99023 8.25757L1.25 7.85229V13.5671L1.50977 13.7097L7.12012 16.781C7.15938 16.8025 7.19198 16.8343 7.21484 16.8728C7.22629 16.8921 7.23533 16.9129 7.24121 16.9343L7.25 17.0007V23.7058L7.50977 23.8484L12.0098 26.3132L12.75 26.7195V14.1501Z\"\n                  stroke=\"currentColor\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[14px]\">{t('copy_id', 'Copy Channel ID')}</div>\n          </div>\n          {canDisable &&\n            findIntegration?.refreshNeeded &&\n            !findIntegration.customFields && (\n              <div\n                className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n                onClick={refreshChannel(findIntegration!)}\n              >\n                <div>\n                  <svg\n                    width={18}\n                    height={18}\n                    viewBox=\"0 0 32 32\"\n                    fill=\"yellow\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <path\n                      d=\"M3.00079 15.9999C3.00343 13.6138 3.95249 11.3262 5.63975 9.63891C7.327 7.95165 9.61465 7.00259 12.0008 6.99995H25.587L24.2933 5.70745C24.1056 5.5198 24.0002 5.26531 24.0002 4.99995C24.0002 4.73458 24.1056 4.48009 24.2933 4.29245C24.4809 4.1048 24.7354 3.99939 25.0008 3.99939C25.2661 3.99939 25.5206 4.10481 25.7083 4.29245L28.7083 7.29245C28.8013 7.38532 28.875 7.49561 28.9253 7.61701C28.9757 7.7384 29.0016 7.86853 29.0016 7.99995C29.0016 8.13136 28.9757 8.26149 28.9253 8.38289C28.875 8.50428 28.8013 8.61457 28.7083 8.70745L25.7083 11.7074C25.5206 11.8951 25.2661 12.0005 25.0008 12.0005C24.7354 12.0005 24.4809 11.8951 24.2933 11.7074C24.1056 11.5198 24.0002 11.2653 24.0002 10.9999C24.0002 10.7346 24.1056 10.4801 24.2933 10.2924L25.587 8.99995H12.0008C10.1449 9.00193 8.36556 9.74007 7.05323 11.0524C5.74091 12.3647 5.00277 14.144 5.00079 15.9999C5.00079 16.2652 4.89543 16.5195 4.70789 16.7071C4.52036 16.8946 4.266 16.9999 4.00079 16.9999C3.73557 16.9999 3.48122 16.8946 3.29368 16.7071C3.10614 16.5195 3.00079 16.2652 3.00079 15.9999ZM28.0008 14.9999C27.7356 14.9999 27.4812 15.1053 27.2937 15.2928C27.1061 15.4804 27.0008 15.7347 27.0008 15.9999C26.9988 17.8559 26.2607 19.6352 24.9483 20.9475C23.636 22.2598 21.8567 22.998 20.0008 22.9999H6.41454L7.70829 21.7074C7.8012 21.6145 7.8749 21.5042 7.92518 21.3828C7.97546 21.2614 8.00134 21.1313 8.00134 20.9999C8.00134 20.8686 7.97546 20.7384 7.92518 20.6171C7.8749 20.4957 7.8012 20.3854 7.70829 20.2924C7.61538 20.1995 7.50508 20.1258 7.38368 20.0756C7.26229 20.0253 7.13218 19.9994 7.00079 19.9994C6.86939 19.9994 6.73928 20.0253 6.61789 20.0756C6.4965 20.1258 6.3862 20.1995 6.29329 20.2924L3.29329 23.2924C3.20031 23.3853 3.12655 23.4956 3.07623 23.617C3.0259 23.7384 3 23.8685 3 23.9999C3 24.1314 3.0259 24.2615 3.07623 24.3829C3.12655 24.5043 3.20031 24.6146 3.29329 24.7074L6.29329 27.7074C6.3862 27.8004 6.4965 27.8741 6.61789 27.9243C6.73928 27.9746 6.86939 28.0005 7.00079 28.0005C7.13218 28.0005 7.26229 27.9746 7.38368 27.9243C7.50508 27.8741 7.61538 27.8004 7.70829 27.7074C7.8012 27.6145 7.8749 27.5042 7.92518 27.3828C7.97546 27.2614 8.00134 27.1313 8.00134 26.9999C8.00134 26.8686 7.97546 26.7384 7.92518 26.6171C7.8749 26.4957 7.8012 26.3854 7.70829 26.2924L6.41454 24.9999H20.0008C22.3869 24.9973 24.6746 24.0482 26.3618 22.361C28.0491 20.6737 28.9981 18.3861 29.0008 15.9999C29.0008 15.7347 28.8954 15.4804 28.7079 15.2928C28.5204 15.1053 28.266 14.9999 28.0008 14.9999Z\"\n                      fill=\"yellow\"\n                    />\n                  </svg>\n                </div>\n                <div className=\"text-[14px]\">\n                  {t('reconnect_channel', 'Reconnect channel')}\n                </div>\n              </div>\n            )}\n          {!!findIntegration?.isCustomFields && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={updateCredentials}\n            >\n              <div>\n                <svg\n                  width={18}\n                  height={18}\n                  viewBox=\"0 0 32 32\"\n                  fill=\"currentColor\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    d=\"M3.00079 15.9999C3.00343 13.6138 3.95249 11.3262 5.63975 9.63891C7.327 7.95165 9.61465 7.00259 12.0008 6.99995H25.587L24.2933 5.70745C24.1056 5.5198 24.0002 5.26531 24.0002 4.99995C24.0002 4.73458 24.1056 4.48009 24.2933 4.29245C24.4809 4.1048 24.7354 3.99939 25.0008 3.99939C25.2661 3.99939 25.5206 4.10481 25.7083 4.29245L28.7083 7.29245C28.8013 7.38532 28.875 7.49561 28.9253 7.61701C28.9757 7.7384 29.0016 7.86853 29.0016 7.99995C29.0016 8.13136 28.9757 8.26149 28.9253 8.38289C28.875 8.50428 28.8013 8.61457 28.7083 8.70745L25.7083 11.7074C25.5206 11.8951 25.2661 12.0005 25.0008 12.0005C24.7354 12.0005 24.4809 11.8951 24.2933 11.7074C24.1056 11.5198 24.0002 11.2653 24.0002 10.9999C24.0002 10.7346 24.1056 10.4801 24.2933 10.2924L25.587 8.99995H12.0008C10.1449 9.00193 8.36556 9.74007 7.05323 11.0524C5.74091 12.3647 5.00277 14.144 5.00079 15.9999C5.00079 16.2652 4.89543 16.5195 4.70789 16.7071C4.52036 16.8946 4.266 16.9999 4.00079 16.9999C3.73557 16.9999 3.48122 16.8946 3.29368 16.7071C3.10614 16.5195 3.00079 16.2652 3.00079 15.9999ZM28.0008 14.9999C27.7356 14.9999 27.4812 15.1053 27.2937 15.2928C27.1061 15.4804 27.0008 15.7347 27.0008 15.9999C26.9988 17.8559 26.2607 19.6352 24.9483 20.9475C23.636 22.2598 21.8567 22.998 20.0008 22.9999H6.41454L7.70829 21.7074C7.8012 21.6145 7.8749 21.5042 7.92518 21.3828C7.97546 21.2614 8.00134 21.1313 8.00134 20.9999C8.00134 20.8686 7.97546 20.7384 7.92518 20.6171C7.8749 20.4957 7.8012 20.3854 7.70829 20.2924C7.61538 20.1995 7.50508 20.1258 7.38368 20.0756C7.26229 20.0253 7.13218 19.9994 7.00079 19.9994C6.86939 19.9994 6.73928 20.0253 6.61789 20.0756C6.4965 20.1258 6.3862 20.1995 6.29329 20.2924L3.29329 23.2924C3.20031 23.3853 3.12655 23.4956 3.07623 23.617C3.0259 23.7384 3 23.8685 3 23.9999C3 24.1314 3.0259 24.2615 3.07623 24.3829C3.12655 24.5043 3.20031 24.6146 3.29329 24.7074L6.29329 27.7074C6.3862 27.8004 6.4965 27.8741 6.61789 27.9243C6.73928 27.9746 6.86939 28.0005 7.00079 28.0005C7.13218 28.0005 7.26229 27.9746 7.38368 27.9243C7.50508 27.8741 7.61538 27.8004 7.70829 27.7074C7.8012 27.6145 7.8749 27.5042 7.92518 27.3828C7.97546 27.2614 8.00134 27.1313 8.00134 26.9999C8.00134 26.8686 7.97546 26.7384 7.92518 26.6171C7.8749 26.4957 7.8012 26.3854 7.70829 26.2924L6.41454 24.9999H20.0008C22.3869 24.9973 24.6746 24.0482 26.3618 22.361C28.0491 20.6737 28.9981 18.3861 29.0008 15.9999C29.0008 15.7347 28.8954 15.4804 28.7079 15.2928C28.5204 15.1053 28.266 14.9999 28.0008 14.9999Z\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('update_credentials', 'Update Credentials')}\n              </div>\n            </div>\n          )}\n          {findIntegration?.additionalSettings !== '[]' && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={additionalSettings}\n            >\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"18\"\n                  height=\"18\"\n                  viewBox=\"0 0 32 32\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M15.9983 10.2501C14.861 10.2501 13.7493 10.5873 12.8037 11.2191C11.8581 11.8509 11.1212 12.749 10.686 13.7996C10.2507 14.8503 10.1369 16.0065 10.3587 17.1218C10.5806 18.2372 11.1282 19.2618 11.9324 20.0659C12.7365 20.8701 13.7611 21.4177 14.8765 21.6396C15.9919 21.8615 17.148 21.7476 18.1987 21.3124C19.2494 20.8772 20.1474 20.1402 20.7792 19.1946C21.411 18.249 21.7483 17.1373 21.7483 16.0001C21.7463 14.4757 21.1398 13.0143 20.0619 11.9364C18.984 10.8585 17.5226 10.2521 15.9983 10.2501ZM15.9983 20.2501C15.1577 20.2501 14.336 20.0008 13.6371 19.5338C12.9382 19.0668 12.3934 18.4031 12.0718 17.6265C11.7501 16.8499 11.6659 15.9954 11.8299 15.1709C11.9939 14.3465 12.3987 13.5892 12.9931 12.9949C13.5874 12.4005 14.3447 11.9957 15.1691 11.8317C15.9935 11.6678 16.8481 11.7519 17.6247 12.0736C18.4012 12.3953 19.065 12.94 19.532 13.6389C19.999 14.3378 20.2483 15.1595 20.2483 16.0001C20.2483 17.1272 19.8005 18.2082 19.0035 19.0053C18.2064 19.8023 17.1254 20.2501 15.9983 20.2501ZM26.7483 16.3551C26.7558 16.1188 26.7558 15.8813 26.7483 15.6451L28.6645 13.2501C28.7378 13.1584 28.7885 13.0508 28.8126 12.936C28.8367 12.8211 28.8335 12.7022 28.8033 12.5888C28.5034 11.4589 28.0545 10.374 27.4683 9.36258C27.41 9.25993 27.3284 9.17247 27.23 9.10731C27.1316 9.04215 27.0192 9.00112 26.902 8.98758L23.8458 8.64883C23.6849 8.47633 23.5183 8.30966 23.3458 8.14883L23.0058 5.09132C22.9922 4.97409 22.9512 4.86171 22.886 4.76332C22.8209 4.66493 22.7334 4.5833 22.6308 4.52507C21.6206 3.94052 20.5374 3.49245 19.4095 3.19258C19.2958 3.16287 19.1768 3.16029 19.0619 3.18504C18.947 3.2098 18.8396 3.26119 18.7483 3.33507L16.3483 5.25632C16.112 5.24882 15.8745 5.24882 15.6383 5.25632L13.2483 3.33382C13.1566 3.26058 13.049 3.20987 12.9341 3.18577C12.8193 3.16167 12.7004 3.16486 12.587 3.19507C11.4571 3.49489 10.3721 3.94383 9.36076 4.53008C9.25811 4.5883 9.17065 4.66993 9.10549 4.76832C9.04033 4.86671 8.99931 4.97909 8.98576 5.09632L8.65201 8.15258C8.47951 8.31424 8.31284 8.48091 8.15201 8.65258L5.09326 9.00007C4.97603 9.01362 4.86365 9.05465 4.76525 9.11981C4.66686 9.18497 4.58523 9.27243 4.52701 9.37507C3.94245 10.3852 3.49438 11.4684 3.19451 12.5963C3.16552 12.7086 3.16297 12.8261 3.18704 12.9395C3.21112 13.0529 3.26117 13.1592 3.33326 13.2501L5.25451 15.6501C5.24701 15.8863 5.24701 16.1238 5.25451 16.3601L3.33201 18.7563C3.25876 18.848 3.20805 18.9556 3.18395 19.0704C3.15986 19.1853 3.16304 19.3042 3.19326 19.4176C3.49359 20.5453 3.94253 21.6281 4.52826 22.6376C4.58648 22.7402 4.66811 22.8277 4.7665 22.8928C4.8649 22.958 4.97728 22.999 5.09451 23.0126L8.15076 23.3513C8.31242 23.5238 8.47909 23.6905 8.65076 23.8513L8.99826 26.9051C9.01181 27.0223 9.05283 27.1347 9.11799 27.2331C9.18315 27.3315 9.27061 27.4131 9.37326 27.4713C10.3834 28.0559 11.4666 28.5039 12.5945 28.8038C12.7079 28.834 12.8268 28.8372 12.9416 28.8131C13.0565 28.789 13.1641 28.7383 13.2558 28.6651L15.6433 26.7501C15.8795 26.7576 16.117 26.7576 16.3533 26.7501L18.7545 28.6726C18.8876 28.7788 19.0529 28.8365 19.2233 28.8363C19.2882 28.8361 19.3529 28.8277 19.4158 28.8113C20.5435 28.511 21.6263 28.0621 22.6358 27.4763C22.7384 27.4181 22.8259 27.3365 22.891 27.2381C22.9562 27.1397 22.9972 27.0273 23.0108 26.9101L23.3495 23.8538C23.522 23.693 23.6887 23.5263 23.8495 23.3538L26.907 23.0138C27.0242 23.0003 27.1366 22.9593 27.235 22.8941C27.3334 22.8289 27.415 22.7415 27.4733 22.6388C28.058 21.6288 28.5061 20.5456 28.8058 19.4176C28.836 19.3042 28.8392 19.1853 28.8151 19.0704C28.791 18.9556 28.7403 18.848 28.667 18.7563L26.7483 16.3551ZM26.3608 21.5588L23.4058 21.8876C23.2211 21.9089 23.0509 21.998 22.9283 22.1376C22.6812 22.4159 22.4178 22.6793 22.1395 22.9263C21.9999 23.049 21.9108 23.2192 21.8895 23.4038L21.5608 26.3576C20.8689 26.7265 20.142 27.0255 19.3908 27.2501L17.0695 25.3926C16.9364 25.2864 16.7711 25.2286 16.6008 25.2288H16.5558C16.1844 25.2501 15.8121 25.2501 15.4408 25.2288C15.2553 25.2175 15.0722 25.2754 14.927 25.3913L12.607 27.2501C11.8565 27.0243 11.1304 26.724 10.4395 26.3538L10.1108 23.4013C10.0895 23.2167 10.0004 23.0465 9.86076 22.9238C9.58245 22.6768 9.31907 22.4134 9.07201 22.1351C8.94932 21.9955 8.77915 21.9064 8.59451 21.8851L5.64076 21.5563C5.27219 20.8665 4.97322 20.1417 4.74826 19.3926L6.60576 17.0713C6.7217 16.9261 6.7796 16.743 6.76826 16.5576C6.74701 16.1862 6.74701 15.8139 6.76826 15.4426C6.7796 15.2571 6.7217 15.074 6.60576 14.9288L4.74826 12.6088C4.97407 11.8583 5.27431 11.1322 5.64451 10.4413L8.59701 10.1126C8.78165 10.0913 8.95182 10.0022 9.07451 9.86258C9.32157 9.58426 9.58495 9.32088 9.86326 9.07382C10.0029 8.95113 10.092 8.78097 10.1133 8.59632L10.442 5.64257C11.1319 5.27401 11.8567 4.97504 12.6058 4.75007L14.927 6.60757C15.0722 6.72351 15.2553 6.78142 15.4408 6.77007C15.8121 6.74882 16.1844 6.74882 16.5558 6.77007C16.7412 6.78142 16.9243 6.72351 17.0695 6.60757L19.3895 4.75007C20.1401 4.97589 20.8662 5.27613 21.557 5.64632L21.8858 8.60132C21.907 8.78597 21.9961 8.95613 22.1358 9.07882C22.4141 9.32588 22.6775 9.58926 22.9245 9.86758C23.0472 10.0072 23.2174 10.0963 23.402 10.1176L26.3558 10.4463C26.7242 11.1354 27.0231 11.8594 27.2483 12.6076L25.3908 14.9288C25.2748 15.074 25.2169 15.2571 25.2283 15.4426C25.2495 15.8139 25.2495 16.1862 25.2283 16.5576C25.2169 16.743 25.2748 16.9261 25.3908 17.0713L27.2483 19.3913C27.0232 20.1418 26.7238 20.8679 26.3545 21.5588H26.3608Z\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('additional_settings', 'Additional Settings')}\n              </div>\n            </div>\n          )}\n          {(canChangeProfilePicture || canChangeNickName) && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={changeBotPicture}\n            >\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"18\"\n                  height=\"18\"\n                  viewBox=\"0 0 32 32\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M26 4H10C9.46957 4 8.96086 4.21071 8.58579 4.58579C8.21071 4.96086 8 5.46957 8 6V8H6C5.46957 8 4.96086 8.21071 4.58579 8.58579C4.21071 8.96086 4 9.46957 4 10V26C4 26.5304 4.21071 27.0391 4.58579 27.4142C4.96086 27.7893 5.46957 28 6 28H22C22.5304 28 23.0391 27.7893 23.4142 27.4142C23.7893 27.0391 24 26.5304 24 26V24H26C26.5304 24 27.0391 23.7893 27.4142 23.4142C27.7893 23.0391 28 22.5304 28 22V6C28 5.46957 27.7893 4.96086 27.4142 4.58579C27.0391 4.21071 26.5304 4 26 4ZM10 6H26V14.6725L23.9125 12.585C23.5375 12.2102 23.029 11.9997 22.4988 11.9997C21.9685 11.9997 21.46 12.2102 21.085 12.585L11.6713 22H10V6ZM22 26H6V10H8V22C8 22.5304 8.21071 23.0391 8.58579 23.4142C8.96086 23.7893 9.46957 24 10 24H22V26ZM26 22H14.5L22.5 14L26 17.5V22ZM15 14C15.5933 14 16.1734 13.8241 16.6667 13.4944C17.1601 13.1648 17.5446 12.6962 17.7716 12.1481C17.9987 11.5999 18.0581 10.9967 17.9424 10.4147C17.8266 9.83279 17.5409 9.29824 17.1213 8.87868C16.7018 8.45912 16.1672 8.1734 15.5853 8.05764C15.0033 7.94189 14.4001 8.0013 13.8519 8.22836C13.3038 8.45542 12.8352 8.83994 12.5056 9.33329C12.1759 9.82664 12 10.4067 12 11C12 11.7956 12.3161 12.5587 12.8787 13.1213C13.4413 13.6839 14.2044 14 15 14ZM15 10C15.1978 10 15.3911 10.0586 15.5556 10.1685C15.72 10.2784 15.8482 10.4346 15.9239 10.6173C15.9996 10.8 16.0194 11.0011 15.9808 11.1951C15.9422 11.3891 15.847 11.5673 15.7071 11.7071C15.5673 11.847 15.3891 11.9422 15.1951 11.9808C15.0011 12.0194 14.8 11.9996 14.6173 11.9239C14.4346 11.8482 14.2784 11.72 14.1685 11.5556C14.0586 11.3911 14 11.1978 14 11C14 10.7348 14.1054 10.4804 14.2929 10.2929C14.4804 10.1054 14.7348 10 15 10Z\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('change_bot', 'Change Bot')}{' '}\n                {[\n                  canChangeProfilePicture && t('picture', 'Picture'),\n                  canChangeNickName && t('label_nickname', 'Nickname'),\n                ]\n                  .filter((f) => f)\n                  .join(' / ')}\n              </div>\n            </div>\n          )}\n          <div\n            className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n            onClick={addToCustomer}\n          >\n            <div>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={18}\n                height={18}\n                viewBox=\"0 0 32 32\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M31.9997 17C31.9997 17.2652 31.8943 17.5196 31.7068 17.7071C31.5192 17.8946 31.2649 18 30.9997 18H28.9997V20C28.9997 20.2652 28.8943 20.5196 28.7068 20.7071C28.5192 20.8946 28.2649 21 27.9997 21C27.7345 21 27.4801 20.8946 27.2926 20.7071C27.105 20.5196 26.9997 20.2652 26.9997 20V18H24.9997C24.7345 18 24.4801 17.8946 24.2926 17.7071C24.105 17.5196 23.9997 17.2652 23.9997 17C23.9997 16.7348 24.105 16.4804 24.2926 16.2929C24.4801 16.1054 24.7345 16 24.9997 16H26.9997V14C26.9997 13.7348 27.105 13.4804 27.2926 13.2929C27.4801 13.1054 27.7345 13 27.9997 13C28.2649 13 28.5192 13.1054 28.7068 13.2929C28.8943 13.4804 28.9997 13.7348 28.9997 14V16H30.9997C31.2649 16 31.5192 16.1054 31.7068 16.2929C31.8943 16.4804 31.9997 16.7348 31.9997 17ZM24.7659 24.3562C24.9367 24.5595 25.0197 24.8222 24.9967 25.0866C24.9737 25.351 24.8466 25.5955 24.6434 25.7662C24.4402 25.937 24.1775 26.02 23.9131 25.997C23.6486 25.974 23.4042 25.847 23.2334 25.6437C20.7184 22.6487 17.2609 21 13.4997 21C9.73843 21 6.28093 22.6487 3.76593 25.6437C3.59519 25.8468 3.35079 25.9737 3.08648 25.9966C2.82217 26.0194 2.55961 25.9364 2.35655 25.7656C2.15349 25.5949 2.02658 25.3505 2.00372 25.0862C1.98087 24.8219 2.06394 24.5593 2.23468 24.3562C4.10218 22.1337 6.42468 20.555 9.00593 19.71C7.43831 18.7336 6.23133 17.2733 5.56759 15.5498C4.90386 13.8264 4.81949 11.9337 5.32724 10.1581C5.83499 8.38242 6.90724 6.82045 8.38176 5.70847C9.85629 4.59649 11.6529 3.995 13.4997 3.995C15.3465 3.995 17.1431 4.59649 18.6176 5.70847C20.0921 6.82045 21.1644 8.38242 21.6721 10.1581C22.1799 11.9337 22.0955 13.8264 21.4318 15.5498C20.768 17.2733 19.561 18.7336 17.9934 19.71C20.5747 20.555 22.8972 22.1337 24.7659 24.3562ZM13.4997 19C14.7853 19 16.042 18.6188 17.1109 17.9045C18.1798 17.1903 19.0129 16.1752 19.5049 14.9874C19.9969 13.7997 20.1256 12.4928 19.8748 11.2319C19.624 9.97103 19.0049 8.81284 18.0959 7.9038C17.1868 6.99476 16.0286 6.37569 14.7678 6.12489C13.5069 5.87409 12.2 6.00281 11.0122 6.49478C9.82451 6.98675 8.80935 7.81987 8.09512 8.88879C7.38089 9.95771 6.99968 11.2144 6.99968 12.5C7.00166 14.2233 7.68712 15.8754 8.90567 17.094C10.1242 18.3126 11.7764 18.998 13.4997 19Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[14px]\">\n              {t('move_add_to_customer', 'Move / add to customer')}\n            </div>\n          </div>\n          <div\n            className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n            onClick={editTimeTable}\n          >\n            <div>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"18\"\n                height=\"18\"\n                viewBox=\"0 0 32 32\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M16 5C13.6266 5 11.3066 5.70379 9.33316 7.02236C7.35977 8.34094 5.8217 10.2151 4.91345 12.4078C4.0052 14.6005 3.76756 17.0133 4.23058 19.3411C4.6936 21.6689 5.83649 23.8071 7.51472 25.4853C9.19295 27.1635 11.3312 28.3064 13.6589 28.7694C15.9867 29.2324 18.3995 28.9948 20.5922 28.0866C22.7849 27.1783 24.6591 25.6402 25.9776 23.6668C27.2962 21.6935 28 19.3734 28 17C27.9964 13.8185 26.7309 10.7684 24.4813 8.51874C22.2316 6.26909 19.1815 5.00364 16 5ZM16 27C14.0222 27 12.0888 26.4135 10.4443 25.3147C8.79981 24.2159 7.51809 22.6541 6.76121 20.8268C6.00433 18.9996 5.8063 16.9889 6.19215 15.0491C6.578 13.1093 7.53041 11.3275 8.92894 9.92893C10.3275 8.53041 12.1093 7.578 14.0491 7.19215C15.9889 6.80629 17.9996 7.00433 19.8268 7.7612C21.6541 8.51808 23.2159 9.79981 24.3147 11.4443C25.4135 13.0888 26 15.0222 26 17C25.997 19.6513 24.9425 22.1931 23.0678 24.0678C21.1931 25.9425 18.6513 26.997 16 27ZM21.7075 11.2925C21.8005 11.3854 21.8742 11.4957 21.9246 11.6171C21.9749 11.7385 22.0008 11.8686 22.0008 12C22.0008 12.1314 21.9749 12.2615 21.9246 12.3829C21.8742 12.5043 21.8005 12.6146 21.7075 12.7075L16.7075 17.7075C16.6146 17.8004 16.5043 17.8741 16.3829 17.9244C16.2615 17.9747 16.1314 18.0006 16 18.0006C15.8686 18.0006 15.7385 17.9747 15.6171 17.9244C15.4957 17.8741 15.3854 17.8004 15.2925 17.7075C15.1996 17.6146 15.1259 17.5043 15.0756 17.3829C15.0253 17.2615 14.9994 17.1314 14.9994 17C14.9994 16.8686 15.0253 16.7385 15.0756 16.6171C15.1259 16.4957 15.1996 16.3854 15.2925 16.2925L20.2925 11.2925C20.3854 11.1995 20.4957 11.1258 20.6171 11.0754C20.7385 11.0251 20.8686 10.9992 21 10.9992C21.1314 10.9992 21.2615 11.0251 21.3829 11.0754C21.5043 11.1258 21.6146 11.1995 21.7075 11.2925ZM12 2C12 1.73478 12.1054 1.48043 12.2929 1.29289C12.4804 1.10536 12.7348 1 13 1H19C19.2652 1 19.5196 1.10536 19.7071 1.29289C19.8946 1.48043 20 1.73478 20 2C20 2.26522 19.8946 2.51957 19.7071 2.70711C19.5196 2.89464 19.2652 3 19 3H13C12.7348 3 12.4804 2.89464 12.2929 2.70711C12.1054 2.51957 12 2.26522 12 2Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[14px]\">\n              {t('edit_time_slots', 'Edit Time Slots')}\n            </div>\n          </div>\n          {canEnable && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={enableChannel}\n            >\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"16\"\n                  height=\"16\"\n                  viewBox=\"0 0 32 32\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M28.2325 12.8525C27.7612 12.36 27.2738 11.8525 27.09 11.4062C26.92 10.9975 26.91 10.32 26.9 9.66375C26.8813 8.44375 26.8612 7.06125 25.9 6.1C24.9387 5.13875 23.5562 5.11875 22.3363 5.1C21.68 5.09 21.0025 5.08 20.5938 4.91C20.1488 4.72625 19.64 4.23875 19.1475 3.7675C18.285 2.93875 17.305 2 16 2C14.695 2 13.7162 2.93875 12.8525 3.7675C12.36 4.23875 11.8525 4.72625 11.4062 4.91C11 5.08 10.32 5.09 9.66375 5.1C8.44375 5.11875 7.06125 5.13875 6.1 6.1C5.13875 7.06125 5.125 8.44375 5.1 9.66375C5.09 10.32 5.08 10.9975 4.91 11.4062C4.72625 11.8512 4.23875 12.36 3.7675 12.8525C2.93875 13.715 2 14.695 2 16C2 17.305 2.93875 18.2837 3.7675 19.1475C4.23875 19.64 4.72625 20.1475 4.91 20.5938C5.08 21.0025 5.09 21.68 5.1 22.3363C5.11875 23.5562 5.13875 24.9387 6.1 25.9C7.06125 26.8612 8.44375 26.8813 9.66375 26.9C10.32 26.91 10.9975 26.92 11.4062 27.09C11.8512 27.2738 12.36 27.7612 12.8525 28.2325C13.715 29.0613 14.695 30 16 30C17.305 30 18.2837 29.0613 19.1475 28.2325C19.64 27.7612 20.1475 27.2738 20.5938 27.09C21.0025 26.92 21.68 26.91 22.3363 26.9C23.5562 26.8813 24.9387 26.8612 25.9 25.9C26.8612 24.9387 26.8813 23.5562 26.9 22.3363C26.91 21.68 26.92 21.0025 27.09 20.5938C27.2738 20.1488 27.7612 19.64 28.2325 19.1475C29.0613 18.285 30 17.305 30 16C30 14.695 29.0613 13.7162 28.2325 12.8525ZM26.7887 17.7638C26.19 18.3888 25.57 19.035 25.2412 19.8288C24.9262 20.5913 24.9125 21.4625 24.9 22.3062C24.8875 23.1812 24.8738 24.0975 24.485 24.485C24.0963 24.8725 23.1862 24.8875 22.3062 24.9C21.4625 24.9125 20.5913 24.9262 19.8288 25.2412C19.035 25.57 18.3888 26.19 17.7638 26.7887C17.1388 27.3875 16.5 28 16 28C15.5 28 14.8562 27.385 14.2362 26.7887C13.6163 26.1925 12.965 25.57 12.1713 25.2412C11.4088 24.9262 10.5375 24.9125 9.69375 24.9C8.81875 24.8875 7.9025 24.8738 7.515 24.485C7.1275 24.0963 7.1125 23.1862 7.1 22.3062C7.0875 21.4625 7.07375 20.5913 6.75875 19.8288C6.43 19.035 5.81 18.3888 5.21125 17.7638C4.6125 17.1388 4 16.5 4 16C4 15.5 4.615 14.8562 5.21125 14.2362C5.8075 13.6163 6.43 12.965 6.75875 12.1713C7.07375 11.4088 7.0875 10.5375 7.1 9.69375C7.1125 8.81875 7.12625 7.9025 7.515 7.515C7.90375 7.1275 8.81375 7.1125 9.69375 7.1C10.5375 7.0875 11.4088 7.07375 12.1713 6.75875C12.965 6.43 13.6112 5.81 14.2362 5.21125C14.8612 4.6125 15.5 4 16 4C16.5 4 17.1438 4.615 17.7638 5.21125C18.3838 5.8075 19.035 6.43 19.8288 6.75875C20.5913 7.07375 21.4625 7.0875 22.3062 7.1C23.1812 7.1125 24.0975 7.12625 24.485 7.515C24.8725 7.90375 24.8875 8.81375 24.9 9.69375C24.9125 10.5375 24.9262 11.4088 25.2412 12.1713C25.57 12.965 26.19 13.6112 26.7887 14.2362C27.3875 14.8612 28 15.5 28 16C28 16.5 27.385 17.1438 26.7887 17.7638ZM21.7075 12.2925C21.8005 12.3854 21.8742 12.4957 21.9246 12.6171C21.9749 12.7385 22.0008 12.8686 22.0008 13C22.0008 13.1314 21.9749 13.2615 21.9246 13.3829C21.8742 13.5043 21.8005 13.6146 21.7075 13.7075L14.7075 20.7075C14.6146 20.8005 14.5043 20.8742 14.3829 20.9246C14.2615 20.9749 14.1314 21.0008 14 21.0008C13.8686 21.0008 13.7385 20.9749 13.6171 20.9246C13.4957 20.8742 13.3854 20.8005 13.2925 20.7075L10.2925 17.7075C10.1049 17.5199 9.99944 17.2654 9.99944 17C9.99944 16.7346 10.1049 16.4801 10.2925 16.2925C10.4801 16.1049 10.7346 15.9994 11 15.9994C11.2654 15.9994 11.5199 16.1049 11.7075 16.2925L14 18.5863L20.2925 12.2925C20.3854 12.1995 20.4957 12.1258 20.6171 12.0754C20.7385 12.0251 20.8686 11.9992 21 11.9992C21.1314 11.9992 21.2615 12.0251 21.3829 12.0754C21.5043 12.1258 21.6146 12.1995 21.7075 12.2925Z\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('enable_channel', 'Enable Channel')}\n              </div>\n            </div>\n          )}\n\n          {canDisable && (\n            <div\n              className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n              onClick={disableChannel}\n            >\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"16\"\n                  height=\"16\"\n                  viewBox=\"0 0 32 32\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9964 12.5533 27.6256 9.24882 25.1884 6.81163C22.7512 4.37445 19.4467 3.00364 16 3ZM27 16C27.0026 18.5719 26.0993 21.0626 24.4488 23.035L8.96501 7.55C10.5713 6.21372 12.5249 5.36255 14.5972 5.0961C16.6696 4.82964 18.775 5.15892 20.667 6.04541C22.5591 6.93189 24.1595 8.33891 25.281 10.1018C26.4026 11.8647 26.9988 13.9106 27 16ZM5.00001 16C4.99745 13.4281 5.90069 10.9374 7.55126 8.965L23.035 24.45C21.4288 25.7863 19.4751 26.6374 17.4028 26.9039C15.3304 27.1704 13.225 26.8411 11.333 25.9546C9.44096 25.0681 7.84053 23.6611 6.71899 21.8982C5.59745 20.1353 5.0012 18.0894 5.00001 16Z\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              </div>\n              <div className=\"text-[14px]\">\n                {t('disable_channel', 'Disable Channel')}\n              </div>\n            </div>\n          )}\n\n          <div\n            className=\"flex gap-[12px] items-center py-[8px] px-[10px]\"\n            onClick={deleteChannel}\n          >\n            <div>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 16 16\"\n                fill=\"currentColor\"\n              >\n                <path\n                  d=\"M13.5 3H11V2.5C11 2.10218 10.842 1.72064 10.5607 1.43934C10.2794 1.15804 9.89782 1 9.5 1H6.5C6.10218 1 5.72064 1.15804 5.43934 1.43934C5.15804 1.72064 5 2.10218 5 2.5V3H2.5C2.36739 3 2.24021 3.05268 2.14645 3.14645C2.05268 3.24021 2 3.36739 2 3.5C2 3.63261 2.05268 3.75979 2.14645 3.85355C2.24021 3.94732 2.36739 4 2.5 4H3V13C3 13.2652 3.10536 13.5196 3.29289 13.7071C3.48043 13.8946 3.73478 14 4 14H12C12.2652 14 12.5196 13.8946 12.7071 13.7071C12.8946 13.5196 13 13.2652 13 13V4H13.5C13.6326 4 13.7598 3.94732 13.8536 3.85355C13.9473 3.75979 14 3.63261 14 3.5C14 3.36739 13.9473 3.24021 13.8536 3.14645C13.7598 3.05268 13.6326 3 13.5 3ZM6 2.5C6 2.36739 6.05268 2.24021 6.14645 2.14645C6.24021 2.05268 6.36739 2 6.5 2H9.5C9.63261 2 9.75979 2.05268 9.85355 2.14645C9.94732 2.24021 10 2.36739 10 2.5V3H6V2.5ZM12 13H4V4H12V13ZM7 6.5V10.5C7 10.6326 6.94732 10.7598 6.85355 10.8536C6.75979 10.9473 6.63261 11 6.5 11C6.36739 11 6.24021 10.9473 6.14645 10.8536C6.05268 10.7598 6 10.6326 6 10.5V6.5C6 6.36739 6.05268 6.24021 6.14645 6.14645C6.24021 6.05268 6.36739 6 6.5 6C6.63261 6 6.75979 6.05268 6.85355 6.14645C6.94732 6.24021 7 6.36739 7 6.5ZM10 6.5V10.5C10 10.6326 9.94732 10.7598 9.85355 10.8536C9.75979 10.9473 9.63261 11 9.5 11C9.36739 11 9.24021 10.9473 9.14645 10.8536C9.05268 10.7598 9 10.6326 9 10.5V6.5C9 6.36739 9.05268 6.24021 9.14645 6.14645C9.24021 6.05268 9.36739 6 9.5 6C9.63261 6 9.75979 6.05268 9.85355 6.14645C9.94732 6.24021 10 6.36739 10 6.5Z\"\n                  fill=\"#F97066\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[14px]\">{t('delete', 'Delete')}</div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/merge.post.tsx",
    "content": "import { Button } from '@gitroom/react/form/button';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { FC, useCallback } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const MergePost: FC<{\n  merge: () => void;\n}> = (props) => {\n  const { merge } = props;\n  const t = useT();\n\n  const notReversible = useCallback(async () => {\n    if (\n      await deleteDialog(\n        'Are you sure you want to merge all comments into one post? This action is not reversible.',\n        'Yes'\n      )\n    ) {\n      merge();\n    }\n  }, [merge]);\n  return (\n    <Button className=\"!h-[30px] !text-sm !bg-red-800\" onClick={notReversible}>\n      {t('merge_comments_into_one_post', 'Merge comments into one post')}\n    </Button>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/missing-release.modal.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useState } from 'react';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { Button } from '@gitroom/react/form/button';\nimport { StatisticsModal } from '@gitroom/frontend/components/launches/statistics';\n\nexport const MissingReleaseModal: FC<{\n  postId: string;\n  onSuccess: () => void;\n}> = ({ postId, onSuccess }) => {\n  const t = useT();\n  const fetch = useFetch();\n  const modal = useModals();\n  const toaster = useToaster();\n  const [selected, setSelected] = useState<string | null>(null);\n  const [saving, setSaving] = useState(false);\n\n  const loadMissingContent = useCallback(async () => {\n    return (await fetch(`/posts/${postId}/missing`)).json();\n  }, [postId, fetch]);\n\n  const { data, isLoading } = useSWR(\n    `/posts/${postId}/missing`,\n    loadMissingContent\n  );\n\n  const handleSave = useCallback(async () => {\n    if (!selected) return;\n    setSaving(true);\n    try {\n      await fetch(`/posts/${postId}/release-id`, {\n        method: 'PUT',\n        body: JSON.stringify({ releaseId: selected }),\n      });\n      onSuccess();\n      modal.closeAll();\n      modal.openModal({\n        title: t('statistics', 'Statistics'),\n        closeOnClickOutside: true,\n        closeOnEscape: true,\n        withCloseButton: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px]',\n        },\n        children: <StatisticsModal postId={postId} />,\n        size: '80%',\n      });\n    } catch {\n      toaster.show(\n        t('release_id_update_failed', 'Failed to connect post'),\n        'warning'\n      );\n    } finally {\n      setSaving(false);\n    }\n  }, [selected, postId, fetch, toaster, t, onSuccess, modal]);\n\n  if (isLoading) {\n    return (\n      <div className=\"flex items-center justify-center py-[40px]\">\n        <LoadingComponent />\n      </div>\n    );\n  }\n\n  if (!data || data.length === 0) {\n    return (\n      <div className=\"text-center text-textColor py-[20px]\">\n        {t(\n          'no_missing_content',\n          'No content found from this provider. The provider may not support this feature.'\n        )}\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[16px]\">\n      <div className=\"text-[14px] text-textColor/70\">\n        {t(\n          'select_matching_content',\n          'Select the content that matches this post:'\n        )}\n      </div>\n      <div className=\"grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 gap-[10px] max-h-[400px] overflow-y-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor p-[4px]\">\n        {data.map((item: { id: string; url: string }) => (\n          <div\n            key={item.id}\n            onClick={() => setSelected(item.id)}\n            className={`cursor-pointer rounded-[8px] overflow-hidden border-2 transition-all ${\n              selected === item.id\n                ? 'border-[#612BD3] scale-[1.02]'\n                : 'border-transparent hover:border-textColor/20'\n            }`}\n          >\n            <img\n              src={item.url}\n              alt={item.id}\n              className=\"w-full aspect-square object-cover\"\n            />\n          </div>\n        ))}\n      </div>\n      <div className=\"flex justify-end gap-[10px] pt-[8px] border-t border-tableBorder\">\n        <Button\n          type=\"button\"\n          onClick={() => modal.closeAll()}\n          className=\"bg-transparent border border-tableBorder text-textColor\"\n        >\n          {t('cancel', 'Cancel')}\n        </Button>\n        <Button\n          type=\"button\"\n          onClick={handleSave}\n          disabled={!selected || saving}\n          loading={saving}\n        >\n          {t('connect_post', 'Connect Post')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/new.post.tsx",
    "content": "import React, { useCallback } from 'react';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport dayjs from 'dayjs';\nimport { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { SetSelectionModal } from '@gitroom/frontend/components/launches/calendar';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';\n\nexport const NewPost = () => {\n  const fetch = useFetch();\n  const modal = useModals();\n  const { integrations, reloadCalendarView, sets } = useCalendar();\n  const t = useT();\n\n  const createAPost = useCallback(async () => {\n    const date = (await (await fetch('/posts/find-slot')).json()).date;\n\n    const set: any = !sets.length\n      ? undefined\n      : await new Promise((resolve) => {\n          modal.openModal({\n            title: t('select_set', 'Select a Set'),\n            closeOnClickOutside: true,\n            closeOnEscape: true,\n            withCloseButton: false,\n            onClose: () => resolve('exit'),\n            classNames: {\n              modal: 'text-textColor',\n            },\n            children: (\n              <SetSelectionModal\n                sets={sets}\n                onSelect={(selectedSet) => {\n                  resolve(selectedSet);\n                  modal.closeAll();\n                }}\n                onContinueWithoutSet={() => {\n                  resolve(undefined);\n                  modal.closeAll();\n                }}\n              />\n            ),\n          });\n        });\n\n    if (set === 'exit') return;\n\n    modal.openModal({\n      id: 'add-edit-modal',\n      closeOnClickOutside: false,\n      removeLayout: true,\n      closeOnEscape: false,\n      withCloseButton: false,\n      askClose: true,\n      fullScreen: true,\n      classNames: {\n        modal: 'w-[100%] max-w-[1400px] text-textColor',\n      },\n      children: (\n        <AddEditModal\n          allIntegrations={integrations.map((p) => ({\n            ...p,\n          }))}\n          {...(set?.content ? { set: JSON.parse(set.content) } : {})}\n          reopenModal={createAPost}\n          mutate={reloadCalendarView}\n          integrations={integrations}\n          date={dayjs.utc(date).local()}\n        />\n      ),\n      size: '80%',\n      title: ``,\n    });\n  }, [integrations, sets]);\n  return (\n    <button\n      onClick={createAPost}\n      className=\"text-white flex-1 pt-[12px] pb-[14px] ps-[16px] pe-[20px] group-[.sidebar]:p-0 min-h-[44px] max-h-[44px] rounded-md bg-btnPrimary flex justify-center items-center gap-[5px] outline-none\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"21\"\n        height=\"20\"\n        viewBox=\"0 0 21 20\"\n        fill=\"none\"\n        className=\"min-w-[21px] min-h-[20px]\"\n      >\n        <path\n          d=\"M10.5001 4.16699V15.8337M4.66675 10.0003H16.3334\"\n          stroke=\"white\"\n          strokeWidth=\"1.5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n      <div className=\"flex-1 text-start text-[14px] group-[.sidebar]:hidden\">\n        {t('create_new_post', 'Create Post')}\n      </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/polonto/polonto.picture.generation.tsx",
    "content": "'use client';\n\nimport React, { useCallback } from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { InputGroup } from '@blueprintjs/core';\nimport { Clean } from '@blueprintjs/icons';\nimport { SectionTab } from 'polotno/side-panel';\nimport { getImageSize } from 'polotno/utils/image';\nimport { ImagesGrid } from 'polotno/side-panel/images-grid';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Button } from '@gitroom/react/form/button';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst GenerateTab = observer(({ store }: any) => {\n  const inputRef = React.useRef<any>(null);\n  const [image, setImage] = React.useState(null);\n  const [loading, setLoading] = React.useState(false);\n  const { billingEnabled } = useVariables();\n  const fetch = useFetch();\n  const toast = useToaster();\n  const loadCredits = useCallback(async () => {\n    if (!billingEnabled) {\n      return {\n        credits: 1000,\n      };\n    }\n    return (\n      await fetch(`/copilot/credits`, {\n        method: 'GET',\n      })\n    ).json();\n  }, []);\n  const { data, mutate } = useSWR('copilot-credits', loadCredits);\n  const t = useT();\n\n  const handleGenerate = async () => {\n    if (data?.credits <= 0) {\n      window.open('/billing', '_blank');\n      return;\n    }\n    if (!inputRef.current.value) {\n      toast.show('Please type your prompt', 'warning');\n      return;\n    }\n    setLoading(true);\n    setImage(null);\n    const req = await fetch(`/media/generate-image`, {\n      method: 'POST',\n      body: JSON.stringify({\n        prompt: inputRef.current.value,\n      }),\n    });\n    setLoading(false);\n    if (!req.ok) {\n      alert('Something went wrong, please try again later...');\n      return;\n    }\n    mutate();\n    const newData = await req.json();\n    setImage(newData.output);\n  };\n  return (\n    <>\n      <div\n        style={{\n          height: '40px',\n          paddingTop: '5px',\n        }}\n      >\n        {t('generate_image_with_ai', 'Generate image with AI')}\n        {data?.credits ? `(${data?.credits} left)` : ``}\n      </div>\n      <InputGroup\n        placeholder=\"Type your image generation prompt here...\"\n        onKeyDown={(e) => {\n          if (e.key === 'Enter') {\n            handleGenerate();\n          }\n        }}\n        style={{\n          marginBottom: '20px',\n        }}\n        inputRef={inputRef}\n      />\n      <Button\n        onClick={handleGenerate}\n        loading={loading}\n        innerClassName=\"invert\"\n        style={{\n          marginBottom: '40px',\n        }}\n      >\n        {data?.credits <= 0 ? 'Click to purchase more credits' : 'Generate'}\n      </Button>\n      {image && (\n        <ImagesGrid\n          shadowEnabled={false}\n          images={image ? [image] : []}\n          getPreview={(item) => item}\n          isLoading={loading}\n          onSelect={async (item, pos, element) => {\n            const src = item;\n            if (element && element.type === 'svg' && element.contentEditable) {\n              element.set({\n                maskSrc: src,\n              });\n              return;\n            }\n            if (\n              element &&\n              element.type === 'image' &&\n              element.contentEditable\n            ) {\n              element.set({\n                src: src,\n              });\n              return;\n            }\n            const { width, height } = await getImageSize(src);\n            const x = (pos?.x || store.width / 2) - width / 2;\n            const y = (pos?.y || store.height / 2) - height / 2;\n            store.activePage?.addElement({\n              type: 'image',\n              src: src,\n              width,\n              height,\n              x,\n              y,\n            });\n          }}\n          rowsNumber={1}\n        />\n      )}\n    </>\n  );\n});\nconst PictureGeneratorPanel = observer(({ store }: any) => {\n  return (\n    <div\n      style={{\n        height: '100%',\n        display: 'flex',\n        flexDirection: 'column',\n      }}\n    >\n      <GenerateTab store={store} />\n    </div>\n  );\n});\n\n// define the new custom section\nexport const PictureGeneratorSection = {\n  name: 'picture-generator-ai',\n  Tab: (props: any) => (\n    <SectionTab name=\"AI Img\" {...props}>\n      <Clean />\n    </SectionTab>\n  ),\n  // we need observer to update component automatically on any store changes\n  Panel: PictureGeneratorPanel,\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/polonto.tsx",
    "content": "'use client';\n\nimport {\n  createContext,\n  FC,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { createStore } from 'polotno/model/store';\nimport Workspace from 'polotno/canvas/workspace';\nimport { PolotnoContainer, SidePanelWrap, WorkspaceWrap } from 'polotno';\nimport { SidePanel, DEFAULT_SECTIONS } from 'polotno/side-panel';\nimport Toolbar from 'polotno/toolbar/toolbar';\nimport ZoomButtons from 'polotno/toolbar/zoom-buttons';\nimport { Button } from '@gitroom/react/form/button';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { PictureGeneratorSection } from '@gitroom/frontend/components/launches/polonto/polonto.picture.generation';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { loadVars } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nconst store = createStore({\n  get key() {\n    return loadVars().plontoKey;\n  },\n  showCredit: false,\n});\n\n// @ts-ignore\nconst CloseContext = createContext({\n  close: {} as any,\n  setMedia: {} as any,\n});\nconst ActionControls = ({ store }: any) => {\n  const t = useT();\n  const close = useContext(CloseContext);\n  const [load, setLoad] = useState(false);\n  const fetch = useFetch();\n  return (\n    <div>\n      <Button\n        loading={load}\n        className=\"outline-none\"\n        innerClassName=\"invert outline-none text-black\"\n        onClick={async () => {\n          setLoad(true);\n          const blob = await store.toBlob();\n          const formData = new FormData();\n          formData.append('file', blob, 'media.png');\n          const data = await (\n            await fetch('/media/upload-simple', {\n              method: 'POST',\n              body: formData,\n            })\n          ).json();\n          close.setMedia([\n            {\n              id: data.id,\n              path: data.path,\n            },\n          ]);\n          close.close();\n        }}\n      >\n        {t('use_this_media', 'Use this media')}\n      </Button>\n    </div>\n  );\n};\nconst Polonto: FC<{\n  setMedia: (params: { id: string; path: string }[]) => void;\n  type?: 'image' | 'video';\n  closeModal: () => void;\n  width?: number;\n  height?: number;\n}> = (props) => {\n  const { setMedia, type, closeModal } = props;\n\n  const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton);\n  useEffect(() => {\n    setActivateExitButton(false);\n    return () => {\n      setActivateExitButton(true);\n    };\n  }, []);\n\n  const user = useUser();\n  const features = useMemo(() => {\n    return [\n      ...DEFAULT_SECTIONS,\n      ...(user?.tier?.image_generator ? [PictureGeneratorSection] : []),\n    ] as any[];\n  }, [user?.tier?.image_generator]);\n  useEffect(() => {\n    store.addPage({\n      width: props.width || 540,\n      height: props.height || 675,\n    });\n    return () => {\n      store.clear();\n    };\n  }, []);\n  return (\n    <div className=\"bg-white text-black relative z-[400] polonto\">\n      <CloseContext.Provider\n        value={{\n          close: () => closeModal(),\n          setMedia,\n        }}\n      >\n        <PolotnoContainer\n          style={{\n            width: '100%',\n            height: '700px',\n          }}\n        >\n          <SidePanelWrap>\n            <SidePanel store={store} sections={features} />\n          </SidePanelWrap>\n          <WorkspaceWrap>\n            <Toolbar\n              store={store}\n              components={{\n                ActionControls,\n              }}\n            />\n            <Workspace store={store} />\n            <ZoomButtons store={store} />\n          </WorkspaceWrap>\n        </PolotnoContainer>\n      </CloseContext.Provider>\n    </div>\n  );\n};\nexport default Polonto;\n"
  },
  {
    "path": "apps/frontend/src/components/launches/repeat.component.tsx",
    "content": "'use client';\n\nimport { FC, useMemo, useState } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useClickOutside } from '@mantine/hooks';\nimport { isUSCitizen } from '@gitroom/frontend/components/launches/helpers/isuscitizen.utils';\nimport clsx from 'clsx';\nimport { RepeatIcon, DropdownArrowIcon } from '@gitroom/frontend/components/ui/icons';\nconst getList = (t: (key: string, fallback: string) => string) => [\n  {\n    value: 1,\n    label: t('day', 'Day'),\n  },\n  {\n    value: 2,\n    label: t('two_days', 'Two Days'),\n  },\n  {\n    value: 3,\n    label: t('three_days', 'Three Days'),\n  },\n  {\n    value: 4,\n    label: t('four_days', 'Four Days'),\n  },\n  {\n    value: 5,\n    label: t('five_days', 'Five Days'),\n  },\n  {\n    value: 6,\n    label: t('six_days', 'Six Days'),\n  },\n  {\n    value: 7,\n    label: t('week', 'Week'),\n  },\n  {\n    value: 14,\n    label: t('two_weeks', 'Two Weeks'),\n  },\n  {\n    value: 30,\n    label: t('month', 'Month'),\n  },\n  {\n    value: null,\n    label: t('cancel', 'Cancel'),\n  },\n];\nexport const RepeatComponent: FC<{\n  repeat: number | null;\n  onChange: (newVal: number) => void;\n}> = (props) => {\n  const { repeat } = props;\n  const t = useT();\n  const list = getList(t);\n  const [isOpen, setIsOpen] = useState(false);\n\n  const ref = useClickOutside(() => {\n    if (!isOpen) {\n      return;\n    }\n    setIsOpen(false);\n  });\n\n  const everyLabel = useMemo(() => {\n    if (!repeat) {\n      return '';\n    }\n    return list.find((p) => p.value === repeat)?.label;\n  }, [repeat, list]);\n\n  return (\n    <div\n      ref={ref}\n      className={clsx(\n        'border rounded-[8px] justify-center flex items-center relative h-[44px] text-[15px] font-[600] select-none',\n        isOpen ? 'border-[#612BD3]' : 'border-newTextColor/10',\n      )}\n    >\n      <div\n        onClick={() => setIsOpen(!isOpen)}\n        className=\"px-[16px] justify-center flex gap-[8px] items-center h-full select-none flex-1\"\n      >\n        <div className=\"cursor-pointer\">\n          <RepeatIcon />\n        </div>\n        <div className=\"cursor-pointer\">\n          {repeat\n            ? `${t('repeat_post_every_label', 'Repeat Post Every')} ${everyLabel}`\n            : t('repeat_post_every', 'Repeat Post Every...')}\n        </div>\n        <div className=\"cursor-pointer\">\n          <DropdownArrowIcon rotated={isOpen} />\n        </div>\n      </div>\n      {isOpen && (\n        <div className=\"z-[300] absolute start-0 bottom-[100%] w-[240px] bg-newBgColorInner p-[12px] menu-shadow -translate-y-[10px] flex flex-col\">\n          {list.map((p) => (\n            <div\n              onClick={() => {\n                props.onChange(Number(p.value));\n                setIsOpen(false);\n              }}\n              key={p.label}\n              className=\"h-[40px] py-[8px] px-[20px] -mx-[12px] hover:bg-newBgColor\"\n            >\n              {p.label}\n            </div>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/select.customer.tsx",
    "content": "'use client';\n\nimport { uniqBy } from 'lodash';\nimport React, { FC, useCallback, useMemo, useRef, useState } from 'react';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport clsx from 'clsx';\nimport { useClickOutside } from '@mantine/hooks';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { UserIcon, DropdownArrowIcon } from '@gitroom/frontend/components/ui/icons';\n\nexport const SelectCustomer: FC<{\n  onChange: (value: string) => void;\n  integrations: Integrations[];\n  customer?: string;\n}> = (props) => {\n  const { onChange, integrations, customer: currentCustomer } = props;\n  const { setCurrent } = useLaunchStore(\n    useShallow((state) => ({\n      setCurrent: state.setCurrent,\n    }))\n  );\n  const toaster = useToaster();\n  const t = useT();\n  const [customer, setCustomer] = useState(currentCustomer || '');\n  const [pos, setPos] = useState<any>({});\n  const [open, setOpen] = useState(false);\n  const ref = useClickOutside(() => {\n    if (open) {\n      setOpen(false);\n    }\n  });\n\n  const openClose = useCallback(() => {\n    if (open) {\n      setOpen(false);\n      return;\n    }\n\n    const { x, y, width, height } = ref.current?.getBoundingClientRect();\n    setPos({ top: y + height, left: x });\n    setOpen(true);\n  }, [open]);\n\n  const totalCustomers = useMemo(() => {\n    return uniqBy(integrations, (i) => i?.customer?.id).length;\n  }, [integrations]);\n  if (totalCustomers <= 1) {\n    return null;\n  }\n\n  return (\n    <div className=\"relative select-none z-[500]\" ref={ref}>\n      <div\n        data-tooltip-id=\"tooltip\"\n        data-tooltip-content={t('select_customer_tooltip', 'Select Customer')}\n        onClick={openClose}\n        className={clsx(\n          'relative z-[20] cursor-pointer h-[42px] rounded-[8px] pl-[16px] pr-[12px] gap-[8px] border flex items-center',\n          open ? 'border-[#612BD3]' : 'border-newColColor'\n        )}\n      >\n        <div>\n          <UserIcon />\n        </div>\n        <div>\n          <DropdownArrowIcon rotated={open} />\n        </div>\n      </div>\n      {open && (\n        <div\n          style={pos}\n          className=\"flex flex-col fixed pt-[12px] bg-newBgColorInner menu-shadow min-w-[250px]\"\n        >\n          <div className=\"text-[14px] font-[600] px-[12px] mb-[5px]\">\n            {t('customers', 'Customers')}\n          </div>\n          {uniqBy(integrations, (u) => u?.customer?.name)\n            .filter((f) => f.customer?.name)\n            .map((p) => (\n              <div\n                onClick={() => {\n                  toaster.show(\n                    t('customer_socials_selected', 'Customer socials selected'),\n                    'success'\n                  );\n                  setCustomer(p.customer?.id);\n                  onChange(p.customer?.id);\n                  setOpen(false);\n                  setCurrent('global')\n                }}\n                key={p.customer?.id}\n                className=\"p-[12px] hover:bg-newBgColor text-[14px] font-[500] h-[32px] flex items-center\"\n              >\n                {p.customer?.name}\n              </div>\n            ))}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/separate.post.tsx",
    "content": "import { Button } from '@gitroom/react/form/button';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { FC, useCallback } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nexport const SeparatePost: FC<{\n  posts: string[];\n  len: number;\n  merge: (posts: string[]) => void;\n  changeLoading: (loading: boolean) => void;\n}> = (props) => {\n  const { len, posts } = props;\n  const t = useT();\n  const fetch = useFetch();\n\n  const notReversible = useCallback(async () => {\n    if (\n      await deleteDialog(\n        'Are you sure you want to separate all posts? This action is not reversible.',\n        'Yes'\n      )\n    ) {\n      props.changeLoading(true);\n      const merge = props.posts.join('\\n');\n      const { posts } = await (\n        await fetch('/posts/separate-posts', {\n          method: 'POST',\n          body: JSON.stringify({\n            content: merge,\n            len: props.len,\n          }),\n        })\n      ).json();\n\n      props.merge(posts);\n      props.changeLoading(false);\n    }\n  }, [len, posts]);\n\n  return (\n    <Button className=\"!h-[30px] !text-sm !bg-red-800\" onClick={notReversible}>\n      {t('separate_post', 'Separate post to multiple posts')}\n    </Button>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/settings.modal.tsx",
    "content": "import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport React, { FC, useCallback, useState } from 'react';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Integration } from '@prisma/client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Button } from '@gitroom/react/form/button';\nimport { Slider } from '@gitroom/react/form/slider';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const Element: FC<{\n  setting: any;\n  onChange: (value: any) => void;\n}> = (props) => {\n  const { setting, onChange } = props;\n  const [value, setValue] = useState(setting.value);\n  return (\n    <div className=\"flex flex-col gap-[10px]\">\n      <div>{setting.title}</div>\n      <div className=\"text-[14px]\">{setting.description}</div>\n      <Slider\n        value={value === true ? 'on' : 'off'}\n        onChange={() => {\n          setValue(!value);\n          onChange(!value);\n        }}\n        fill={true}\n      />\n    </div>\n  );\n};\nexport const SettingsModal: FC<{\n  integration: Integration & {\n    customer?: {\n      id: string;\n      name: string;\n    };\n  };\n  onClose: () => void;\n}> = (props) => {\n  const fetch = useFetch();\n  const t = useT();\n  const { onClose, integration } = props;\n  const modal = useModals();\n  const [values, setValues] = useState(\n    JSON.parse(integration?.additionalSettings || '[]')\n  );\n  const changeValue = useCallback(\n    (index: number) => (value: any) => {\n      const newValues = [...values];\n      newValues[index].value = value;\n      setValues(newValues);\n    },\n    [values]\n  );\n  const save = useCallback(async () => {\n    await fetch(`/integrations/${integration.id}/settings`, {\n      method: 'POST',\n      body: JSON.stringify({\n        additionalSettings: JSON.stringify(values),\n      }),\n    });\n    modal.closeAll();\n    onClose();\n  }, [values, integration]);\n  return (\n    <div>\n      <div className=\"mt-[16px]\">\n        {values.map((setting: any, index: number) => (\n          <Element\n            key={setting.title}\n            setting={setting}\n            onChange={changeValue(index)}\n          />\n        ))}\n      </div>\n\n      <div className=\"my-[16px] flex gap-[10px]\">\n        <Button onClick={save}>{t('save', 'Save')}</Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/statistics.tsx",
    "content": "import React, { FC, Fragment, useCallback, useMemo, useState } from 'react';\nimport useSWR, { useSWRConfig } from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { ChartSocial } from '@gitroom/frontend/components/analytics/chart-social';\nimport { Select } from '@gitroom/react/form/select';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { MissingReleaseModal } from '@gitroom/frontend/components/launches/missing-release.modal';\n\ninterface AnalyticsData {\n  label: string;\n  data: Array<{ total: number; date: string }>;\n  percentageChange: number;\n  average?: boolean;\n}\n\nexport const StatisticsModal: FC<{\n  postId: string;\n}> = (props) => {\n  const { postId } = props;\n  const t = useT();\n  const fetch = useFetch();\n  const [dateRange, setDateRange] = useState(7);\n\n  const loadStatistics = useCallback(async () => {\n    return (await fetch(`/posts/${postId}/statistics`)).json();\n  }, [postId, fetch]);\n\n  const loadPostAnalytics = useCallback(async () => {\n    return (await fetch(`/analytics/post/${postId}?date=${dateRange}`)).json();\n  }, [postId, dateRange, fetch]);\n\n  const { data: statisticsData, isLoading: isLoadingStatistics } = useSWR(\n    `/posts/${postId}/statistics`,\n    loadStatistics\n  );\n\n  const { data: analyticsData, isLoading: isLoadingAnalytics, mutate: mutateAnalytics } = useSWR(\n    `/analytics/post/${postId}?date=${dateRange}`,\n    loadPostAnalytics,\n    {\n      revalidateOnFocus: false,\n      revalidateOnReconnect: false,\n      revalidateIfStale: false,\n      revalidateOnMount: true,\n      refreshWhenHidden: false,\n      refreshWhenOffline: false,\n    }\n  );\n\n  const isMissing = analyticsData && !Array.isArray(analyticsData) && analyticsData.missing;\n\n  const dateOptions = useMemo(() => {\n    return [\n      { key: 7, value: t('7_days', '7 Days') },\n      { key: 30, value: t('30_days', '30 Days') },\n      { key: 90, value: t('90_days', '90 Days') },\n    ];\n  }, [t]);\n\n  const totals = useMemo(() => {\n    if (!analyticsData || !Array.isArray(analyticsData)) return [];\n    return analyticsData.map((p: AnalyticsData) => {\n      const value =\n        (p?.data?.reduce((acc: number, curr: any) => acc + Number(curr.total), 0) || 0) /\n        (p.average ? p.data.length : 1);\n      if (p.average) {\n        return value.toFixed(2) + '%';\n      }\n      return Math.round(value);\n    });\n  }, [analyticsData]);\n\n  const isLoading = isLoadingStatistics || isLoadingAnalytics;\n\n  return (\n    <div className=\"relative min-h-[200px]\">\n      {isLoading ? (\n        <div className=\"flex items-center justify-center py-[40px]\">\n          <LoadingComponent />\n        </div>\n      ) : isMissing ? (\n        <MissingReleaseModal postId={postId} onSuccess={() => mutateAnalytics()} />\n      ) : (\n        <div className=\"flex flex-col gap-[24px]\">\n          {/* Post Analytics Section */}\n          {analyticsData && Array.isArray(analyticsData) && analyticsData.length > 0 && (\n            <div className=\"flex flex-col gap-[14px]\">\n              <div className=\"flex items-center justify-between\">\n                <h3 className=\"text-[18px] font-[500]\">\n                  {t('post_analytics', 'Post Analytics')}\n                </h3>\n                <div className=\"max-w-[150px]\">\n                  <Select\n                    label=\"\"\n                    name=\"date\"\n                    disableForm={true}\n                    hideErrors={true}\n                    value={dateRange}\n                    onChange={(e) => setDateRange(+e.target.value)}\n                  >\n                    {dateOptions.map((option) => (\n                      <option key={option.key} value={option.key}>\n                        {option.value}\n                      </option>\n                    ))}\n                  </Select>\n                </div>\n              </div>\n              <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-[16px]\">\n                {analyticsData.map((p: AnalyticsData, index: number) => {\n                  const colorVariants = ['purple', 'green', 'blue'] as const;\n                  const color = colorVariants[index % colorVariants.length];\n                  return (\n                    <div key={`analytics-${index}`} className=\"group\">\n                      <div className=\"flex flex-col h-full bg-newTableHeader border border-newTableBorder rounded-[12px] overflow-hidden transition-all duration-200 hover:border-[#612bd3]/50\">\n                        <div className=\"flex items-center justify-between px-[16px] pt-[14px] pb-[8px]\">\n                          <div className=\"flex items-center gap-[10px]\">\n                            <div\n                              className={`w-[8px] h-[8px] rounded-full ${\n                                color === 'purple' ? 'bg-[#612bd3]' : ''\n                              } ${color === 'green' ? 'bg-[#32d583]' : ''} ${\n                                color === 'blue' ? 'bg-[#1d9bf0]' : ''\n                              }`}\n                            />\n                            <span className=\"text-[15px] font-medium text-newTableText\">\n                              {p.label}\n                            </span>\n                          </div>\n                        </div>\n                        <div className=\"flex-1 px-[12px] py-[8px]\">\n                          <div className=\"h-[120px] relative\">\n                            <ChartSocial data={p.data} color={color} key={`chart-${index}`} />\n                          </div>\n                        </div>\n                        <div className=\"px-[16px] pb-[14px]\">\n                          <div className=\"text-[36px] leading-[42px] font-semibold tracking-tight\">\n                            {totals[index]}\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n\n          {/* Short Links Statistics Section */}\n          <div className=\"flex flex-col gap-[14px]\">\n            <h3 className=\"text-[18px] font-[500]\">\n              {t('short_links_statistics', 'Short Links Statistics')}\n            </h3>\n            {statisticsData?.clicks?.length === 0 ? (\n              <div className=\"text-gray-400\">\n                {t('no_short_link_results', 'No short link results')}\n              </div>\n            ) : (\n              <div className=\"grid grid-cols-3\">\n                <div className=\"bg-forth p-[4px] rounded-tl-lg\">\n                  {t('short_link', 'Short Link')}\n                </div>\n                <div className=\"bg-forth p-[4px]\">\n                  {t('original_link', 'Original Link')}\n                </div>\n                <div className=\"bg-forth p-[4px] rounded-tr-lg\">\n                  {t('clicks', 'Clicks')}\n                </div>\n                {statisticsData?.clicks?.map((p: any) => (\n                  <Fragment key={p.short}>\n                    <div className=\"p-[4px] py-[10px] bg-customColor6\">\n                      {p.short}\n                    </div>\n                    <div className=\"p-[4px] py-[10px] bg-customColor6\">\n                      {p.original}\n                    </div>\n                    <div className=\"p-[4px] py-[10px] bg-customColor6\">\n                      {p.clicks}\n                    </div>\n                  </Fragment>\n                ))}\n              </div>\n            )}\n          </div>\n\n          {/* No analytics available message */}\n          {(!analyticsData || !Array.isArray(analyticsData) || analyticsData.length === 0) &&\n            (!statisticsData?.clicks || statisticsData.clicks.length === 0) && (\n              <div className=\"text-center text-gray-400 py-[20px]\">\n                {t('no_statistics_available', 'No statistics available for this post')}\n              </div>\n            )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/tags.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useMemo, useState } from 'react';\nimport { ReactTags } from 'react-tag-autocomplete';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Input } from '@gitroom/react/form/input';\nimport { ColorPicker } from '@gitroom/react/form/color.picker';\nimport { Button } from '@gitroom/react/form/button';\nimport { uniqBy } from 'lodash';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useClickOutside } from '@mantine/hooks';\nimport clsx from 'clsx';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport {\n  TagIcon,\n  DropdownArrowIcon,\n  PlusIcon,\n  CheckmarkIcon,\n} from '@gitroom/frontend/components/ui/icons';\n\nexport const TagsComponent: FC<{\n  name: string;\n  label: string;\n  initial: any[];\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const fetch = useFetch();\n\n  const loadTags = useCallback(async () => {\n    return (await fetch('/posts/tags')).json();\n  }, []);\n\n  const { data, isLoading, mutate } = useSWR('load-tags', loadTags);\n\n  if (isLoading) {\n    return null;\n  }\n\n  return <TagsComponentInner {...props} allTags={data} mutate={mutate} />;\n};\n\nexport const TagsComponentInner: FC<{\n  name: string;\n  label: string;\n  initial: any[];\n  allTags: any;\n  mutate: () => Promise<any>;\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = ({ initial, onChange, name, mutate, allTags: data }) => {\n  const t = useT();\n  const fetch = useFetch();\n  const [isOpen, setIsOpen] = useState(false);\n  const [allowClose, setAllowClose] = useState(true);\n  const [tagValue, setTagValue] = useState<any[]>(\n    (initial?.slice(0) || []).map((p: any) => {\n      return data?.tags.find((a: any) => a.name === p.value) || p;\n    })\n  );\n  const modals = useModals();\n\n  const ref = useClickOutside(() => {\n    if (!isOpen || !allowClose) {\n      return;\n    }\n    setIsOpen(false);\n  });\n\n  const addTag = useCallback(async () => {\n    const val: string | undefined = await new Promise((resolve) => {\n      modals.openModal({\n        title: t('add_new_tag', 'Add New Tag'),\n        children: (close) => (\n          <ShowModal tag=\"\" close={close} resolve={resolve} />\n        ),\n      });\n    });\n\n    const newValues = await mutate();\n\n    if (!val) {\n      return;\n    }\n\n    const newTag = newValues.tags.find((p: any) => p.name === val);\n    if (newTag) {\n      const modify = [...tagValue, newTag];\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    }\n  }, []);\n\n  const deleteTag = useCallback(\n    async (tag: any, e: React.MouseEvent) => {\n      setAllowClose(false);\n      e.stopPropagation();\n      const confirmed: boolean = await new Promise((resolve) => {\n        modals.openModal({\n          title: t('delete_tag', 'Delete Tag'),\n          children: (close) => (\n            <ConfirmDeleteModal\n              tagName={tag.name}\n              close={close}\n              resolve={resolve}\n            />\n          ),\n        });\n      });\n\n      if (!confirmed) {\n        setTimeout(() => {\n          setAllowClose(true);\n        }, 500);\n        return;\n      }\n\n      await fetch(`/posts/tags/${tag.id}`, {\n        method: 'DELETE',\n      });\n\n      // Remove the tag from current selection if it was selected\n      const modify = tagValue.filter((a) => a.id !== tag.id);\n      if (modify.length !== tagValue.length) {\n        setTagValue(modify);\n        onChange({\n          target: {\n            value: modify.map((p: any) => ({\n              label: p.name,\n              value: p.name,\n            })),\n            name,\n          },\n        });\n      }\n\n      await mutate();\n\n      setTimeout(() => {\n        setAllowClose(true);\n      }, 500);\n    },\n    [tagValue, name, onChange, mutate, fetch, modals, t]\n  );\n\n  return (\n    <div\n      ref={ref}\n      className={clsx(\n        'border rounded-[8px] justify-center flex items-center relative h-[44px] text-[15px] font-[600] select-none',\n        isOpen ? 'border-[#612BD3]' : 'border-newTextColor/10'\n      )}\n    >\n      <div\n        onClick={() => setIsOpen(!isOpen)}\n        className=\"px-[16px] justify-center flex gap-[8px] items-center h-full select-none flex-1\"\n      >\n        <div className=\"cursor-pointer\">\n          <TagIcon />\n        </div>\n        <div className=\"cursor-pointer flex gap-[4px]\">\n          {tagValue.length === 0 ? (\n            t('add_new_tag', 'Add New Tag')\n          ) : (\n            <>\n              <div\n                className=\"h-full flex justify-center items-center px-[8px] rounded-[4px]\"\n                style={{ backgroundColor: tagValue[0].color }}\n              >\n                <span className=\"text-shadow-tags text-[#fff]\">\n                  {tagValue[0].name}\n                </span>\n              </div>\n              {tagValue.length > 1 ? <span>+{tagValue.length - 1}</span> : null}\n            </>\n          )}\n        </div>\n        <div className=\"cursor-pointer\">\n          <DropdownArrowIcon rotated={isOpen} />\n        </div>\n      </div>\n      {isOpen && (\n        <div className=\"z-[300] absolute start-0 bottom-[100%] w-[240px] bg-newBgColorInner p-[12px] menu-shadow -translate-y-[10px] flex flex-col\">\n          {(data?.tags || []).map((p: any) => (\n            <div\n              onClick={() => {\n                const exists = !!tagValue.find((a) => a.id === p.id);\n                let modify = [];\n                if (exists) {\n                  modify = tagValue.filter((a) => a.id !== p.id);\n                } else {\n                  modify = [...tagValue, p];\n                }\n                setTagValue(modify);\n                onChange({\n                  target: {\n                    value: modify.map((p: any) => ({\n                      label: p.name,\n                      value: p.name,\n                    })),\n                    name,\n                  },\n                });\n              }}\n              key={p.name}\n              className=\"min-h-[40px] py-[8px] px-[20px] -mx-[12px] flex gap-[8px] items-center group\"\n            >\n              <Check\n                onChange={() => {}}\n                value={!!tagValue.find((a) => a.id === p.id)}\n              />\n              <div className=\"h-full flex items-center flex-1 break-all\">\n                <span\n                  className=\"text-[#fff] px-[8px] rounded-[8px] text-shadow-tags\"\n                  style={{ backgroundColor: p.color }}\n                >\n                  {p.name}\n                </span>\n              </div>\n              {!tagValue.find((a) => a.id === p.id) && (\n                <div\n                  onClick={(e) => deleteTag(p, e)}\n                  className=\"ms-auto transition-opacity cursor-pointer text-red-500 text-[14px] font-[600]\"\n                >\n                  ×\n                </div>\n              )}\n            </div>\n          ))}\n          <div\n            onClick={addTag}\n            className=\"cursor-pointer gap-[8px] flex w-full h-[34px] rounded-[8px] mt-[12px] px-[16px] justify-center items-center bg-[#612BD3] text-white\"\n          >\n            <div>\n              <PlusIcon />\n            </div>\n            <div className=\"text-[13px] font-[600]\">\n              {t('add_new_tag', 'Add New Tag')}\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nconst Check: FC<{ value: boolean; onChange: (value: boolean) => void }> = ({\n  value,\n  onChange,\n}) => {\n  return (\n    <div\n      onClick={() => onChange(!value)}\n      className={clsx(\n        'text-[10px] font-[500] text-center flex border border-btnSimple rounded-[6px] min-w-[20px] min-h-[20px] w-[20px] h-[20px] justify-center items-center',\n        value && 'bg-[#612BD3]'\n      )}\n    >\n      {value ? <CheckmarkIcon className=\"text-white\" /> : ''}\n    </div>\n  );\n};\nexport const TagsComponentA: FC<{\n  name: string;\n  label: string;\n  initial: any[];\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, initial } = props;\n  const fetch = useFetch();\n  const [tagValue, setTagValue] = useState<any[]>(initial?.slice(0) || []);\n  const [suggestions, setSuggestions] = useState<string>('');\n  const [showModal, setShowModal] = useState<any>(false);\n  const loadTags = useCallback(async () => {\n    return (await fetch('/posts/tags')).json();\n  }, []);\n  const { isLoading, data, mutate } = useSWR<{\n    tags: {\n      name: string;\n      color: string;\n    }[];\n  }>('tags', loadTags, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n  const onDelete = useCallback(\n    (tagIndex: number) => {\n      const modify = tagValue.filter((_, i) => i !== tagIndex);\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  const createNewTag = useCallback(\n    async (newTag: any) => {\n      const val = await new Promise((resolve) => {\n        setShowModal({\n          tag: newTag.value,\n          resolve,\n          close: () => setShowModal(false),\n        });\n      });\n      setShowModal(false);\n      mutate();\n      return val;\n    },\n    [mutate]\n  );\n  const edit = useCallback(\n    (tag: any) => async (e: any) => {\n      e.stopPropagation();\n      e.preventDefault();\n      const val = await new Promise((resolve) => {\n        setShowModal({\n          tag: tag.name,\n          color: tag.color,\n          id: tag.id,\n          resolve,\n          close: () => setShowModal(false),\n        });\n      });\n      setShowModal(false);\n      mutate();\n      const modify = tagValue.map((t) => {\n        if (t.label === tag.name) {\n          return {\n            value: val,\n            label: val,\n          };\n        }\n        return t;\n      });\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue, data]\n  );\n  const onAddition = useCallback(\n    async (newTag: any) => {\n      if (tagValue.length >= 3) {\n        return;\n      }\n      const getTag = data?.tags?.find((f) => f.name === newTag.label)\n        ? newTag.label\n        : await createNewTag(newTag);\n      const modify = [\n        ...tagValue,\n        {\n          value: getTag,\n          label: getTag,\n        },\n      ];\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue, data]\n  );\n\n  // useEffect(() => {\n  //   const settings = getValues()[props.name];\n  //   if (settings) {\n  //     setTagValue(settings);\n  //   }\n  // }, []);\n\n  const suggestionsArray = useMemo(() => {\n    return uniqBy<{\n      label: string;\n      value: string;\n    }>(\n      [\n        ...(data?.tags.map((p) => ({\n          label: p.name,\n          value: p.name,\n        })) || []),\n        ...tagValue,\n        {\n          label: suggestions,\n          value: suggestions,\n        },\n      ].filter((f) => f.label),\n      (o) => o.label\n    );\n  }, [suggestions, tagValue]);\n\n  const t = useT();\n\n  if (isLoading) {\n    return null;\n  }\n  return (\n    <>\n      {showModal && <ShowModal {...showModal} />}\n      <div className=\"flex-1 flex tags-top\">\n        <ReactTags\n          placeholderText={t('add_a_tag', 'Add a tag')}\n          suggestions={suggestionsArray}\n          selected={tagValue}\n          onAdd={onAddition}\n          onInput={setSuggestions}\n          onDelete={onDelete}\n          renderTag={(tag) => {\n            const findTag = data?.tags?.find((f) => f.name === tag.tag.label);\n            const findIndex = tagValue.findIndex(\n              (f) => f.label === tag.tag.label\n            );\n            return (\n              <div\n                className={`min-w-[50px] float-left ms-[4px] p-[3px] rounded-sm relative`}\n                style={{\n                  backgroundColor: findTag?.color,\n                }}\n              >\n                <div\n                  className=\"absolute -top-[5px] start-[10px] text-[12px] text-red-600 bg-white px-[3px] rounded-full\"\n                  onClick={edit(findTag)}\n                >\n                  {t('edit', 'Edit')}\n                </div>\n                <div\n                  className=\"absolute -top-[5px] -start-[5px] text-[12px] text-red-600 bg-white px-[3px] rounded-full\"\n                  onClick={() => onDelete(findIndex)}\n                >\n                  X\n                </div>\n                <div className=\"text-white mix-blend-difference\">\n                  {tag.tag.label}\n                </div>\n              </div>\n            );\n          }}\n        />\n      </div>\n    </>\n  );\n};\nconst ConfirmDeleteModal: FC<{\n  tagName: string;\n  close: () => void;\n  resolve: (value: boolean) => void;\n}> = ({ tagName, close, resolve }) => {\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col gap-[16px]\">\n      <p className=\"text-[14px]\">\n        {t(\n          'confirm_delete_tag',\n          'Are you sure you want to delete the tag \"{{tagName}}\"?',\n          { tagName }\n        )}\n      </p>\n      <div className=\"flex gap-[8px] justify-end\">\n        <Button\n          onClick={() => {\n            resolve(false);\n            close();\n          }}\n        >\n          {t('cancel', 'Cancel')}\n        </Button>\n        <Button\n          onClick={() => {\n            resolve(true);\n            close();\n          }}\n          className=\"bg-red-500 hover:bg-red-600\"\n        >\n          {t('delete', 'Delete')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nconst ShowModal: FC<{\n  tag: string;\n  color?: string;\n  id?: string;\n  close: () => void;\n  resolve: (value: string) => void;\n}> = (props) => {\n  const t = useT();\n\n  const { close, tag, resolve, color: theColor, id } = props;\n  const fetch = useFetch();\n  const [color, setColor] = useState<string>(theColor || '#942828');\n  const [tagName, setTagName] = useState<string>(tag);\n  const save = useCallback(async () => {\n    await fetch(id ? `/posts/tags/${id}` : '/posts/tags', {\n      method: id ? 'PUT' : 'POST',\n      body: JSON.stringify({\n        name: tagName,\n        color,\n      }),\n    });\n    resolve(tagName);\n    close();\n  }, [tagName, color, id]);\n  return (\n    <div>\n      <Input\n        name=\"name\"\n        disableForm={true}\n        label={t('tag_name', 'Name')}\n        value={tagName}\n        onChange={(e) => setTagName(e.target.value)}\n      />\n      <ColorPicker\n        onChange={(e) => setColor(e.target.value)}\n        label={t('label_tag_color', 'Tag Color')}\n        name=\"color\"\n        value={color}\n        enabled={true}\n        canBeCancelled={false}\n      />\n      <Button onClick={save} className=\"mt-[16px]\">\n        {t('save', 'Save')}\n      </Button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/time.table.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useMemo, useState } from 'react';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport dayjs from 'dayjs';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { Select } from '@gitroom/react/form/select';\nimport { Button } from '@gitroom/react/form/button';\nimport utc from 'dayjs/plugin/utc';\nimport timezone from 'dayjs/plugin/timezone';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\n// @ts-ignore\nimport useKeypress from 'react-use-keypress';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { sortBy } from 'lodash';\nimport { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport clsx from 'clsx';\nimport {\n  TrashIcon,\n  PlusIcon,\n  DelayIcon,\n} from '@gitroom/frontend/components/ui/icons';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\n\nconst hours = [...Array(24).keys()].map((i) => ({\n  value: i,\n}));\n\nconst minutes = [...Array(60).keys()].map((i) => ({\n  value: i,\n}));\n\nexport const TimeTable: FC<{\n  integration: Integrations;\n  mutate: () => void;\n}> = (props) => {\n  const t = useT();\n  const {\n    integration: { time },\n    mutate,\n  } = props;\n  const [currentTimes, setCurrentTimes] = useState([...time]);\n  const [hour, setHour] = useState(0);\n  const [minute, setMinute] = useState(0);\n  const fetch = useFetch();\n  const modal = useModals();\n  usePreventWindowUnload(true);\n\n  const askClose = useCallback(async () => {\n    if (\n      !(await deleteDialog(\n        t(\n          'are_you_sure_you_want_to_close_the_window',\n          'Are you sure you want to close the window?'\n        ),\n        t('yes_close', 'Yes, close')\n      ))\n    ) {\n      return;\n    }\n    modal.closeAll();\n  }, []);\n\n  useKeypress('Escape', askClose);\n\n  const removeSlot = useCallback(\n    (index: number) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_this_slot',\n            'Are you sure you want to delete this slot?'\n          )\n        ))\n      ) {\n        return;\n      }\n      setCurrentTimes((prev) => prev.filter((_, i) => i !== index));\n    },\n    []\n  );\n\n  const addHour = useCallback(() => {\n    const calculateMinutes =\n      newDayjs()\n        .utc()\n        .startOf('day')\n        .add(hour, 'hours')\n        .add(minute, 'minutes')\n        .diff(newDayjs().utc().startOf('day'), 'minutes') -\n      dayjs.tz().utcOffset();\n    setCurrentTimes((prev) => [\n      ...prev,\n      {\n        time: calculateMinutes,\n      },\n    ]);\n  }, [hour, minute]);\n\n  const times = useMemo(() => {\n    return sortBy(\n      currentTimes.map(({ time }) => ({\n        value: time,\n        formatted: dayjs\n          .utc()\n          .startOf('day')\n          .add(time, 'minutes')\n          .local()\n          .format('HH:mm'),\n      })),\n      (p) => p.value\n    );\n  }, [currentTimes]);\n\n  const save = useCallback(async () => {\n    await fetch(`/integrations/${props.integration.id}/time`, {\n      method: 'POST',\n      body: JSON.stringify({\n        time: currentTimes,\n      }),\n    });\n    mutate();\n    modal.closeAll();\n  }, [currentTimes]);\n\n  return (\n    <div className=\"relative w-full max-w-[400px] mx-auto\">\n      {/* Add Time Slot Section */}\n      <div className=\"bg-newBgColorInner rounded-[12px] p-[20px] border border-newTableBorder\">\n        <div className=\"text-[15px] font-semibold mb-[16px] flex items-center gap-[8px]\">\n          <DelayIcon size={18} className=\"text-[#612BD3]\" />\n          {t('add_time_slot', 'Add Time Slot')}\n        </div>\n\n        <div className=\"flex gap-[12px] items-end\">\n          <div className=\"flex-1\">\n            <Select\n              label={t('hour', 'Hour')}\n              name=\"hour\"\n              disableForm={true}\n              hideErrors={true}\n              value={hour}\n              onChange={(e) => setHour(Number(e.target.value))}\n            >\n              {hours.map((h) => (\n                <option key={h.value} value={h.value}>\n                  {h.value.toString().padStart(2, '0')}\n                </option>\n              ))}\n            </Select>\n          </div>\n          <div className=\"flex-1\">\n            <Select\n              label={t('minutes', 'Minutes')}\n              name=\"minutes\"\n              disableForm={true}\n              hideErrors={true}\n              value={minute}\n              onChange={(e) => setMinute(Number(e.target.value))}\n            >\n              {minutes.map((m) => (\n                <option key={m.value} value={m.value}>\n                  {m.value.toString().padStart(2, '0')}\n                </option>\n              ))}\n            </Select>\n          </div>\n          <button\n            type=\"button\"\n            onClick={addHour}\n            className=\"h-[42px] px-[16px] bg-[#612BD3] hover:bg-[#7640e0] transition-colors rounded-[8px] flex items-center gap-[6px] text-white text-[14px] font-medium\"\n          >\n            <PlusIcon size={14} />\n            {t('add', 'Add')}\n          </button>\n        </div>\n      </div>\n\n      {/* Time Slots List */}\n      <div className=\"mt-[20px]\">\n        <div className=\"text-[14px] text-newTextColor/60 mb-[12px]\">\n          {t('scheduled_times', 'Scheduled Times')} ({times.length})\n        </div>\n\n        {times.length === 0 ? (\n          <div className=\"text-center py-[32px] text-newTextColor/40 text-[14px] border border-dashed border-newTableBorder rounded-[12px]\">\n            {t('no_time_slots', 'No time slots added yet')}\n          </div>\n        ) : (\n          <div className=\"flex flex-col gap-[8px]\">\n            {times.map((timeSlot, index) => (\n              <div\n                key={`${timeSlot.value}-${index}`}\n                className={clsx(\n                  'group flex items-center justify-between',\n                  'h-[48px] px-[16px] rounded-[8px]',\n                  'bg-newBgColorInner border border-newTableBorder',\n                  'hover:border-[#612BD3]/40 transition-colors'\n                )}\n              >\n                <div className=\"flex items-center gap-[12px]\">\n                  <div className=\"w-[8px] h-[8px] rounded-full bg-[#612BD3]\" />\n                  <span className=\"text-[15px] font-medium tabular-nums\">\n                    {timeSlot.formatted}\n                  </span>\n                </div>\n                <button\n                  type=\"button\"\n                  onClick={removeSlot(index)}\n                  className=\"opacity-0 group-hover:opacity-100 transition-opacity p-[8px] hover:bg-red-500/10 rounded-[6px] text-red-400 hover:text-red-500\"\n                >\n                  <TrashIcon size={16} />\n                </button>\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n\n      {/* Save Button */}\n      <div className=\"mt-[24px]\">\n        <Button type=\"button\" className=\"w-full rounded-[8px]\" onClick={save}>\n          {t('save_changes', 'Save Changes')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/up.down.arrow.tsx",
    "content": "import { FC, useCallback } from 'react';\nimport clsx from 'clsx';\nimport { ChevronUpIcon } from '@gitroom/frontend/components/ui/icons';\n\nconst Arrow: FC<{\n  flip: boolean;\n}> = (props) => {\n  const { flip } = props;\n  return (\n    <ChevronUpIcon\n      style={{\n        transform: !flip ? 'rotate(180deg)' : '',\n      }}\n    />\n  );\n};\nexport const UpDownArrow: FC<{\n  isUp: boolean;\n  isDown: boolean;\n  onChange: (type: 'up' | 'down') => void;\n}> = (props) => {\n  const { isUp, isDown, onChange } = props;\n  const changePosition = useCallback(\n    (type: 'up' | 'down') => () => {\n      onChange(type);\n    },\n    []\n  );\n  return (\n    <div className=\"flex flex-col gap-[8px] pt-[8px]\">\n      <button\n        onClick={changePosition('up')}\n        className={clsx(\n          'outline-none w-[20px] h-[20px] flex justify-center items-center',\n          isUp\n            ? 'cursor-pointer'\n            : 'pointer-events-none text-textColor opacity-50'\n        )}\n      >\n        <Arrow flip={true} />\n      </button>\n      <button\n        onClick={changePosition('down')}\n        className={clsx(\n          'outline-none rounded-bl-[20px] w-[20px] h-[20px] flex justify-center items-center',\n          isDown\n            ? 'cursor-pointer'\n            : 'pointer-events-none text-textColor opacity-50'\n        )}\n      >\n        <Arrow flip={false} />\n      </button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/web3/providers/moltbook.provider.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useEffect, useRef, useState } from 'react';\nimport { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { Input } from '@gitroom/react/form/input';\nimport { Button } from '@gitroom/react/form/button';\nimport copy from 'copy-to-clipboard';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const MoltbookProvider: FC<Web3ProviderInterface> = (props) => {\n  const { onComplete, nonce } = props;\n  const fetch = useFetch();\n  const stop = useRef(false);\n  const [step, setStep] = useState<'init' | 'registering' | 'waiting' | 'error'>('init');\n  const [agentName, setAgentName] = useState('');\n  const [agentDescription, setAgentDescription] = useState('');\n  const [claimUrl, setClaimUrl] = useState('');\n  const [apiKey, setApiKey] = useState('');\n  const [error, setError] = useState('');\n  const toaster = useToaster();\n  const t = useT();\n\n  const register = async () => {\n    if (!agentName.trim()) {\n      toaster.show('Please enter an agent name', 'warning');\n      return;\n    }\n\n    setStep('registering');\n    setError('');\n\n    try {\n      const response = await fetch('/integrations/moltbook/register', {\n        method: 'POST',\n        body: JSON.stringify({\n          name: agentName.trim(),\n          description: agentDescription.trim() || 'Postiz social media scheduler',\n        }),\n      });\n\n      const data = await response.json();\n\n      if (data.error) {\n        setError(data.error);\n        setStep('error');\n        return;\n      }\n\n      setApiKey(data.apiKey);\n      setClaimUrl(data.claimUrl);\n      setStep('waiting');\n\n      pollForClaim(data.apiKey);\n    } catch (err) {\n      setError('Failed to register agent');\n      setStep('error');\n    }\n  };\n\n  const pollForClaim = async (key: string) => {\n    stop.current = false;\n\n    while (!stop.current) {\n      try {\n        const response = await fetch(`/integrations/moltbook/status?apiKey=${encodeURIComponent(key)}`);\n        const data = await response.json();\n\n        if (data.claimed) {\n          onComplete(key, nonce);\n          return;\n        }\n      } catch (err) {\n        // Continue polling\n      }\n\n      await timer(3000);\n    }\n  };\n\n  const copyClaimUrl = useCallback(() => {\n    copy(claimUrl);\n    toaster.show('Claim URL copied to clipboard', 'success');\n  }, [claimUrl, toaster]);\n\n  useEffect(() => {\n    return () => {\n      stop.current = true;\n    };\n  }, []);\n\n  return (\n    <div className=\"justify-center items-center flex flex-col pt-[16px]\">\n      {step === 'init' && (\n        <>\n          <div className=\"text-center mb-[16px]\">\n            {t('moltbook_register_description', 'Register your Moltbook agent to connect:')}\n          </div>\n          <div className=\"w-full space-y-[12px]\">\n            <Input\n              label={t('agent_name', 'Agent Name')}\n              value={agentName}\n              name=\"agentName\"\n              disableForm={true}\n              onChange={(e) => setAgentName(e.target.value)}\n              placeholder=\"MyPostizAgent\"\n            />\n            <Input\n              label={t('description_optional', 'Description (optional)')}\n              value={agentDescription}\n              name=\"agentDescription\"\n              disableForm={true}\n              onChange={(e) => setAgentDescription(e.target.value)}\n              placeholder=\"Social media scheduler\"\n            />\n            <Button className=\"w-full\" onClick={register}>\n              {t('register_agent', 'Register Agent')}\n            </Button>\n          </div>\n        </>\n      )}\n\n      {step === 'registering' && (\n        <div className=\"text-center\">\n          {t('registering_agent', 'Registering agent...')}\n        </div>\n      )}\n\n      {step === 'waiting' && (\n        <div className=\"w-full text-center\">\n          <div className=\"mb-[16px]\">\n            {t('moltbook_claim_instructions', 'Please visit the claim URL to verify your agent:')}\n          </div>\n          <div className=\"flex gap-[8px]\">\n            <div className=\"flex-1\">\n              <Input\n                label=\"\"\n                value={claimUrl}\n                name=\"claimUrl\"\n                disableForm={true}\n                readOnly\n              />\n            </div>\n            <Button onClick={copyClaimUrl}>{t('copy', 'Copy')}</Button>\n          </div>\n          <div className=\"mt-[16px] text-sm opacity-70\">\n            {t('waiting_for_claim', 'Waiting for you to claim your agent...')}\n          </div>\n          <div className=\"mt-[8px]\">\n            <a\n              href={claimUrl}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"text-blue-500 hover:underline\"\n            >\n              {t('open_claim_page', 'Open claim page')}\n            </a>\n          </div>\n        </div>\n      )}\n\n      {step === 'error' && (\n        <div className=\"w-full text-center\">\n          <div className=\"text-red-500 mb-[16px]\">{error}</div>\n          <Button onClick={() => setStep('init')}>\n            {t('try_again', 'Try Again')}\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/web3/providers/telegram.provider.tsx",
    "content": "'use client';\n\nimport '@neynar/react/dist/style.css';\nimport React, { FC, useCallback, useEffect, useRef, useState } from 'react';\nimport { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { Input } from '@gitroom/react/form/input';\nimport { Button } from '@gitroom/react/form/button';\nimport copy from 'copy-to-clipboard';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const TelegramProvider: FC<Web3ProviderInterface> = (props) => {\n  const { onComplete, nonce } = props;\n  const { telegramBotName } = useVariables();\n  const fetch = useFetch();\n  const word = useRef(makeId(4));\n  const stop = useRef(false);\n  const [step, setStep] = useState(false);\n  const toaster = useToaster();\n  async function* load() {\n    let id = '';\n    while (true) {\n      const data = await (\n        await fetch(\n          `/integrations/telegram/updates?word=${word.current}${\n            id ? `&id=${id}` : ''\n          }`\n        )\n      ).json();\n      if (data.lastChatId) {\n        id = data.lastChatId;\n      }\n      yield data;\n    }\n  }\n  const t = useT();\n\n  const loadAll = async () => {\n    stop.current = false;\n    setStep(true);\n    const generator = load();\n    for await (const data of generator) {\n      if (stop.current) {\n        return;\n      }\n      if (data.chatId) {\n        onComplete(data.chatId, nonce);\n        return;\n      }\n      await timer(2000);\n    }\n  };\n  const copyText = useCallback(() => {\n    copy(`/connect ${word.current}`);\n    toaster.show('Copied to clipboard', 'success');\n  }, []);\n  useEffect(() => {\n    return () => {\n      stop.current = true;\n    };\n  }, []);\n  return (\n    <>\n      <div className=\"justify-center items-center flex flex-col pt-[16px]\">\n        <div>\n          {t('please_add', 'Please add')} <strong>@{telegramBotName}</strong>{' '}\n          {t(\n            'to_your_telegram_group_channel_and_click_here',\n            'to your\\n          telegram group / channel and click here:'\n          )}\n        </div>\n        {!step ? (\n          <div className=\"w-full mt-[16px]\" onClick={loadAll}>\n            <div\n              className={`cursor-pointer bg-[#2EA6DD] h-[44px] rounded-[4px] flex justify-center items-center text-white gap-[4px]`}\n            >\n              <svg\n                width=\"51\"\n                height=\"22\"\n                viewBox=\"0 0 72 63\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M71.85 3.00001L60.612 60.378C60.612 60.378 60.129 63 56.877 63C55.149 63 54.258 62.178 54.258 62.178L29.916 41.979L18.006 35.976L2.721 31.911C2.721 31.911 0 31.125 0 28.875C0 27 2.799 26.106 2.799 26.106L66.747 0.70201C66.747 0.70201 68.7 -0.00299041 70.125 9.58803e-06C71.001 9.58803e-06 72 0.37501 72 1.50001C72 2.25001 71.85 3.00001 71.85 3.00001Z\"\n                  fill=\"white\"\n                />\n                <path\n                  d=\"M39.0005 49.5147L28.7225 59.6367C28.7225 59.6367 28.2755 59.9817 27.6785 59.9967C27.4715 60.0027 27.2495 59.9697 27.0215 59.8677L29.9135 41.9727L39.0005 49.5147Z\"\n                  fill=\"#B0BEC5\"\n                />\n                <path\n                  d=\"M59.691 12.5877C59.184 11.9277 58.248 11.8077 57.588 12.3087L18 35.9997C18 35.9997 24.318 53.6757 25.281 56.7357C26.247 59.7987 27.021 59.8707 27.021 59.8707L29.913 41.9757L59.409 14.6877C60.069 14.1867 60.192 13.2477 59.691 12.5877Z\"\n                  fill=\"#CFD8DC\"\n                />\n              </svg>\n              <div>{t('connect_telegram', 'Connect Telegram')}</div>\n            </div>\n          </div>\n        ) : (\n          <div className=\"w-full text-center\" onClick={copyText}>\n            {t(\n              'please_add_the_following_command_in_your_chat',\n              'Please add the following command in your chat:'\n            )}\n            <div className=\"mt-[16px] flex\">\n              <div className=\"flex-1\">\n                <Input\n                  label=\"\"\n                  value={`/connect ${word.current}`}\n                  name=\"\"\n                  disableForm={true}\n                />\n              </div>\n              <Button>{t('copy', 'Copy')}</Button>\n            </div>\n          </div>\n        )}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/web3/providers/wrapcaster.provider.tsx",
    "content": "'use client';\n\nimport '@neynar/react/dist/style.css';\nimport React, { FC, useMemo, useState, useCallback, useEffect } from 'react';\nimport { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport {\n  NeynarAuthButton,\n  NeynarContextProvider,\n  Theme,\n  useNeynarContext,\n} from '@neynar/react';\nimport { INeynarAuthenticatedUser } from '@neynar/react/dist/types/common';\nimport { ButtonCaster } from '@gitroom/frontend/components/auth/providers/farcaster.provider';\nexport const WrapcasterProvider: FC<Web3ProviderInterface> = (props) => {\n  const [_, state] = props.nonce.split('||');\n  const modal = useModals();\n  const [hide, setHide] = useState(false);\n  const auth = useCallback(\n    (code: string) => {\n      setHide(true);\n      return props.onComplete(code, state);\n    },\n    [state]\n  );\n  return (\n    <div className=\"justify-center items-center flex\">\n      {hide ? (\n        <div className=\"justify-center items-center flex -mt-[90px]\">\n          <LoadingComponent width={100} height={100} />\n        </div>\n      ) : (\n        <div className=\"justify-center items-center py-[20px] flex-col w-[500px]\">\n          <div>Click on the bottom below to start the process</div>\n          <ButtonCaster login={auth} />\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/launches/web3/web3.list.tsx",
    "content": "import { FC } from 'react';\nimport { Web3ProviderInterface } from '@gitroom/frontend/components/launches/web3/web3.provider.interface';\nimport { WrapcasterProvider } from '@gitroom/frontend/components/launches/web3/providers/wrapcaster.provider';\nimport { TelegramProvider } from '@gitroom/frontend/components/launches/web3/providers/telegram.provider';\nimport { MoltbookProvider } from '@gitroom/frontend/components/launches/web3/providers/moltbook.provider';\nexport const web3List: {\n  identifier: string;\n  component: FC<Web3ProviderInterface>;\n}[] = [\n  {\n    identifier: 'telegram',\n    component: TelegramProvider,\n  },\n  {\n    identifier: 'wrapcast',\n    component: WrapcasterProvider,\n  },\n  {\n    identifier: 'moltbook',\n    component: MoltbookProvider,\n  },\n];\n"
  },
  {
    "path": "apps/frontend/src/components/launches/web3/web3.provider.interface.ts",
    "content": "export interface Web3ProviderInterface {\n  onComplete: (code: string, state: string) => void;\n  nonce: string;\n}\n"
  },
  {
    "path": "apps/frontend/src/components/layout/check.payment.tsx",
    "content": "import { FC, ReactNode, useCallback, useEffect, useState } from 'react';\nimport Loading from 'react-loading';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useDecisionModal } from '@gitroom/frontend/components/layout/new-modal';\nexport const CheckPayment: FC<{\n  check: string;\n  mutate: () => void;\n  children: ReactNode;\n}> = (props) => {\n  if (!props.check) {\n    return <>{props.children}</>;\n  }\n  return <CheckPaymentInner {...props} />;\n};\n\nexport const CheckPaymentInner: FC<{\n  check: string;\n  mutate: () => void;\n  children: ReactNode;\n}> = (props) => {\n  const [showLoader, setShowLoader] = useState(true);\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const modal = useDecisionModal();\n\n  useEffect(() => {\n    if (showLoader) {\n      document.querySelector('body')?.classList.add('overflow-hidden');\n      Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>\n        p.classList.add('blur-xs', 'pointer-events-none')\n      );\n    } else {\n      document.querySelector('body')?.classList.remove('overflow-hidden');\n      Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>\n        p.classList.remove('blur-xs', 'pointer-events-none')\n      );\n    }\n  }, [showLoader]);\n\n  const checkSubscription = useCallback(async () => {\n    const { status } = await (\n      await fetch('/billing/check/' + props.check)\n    ).json();\n    if (status === 0) {\n      await timer(1000);\n      return checkSubscription();\n    }\n    if (status === 1) {\n      modal.open({\n        title: 'Invalid Payment',\n        onlyApprove: true,\n        approveLabel: 'OK',\n        description:\n          'We could not validate your payment method, please try again',\n      });\n      setShowLoader(false);\n    }\n    if (status === 2) {\n      setShowLoader(false);\n      props.mutate();\n    }\n  }, []);\n  useEffect(() => {\n    checkSubscription();\n  }, []);\n  if (showLoader) {\n    return (\n      <div className=\"fixed bg-black/40 w-full h-full flex justify-center items-center z-[400]\">\n        <div>\n          <Loading type=\"spin\" color=\"#612AD5\" height={250} width={250} />\n        </div>\n      </div>\n    );\n  }\n  return props.children;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/chrome.extension.component.tsx",
    "content": "import { useVariables } from '@gitroom/react/helpers/variable.context';\nexport const ChromeExtensionComponent = () => {\n  const { billingEnabled } = useVariables();\n  if (!billingEnabled) {\n    return null;\n  }\n  return (\n    <a\n      href=\"https://chromewebstore.google.com/detail/postiz/cidhffagahknaeodkplfbcpfeielnkjl\"\n      target=\"_blank\"\n      className=\"hover:text-newTextColor\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        fill=\"none\"\n        viewBox=\"0.75 0.25 21.5 21.5\"\n        width=\"22\"\n        height=\"22\"\n      >\n        <path\n          d=\"M11.5 7C9.29086 7 7.5 8.79086 7.5 11C7.5 13.2091 9.29086 15 11.5 15C13.7091 15 15.5 13.2091 15.5 11C15.5 8.79086 13.7091 7 11.5 7ZM11.5 7H20.67M3.45 5.06L8.04 13M10.38 20.94L14.96 13M21.5 11C21.5 16.5228 17.0228 21 11.5 21C5.97715 21 1.5 16.5228 1.5 11C1.5 5.47715 5.97715 1 11.5 1C17.0228 1 21.5 5.47715 21.5 11Z\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    </a>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/click.outside.tsx",
    "content": "import { useEffect } from 'react';\nexport const useClickOutside = (callback: () => Promise<void>) => {\n  const handleClick = (event: MouseEvent) => {\n    const selector = document.querySelector('#add-edit-modal');\n    const copilotkit = document.querySelector('.copilotKitPopup');\n    const emoji = document.querySelector('.EmojiPickerReact');\n    if (\n      !selector?.contains(event.target as HTMLElement) &&\n      !copilotkit?.contains(event.target as HTMLElement) &&\n      !emoji\n    ) {\n      callback();\n    }\n  };\n  useEffect(() => {\n    document\n      .querySelector('.mantine-Modal-root')\n      // @ts-ignore\n      ?.addEventListener('click', handleClick);\n    return () => {\n      document\n        .querySelector('.mantine-Modal-root')\n        // @ts-ignore\n        ?.removeEventListener('click', handleClick);\n    };\n  });\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/continue.provider.tsx",
    "content": "import React, { FC, useCallback, useEffect, useMemo } from 'react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport dayjs from 'dayjs';\nimport useSWR, { useSWRConfig } from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { continueProviderList } from '@gitroom/frontend/components/new-launch/providers/continue-provider/list';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nexport const Null: FC<{\n  onSave: (data: any) => Promise<void>;\n  existingId: string[];\n}> = () => null;\nexport const ContinueProvider: FC = () => {\n  const { mutate } = useSWRConfig();\n  const fetch = useFetch();\n  const searchParams = useSearchParams();\n  const added = searchParams.get('added');\n  const continueId = searchParams.get('continue');\n  const router = useRouter();\n  const load = useCallback(async (path: string) => {\n    const list = (await (await fetch(path)).json()).integrations;\n    return list;\n  }, []);\n  const { data: integrations } = useSWR('/integrations/list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n  const refreshList = useCallback(() => {\n    mutate('/integrations/list');\n    const url = new URL(window.location.href);\n    url.searchParams.delete('added');\n    url.searchParams.delete('continue');\n    router.push(url.toString());\n  }, []);\n  const Provider = useMemo(() => {\n    if (!added) {\n      return Null;\n    }\n    return (\n      continueProviderList[added as keyof typeof continueProviderList] || Null\n    );\n  }, [added]);\n\n  if (!added || !continueId || !integrations) {\n    return null;\n  }\n\n  return (\n    <ContinueModal\n      refreshList={refreshList}\n      added={added}\n      continueId={continueId}\n      integrations={integrations.map((p: any) => p.internalId)}\n      provider={Provider}\n    />\n  );\n};\n\nconst ModalContent: FC<{\n  continueId: string;\n  added: any;\n  provider: any;\n  closeModal: () => void;\n  integrations: string[];\n}> = ({ continueId, added, provider: Provider, closeModal, integrations }) => {\n  const fetch = useFetch();\n\n  const onSave = useCallback(\n    async (data: any) => {\n      await fetch(`/integrations/provider/${continueId}/connect`, {\n        method: 'POST',\n        body: JSON.stringify(data),\n      });\n      closeModal();\n    },\n    [continueId, closeModal]\n  );\n\n  return (\n    <IntegrationContext.Provider\n      value={{\n        date: newDayjs(),\n        value: [],\n        allIntegrations: [],\n        integration: {\n          editor: 'normal',\n          additionalSettings: '',\n          display: '',\n          time: [\n            {\n              time: 0,\n            },\n          ],\n          id: continueId,\n          type: '',\n          name: '',\n          picture: '',\n          inBetweenSteps: true,\n          changeNickName: false,\n          changeProfilePicture: false,\n          identifier: added,\n        },\n      }}\n    >\n      <Provider onSave={onSave} existingId={integrations} />\n    </IntegrationContext.Provider>\n  );\n};\n\nconst ContinueModal: FC<{\n  continueId: string;\n  added: any;\n  provider: any;\n  integrations: string[];\n  refreshList: () => void;\n}> = (props) => {\n  const modals = useModals();\n\n  useEffect(() => {\n    modals.openModal({\n      title: 'Configure Channel',\n      children: (close) => (\n        <ModalContent\n          {...props}\n          closeModal={() => {\n            props.refreshList();\n            close();\n          }}\n        />\n      ),\n    });\n  }, []);\n\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/drop.files.tsx",
    "content": "import { useDropzone } from 'react-dropzone';\nimport { FC, ReactNode } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport clsx from 'clsx';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nexport const DropFiles: FC<{\n  children: ReactNode;\n  className?: string;\n  onDrop: (files: File[]) => void;\n  disabled?: boolean;\n}> = (props) => {\n  const t = useT();\n  const toaster = useToaster();\n\n  const { getRootProps, isDragActive } = useDropzone({\n    onDrop: (files) => {\n      if (props.disabled) {\n        toaster.show('Upload current in progress, please wait and then try again.', 'warning');\n        return ;\n      }\n      props.onDrop(files);\n    },\n  });\n  return (\n    <div {...getRootProps()} className={clsx(\"relative\", props.className)}>\n      {isDragActive && (\n        <div className=\"absolute start-0 top-0 w-full h-full bg-black/90 flex items-center justify-center z-[200] animate-normalFadeIn\">\n          {t('drag_n_drop_some_files_here', 'Drag n drop some files here')}\n        </div>\n      )}\n      {props.children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/dubAnalytics.tsx",
    "content": "'use client';\n\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { Analytics as DubAnalyticsIn } from '@dub/analytics/react';\nimport { getCookie } from 'react-use-cookie';\n\nexport const DubAnalytics = () => {\n  const { dub } = useVariables();\n  if (!dub) return null;\n  return (\n    <DubAnalyticsIn\n      domainsConfig={{\n        refer: 'postiz.pro',\n      }}\n    />\n  );\n};\n\nexport const useDubClickId = () => {\n  const { dub } = useVariables();\n  if (!dub) return undefined;\n\n  const dubCookie = getCookie('dub_partner_data', '{}');\n  return JSON.parse(dubCookie)?.clickId || undefined;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/facebook.component.tsx",
    "content": "'use client';\n\nimport Script from 'next/script';\nexport const FacebookComponent = () => {\n  if (!process.env.NEXT_PUBLIC_FACEBOOK_PIXEL) {\n    return null;\n  }\n  return (\n    <Script strategy=\"afterInteractive\" id=\"fb-pixel\">\n      {`!function(f,b,e,v,n,t,s)\n{if(f.fbq)return;n=f.fbq=function(){n.callMethod?\nn.callMethod.apply(n,arguments):n.queue.push(arguments)};\nif(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';\nn.queue=[];t=b.createElement(e);t.async=!0;\nt.src=v;s=b.getElementsByTagName(e)[0];\ns.parentNode.insertBefore(t,s)}(window, document,'script',\n'/f.js');\nfbq('init', '${process.env.NEXT_PUBLIC_FACEBOOK_PIXEL}');\n`}\n    </Script>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/html.component.tsx",
    "content": "'use client';\nimport { FC, ReactNode, useEffect, useState } from 'react';\nimport { useTranslationSettings } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const HtmlComponent: FC = () => {\n  const settings = useTranslationSettings();\n  const [dir, setDir] = useState(settings.dir());\n\n  useEffect(() => {\n    settings.on('languageChanged', (lng) => {\n      setDir(settings.dir());\n    });\n  }, []);\n\n  useEffect(() => {\n    const htmlElement = document.querySelector('html');\n    if (htmlElement) {\n      htmlElement.setAttribute('dir', dir);\n    }\n  }, [dir]);\n\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/impersonate.tsx",
    "content": "import { Input } from '@gitroom/react/form/input';\nimport { ChangeEventHandler, FC, useCallback, useMemo, useState } from 'react';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { Select } from '@gitroom/react/form/select';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { setCookie } from '@gitroom/frontend/components/layout/layout.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Button } from '@gitroom/react/form/button';\n\ninterface Charge {\n  id: string;\n  amount: number;\n  currency: string;\n  created: number;\n  status: string;\n  refunded: boolean;\n  amount_refunded: number;\n  description: string | null;\n}\n\nconst useCharges = () => {\n  const fetch = useFetch();\n  return useSWR<Charge[]>('/billing/charges', async () => {\n    return (await fetch('/billing/charges')).json();\n  }, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n  });\n};\n\nconst ChargesModal: FC<{ close: () => void }> = ({ close }) => {\n  const fetch = useFetch();\n  const t = useT();\n  const { data: charges, mutate } = useCharges();\n  const [selected, setSelected] = useState<Set<string>>(new Set());\n  const [refunding, setRefunding] = useState(false);\n  const [cancelling, setCancelling] = useState(false);\n\n  const toggleCharge = useCallback((chargeId: string) => {\n    setSelected((prev) => {\n      const next = new Set(prev);\n      if (next.has(chargeId)) {\n        next.delete(chargeId);\n      } else {\n        next.add(chargeId);\n      }\n      return next;\n    });\n  }, []);\n\n  const handleRefund = useCallback(async () => {\n    if (!selected.size) return;\n    if (\n      !(await deleteDialog(\n        t(\n          'refund_selected_confirm',\n          `Are you sure you want to refund ${selected.size} charge(s)? This cannot be undone.`\n        ),\n        t('yes_refund', 'Yes, refund'),\n        t('confirm_refund', 'Confirm Refund'),\n        t('no_cancel', 'No, cancel')\n      ))\n    ) {\n      return;\n    }\n    setRefunding(true);\n    try {\n      await fetch('/billing/refund-charges', {\n        method: 'POST',\n        body: JSON.stringify({ chargeIds: Array.from(selected) }),\n      });\n      setSelected(new Set());\n      await mutate();\n    } finally {\n      setRefunding(false);\n    }\n  }, [selected]);\n\n  const handleCancel = useCallback(async () => {\n    if (\n      !(await deleteDialog(\n        t(\n          'cancel_subscription_confirm',\n          'This will immediately cancel the subscription. The user will be downgraded to the FREE plan. This cannot be undone.'\n        ),\n        t('yes_cancel_subscription', 'Yes, cancel subscription'),\n        t('cancel_subscription_title', 'Cancel Subscription?'),\n        t('no_go_back', 'No, go back')\n      ))\n    ) {\n      return;\n    }\n    setCancelling(true);\n    try {\n      await fetch('/billing/cancel-subscription', {\n        method: 'POST',\n      });\n      close();\n      window.location.reload();\n    } catch {\n      setCancelling(false);\n    }\n  }, []);\n\n  return (\n    <div className=\"flex flex-col gap-[16px] min-w-[500px]\">\n      <div className=\"max-h-[400px] overflow-y-auto\">\n        {!charges?.length ? (\n          <div className=\"text-center py-[20px] text-newTextColor/60\">\n            {t('no_charges', 'No charges found')}\n          </div>\n        ) : (\n          <table className=\"w-full\">\n            <thead>\n              <tr className=\"text-left border-b border-newTableBorder\">\n                <th className=\"p-[8px] w-[40px]\" />\n                <th className=\"p-[8px]\">{t('date', 'Date')}</th>\n                <th className=\"p-[8px]\">{t('amount', 'Amount')}</th>\n                <th className=\"p-[8px]\">{t('status', 'Status')}</th>\n              </tr>\n            </thead>\n            <tbody>\n              {charges.map((charge) => (\n                <tr\n                  key={charge.id}\n                  className=\"border-b border-newTableBorder hover:bg-tableBorder cursor-pointer\"\n                  onClick={() => !charge.refunded && toggleCharge(charge.id)}\n                >\n                  <td className=\"p-[8px]\">\n                    <div\n                      className={`w-[20px] h-[20px] rounded-[4px] border-2 flex items-center justify-center ${\n                        charge.refunded\n                          ? 'border-newTextColor/20 opacity-40'\n                          : selected.has(charge.id)\n                          ? 'bg-forth border-forth'\n                          : 'border-newTextColor/40'\n                      }`}\n                    >\n                      {(selected.has(charge.id) || charge.refunded) && (\n                        <svg\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          viewBox=\"0 0 24 24\"\n                          width=\"14\"\n                          height=\"14\"\n                          fill=\"none\"\n                          stroke=\"currentColor\"\n                          strokeWidth=\"3\"\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                        >\n                          <polyline points=\"20 6 9 17 4 12\" />\n                        </svg>\n                      )}\n                    </div>\n                  </td>\n                  <td className=\"p-[8px]\">\n                    {new Date(charge.created * 1000).toLocaleDateString()}\n                  </td>\n                  <td className=\"p-[8px]\">\n                    ${(charge.amount / 100).toFixed(2)}{' '}\n                    {charge.currency.toUpperCase()}\n                  </td>\n                  <td className=\"p-[8px]\">\n                    {charge.refunded ? (\n                      <span className=\"text-red-400\">\n                        {t('refunded', 'Refunded')}\n                      </span>\n                    ) : (\n                      <span className=\"text-green-400\">\n                        {t('paid', 'Paid')}\n                      </span>\n                    )}\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        )}\n      </div>\n      <div className=\"flex gap-[12px] justify-end\">\n        <Button\n          onClick={handleRefund}\n          loading={refunding}\n          disabled={!selected.size}\n          className=\"rounded-[4px]\"\n        >\n          {t('refund_selected', 'Refund Selected')}\n          {selected.size > 0 && ` (${selected.size})`}\n        </Button>\n        <Button\n          onClick={handleCancel}\n          loading={cancelling}\n          className=\"!bg-red-700 rounded-[4px]\"\n        >\n          {t('cancel_subscription', 'Cancel Subscription')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nconst ManageBilling = () => {\n  const { openModal } = useModals();\n  const t = useT();\n\n  const handleClick = useCallback(() => {\n    openModal({\n      title: t('manage_billing', 'Manage Billing'),\n      children: (close) => <ChargesModal close={close} />,\n    });\n  }, []);\n\n  return (\n    <div\n      className=\"px-[10px] rounded-[4px] bg-red-700 text-white cursor-pointer whitespace-nowrap\"\n      onClick={handleClick}\n    >\n      {t('manage_billing', 'Manage Billing')}\n    </div>\n  );\n};\n\nexport const Subscription = () => {\n  const fetch = useFetch();\n  const t = useT();\n\n  const addSubscription: ChangeEventHandler<HTMLSelectElement> = useCallback(\n    async (e) => {\n      const value = e.target.value;\n      if (\n        await deleteDialog(\n          'Are you sure you want to add a user subscription?',\n          'Add'\n        )\n      ) {\n        await fetch('/billing/add-subscription', {\n          method: 'POST',\n          body: JSON.stringify({\n            subscription: value,\n          }),\n        });\n        window.location.reload();\n      }\n    },\n    []\n  );\n  return (\n    <Select\n      onChange={addSubscription}\n      hideErrors={true}\n      disableForm={true}\n      name=\"sub\"\n      label=\"\"\n      value=\"\"\n    >\n      <option>\n        {t('add_free_subscription', '-- ADD FREE SUBSCRIPTION --')}\n      </option>\n      {Object.keys(pricing)\n        .filter((f) => !f.includes('FREE'))\n        .map((key) => (\n          <option key={key} value={key}>\n            {key}\n          </option>\n        ))}\n    </Select>\n  );\n};\nexport const Impersonate = () => {\n  const fetch = useFetch();\n  const [name, setName] = useState('');\n  const { isSecured, billingEnabled } = useVariables();\n  const user = useUser();\n  const load = useCallback(async () => {\n    if (!name) {\n      return [];\n    }\n    const value = await (await fetch(`/user/impersonate?name=${name}`)).json();\n    return value;\n  }, [name]);\n  const stopImpersonating = useCallback(async () => {\n    if (!isSecured) {\n      setCookie('impersonate', '', -10);\n    } else {\n      await fetch(`/user/impersonate`, {\n        method: 'POST',\n        body: JSON.stringify({\n          id: '',\n        }),\n      });\n    }\n    window.location.reload();\n  }, []);\n  const t = useT();\n\n  const setUser = useCallback(\n    (userId: string) => async () => {\n      await fetch(`/user/impersonate`, {\n        method: 'POST',\n        body: JSON.stringify({\n          id: userId,\n        }),\n      });\n      window.location.reload();\n    },\n    []\n  );\n  const { data } = useSWR(`/impersonate-${name}`, load, {\n    refreshWhenHidden: false,\n    revalidateOnMount: true,\n    revalidateOnReconnect: false,\n    revalidateOnFocus: false,\n    refreshWhenOffline: false,\n    revalidateIfStale: false,\n    refreshInterval: 0,\n  });\n  const mapData = useMemo(() => {\n    return data?.map(\n      (curr: any) => ({\n        id: curr.id,\n        name: curr.user.name,\n        email: curr.user.email,\n      }),\n      []\n    );\n  }, [data]);\n  return (\n    <div>\n      <div className=\"bg-forth h-[52px] flex justify-center items-center border-input border rounded-[8px] text-white\">\n        <div className=\"relative flex flex-col w-[600px]\">\n          <div className=\"relative z-[999]\">\n            {user?.impersonate ? (\n              <div className=\"text-center flex justify-center items-center gap-[20px]\">\n                <div>\n                  {t('currently_impersonating', 'Currently Impersonating')}\n                </div>\n                <div>\n                  <div\n                    className=\"px-[10px] rounded-[4px] bg-red-500 text-white cursor-pointer\"\n                    onClick={stopImpersonating}\n                  >\n                    X\n                  </div>\n                </div>\n                {user?.tier?.current === 'FREE' && <Subscription />}\n                {billingEnabled && <ManageBilling />}\n              </div>\n            ) : (\n              <Input\n                autoComplete=\"off\"\n                placeholder=\"Write the user details\"\n                name=\"impersonate\"\n                disableForm={true}\n                label=\"\"\n                removeError={true}\n                value={name}\n                onChange={(e) => setName(e.target.value)}\n              />\n            )}\n          </div>\n          {!!data?.length && (\n            <>\n              <div\n                className=\"bg-primary/80 fixed start-0 top-0 w-full h-full z-[998]\"\n                onClick={() => setName('')}\n              />\n              <div className=\"absolute top-[100%] w-full start-0 bg-sixth border border-customColor6 text-textColor z-[999]\">\n                {mapData?.map((user: any) => (\n                  <div\n                    onClick={setUser(user.id)}\n                    key={user.id}\n                    className=\"p-[10px] border-b border-customColor6 hover:bg-tableBorder cursor-pointer\"\n                  >\n                    {t('user_1', 'user:')}\n                    {user.id.split('-').at(-1)} - {user.name} - {user.email}\n                  </div>\n                ))}\n              </div>\n            </>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/language.component.tsx",
    "content": "'use client';\n\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport {\n  cookieName,\n  fallbackLng,\n  languages,\n} from '@gitroom/react/translation/i18n.config';\nimport i18next from 'i18next';\nimport useCookie from 'react-use-cookie';\nimport ReactCountryFlag from 'react-country-flag';\nimport { List, Box, Group, Text } from '@mantine/core';\nimport React, { useCallback } from 'react';\nimport countries from 'i18n-iso-countries';\n\n// Register required locales\nimport countriesEn from 'i18n-iso-countries/langs/en.json';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { ModalWrapperComponent } from '../new-launch/modal.wrapper.component';\n\nimport clsx from 'clsx';\ncountries.registerLocale(countriesEn);\n\nconst getCountryCodeForFlag = (languageCode: string) => {\n  // For multi-region languages, here are some common defaults\n  if (languageCode === 'en') return 'GB';\n  if (languageCode === 'es') return 'ES';\n  if (languageCode === 'ar') return 'SA';\n  if (languageCode === 'zh') return 'CN';\n  if (languageCode === 'he') return 'IL';\n  if (languageCode === 'ja') return 'JP';\n  if (languageCode === 'ko') return 'KR';\n  if (languageCode === 'vi') return 'VN';\n\n  // Check if language code itself is a valid country code\n  try {\n    const countryName = countries.getName(languageCode.toUpperCase(), 'en');\n    if (countryName) {\n      return languageCode.toUpperCase();\n    }\n  } catch (e) {\n    // Not a valid country code, continue to next approach\n  }\n\n  // Try to extract region code if language code has a region component (e.g., en-US)\n  const parts = languageCode.split('-');\n  if (parts.length > 1) {\n    const regionCode = parts[1].toUpperCase();\n    try {\n      const countryName = countries.getName(regionCode, 'en');\n      if (countryName) {\n        return regionCode;\n      }\n    } catch (e) {\n      // Not a valid country code, continue to next approach\n    }\n  }\n\n  // For most language codes that match their primary country\n  // Examples: fr->FR, it->IT, de->DE, etc.\n  return languageCode.toUpperCase();\n};\n\nexport const ChangeLanguageComponent = () => {\n  const currentLanguage = i18next.resolvedLanguage || fallbackLng;\n  const availableLanguages = languages;\n  const [_, setCookie] = useCookie(cookieName, currentLanguage || fallbackLng);\n  const modals = useModals();\n  const t = useT();\n\n  const handleLanguageChange = (language: string) => {\n    setCookie(language);\n    i18next.changeLanguage(language);\n    modals.closeCurrent();\n  };\n\n  // Function to get language name in its native script\n  const getLanguageName = useCallback((code: string) => {\n    try {\n      // Use browser's Intl API to get language name in native script\n      const displayNames = new Intl.DisplayNames([code], {\n        type: 'language',\n      });\n      return displayNames.of(code);\n    } catch (error) {\n      // Fallback to language code if the API isn't supported or language is not found\n      return code;\n    }\n  }, []);\n\n  return (\n    <div className=\"relative\">\n      <div className=\"grid grid-cols-4 gap-2\">\n        {availableLanguages.map((language) => (\n          <div\n            className={clsx(\n              'flex items-center flex-col bg-newTableHeader hover:bg-newTableBorder p-[20px] cursor-pointer gap-2',\n              language === currentLanguage ? 'border border-textColor' : ''\n            )}\n            key={language}\n            onClick={() => handleLanguageChange(language)}\n          >\n            <ReactCountryFlag\n              countryCode={getCountryCodeForFlag(language)}\n              svg\n              style={{\n                width: '1.5em',\n                height: '1.5em',\n              }}\n              title={language}\n            />\n            <Text weight={language === currentLanguage ? 'bold' : 'normal'}>\n              {getLanguageName(language)}\n            </Text>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\nexport const LanguageComponent = () => {\n  const modal = useModals();\n  const currentLanguage = i18next.resolvedLanguage || fallbackLng;\n  const t = useT();\n  const openModal = () => {\n    modal.openModal({\n      title: t('change_language', 'Change Language'),\n      withCloseButton: true,\n      children: <ChangeLanguageComponent />,\n    });\n  };\n  return (\n    <div\n      onClick={openModal}\n      className=\"rounded-full overflow-hidden h-[22px] w-[22px] relative cursor-pointer\"\n    >\n      <ReactCountryFlag\n        countryCode={getCountryCodeForFlag(currentLanguage)}\n        svg\n        style={{\n          width: '22px',\n          height: '22px',\n          position: 'absolute',\n          left: '50%',\n          top: '50%',\n          transform: 'translate(-50%, -50%)',\n          objectFit: 'cover',\n        }}\n        title={currentLanguage}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/layout.context.tsx",
    "content": "'use client';\n\nimport { ReactNode, useCallback } from 'react';\nimport { FetchWrapperComponent } from '@gitroom/helpers/utils/custom.fetch';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useReturnUrl } from '@gitroom/frontend/app/(app)/auth/return.url.component';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nexport default function LayoutContext(params: { children: ReactNode }) {\n  if (params?.children) {\n    // eslint-disable-next-line react/no-children-prop\n    return <LayoutContextInner children={params.children} />;\n  }\n  return <></>;\n}\nexport function setCookie(cname: string, cvalue: string, exdays: number) {\n  if (typeof document === 'undefined') {\n    return;\n  }\n  const d = new Date();\n  d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);\n  const expires = 'expires=' + d.toUTCString();\n  document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/';\n}\nfunction LayoutContextInner(params: { children: ReactNode }) {\n  const returnUrl = useReturnUrl();\n  const { backendUrl, isGeneral, isSecured } = useVariables();\n  const afterRequest = useCallback(\n    async (url: string, options: RequestInit, response: Response) => {\n      if (\n        typeof window !== 'undefined' &&\n        window.location.href.includes('/p/')\n      ) {\n        return true;\n      }\n      const headerAuth =\n        response?.headers?.get('auth') || response?.headers?.get('Auth');\n      const showOrg =\n        response?.headers?.get('showorg') || response?.headers?.get('Showorg');\n      const impersonate =\n        response?.headers?.get('impersonate') ||\n        response?.headers?.get('Impersonate');\n      const logout =\n        response?.headers?.get('logout') || response?.headers?.get('Logout');\n      if (headerAuth) {\n        setCookie('auth', headerAuth, 365);\n      }\n      if (showOrg) {\n        setCookie('showorg', showOrg, 365);\n      }\n      if (impersonate) {\n        setCookie('impersonate', impersonate, 365);\n      }\n      if (logout && !isSecured) {\n        setCookie('auth', '', -10);\n        setCookie('showorg', '', -10);\n        setCookie('impersonate', '', -10);\n        window.location.href = '/';\n        return true;\n      }\n      const reloadOrOnboarding =\n        response?.headers?.get('reload') ||\n        response?.headers?.get('onboarding');\n      if (reloadOrOnboarding) {\n        const getAndClear = returnUrl.getAndClear();\n        if (getAndClear) {\n          window.location.href = getAndClear;\n          return true;\n        }\n      }\n      if (response?.headers?.get('onboarding')) {\n        window.location.href = isGeneral\n          ? '/launches?onboarding=true'\n          : '/analytics?onboarding=true';\n        return true;\n      }\n\n      if (response?.headers?.get('reload')) {\n        window.location.reload();\n        return true;\n      }\n\n      if (response.status === 401 || response?.headers?.get('logout')) {\n        if (!isSecured) {\n          setCookie('auth', '', -10);\n          setCookie('showorg', '', -10);\n          setCookie('impersonate', '', -10);\n        }\n        window.location.href = '/';\n      }\n      if (response.status === 406) {\n        if (\n          await deleteDialog(\n            'You are currently on trial, in order to use the feature you must finish the trial',\n            'Finish the trial, charge me now',\n            'Trial',\n\n          )\n        ) {\n          window.open('/billing?finishTrial=true', '_blank');\n          return false;\n        }\n        return false;\n      }\n\n      if (response.status === 402) {\n        if (\n          await deleteDialog(\n            (\n              await response.json()\n            ).message,\n            'Move to billing',\n            'Payment Required'\n          )\n        ) {\n          window.open('/billing', '_blank');\n          return false;\n        }\n        return true;\n      }\n      return true;\n    },\n    []\n  );\n  return (\n    <FetchWrapperComponent baseUrl={backendUrl} afterRequest={afterRequest}>\n      {params?.children || <></>}\n    </FetchWrapperComponent>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/layout/loading.tsx",
    "content": "'use client';\n\nimport ReactLoading from 'react-loading';\nimport { FC } from 'react';\nexport const LoadingComponent: FC<{\n  width?: number;\n  height?: number;\n}> = (props) => {\n  return (\n    <div className=\"flex-1 flex justify-center pt-[100px]\">\n      <ReactLoading\n        type=\"spin\"\n        color=\"#612bd3\"\n        width={props.width || 100}\n        height={props.height || 100}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/logout.component.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback } from 'react';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { setCookie } from '@gitroom/frontend/components/layout/layout.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const LogoutComponent: FC<{ isIcon?: boolean }> = ({ isIcon }) => {\n  const fetch = useFetch();\n  const { isGeneral, isSecured } = useVariables();\n  const t = useT();\n\n  const logout = useCallback(async () => {\n    if (\n      await deleteDialog(\n        t(\n          'are_you_sure_you_want_to_logout',\n          'Are you sure you want to logout?'\n        ),\n        t('yes_logout', 'Yes logout')\n      )\n    ) {\n      if (!isSecured) {\n        setCookie('auth', '', -10);\n      } else {\n        await fetch('/user/logout', {\n          method: 'POST',\n        });\n      }\n      window.location.href = '/';\n    }\n  }, []);\n  return (\n    <>\n      <div className=\"cursor-pointer\" onClick={logout}>\n        {isIcon ? (\n          <svg\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 25 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            data-tooltip-id=\"tooltip\"\n            data-tooltip-content={`\n            ${t('logout_from', 'Logout from')}${' '}\n            ${isGeneral ? ' Postiz' : ' Gitroom'}\n            `}\n          >\n            <path\n              d=\"M20.28 11.22C20.4205 11.3606 20.4993 11.5512 20.4993 11.75C20.4993 11.9488 20.4205 12.1394 20.28 12.28L11.28 21.28C11.1378 21.4125 10.9498 21.4846 10.7555 21.4812C10.5612 21.4777 10.3758 21.399 10.2384 21.2616C10.101 21.1242 10.0223 20.9388 10.0188 20.7445C10.0154 20.5502 10.0875 20.3622 10.22 20.22L17.9387 12.5H0.75C0.551088 12.5 0.360322 12.421 0.21967 12.2803C0.0790175 12.1397 0 11.9489 0 11.75C0 11.5511 0.0790175 11.3603 0.21967 11.2197C0.360322 11.079 0.551088 11 0.75 11H17.9387L10.22 3.28C10.0875 3.13783 10.0154 2.94978 10.0188 2.75548C10.0223 2.56118 10.101 2.37579 10.2384 2.23838C10.3758 2.10097 10.5612 2.02225 10.7555 2.01883C10.9498 2.0154 11.1378 2.08752 11.28 2.22L20.28 11.22ZM23.75 0C23.5511 0 23.3603 0.0790175 23.2197 0.21967C23.079 0.360322 23 0.551088 23 0.75V22.75C23 22.9489 23.079 23.1397 23.2197 23.2803C23.3603 23.421 23.5511 23.5 23.75 23.5C23.9489 23.5 24.1397 23.421 24.2803 23.2803C24.421 23.1397 24.5 22.9489 24.5 22.75V0.75C24.5 0.551088 24.421 0.360322 24.2803 0.21967C24.1397 0.0790175 23.9489 0 23.75 0Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        ) : (\n          <span className=\"text-red-400\">\n            {t('logout_from', 'Logout from')}\n            {isGeneral ? ' Postiz' : ' Gitroom'}\n          </span>\n        )}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/mode.component.tsx",
    "content": "'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\nimport useCookie from 'react-use-cookie';\nimport EventEmitter from 'events';\n\nexport const modeEmitter = new EventEmitter();\n\nconst ModeComponent = () => {\n  const [mode, setMode] = useCookie('mode', 'dark');\n\n  const changeMode = useCallback(() => {\n    modeEmitter.emit('mode', mode === 'dark' ? 'light' : 'dark');\n    setMode(mode === 'dark' ? 'light' : 'dark');\n  }, [mode]);\n\n  useEffect(() => {\n    document.body.classList.remove('dark', 'light');\n    document.body.classList.add(mode);\n  }, [mode]);\n  return (\n    <div onClick={changeMode} className=\"select-none cursor-pointer\">\n      {mode === 'dark' ? (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"22\"\n          height=\"22\"\n          viewBox=\"0 0 22 22\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M10.75 1V3M10.75 19V21M2.75 11H0.75M5.06412 5.31412L3.6499 3.8999M16.4359 5.31412L17.8501 3.8999M5.06412 16.69L3.6499 18.1042M16.4359 16.69L17.8501 18.1042M20.75 11H18.75M15.75 11C15.75 13.7614 13.5114 16 10.75 16C7.98858 16 5.75 13.7614 5.75 11C5.75 8.23858 7.98858 6 10.75 6C13.5114 6 15.75 8.23858 15.75 11Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ) : (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M21.625 12.9011C20.2967 15.231 17.7898 16.8019 14.916 16.8019C10.6539 16.8019 7.19884 13.3468 7.19884 9.08473C7.19884 6.21071 8.76993 3.70363 11.1001 2.37549C6.20501 2.83962 2.37561 6.96182 2.37561 11.9784C2.37561 17.306 6.69447 21.6248 12.0221 21.6248C17.0384 21.6248 21.1605 17.7959 21.625 12.9011Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      )}\n    </div>\n  );\n};\nexport default ModeComponent;\n"
  },
  {
    "path": "apps/frontend/src/components/layout/new-modal.tsx",
    "content": "import { create } from 'zustand';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { useShallow } from 'zustand/react/shallow';\nimport React, {\n  createContext,\n  FC,\n  memo,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from 'react';\nimport { Button } from '@gitroom/react/form/button';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport clsx from 'clsx';\nimport { EventEmitter } from 'events';\n\ninterface OpenModalInterface {\n  title?: string;\n  closeOnClickOutside?: boolean;\n  removeLayout?: boolean;\n  fullScreen?: boolean;\n  top?: string | number;\n  closeOnEscape?: boolean;\n  withCloseButton?: boolean;\n  askClose?: boolean;\n  onClose?: () => void;\n  children: ReactNode | ((close: () => void) => ReactNode);\n  classNames?: {\n    modal?: string;\n  };\n  size?: string | number;\n  height?: string | number;\n  id?: string;\n}\n\ninterface ModalManagerStoreInterface {\n  closeById(id: string): void;\n  openModal(params: OpenModalInterface): void;\n  closeAll(): void;\n}\n\ninterface State extends ModalManagerStoreInterface {\n  modalManager: Array<{ id: string } & OpenModalInterface>;\n}\n\nconst useModalStore = create<State>((set) => ({\n  modalManager: [],\n  openModal: (params) => {\n    const newId = params.id || makeId(20);\n    set((state) => ({\n      modalManager: [\n        ...state.modalManager,\n        ...(!state.modalManager.some((p) => p.id === newId)\n          ? [{ id: newId, ...params }]\n          : []),\n      ],\n    }));\n  },\n  closeById: (id) =>\n    set((state) => ({\n      modalManager: state.modalManager.filter((modal) => modal.id !== id),\n    })),\n  closeAll: () => set({ modalManager: [] }),\n}));\n\nconst CurrentModalContext = createContext({ id: '' });\n\ninterface ModalManagerInterface extends ModalManagerStoreInterface {\n  closeCurrent(): void;\n}\n\nexport const useModals = () => {\n  const { closeAll, openModal, closeById } = useModalStore(\n    useShallow((state) => ({\n      openModal: state.openModal,\n      closeById: state.closeById,\n      closeAll: state.closeAll,\n    }))\n  );\n\n  const modalContext = useContext(CurrentModalContext);\n\n  return {\n    openModal,\n    closeAll,\n    closeById,\n    closeCurrent: () => {\n      if (modalContext.id) {\n        closeById(modalContext.id);\n      }\n    },\n  } satisfies ModalManagerInterface;\n};\n\nexport const Component: FC<{\n  closeModal: (id: string) => void;\n  zIndex: number;\n  isLast: boolean;\n  modal: { id: string } & OpenModalInterface;\n}> = memo(({ isLast, modal, closeModal, zIndex }) => {\n  const decision = useDecisionModal();\n  const closeModalFunction = useCallback(async () => {\n    if (modal.askClose) {\n      const open = await decision.open();\n      if (!open) {\n        return;\n      }\n    }\n    modal?.onClose?.();\n    closeModal(modal.id);\n  }, [modal.id, closeModal]);\n\n  const RenderComponent = useMemo(() => {\n    return typeof modal.children === 'function'\n      ? modal.children(closeModalFunction)\n      : modal.children;\n  }, [modal, closeModalFunction]);\n\n  useHotkeys(\n    'Escape',\n    () => {\n      if (isLast) {\n        closeModalFunction();\n      }\n    },\n    [isLast, closeModalFunction]\n  );\n\n  if (modal.removeLayout) {\n    return (\n      <div\n        style={{ zIndex }}\n        className={clsx(\n          !modal.fullScreen\n            ? 'pb-[50px] min-w-full min-h-full'\n            : 'w-full h-full',\n          'fixed flex left-0 top-0 bg-popup transition-all animate-fadeIn overflow-y-auto text-newTextColor',\n          !isLast && '!overflow-hidden'\n        )}\n      >\n        <div className={clsx(modal.fullScreen && 'flex', 'relative flex-1')}>\n          <div\n            className={clsx(\n              modal.fullScreen\n                ? 'flex flex-1'\n                : 'absolute top-0 left-0 min-w-full min-h-full'\n            )}\n          >\n            <div\n              className={clsx(\n                modal.fullScreen ? 'w-full h-full flex-1' : 'mx-auto py-[48px]'\n              )}\n              {...(modal.size && { style: { width: modal.size } })}\n            >\n              {typeof modal.children === 'function'\n                ? modal.children(closeModalFunction)\n                : modal.children}\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <CurrentModalContext.Provider value={{ id: modal.id }}>\n      <div\n        onClick={closeModalFunction}\n        style={{ zIndex }}\n        className={clsx(\n          'fixed flex left-0 top-0 min-w-full min-h-full bg-popup transition-all animate-fadeIn overflow-y-auto text-newTextColor',\n          !modal.fullScreen && 'pb-[50px]'\n        )}\n      >\n        <div className=\"relative flex-1\">\n          <div\n            style={\n              modal.top\n                ? { paddingTop: modal.top, paddingBottom: modal.top }\n                : {}\n            }\n            className={clsx(\n              'absolute min-w-full',\n              !modal.fullScreen\n                ? modal.top\n                  ? ''\n                  : 'min-h-full pt-[100px] pb-[100px]'\n                : 'h-screen',\n              modal.size && modal.height\n                ? 'flex justify-center items-center'\n                : 'top-0 left-0'\n            )}\n          >\n            <div\n              className={clsx(\n                !modal.removeLayout && 'gap-[40px] p-[32px]',\n                'bg-newBgColorInner mx-auto flex flex-col w-fit rounded-[24px] relative',\n                modal.size ? '' : 'min-w-[600px]',\n                modal.fullScreen && 'h-full'\n              )}\n              {...((!!modal.size || !!modal.height) && {\n                style: {\n                  ...(modal.size ? { width: modal.size } : {}),\n                  ...(modal.height ? { height: modal.height } : {}),\n                },\n              })}\n              onClick={(e) => e.stopPropagation()}\n            >\n              <div className=\"flex items-center\">\n                <div className=\"text-[24px] font-[600] flex-1\">\n                  {modal.title}\n                </div>\n                {typeof modal.withCloseButton === 'undefined' ||\n                modal.withCloseButton ? (\n                  <div className=\"cursor-pointer\">\n                    <button\n                      className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n                      type=\"button\"\n                      onClick={closeModalFunction}\n                    >\n                      <svg\n                        viewBox=\"0 0 15 15\"\n                        fill=\"none\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width=\"16\"\n                        height=\"16\"\n                      >\n                        <path\n                          d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                          fill=\"currentColor\"\n                          fillRule=\"evenodd\"\n                          clipRule=\"evenodd\"\n                        ></path>\n                      </svg>\n                    </button>\n                  </div>\n                ) : null}\n              </div>\n              <div\n                className={clsx(\n                  'whitespace-pre-line',\n                  !!modal.height && !!modal.size && 'flex flex-1 flex-col'\n                )}\n              >\n                {RenderComponent}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </CurrentModalContext.Provider>\n  );\n});\n\nexport const ModalManagerInner: FC = () => {\n  const { closeModal, modalManager } = useModalStore(\n    useShallow((state) => ({\n      closeModal: state.closeById,\n      modalManager: state.modalManager,\n    }))\n  );\n\n  useEffect(() => {\n    if (modalManager.length > 0) {\n      document.querySelector('body')?.classList.add('overflow-hidden');\n      Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>\n        p.classList.add('blur-xs', 'pointer-events-none')\n      );\n    } else {\n      document.querySelector('body')?.classList.remove('overflow-hidden');\n      Array.from(document.querySelectorAll('.blurMe') || []).map((p) =>\n        p.classList.remove('blur-xs', 'pointer-events-none')\n      );\n    }\n  }, [modalManager]);\n\n  if (modalManager.length === 0) {\n    return null;\n  }\n\n  return (\n    <>\n      <style>{`body, html { overflow: hidden !important; }`}</style>\n      {modalManager.map((modal, index) => (\n        <Component\n          isLast={modalManager.length - 1 === index}\n          key={modal.id}\n          modal={modal}\n          zIndex={200 + index}\n          closeModal={closeModal}\n        />\n      ))}\n    </>\n  );\n};\nexport const ModalManager: FC<{ children: ReactNode }> = ({ children }) => {\n  return (\n    <div>\n      <ModalManagerEmitter />\n      <ModalManagerInner />\n      <div className=\"transition-all w-full\">{children}</div>\n    </div>\n  );\n};\n\nconst emitter = new EventEmitter();\nexport const showModalEmitter = (params: ModalManagerInterface) => {\n  emitter.emit('show', params);\n};\n\nexport const ModalManagerEmitter: FC = () => {\n  const { showModal } = useModalStore(\n    useShallow((state) => ({\n      showModal: state.openModal,\n    }))\n  );\n\n  useEffect(() => {\n    emitter.on('show', (params: OpenModalInterface) => {\n      showModal(params);\n    });\n\n    return () => {\n      emitter.removeAllListeners('show');\n    };\n  }, []);\n  return null;\n};\n\nexport const DecisionModal: FC<{\n  description: string;\n  approveLabel: string;\n  cancelLabel: string;\n  onlyApprove: boolean;\n  resolution: (value: boolean) => void;\n}> = ({ description, cancelLabel, approveLabel, resolution, onlyApprove }) => {\n  const { closeCurrent } = useModals();\n  return (\n    <div className=\"flex flex-col\">\n      <div>{description}</div>\n      <div className=\"flex gap-[12px] mt-[16px]\">\n        <Button\n          onClick={() => {\n            resolution(true);\n            closeCurrent();\n          }}\n        >\n          {approveLabel}\n        </Button>\n        {!onlyApprove && (\n          <Button\n            onClick={() => {\n              resolution(false);\n              closeCurrent();\n            }}\n          >\n            {cancelLabel}\n          </Button>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport const decisionModalEmitter = new EventEmitter();\n\nexport const areYouSure = ({\n  title = 'Are you sure?',\n  description = 'Are you sure you want to close this modal?' as any,\n  approveLabel = 'Yes',\n  cancelLabel = 'No',\n} = {}): Promise<boolean> => {\n  return new Promise<boolean>((newRes) => {\n    decisionModalEmitter.emit('open', {\n      title,\n      description,\n      approveLabel,\n      cancelLabel,\n      newRes,\n    });\n  });\n};\n\nexport const DecisionEverywhere: FC = () => {\n  const decision = useDecisionModal();\n  useEffect(() => {\n    decisionModalEmitter.on('open', decision.open);\n  }, []);\n  return null;\n};\n\nexport const useDecisionModal = () => {\n  const modals = useModals();\n  const open = useCallback(\n    ({\n      title = 'Are you sure?',\n      description = 'Are you sure you want to close this modal?' as any,\n      onlyApprove = false,\n      approveLabel = 'Yes',\n      cancelLabel = 'No',\n      newRes = undefined as any,\n    } = {}) => {\n      return new Promise<boolean>((res) => {\n        modals.openModal({\n          title,\n          askClose: false,\n          onClose: () => res(false),\n          children: (\n            <DecisionModal\n              onlyApprove={onlyApprove}\n              resolution={(value) => (newRes ? newRes(value) : res(value))}\n              description={description}\n              approveLabel={approveLabel}\n              cancelLabel={cancelLabel}\n            />\n          ),\n        });\n      });\n    },\n    [modals]\n  );\n\n  return { open };\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/new.subscription.tsx",
    "content": "import { useSearchParams } from 'next/navigation';\nimport { FC, useEffect } from 'react';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nexport const NewSubscription: FC = () => {\n  const query = useSearchParams();\n  const fireEvents = useFireEvents();\n  useEffect(() => {\n    const check = query.get('check');\n    if (check) {\n      fireEvents('purchase');\n    }\n  }, [query]);\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/organization.selector.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useMemo } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport clsx from 'clsx';\nexport const OrganizationSelector: FC<{ asOpenSelect?: boolean }> = ({\n  asOpenSelect,\n}) => {\n  const fetch = useFetch();\n  const user = useUser();\n  const load = useCallback(async () => {\n    return await (await fetch('/user/organizations')).json();\n  }, []);\n  const { isLoading, data } = useSWR('organizations', load, {\n    revalidateIfStale: false,\n    revalidateOnFocus: false,\n    refreshWhenOffline: false,\n    refreshWhenHidden: false,\n    revalidateOnReconnect: false,\n  });\n  const current = useMemo(() => {\n    return data?.find((d: any) => d.id === user?.orgId);\n  }, [data]);\n  const withoutCurrent = useMemo(() => {\n    return data?.filter((d: any) => d.id !== user?.orgId);\n  }, [current, data]);\n  const changeOrg = useCallback(\n    (org: { name: string; id: string }) => async () => {\n      await fetch('/user/change-org', {\n        method: 'POST',\n        body: JSON.stringify({\n          id: org.id,\n        }),\n      });\n      window.location.reload();\n    },\n    []\n  );\n  if (isLoading || (!isLoading && data?.length === 1)) {\n    return null;\n  }\n  return (\n    <>\n      <div className=\"hover:text-newTextColor\">\n        <div className=\"group text-[12px] relative\">\n          {asOpenSelect && (\n            <div className=\"bg-btnPrimary !flex !relative max-w-[500px] mx-auto py-[12px] px-[12px]\">Select Organization</div>\n          )}\n          {!asOpenSelect && (\n            <div className=\"flex items-center\">\n              <svg\n                className={user?.tier.current === 'FREE' ? 'animate-bounce drop-shadow-glow': ''}\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 26 26\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M13 0.25C10.4783 0.25 8.01321 0.997774 5.91648 2.39876C3.81976 3.79975 2.18556 5.79103 1.22054 8.12079C0.255524 10.4505 0.00303191 13.0141 0.494993 15.4874C0.986955 17.9607 2.20127 20.2325 3.98439 22.0156C5.76751 23.7987 8.03935 25.0131 10.5126 25.505C12.9859 25.997 15.5495 25.7445 17.8792 24.7795C20.209 23.8144 22.2003 22.1802 23.6012 20.0835C25.0022 17.9868 25.75 15.5217 25.75 13C25.746 9.61971 24.4015 6.379 22.0112 3.98877C19.621 1.59854 16.3803 0.25397 13 0.25ZM5.93001 21.75C6.66349 20.5303 7.70003 19.5212 8.93889 18.8206C10.1777 18.12 11.5768 17.7518 13 17.7518C14.4232 17.7518 15.8223 18.12 17.0611 18.8206C18.3 19.5212 19.3365 20.5303 20.07 21.75C18.0705 23.3714 15.5743 24.2563 13 24.2563C10.4257 24.2563 7.92955 23.3714 5.93001 21.75ZM8.75001 12C8.75001 11.1594 8.99926 10.3377 9.46626 9.63883C9.93326 8.93992 10.597 8.39518 11.3736 8.07351C12.1502 7.75184 13.0047 7.66768 13.8291 7.83166C14.6536 7.99565 15.4108 8.40042 16.0052 8.9948C16.5996 9.58917 17.0044 10.3464 17.1683 11.1709C17.3323 11.9953 17.2482 12.8498 16.9265 13.6264C16.6048 14.403 16.0601 15.0668 15.3612 15.5337C14.6623 16.0007 13.8406 16.25 13 16.25C11.8728 16.25 10.7918 15.8022 9.9948 15.0052C9.19777 14.2082 8.75001 13.1272 8.75001 12ZM21.1888 20.705C20.0103 18.8727 18.2489 17.4908 16.1888 16.7825C17.216 16.0983 17.9959 15.1016 18.413 13.9399C18.8301 12.7783 18.8623 11.5132 18.5049 10.3318C18.1475 9.15035 17.4194 8.11531 16.4282 7.37968C15.4371 6.64404 14.2356 6.24686 13.0013 6.24686C11.767 6.24686 10.5654 6.64404 9.57429 7.37968C8.58316 8.11531 7.85505 9.15035 7.49762 10.3318C7.14019 11.5132 7.17241 12.7783 7.58952 13.9399C8.00662 15.1016 8.78647 16.0983 9.81376 16.7825C7.75358 17.4908 5.99217 18.8727 4.81376 20.705C3.30729 19.1064 2.30179 17.1017 1.92131 14.9382C1.54082 12.7748 1.80201 10.5474 2.67264 8.53066C3.54327 6.51396 4.98524 4.79624 6.82066 3.58946C8.65609 2.38267 10.8046 1.7396 13.0013 1.7396C15.1979 1.7396 17.3464 2.38267 19.1818 3.58946C21.0173 4.79624 22.4592 6.51396 23.3299 8.53066C24.2005 10.5474 24.4617 12.7748 24.0812 14.9382C23.7007 17.1017 22.6952 19.1064 21.1888 20.705Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </div>\n          )}\n          {data?.length > 1 && (\n            <div\n              className={clsx(\n                'hidden py-[12px] px-[12px] group-hover:flex absolute top-[100%] end-0 bg-third border-tableBorder border gap-[12px] cursor-pointer flex-col',\n                asOpenSelect ? '!flex !relative max-w-[500px] mx-auto mb-[10px]' : '',\n              )}\n            >\n              {data?.map((org: { name: string; id: string }) => (\n                <div key={org.id} onClick={changeOrg(org)}>\n                  {org.name}\n                </div>\n              ))}\n            </div>\n          )}\n        </div>\n      </div>\n      {!asOpenSelect && <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/pre-condition.component.tsx",
    "content": "import React, { FC, useEffect } from 'react';\nimport { useSearchParams } from 'next/navigation';\nimport { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Button } from '@gitroom/react/form/button';\n\nexport const PreConditionComponentModal: FC = () => {\n  const modal = useModals();\n  return (\n    <div className=\"flex flex-col gap-[16px]\">\n      <div className=\"whitespace-pre-line\">\n        This social channel was connected previously to another Postiz account.\n        {'\\n'}\n        To continue, please fast-track your trial for an immediate charge.{'\\n'}\n        {'\\n'}\n        ** Please be advised that the account will not eligible for a refund,\n        and the charge is final.\n      </div>\n      <div className=\"flex gap-[2px] justify-center\">\n        <Button\n          onClick={() => (window.location.href = '/billing?finishTrial=true')}\n        >\n          Fast track - Charge me now\n        </Button>\n        <Button onClick={modal.closeCurrent} secondary={true}>Cancel</Button>\n      </div>\n    </div>\n  );\n};\nexport const PreConditionComponent: FC = () => {\n  const modal = useModals();\n  const query = useSearchParams();\n  useEffect(() => {\n    if (query.get('precondition')) {\n      modal.openModal({\n        title: 'Suspicious activity detected',\n        withCloseButton: true,\n        classNames: {\n          modal: 'text-textColor',\n        },\n        children: <PreConditionComponentModal />,\n      });\n    }\n  }, []);\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/redirect.tsx",
    "content": "'use client';\n\nimport { FC, useEffect } from 'react';\nimport { useRouter } from 'next/navigation';\nexport const Redirect: FC<{\n  url: string;\n  delay: number;\n}> = (props) => {\n  const { url, delay } = props;\n  const router = useRouter();\n  useEffect(() => {\n    setTimeout(() => {\n      router.push(url);\n    }, delay);\n  }, []);\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/sentry.component.tsx",
    "content": "'use client';\n\nimport { FC, ReactNode, useEffect } from 'react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { initializeSentryClient } from '@gitroom/react/sentry/initialize.sentry.client';\n\nexport const SentryComponent: FC<{ children: ReactNode }> = ({ children }) => {\n  const { sentryDsn: dsn, environment } = useVariables();\n\n  useEffect(() => {\n    if (!dsn) {\n      return;\n    }\n\n    initializeSentryClient(environment, dsn);\n  }, [dsn]);\n\n  // Always render children - don't block the app\n  return <>{children}</>;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/set.timezone.tsx",
    "content": "'use client';\nimport dayjs, { ConfigType } from 'dayjs';\nimport { FC, useEffect } from 'react';\nimport timezone from 'dayjs/plugin/timezone';\nimport utc from 'dayjs/plugin/utc';\ndayjs.extend(timezone);\ndayjs.extend(utc);\n\nconst { utc: originalUtc } = dayjs;\n\nexport const getTimezone = () => {\n  if (typeof window === 'undefined') {\n    return dayjs.tz.guess();\n  }\n  return localStorage.getItem('timezone') || dayjs.tz.guess();\n};\n\nexport const newDayjs = (config?: ConfigType) => {\n  return dayjs(config);\n};\n\nconst SetTimezone: FC = () => {\n  useEffect(() => {\n    dayjs.utc = (config?: ConfigType, format?: string, strict?: boolean) => {\n      const result = originalUtc(config, format, strict);\n\n      // Attach `.local()` method to the returned Dayjs object\n      result.local = function () {\n        return result.tz(getTimezone());\n      };\n\n      return result;\n    };\n    if (localStorage.getItem('timezone')) {\n      dayjs.tz.setDefault(getTimezone());\n    }\n  }, []);\n  return null;\n};\n\nexport default SetTimezone;\n"
  },
  {
    "path": "apps/frontend/src/components/layout/settings.component.tsx",
    "content": "'use client';\n\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport React, {\n  FC,\n  Ref,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { showMediaBox } from '@gitroom/frontend/components/media/media.component';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useSWRConfig } from 'swr';\nimport clsx from 'clsx';\nimport { TeamsComponent } from '@gitroom/frontend/components/settings/teams.component';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { LogoutComponent } from '@gitroom/frontend/components/layout/logout.component';\nimport { useSearchParams } from 'next/navigation';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { PublicComponent } from '@gitroom/frontend/components/public-api/public.component';\nimport Link from 'next/link';\nimport { Webhooks } from '@gitroom/frontend/components/webhooks/webhooks';\nimport { Sets } from '@gitroom/frontend/components/sets/sets';\nimport { SignaturesComponent } from '@gitroom/frontend/components/settings/signatures.component';\nimport { Autopost } from '@gitroom/frontend/components/autopost/autopost';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { SVGLine } from '@gitroom/frontend/components/launches/launches.component';\nimport { GlobalSettings } from '@gitroom/frontend/components/settings/global.settings';\nimport { ApprovedAppsComponent } from '@gitroom/frontend/components/approved-apps/approved-apps.component';\nexport const SettingsPopup: FC<{\n  getRef?: Ref<any>;\n}> = (props) => {\n  const { isGeneral } = useVariables();\n  const { getRef } = props;\n  const fetch = useFetch();\n  const toast = useToaster();\n  const swr = useSWRConfig();\n  const user = useUser();\n  const resolver = useMemo(() => {\n    return classValidatorResolver(UserDetailDto);\n  }, []);\n  const form = useForm({\n    resolver,\n  });\n  const picture = form.watch('picture');\n  const modal = useModals();\n  const close = useCallback(() => {\n    return modal.closeAll();\n  }, []);\n  const url = useSearchParams();\n  const showLogout = !url.get('onboarding') || user?.tier?.current === 'FREE';\n  const loadProfile = useCallback(async () => {\n    const personal = await (await fetch('/user/personal')).json();\n    form.setValue('fullname', personal.name || '');\n    form.setValue('bio', personal.bio || '');\n    form.setValue('picture', personal.picture);\n  }, []);\n  const openMedia = useCallback(() => {\n    showMediaBox((values) => {\n      form.setValue('picture', values);\n    });\n  }, []);\n  const remove = useCallback(() => {\n    form.setValue('picture', null);\n  }, []);\n\n  const submit = useCallback(async (val: any) => {\n    await fetch('/user/personal', {\n      method: 'POST',\n      body: JSON.stringify(val),\n    });\n    if (getRef) {\n      return;\n    }\n    toast.show(t('profile_updated', 'Profile updated'));\n    close();\n  }, []);\n\n  const [tab, setTab] = useState('global_settings');\n\n  const t = useT();\n  const list = useMemo(() => {\n    const arr = [];\n    arr.push({ tab: 'global_settings', label: t('global_settings', 'Global Settings') });\n    // Populate tabs based on user permissions\n    if (user?.tier?.team_members && isGeneral) {\n      arr.push({ tab: 'teams', label: t('teams', 'Teams') });\n    }\n    if (user?.tier?.webhooks) {\n      arr.push({ tab: 'webhooks', label: t('webhooks_1', 'Webhooks') });\n    }\n    if (user?.tier?.autoPost) {\n      arr.push({ tab: 'autopost', label: t('auto_post', 'Auto Post') });\n    }\n    if (user?.tier.current !== 'FREE') {\n      arr.push({ tab: 'sets', label: t('sets', 'Sets') });\n    }\n    if (user?.tier.current !== 'FREE') {\n      arr.push({ tab: 'signatures', label: t('signatures', 'Signatures') });\n    }\n    if (user?.tier?.public_api && isGeneral && showLogout) {\n      arr.push({ tab: 'api', label: t('developers', 'Developers') });\n    }\n    arr.push({ tab: 'approved_apps', label: t('approved_apps', 'Approved Apps') });\n\n    return arr;\n  }, [user, isGeneral, showLogout, t]);\n\n  useEffect(() => {\n    loadProfile();\n  }, []);\n\n  return (\n    <>\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-col transition-all w-[260px]\">\n        <div className=\"flex flex-1 flex-col gap-[15px]\">\n          {list.map(({ tab: tabKey, label }) => (\n            <div\n              key={tabKey}\n              className={clsx(\n                'cursor-pointer flex items-center gap-[12px] group/profile hover:bg-boxHover rounded-e-[8px]',\n                tabKey === tab && 'bg-boxHover'\n              )}\n              onClick={() => setTab(tabKey)}\n            >\n              <div\n                className={clsx(\n                  'h-full w-[4px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity',\n                  tabKey === tab && 'opacity-100'\n                )}\n              >\n                <SVGLine />\n              </div>\n              {label}\n            </div>\n          ))}\n        </div>\n        <div>\n          {showLogout && (\n            <div className=\"mt-4\">\n              <LogoutComponent />\n            </div>\n          )}\n        </div>\n      </div>\n      <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n        <FormProvider {...form}>\n          <form onSubmit={form.handleSubmit(submit)}>\n            {!!getRef && (\n              <button type=\"submit\" className=\"hidden\" ref={getRef}></button>\n            )}\n            <div\n              className={clsx(\n                'w-full mx-auto gap-[24px] flex flex-col relative',\n                !getRef && 'rounded-[4px]'\n              )}\n            >\n              {tab === 'global_settings' && (\n                <div>\n                  <GlobalSettings />\n                </div>\n              )}\n              {tab === 'teams' && !!user?.tier?.team_members && isGeneral && (\n                <div>\n                  <TeamsComponent />\n                </div>\n              )}\n\n              {tab === 'webhooks' && !!user?.tier?.webhooks && (\n                <div>\n                  <Webhooks />\n                </div>\n              )}\n\n              {tab === 'autopost' && !!user?.tier?.autoPost && (\n                <div>\n                  <Autopost />\n                </div>\n              )}\n\n              {tab === 'sets' && user?.tier.current !== 'FREE' && (\n                <div>\n                  <Sets />\n                </div>\n              )}\n\n              {tab === 'signatures' && user?.tier.current !== 'FREE' && (\n                <div>\n                  <SignaturesComponent />\n                </div>\n              )}\n\n              {tab === 'api' &&\n                !!user?.tier?.public_api &&\n                isGeneral &&\n                showLogout && (\n                  <div>\n                    <PublicComponent />\n                  </div>\n                )}\n\n              {tab === 'approved_apps' && (\n                <div>\n                  <ApprovedAppsComponent />\n                </div>\n              )}\n            </div>\n          </form>\n        </FormProvider>\n      </div>\n    </>\n  );\n};\nexport const SettingsComponent = () => {\n  const settings = useModals();\n  const user = useUser();\n  const openModal = useCallback(() => {\n    if (user?.tier.current !== 'FREE') {\n      return;\n    }\n    settings.openModal({\n      children: (\n        <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] w-[500px] mx-auto\">\n          <SettingsPopup />\n        </div>\n      ),\n      classNames: {\n        modal: 'bg-transparent text-textColor',\n      },\n      withCloseButton: false,\n      size: '100%',\n    });\n  }, [user]);\n  return (\n    <Link href=\"/settings\" onClick={openModal}>\n      <svg\n        width=\"40\"\n        height=\"40\"\n        viewBox=\"0 0 40 40\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        className=\"cursor-pointer relative z-[200]\"\n      >\n        <path\n          d=\"M19.9987 15.5C19.1087 15.5 18.2387 15.7639 17.4986 16.2584C16.7586 16.7528 16.1818 17.4556 15.8413 18.2779C15.5007 19.1002 15.4115 20.005 15.5852 20.8779C15.7588 21.7508 16.1874 22.5526 16.8167 23.182C17.4461 23.8113 18.2479 24.2399 19.1208 24.4135C19.9937 24.5871 20.8985 24.498 21.7208 24.1574C22.5431 23.8168 23.2459 23.2401 23.7403 22.5C24.2348 21.76 24.4987 20.89 24.4987 20C24.4975 18.8069 24.023 17.663 23.1793 16.8194C22.3357 15.9757 21.1918 15.5012 19.9987 15.5ZM19.9987 23C19.4054 23 18.8254 22.824 18.332 22.4944C17.8387 22.1647 17.4541 21.6962 17.2271 21.148C17 20.5999 16.9406 19.9967 17.0564 19.4147C17.1721 18.8328 17.4578 18.2982 17.8774 17.8787C18.297 17.4591 18.8315 17.1734 19.4134 17.0576C19.9954 16.9419 20.5986 17.0013 21.1468 17.2283C21.6949 17.4554 22.1635 17.8399 22.4931 18.3333C22.8228 18.8266 22.9987 19.4066 22.9987 20C22.9987 20.7956 22.6826 21.5587 22.12 22.1213C21.5574 22.6839 20.7944 23 19.9987 23ZM30.3056 18.0509C30.2847 17.9453 30.2413 17.8454 30.1784 17.7581C30.1155 17.6707 30.0345 17.5979 29.9409 17.5447L27.1443 15.9509L27.1331 12.799C27.1327 12.6905 27.1089 12.5833 27.063 12.4849C27.0172 12.3865 26.9506 12.2992 26.8678 12.229C25.8533 11.3709 24.6851 10.7134 23.4253 10.2912C23.3261 10.2577 23.2209 10.2452 23.1166 10.2547C23.0123 10.2643 22.9111 10.2955 22.8197 10.3465L19.9987 11.9234L17.175 10.3437C17.0834 10.2924 16.9821 10.2609 16.8776 10.2513C16.7732 10.2416 16.6678 10.2539 16.5684 10.2875C15.3095 10.7127 14.1426 11.3728 13.1297 12.2328C13.0469 12.3028 12.9804 12.39 12.9346 12.4882C12.8888 12.5865 12.8648 12.6935 12.8643 12.8019L12.8503 15.9565L10.0537 17.5503C9.96015 17.6036 9.87916 17.6763 9.81623 17.7637C9.7533 17.8511 9.70992 17.9509 9.68903 18.0565C9.43309 19.3427 9.43309 20.6667 9.68903 21.9528C9.70992 22.0584 9.7533 22.1583 9.81623 22.2456C9.87916 22.333 9.96015 22.4058 10.0537 22.459L12.8503 24.0528L12.8615 27.2047C12.8619 27.3132 12.8858 27.4204 12.9316 27.5188C12.9774 27.6172 13.044 27.7045 13.1268 27.7747C14.1413 28.6328 15.3095 29.2904 16.5693 29.7125C16.6686 29.7461 16.7737 29.7585 16.878 29.749C16.9823 29.7394 17.0835 29.7082 17.175 29.6572L19.9987 28.0765L22.8225 29.6562C22.9342 29.7185 23.0602 29.7508 23.1881 29.75C23.27 29.75 23.3514 29.7367 23.429 29.7106C24.6878 29.286 25.8547 28.6265 26.8678 27.7672C26.9505 27.6971 27.017 27.61 27.0628 27.5117C27.1087 27.4135 27.1326 27.3065 27.1331 27.1981L27.1472 24.0434L29.9437 22.4497C30.0373 22.3964 30.1183 22.3236 30.1812 22.2363C30.2441 22.1489 30.2875 22.049 30.3084 21.9434C30.5629 20.6583 30.562 19.3357 30.3056 18.0509ZM28.8993 21.3237L26.2209 22.8472C26.1035 22.9139 26.0064 23.0111 25.9397 23.1284C25.8853 23.2222 25.8281 23.3215 25.77 23.4153C25.6956 23.5335 25.6559 23.6703 25.6556 23.81L25.6415 26.8334C24.9216 27.3988 24.1195 27.8509 23.2631 28.174L20.5612 26.6684C20.449 26.6064 20.3228 26.5741 20.1947 26.5747H20.1768C20.0634 26.5747 19.949 26.5747 19.8356 26.5747C19.7014 26.5713 19.5688 26.6037 19.4512 26.6684L16.7475 28.1778C15.8892 27.8571 15.0849 27.4072 14.3625 26.8437L14.3522 23.825C14.3517 23.685 14.3121 23.548 14.2378 23.4294C14.1797 23.3356 14.1225 23.2419 14.069 23.1425C14.0028 23.0233 13.9056 22.9242 13.7878 22.8556L11.1065 21.3284C10.9678 20.4507 10.9678 19.5567 11.1065 18.679L13.7803 17.1528C13.8976 17.0861 13.9948 16.9889 14.0615 16.8715C14.1159 16.7778 14.1731 16.6784 14.2312 16.5847C14.3056 16.4664 14.3453 16.3297 14.3456 16.19L14.3597 13.1665C15.0796 12.6012 15.8816 12.1491 16.7381 11.8259L19.4362 13.3315C19.5536 13.3966 19.6864 13.429 19.8206 13.4253C19.934 13.4253 20.0484 13.4253 20.1618 13.4253C20.296 13.4286 20.4287 13.3963 20.5462 13.3315L23.25 11.8222C24.1082 12.1429 24.9125 12.5927 25.635 13.1562L25.6453 16.175C25.6457 16.3149 25.6854 16.452 25.7597 16.5706C25.8178 16.6644 25.875 16.7581 25.9284 16.8575C25.9947 16.9767 26.0918 17.0758 26.2097 17.1444L28.8909 18.6715C29.0315 19.5499 29.0331 20.4449 28.8956 21.3237H28.8993Z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    </Link>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/streak.component.tsx",
    "content": "'use client';\n\nimport { FC, useMemo } from 'react';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\n\nexport const StreakComponent: FC = () => {\n  const user = useUser();\n\n  const streakDays = useMemo(() => {\n    if (!user?.streakSince) return 0;\n    const streakStart = new Date(user.streakSince);\n    const now = new Date();\n    const diffTime = now.getTime() - streakStart.getTime();\n    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));\n    if (diffDays + 1 <= 0) {\n      return 1;\n    }\n\n    return diffDays + 1;\n  }, [user?.streakSince]);\n\n  const tooltipContent = useMemo(() => {\n    if (streakDays === 1) {\n      return 'You started your streak today! Keep posting daily to maintain it.';\n    }\n    return `You're on a ${streakDays} day posting streak! Keep it going!`;\n  }, [streakDays]);\n\n  if (!user?.streakSince || streakDays <= 0) {\n    return null;\n  }\n\n  return (\n    <div\n      className=\"flex items-center gap-[6px] text-orange-500 hover:text-orange-400 cursor-default\"\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={tooltipContent}\n    >\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 22 27\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M17.9862 17.1673C17.7269 18.6157 17.0301 19.9498 15.9896 20.9902C14.949 22.0305 13.6147 22.7271 12.1663 22.986C12.1113 22.9949 12.0557 22.9995 12 22.9998C11.7492 22.9997 11.5075 22.9054 11.323 22.7355C11.1384 22.5656 11.0245 22.3326 11.0037 22.0826C10.9829 21.8326 11.0569 21.584 11.2108 21.3859C11.3648 21.1879 11.5876 21.055 11.835 21.0135C13.9062 20.6648 15.6637 18.9073 16.015 16.8323C16.0594 16.5707 16.2059 16.3375 16.4223 16.184C16.6387 16.0304 16.9072 15.9691 17.1688 16.0135C17.4303 16.058 17.6635 16.2045 17.8171 16.4209C17.9706 16.6372 18.0319 16.9057 17.9875 17.1673H17.9862ZM22 15.9998C22 18.9172 20.8411 21.7151 18.7782 23.778C16.7153 25.8409 13.9174 26.9998 11 26.9998C8.08262 26.9998 5.28473 25.8409 3.22183 23.778C1.15893 21.7151 0 18.9172 0 15.9998C0 12.5098 1.375 8.94105 4.0825 5.39355C4.1682 5.28122 4.27674 5.18833 4.40095 5.12099C4.52516 5.05365 4.66223 5.01341 4.80313 5.00289C4.94403 4.99238 5.08556 5.01185 5.21838 5.06001C5.35121 5.10817 5.47233 5.18393 5.57375 5.2823L8.58875 8.20855L11.3388 0.657298C11.3937 0.50669 11.484 0.371499 11.6022 0.263121C11.7203 0.154744 11.8628 0.0763568 12.0175 0.0345691C12.1723 -0.00721869 12.3349 -0.0111825 12.4915 0.023012C12.6481 0.0572064 12.7942 0.128557 12.9175 0.231048C15.6512 2.4998 22 8.56855 22 15.9998ZM20 15.9998C20 10.2385 15.5262 5.2598 12.7237 2.70855L9.94 10.3423C9.88287 10.4991 9.78741 10.6391 9.66232 10.7495C9.53723 10.86 9.38648 10.9374 9.22383 10.9747C9.06117 11.0119 8.89177 11.0079 8.73107 10.963C8.57036 10.918 8.42346 10.8336 8.30375 10.7173L5.0075 7.5198C3.01125 10.401 2 13.2498 2 15.9998C2 18.3867 2.94821 20.6759 4.63604 22.3638C6.32387 24.0516 8.61305 24.9998 11 24.9998C13.3869 24.9998 15.6761 24.0516 17.364 22.3638C19.0518 20.6759 20 18.3867 20 15.9998Z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n      <span className=\"text-[14px] font-semibold\">{streakDays}</span>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/support.tsx",
    "content": "'use client';\n\nimport { EventEmitter } from 'events';\nimport { useEffect, useState } from 'react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const supportEmitter = new EventEmitter();\nexport const Support = () => {\n  const [show, setShow] = useState(true);\n  const { discordUrl } = useVariables();\n  const t = useT();\n\n  useEffect(() => {\n    supportEmitter.on('change', setShow);\n    return () => {\n      supportEmitter.off('state', setShow);\n    };\n  }, []);\n  if (!discordUrl || !show) return null;\n  return (\n    <div\n      id=\"support-discord\"\n      className=\"bg-customColor39 w-[194px] h-[58px] fixed end-[20px] bottom-[20px] z-[500] text-[16px] text-customColor40 rounded-[30px] !rounded-br-[0] cursor-pointer flex justify-center items-center gap-[10px]\"\n      onClick={() => window.open(discordUrl)}\n    >\n      <div>\n        <svg\n          width=\"32\"\n          height=\"33\"\n          viewBox=\"0 0 32 33\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className=\"mb-[4px]\"\n        >\n          <path\n            d=\"M26.1303 11.347C24.3138 9.93899 22.134 9.23502 19.8331 9.11768L19.4697 9.4697C21.5284 9.93899 23.345 10.8776 25.0404 12.1683C22.9817 11.1123 20.6807 10.4084 18.2587 10.1737C17.5321 10.0563 16.9266 10.0563 16.2 10.0563C15.4734 10.0563 14.8679 10.0563 14.1413 10.1737C11.7193 10.4084 9.41833 11.1123 7.35963 12.1683C9.05501 10.8776 10.8716 9.93899 12.9303 9.4697L12.5669 9.11768C10.266 9.23502 8.08621 9.93899 6.26972 11.347C4.21101 15.1017 3.1211 19.3257 3 23.6669C4.81649 25.5443 7.35963 26.7177 10.0239 26.7177C10.0239 26.7177 10.8716 25.779 11.477 24.9576C9.90277 24.6057 8.44954 23.7843 7.48074 22.4937C8.32843 22.963 9.17611 23.4323 10.0239 23.7843C11.1138 24.2537 12.2037 24.4883 13.2936 24.723C14.2624 24.8403 15.2312 24.9576 16.2 24.9576C17.1688 24.9576 18.1376 24.8403 19.1064 24.723C20.1963 24.4883 21.2862 24.2537 22.3761 23.7843C23.2239 23.4323 24.0716 22.963 24.9193 22.4937C23.9505 23.7843 22.4972 24.6057 20.923 24.9576C21.5284 25.779 22.3761 26.7177 22.3761 26.7177C25.0404 26.7177 27.5835 25.5443 29.4 23.6669C29.2789 19.3257 28.189 15.1017 26.1303 11.347ZM12.2037 21.555C10.9927 21.555 9.90278 20.499 9.90278 19.2084C9.90278 17.9177 10.9927 16.8617 12.2037 16.8617C13.4147 16.8617 14.5046 17.9177 14.5046 19.2084C14.5046 20.499 13.4147 21.555 12.2037 21.555ZM20.1963 21.555C18.9853 21.555 17.8954 20.499 17.8954 19.2084C17.8954 17.9177 18.9853 16.8617 20.1963 16.8617C21.4073 16.8617 22.4972 17.9177 22.4972 19.2084C22.4972 20.499 21.4073 21.555 20.1963 21.555Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      </div>\n      <div>{t('discord_support', 'Discord Support')}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/title.tsx",
    "content": "'use client';\n\nimport { usePathname } from 'next/navigation';\nimport { useMemo } from 'react';\nimport { useMenuItem } from '@gitroom/frontend/components/layout/top.menu';\nexport const Title = () => {\n  const path = usePathname();\n  const { all: menuItems } = useMenuItem();\n  const currentTitle = useMemo(() => {\n    return menuItems.find((item) => path.indexOf(item.path) > -1)?.name;\n  }, [path]);\n\n  return <h1>{currentTitle}</h1>;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/top.menu.tsx",
    "content": "'use client';\n\nimport { FC, ReactNode, useCallback } from 'react';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { MenuItem } from '@gitroom/frontend/components/new-layout/menu-item';\n\ninterface MenuItemInterface {\n  name: string;\n  icon: ReactNode;\n  path: string;\n  role?: string[];\n  hide?: boolean;\n  requireBilling?: boolean;\n  onClick?: () => void;\n}\n\nexport const useMenuItem = () => {\n  const { isGeneral } = useVariables();\n  const t = useT();\n  const fetch = useFetch();\n\n  const handleAgentMediaClick = useCallback(async () => {\n    try {\n      const response = await fetch('/user/agent-media-sso');\n      const data = await response.json();\n      if (data.url) {\n        window.open(data.url, '_blank');\n      }\n    } catch (e) {\n      // ignore\n    }\n  }, [fetch]);\n\n  const firstMenu = [\n    {\n      name: isGeneral ? t('calendar', 'Calendar') : t('launches', 'Launches'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"21\"\n          height=\"23\"\n          viewBox=\"0 0 21 23\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M19.5 9.5H1.5M14.5 1.5V5.5M6.5 1.5V5.5M6.3 21.5H14.7C16.3802 21.5 17.2202 21.5 17.862 21.173C18.4265 20.8854 18.8854 20.4265 19.173 19.862C19.5 19.2202 19.5 18.3802 19.5 16.7V8.3C19.5 6.61984 19.5 5.77976 19.173 5.13803C18.8854 4.57354 18.4265 4.1146 17.862 3.82698C17.2202 3.5 16.3802 3.5 14.7 3.5H6.3C4.61984 3.5 3.77976 3.5 3.13803 3.82698C2.57354 4.1146 2.1146 4.57354 1.82698 5.13803C1.5 5.77976 1.5 6.61984 1.5 8.3V16.7C1.5 18.3802 1.5 19.2202 1.82698 19.862C2.1146 20.4265 2.57354 20.8854 3.13803 21.173C3.77976 21.5 4.61984 21.5 6.3 21.5Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.8\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/launches',\n    },\n    {\n      name: 'Agent',\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"23\"\n          height=\"23\"\n          viewBox=\"0 0 32 32\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M21.1963 9.07375C20.2913 6.95494 18.6824 5.21364 16.6416 4.14422C14.6009 3.0748 12.2534 2.74287 9.99616 3.20455C7.73891 3.66623 5.71031 4.8932 4.25334 6.67802C2.79637 8.46284 2.0004 10.696 2 13V21.25C2 21.7141 2.18437 22.1592 2.51256 22.4874C2.84075 22.8156 3.28587 23 3.75 23H10.8337C11.6141 24.7821 12.8964 26.2984 14.5241 27.3638C16.1519 28.4293 18.0546 28.9978 20 29H28.25C28.7141 29 29.1592 28.8156 29.4874 28.4874C29.8156 28.1592 30 27.7141 30 27.25V19C29.9995 16.5553 29.1036 14.1955 27.4814 12.3666C25.8593 10.5376 23.6234 9.36619 21.1963 9.07375ZM4 13C4 11.4177 4.46919 9.87103 5.34824 8.55544C6.22729 7.23984 7.47672 6.21446 8.93853 5.60896C10.4003 5.00346 12.0089 4.84504 13.5607 5.15372C15.1126 5.4624 16.538 6.22432 17.6569 7.34314C18.7757 8.46197 19.5376 9.88743 19.8463 11.4393C20.155 12.9911 19.9965 14.5997 19.391 16.0615C18.7855 17.5233 17.7602 18.7727 16.4446 19.6518C15.129 20.5308 13.5823 21 12 21H4V13ZM28 27H20C18.5854 26.9984 17.1964 26.6225 15.974 25.9106C14.7516 25.1986 13.7394 24.1759 13.04 22.9463C14.4096 22.8041 15.7351 22.3804 16.9333 21.7017C18.1314 21.023 19.1763 20.104 20.0024 19.0023C20.8284 17.9006 21.4179 16.6401 21.7337 15.2998C22.0495 13.9595 22.0848 12.5684 21.8375 11.2137C23.5916 11.6277 25.1545 12.6218 26.273 14.035C27.3915 15.4482 28 17.1977 28 19V27Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      ),\n      path: '/agents',\n    },\n    {\n      name: t('analytics', 'Analytics'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"19\"\n          viewBox=\"0 0 20 19\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M18.5 18H3.01111C2.48217 18 2.2177 18 2.01568 17.8971C1.83797 17.8065 1.69349 17.662 1.60294 17.4843C1.5 17.2823 1.5 17.0178 1.5 16.4889V1M18.5 4.77778L13.3676 9.91019C13.1806 10.0972 13.0871 10.1907 12.9793 10.2257C12.8844 10.2565 12.7823 10.2565 12.6874 10.2257C12.5796 10.1907 12.4861 10.0972 12.2991 9.91019L10.5343 8.14537C10.3472 7.95836 10.2537 7.86486 10.1459 7.82982C10.0511 7.79901 9.94892 7.79901 9.85407 7.82982C9.74625 7.86486 9.65275 7.95836 9.46574 8.14537L5.27778 12.3333M18.5 4.77778H14.7222M18.5 4.77778V8.55556\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/analytics',\n    },\n    {\n      name: t('media', 'Media'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"21\"\n          viewBox=\"0 0 20 21\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M7.50008 3L6.66675 7.16667M13.3334 3L12.5001 7.16667M18.3334 7.16667H1.66675M5.66675 18H14.3334C15.7335 18 16.4336 18 16.9684 17.7275C17.4388 17.4878 17.8212 17.1054 18.0609 16.635C18.3334 16.1002 18.3334 15.4001 18.3334 14V7C18.3334 5.59987 18.3334 4.8998 18.0609 4.36502C17.8212 3.89462 17.4388 3.51217 16.9684 3.27248C16.4336 3 15.7335 3 14.3334 3H5.66675C4.26662 3 3.56655 3 3.03177 3.27248C2.56137 3.51217 2.17892 3.89462 1.93923 4.36502C1.66675 4.8998 1.66675 5.59987 1.66675 7V14C1.66675 15.4001 1.66675 16.1002 1.93923 16.635C2.17892 17.1054 2.56137 17.4878 3.03177 17.7275C3.56655 18 4.26662 18 5.66675 18Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/media',\n    },\n    {\n      name: t('plugs', 'Plugs'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"18\"\n          height=\"19\"\n          viewBox=\"0 0 18 19\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M11.6711 6.21205C11.3397 5.88068 11.174 5.715 11.1119 5.52395C11.0573 5.35589 11.0573 5.17486 11.1119 5.00681C11.174 4.81575 11.3397 4.65007 11.6711 4.3187L14.0461 1.94369C13.4158 1.65867 12.7162 1.5 11.9795 1.5C9.20677 1.5 6.95901 3.74776 6.95901 6.5205C6.95901 6.93138 7.00837 7.33073 7.10148 7.71294C7.20119 8.12224 7.25104 8.32689 7.24219 8.45618C7.23292 8.59154 7.21274 8.66355 7.15032 8.78401C7.0907 8.89907 6.97646 9.0133 6.748 9.24177L1.52013 14.4696C0.826947 15.1628 0.826948 16.2867 1.52013 16.9799C2.21332 17.6731 3.3372 17.6731 4.03039 16.9799L9.25825 11.752C9.48672 11.5236 9.60095 11.4093 9.71601 11.3497C9.83647 11.2873 9.90848 11.2671 10.0438 11.2578C10.1731 11.249 10.3778 11.2988 10.7871 11.3985C11.1693 11.4916 11.5686 11.541 11.9795 11.541C14.7523 11.541 17 9.29325 17 6.5205C17 5.78382 16.8413 5.0842 16.5563 4.45394L14.1813 6.82895C13.8499 7.16032 13.6843 7.326 13.4932 7.38808C13.3252 7.44269 13.1441 7.44269 12.9761 7.38808C12.785 7.326 12.6193 7.16032 12.288 6.82895L11.6711 6.21205Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/plugs',\n    },\n    {\n      name: t('integrations', 'Integrations'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"19\"\n          viewBox=\"0 0 20 19\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M6.175 3.125C6.175 1.9514 7.1264 1 8.3 1C9.47361 1 10.425 1.9514 10.425 3.125V4.4H11.275C12.4632 4.4 13.0572 4.4 13.5258 4.59411C14.1507 4.85292 14.6471 5.34934 14.9059 5.97416C15.1 6.44277 15.1 7.03685 15.1 8.225H16.375C17.5486 8.225 18.5 9.1764 18.5 10.35C18.5 11.5236 17.5486 12.475 16.375 12.475H15.1V13.92C15.1 15.3481 15.1 16.0622 14.8221 16.6077C14.5776 17.0875 14.1875 17.4776 13.7077 17.7221C13.1622 18 12.4481 18 11.02 18H10.425V16.5125C10.425 15.4563 9.56874 14.6 8.5125 14.6C7.45626 14.6 6.6 15.4563 6.6 16.5125V18H5.58C4.15187 18 3.4378 18 2.89232 17.7221C2.41251 17.4776 2.02241 17.0875 1.77793 16.6077C1.5 16.0622 1.5 15.3481 1.5 13.92V12.475H2.775C3.94861 12.475 4.9 11.5236 4.9 10.35C4.9 9.1764 3.94861 8.225 2.775 8.225H1.5C1.5 7.03685 1.5 6.44277 1.69411 5.97416C1.95292 5.34934 2.44934 4.85292 3.07416 4.59411C3.54277 4.4 4.13685 4.4 5.325 4.4H6.175V3.125Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/third-party',\n    },\n  ] satisfies MenuItemInterface[] as MenuItemInterface[];\n\n  const secondMenu = [\n    {\n      name: t('UGC', 'UGC'),\n      icon: (\n        <svg\n          fill=\"#c52e2e\"\n          height=\"30\"\n          width=\"20\"\n          version=\"1.1\"\n          id=\"Layer_1\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 408.333 408.333\"\n        >\n          <g id=\"SVGRepo_bgCarrier\" strokeWidth=\"0\" />\n          <g\n            id=\"SVGRepo_tracerCarrier\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n\n          <g id=\"SVGRepo_iconCarrier\">\n            <g id=\"XMLID_230_\">\n              <g>\n                <path d=\"M311.056,164.871c41.82-8.912,73.109-49.495,73.109-84.871c0-70-40-80-40-80c0,71.666-19.216,85.621-20,86.666V30 c-50,10-50,96.666-50,116.667c0,7.415-5.414,13.575-12.494,14.773c-5.775-44.153-20.583-71.803-33.729-88.309 c5.77-6.311,12.488-17.252,15.099-35.229c6.586-3.272,11.124-10.049,11.124-17.902c0-11.045-8.955-20-20-20 c-11.046,0-20,8.955-20,20c0,6.912,3.506,13.003,8.837,16.596c-1.916,11.42-5.84,18.233-8.98,22.042 c-5.889-4.981-9.856-6.971-9.856-6.971s-3.968,1.99-9.856,6.971c-3.141-3.809-7.064-10.622-8.98-22.042 c5.33-3.593,8.837-9.685,8.837-16.596c0-11.045-8.955-20-20-20c-11.046,0-20,8.955-20,20c0,7.854,4.537,14.63,11.124,17.902 c2.61,17.977,9.329,28.917,15.099,35.229c-13.146,16.506-27.953,44.156-33.729,88.309c-7.08-1.198-12.494-7.358-12.494-14.773 c0-20,0-106.667-50-116.667v56.666c-0.784-1.045-20-15-20-86.666c0,0-40,10-40,80c0,35.377,31.289,75.96,73.109,84.871 c7.002,19.898,25.135,34.588,46.893,36.558c0,0.08-0.002,0.157-0.002,0.237v15h-21c-13.785,0-25-11.215-25-25h-20 c0,24.813,20.187,45,45,45h21v15h-21c-24.813,0-45,20.187-45,45h20c0-13.785,11.215-25,25-25h21 c0,8.095,3.213,15.436,8.425,20.833c-5.212,5.397-8.425,12.737-8.425,20.833c0,10.41,5.304,19.578,13.355,24.958L134.166,385 c0,0,19.213,23.333,70,23.333c50.787,0,70-23.333,70-23.333l-23.354-46.709c8.051-5.38,13.354-14.548,13.354-24.958 c0-8.096-3.213-15.436-8.425-20.833c5.212-5.397,8.425-12.738,8.425-20.833h21c13.785,0,25,11.215,25,25h20 c0-24.813-20.186-45-45-45h-21v-15h21c24.814,0,45-20.187,45-45h-20c0,13.785-11.215,25-25,25h-21v-15 c0-0.08-0.002-0.158-0.002-0.237C285.923,199.459,304.054,184.77,311.056,164.871z\" />\n              </g>\n            </g>\n          </g>\n        </svg>\n      ),\n      path: '#',\n      role: ['ADMIN', 'SUPERADMIN', 'USER'],\n      requireBilling: true,\n      onClick: handleAgentMediaClick,\n    },\n    {\n      name: t('affiliate', 'Affiliate'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"21\"\n          viewBox=\"0 0 20 21\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M15.0004 6.467C14.9504 6.45866 14.8921 6.45866 14.8421 6.467C13.6921 6.42533 12.7754 5.48366 12.7754 4.31699C12.7754 3.12533 13.7337 2.16699 14.9254 2.16699C16.1171 2.16699 17.0754 3.13366 17.0754 4.31699C17.0671 5.48366 16.1504 6.42533 15.0004 6.467Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M14.1419 12.5338C15.2836 12.7255 16.5419 12.5255 17.4253 11.9338C18.6003 11.1505 18.6003 9.86713 17.4253 9.08379C16.5336 8.49213 15.2586 8.29212 14.1169 8.49212\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M4.97466 6.467C5.02466 6.45866 5.08299 6.45866 5.13299 6.467C6.28299 6.42533 7.19966 5.48366 7.19966 4.31699C7.19966 3.12533 6.24133 2.16699 5.04966 2.16699C3.85799 2.16699 2.89966 3.13366 2.89966 4.31699C2.90799 5.48366 3.82466 6.42533 4.97466 6.467Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M5.83304 12.5338C4.69137 12.7255 3.43304 12.5255 2.54971 11.9338C1.37471 11.1505 1.37471 9.86713 2.54971 9.08379C3.44137 8.49213 4.71637 8.29212 5.85804 8.49212\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M10.0001 12.6916C9.95014 12.6833 9.89181 12.6833 9.84181 12.6916C8.69181 12.6499 7.77515 11.7083 7.77515 10.5416C7.77515 9.34994 8.73348 8.3916 9.92514 8.3916C11.1168 8.3916 12.0751 9.35827 12.0751 10.5416C12.0668 11.7083 11.1501 12.6583 10.0001 12.6916Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M7.5751 15.3158C6.4001 16.0992 6.4001 17.3825 7.5751 18.1658C8.90843 19.0575 11.0918 19.0575 12.4251 18.1658C13.6001 17.3825 13.6001 16.0992 12.4251 15.3158C11.1001 14.4325 8.90843 14.4325 7.5751 15.3158Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: 'https://affiliate.postiz.com',\n      role: ['ADMIN', 'SUPERADMIN', 'USER'],\n      requireBilling: true,\n    },\n    {\n      name: t('billing', 'Billing'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"21\"\n          viewBox=\"0 0 20 21\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M7.08341 12.7225C7.08341 13.7964 7.95397 14.667 9.02786 14.667H10.8334C11.984 14.667 12.9167 13.7343 12.9167 12.5837C12.9167 11.4331 11.984 10.5003 10.8334 10.5003H9.16675C8.01615 10.5003 7.08341 9.56759 7.08341 8.41699C7.08341 7.2664 8.01615 6.33366 9.16675 6.33366H10.9723C12.0462 6.33366 12.9167 7.20422 12.9167 8.2781M10.0001 5.08366V6.33366M10.0001 14.667V15.917M18.3334 10.5003C18.3334 15.1027 14.6025 18.8337 10.0001 18.8337C5.39771 18.8337 1.66675 15.1027 1.66675 10.5003C1.66675 5.89795 5.39771 2.16699 10.0001 2.16699C14.6025 2.16699 18.3334 5.89795 18.3334 10.5003Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/billing',\n      role: ['ADMIN', 'SUPERADMIN'],\n      requireBilling: true,\n    },\n    {\n      name: t('settings', 'Settings'),\n      icon: (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"20\"\n          height=\"21\"\n          viewBox=\"0 0 20 21\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M7.82912 16.6429L8.31616 17.7383C8.46094 18.0644 8.69722 18.3414 8.99635 18.5358C9.29547 18.7303 9.64458 18.8337 10.0013 18.8337C10.3581 18.8337 10.7072 18.7303 11.0063 18.5358C11.3055 18.3414 11.5417 18.0644 11.6865 17.7383L12.1736 16.6429C12.3469 16.2542 12.6386 15.9302 13.0069 15.717C13.3776 15.5032 13.8063 15.4121 14.2319 15.4568L15.4236 15.5837C15.7783 15.6212 16.1363 15.555 16.4541 15.3931C16.772 15.2312 17.0361 14.9806 17.2143 14.6716C17.3928 14.3628 17.4778 14.0089 17.4591 13.6527C17.4403 13.2966 17.3186 12.9535 17.1087 12.6651L16.4032 11.6957C16.152 11.3479 16.0177 10.9293 16.0199 10.5003C16.0198 10.0725 16.1553 9.65562 16.4069 9.30959L17.1125 8.34014C17.3223 8.05179 17.444 7.70872 17.4628 7.35255C17.4815 6.99639 17.3965 6.64244 17.218 6.33366C17.0398 6.02469 16.7757 5.77407 16.4578 5.61218C16.14 5.4503 15.782 5.3841 15.4273 5.42162L14.2356 5.54847C13.81 5.59317 13.3813 5.50209 13.0106 5.28829C12.6415 5.07387 12.3498 4.74812 12.1773 4.35773L11.6865 3.26236C11.5417 2.9363 11.3055 2.65925 11.0063 2.46482C10.7072 2.27039 10.3581 2.16693 10.0013 2.16699C9.64458 2.16693 9.29547 2.27039 8.99635 2.46482C8.69722 2.65925 8.46094 2.9363 8.31616 3.26236L7.82912 4.35773C7.65656 4.74812 7.36485 5.07387 6.99579 5.28829C6.62513 5.50209 6.19634 5.59317 5.77079 5.54847L4.57542 5.42162C4.22069 5.3841 3.8627 5.4503 3.54485 5.61218C3.22699 5.77407 2.96293 6.02469 2.78468 6.33366C2.60619 6.64244 2.52116 6.99639 2.5399 7.35255C2.55864 7.70872 2.68034 8.05179 2.89023 8.34014L3.59579 9.30959C3.8474 9.65562 3.9829 10.0725 3.98282 10.5003C3.9829 10.9282 3.8474 11.345 3.59579 11.6911L2.89023 12.6605C2.68034 12.9489 2.55864 13.2919 2.5399 13.6481C2.52116 14.0043 2.60619 14.3582 2.78468 14.667C2.96311 14.9758 3.2272 15.2263 3.54501 15.3882C3.86282 15.55 4.22072 15.6163 4.57542 15.579L5.76708 15.4522C6.19264 15.4075 6.62143 15.4986 6.99208 15.7124C7.36252 15.9262 7.65559 16.252 7.82912 16.6429Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          <path\n            d=\"M9.99985 13.0003C11.3806 13.0003 12.4999 11.881 12.4999 10.5003C12.4999 9.11961 11.3806 8.00033 9.99985 8.00033C8.61914 8.00033 7.49985 9.11961 7.49985 10.5003C7.49985 11.881 8.61914 13.0003 9.99985 13.0003Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      ),\n      path: '/settings',\n      role: ['ADMIN', 'USER', 'SUPERADMIN'],\n    },\n  ] satisfies MenuItemInterface[] as MenuItemInterface[];\n\n  return {\n    all: [...firstMenu, ...secondMenu],\n    firstMenu,\n    secondMenu,\n  };\n};\n\nexport const TopMenu: FC = () => {\n  const user = useUser();\n  const { firstMenu, secondMenu } = useMenuItem();\n  const { isGeneral, billingEnabled } = useVariables();\n  return (\n    <>\n      <div className=\"flex flex-1 flex-col minCustom:gap-[16px] blurMe\">\n        {\n          // @ts-ignore\n          user?.orgId &&\n            // @ts-ignore\n            (user.tier !== 'FREE' || !isGeneral || !billingEnabled) &&\n            firstMenu\n              .filter((f) => {\n                if (f.hide) {\n                  return false;\n                }\n                if (f.requireBilling && !billingEnabled) {\n                  return false;\n                }\n                if (f.name === 'Billing' && user?.isLifetime) {\n                  return false;\n                }\n                if (f.role) {\n                  return f.role.includes(user?.role!);\n                }\n                return true;\n              })\n              .map((item, index) => (\n                <MenuItem\n                  path={item.path}\n                  label={item.name}\n                  icon={item.icon}\n                  key={item.name}\n                  onClick={item.onClick}\n                />\n              ))\n        }\n      </div>\n      <div className=\"flex flex-col minCustom:gap-[16px] blurMe\">\n        {secondMenu\n          .filter((f) => {\n            if (f.hide) {\n              return false;\n            }\n            if (f.requireBilling && !billingEnabled) {\n              return false;\n            }\n            if (f.name === 'Billing' && user?.isLifetime) {\n              return false;\n            }\n            if (f.role) {\n              return f.role.includes(user?.role!);\n            }\n            return true;\n          })\n          .map((item, index) => (\n            <MenuItem\n              path={item.path}\n              label={item.name}\n              icon={item.icon}\n              key={item.name}\n              onClick={item.onClick}\n            />\n          ))}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/top.tip.tsx",
    "content": "'use client';\n\nimport { Tooltip } from 'react-tooltip';\nexport const ToolTip = () => {\n  return <Tooltip className=\"z-[200]\" id=\"tooltip\" />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/layout/user.context.tsx",
    "content": "'use client';\n\nimport { createContext, FC, ReactNode, useContext } from 'react';\nimport { User } from '@prisma/client';\nimport {\n  pricing,\n  PricingInnerInterface,\n} from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nexport const UserContext = createContext<\n  | undefined\n  | (User & {\n      orgId: string;\n      tier: PricingInnerInterface;\n      publicApi: string;\n      role: 'USER' | 'ADMIN' | 'SUPERADMIN';\n      totalChannels: number;\n      isLifetime?: boolean;\n      impersonate: boolean;\n      allowTrial: boolean;\n      isTrailing: boolean;\n      streakSince: string | null;\n    })\n>(undefined);\nexport const ContextWrapper: FC<{\n  user: User & {\n    orgId: string;\n    tier: 'FREE' | 'STANDARD' | 'PRO' | 'ULTIMATE' | 'TEAM';\n    role: 'USER' | 'ADMIN' | 'SUPERADMIN';\n    publicApi: string;\n    totalChannels: number;\n  };\n  children: ReactNode;\n}> = ({ user, children }) => {\n  const values = user\n    ? {\n        ...user,\n        tier: pricing[user.tier],\n      }\n    : ({} as any);\n  return <UserContext.Provider value={values}>{children}</UserContext.Provider>;\n};\nexport const useUser = () => useContext(UserContext);\n"
  },
  {
    "path": "apps/frontend/src/components/media/media.component.tsx",
    "content": "'use client';\n\nimport React, {\n  ChangeEvent,\n  ClipboardEvent,\n  FC,\n  Fragment,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Button } from '@gitroom/react/form/button';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Media } from '@prisma/client';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport EventEmitter from 'events';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport clsx from 'clsx';\nimport { VideoFrame } from '@gitroom/react/helpers/video.frame';\nimport { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader';\nimport dynamic from 'next/dynamic';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { AiImage } from '@gitroom/frontend/components/launches/ai.image';\nimport { DropFiles } from '@gitroom/frontend/components/layout/drop.files';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { ThirdPartyMedia } from '@gitroom/frontend/components/third-parties/third-party.media';\nimport { ReactSortable } from 'react-sortablejs';\nimport { MediaComponentInner } from '@gitroom/frontend/components/launches/helpers/media.settings.component';\nimport { AiVideo } from '@gitroom/frontend/components/launches/ai.video';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Dashboard } from '@uppy/react';\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  PlusIcon,\n  DeleteCircleIcon,\n  CloseCircleIcon,\n  DragHandleIcon,\n  MediaSettingsIcon,\n  InsertMediaIcon,\n  DesignMediaIcon,\n  VerticalDividerIcon,\n  NoMediaIcon,\n} from '@gitroom/frontend/components/ui/icons';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nconst Polonto = dynamic(\n  () => import('@gitroom/frontend/components/launches/polonto')\n);\nconst showModalEmitter = new EventEmitter();\nexport const Pagination: FC<{\n  current: number;\n  totalPages: number;\n  setPage: (num: number) => void;\n}> = (props) => {\n  const t = useT();\n\n  const { current, totalPages, setPage } = props;\n\n  const paginationItems = useMemo(() => {\n    // Convert to 1-based for algorithm (current is 0-based)\n    const c = current + 1;\n    const m = totalPages;\n\n    // If total pages <= 10, show all pages\n    if (m <= 10) {\n      return Array.from({ length: m }, (_, i) => i + 1);\n    }\n\n    const delta = 3;\n    const left = c - delta;\n    const right = c + delta + 1;\n    const range: number[] = [];\n    const rangeWithDots: (number | '...')[] = [];\n    let l: number | undefined;\n\n    // Build the range of pages to show\n    for (let i = 1; i <= m; i++) {\n      if (i === 1 || i === m || (i >= left && i < right)) {\n        range.push(i);\n      }\n    }\n\n    // Add dots where there are gaps\n    for (const i of range) {\n      if (l !== undefined) {\n        if (i - l === 2) {\n          rangeWithDots.push(l + 1);\n        } else if (i - l !== 1) {\n          rangeWithDots.push('...');\n        }\n      }\n      rangeWithDots.push(i);\n      l = i;\n    }\n\n    // Limit to maximum 10 items by trimming pages near edges if needed\n    while (rangeWithDots.length > 10) {\n      const currentIndex = rangeWithDots.findIndex((item) => item === c);\n      if (currentIndex !== -1 && currentIndex > rangeWithDots.length / 2) {\n        // Current is in second half, remove one item from start side\n        rangeWithDots.splice(2, 1);\n      } else {\n        // Current is in first half, remove one item from end side\n        rangeWithDots.splice(-3, 1);\n      }\n    }\n\n    return rangeWithDots;\n  }, [current, totalPages]);\n\n  return (\n    <ul className=\"flex flex-row items-center gap-1 justify-center mt-[15px]\">\n      <li className={clsx(current === 0 && 'opacity-20 pointer-events-none')}>\n        <div\n          className=\"cursor-pointer inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-4 py-2 gap-1 ps-2.5 text-gray-400 hover:text-white border-[#1F1F1F] hover:bg-forth\"\n          aria-label=\"Go to previous page\"\n          onClick={() => setPage(current - 1)}\n        >\n          <ChevronLeftIcon className=\"lucide lucide-chevron-left h-4 w-4\" />\n          <span>{t('previous', 'Previous')}</span>\n        </div>\n      </li>\n      {paginationItems.map((item, index) => (\n        <li key={index}>\n          {item === '...' ? (\n            <span className=\"inline-flex items-center justify-center h-10 w-10 text-textColor select-none\">\n              ...\n            </span>\n          ) : (\n            <div\n              aria-current=\"page\"\n              onClick={() => setPage(item - 1)}\n              className={clsx(\n                'cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border hover:bg-forth h-10 w-10 hover:text-white border-newBorder',\n                current === item - 1\n                  ? 'bg-forth !text-white'\n                  : 'text-textColor hover:text-white'\n              )}\n            >\n              {item}\n            </div>\n          )}\n        </li>\n      ))}\n      <li\n        className={clsx(\n          current + 1 === totalPages && 'opacity-20 pointer-events-none'\n        )}\n      >\n        <a\n          className=\"text-textColor hover:text-white group cursor-pointer inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-4 py-2 gap-1 pe-2.5 text-gray-400 border-[#1F1F1F] hover:bg-forth\"\n          aria-label=\"Go to next page\"\n          onClick={() => setPage(current + 1)}\n        >\n          <span>{t('next', 'Next')}</span>\n          <ChevronRightIcon className=\"lucide lucide-chevron-right h-4 w-4\" />\n        </a>\n      </li>\n    </ul>\n  );\n};\nexport const ShowMediaBoxModal: FC = () => {\n  const [showModal, setShowModal] = useState(false);\n  const [callBack, setCallBack] =\n    useState<(params: { id: string; path: string }[]) => void | undefined>();\n  const closeModal = useCallback(() => {\n    setShowModal(false);\n    setCallBack(undefined);\n  }, []);\n  useEffect(() => {\n    showModalEmitter.on('show-modal', (cCallback) => {\n      setShowModal(true);\n      setCallBack(() => cCallback);\n    });\n    return () => {\n      showModalEmitter.removeAllListeners('show-modal');\n    };\n  }, []);\n  if (!showModal) return null;\n  return (\n    <div className=\"text-textColor\">\n      <MediaBox setMedia={callBack!} closeModal={closeModal} />\n    </div>\n  );\n};\nexport const showMediaBox = (\n  callback: (params: { id: string; path: string }) => void\n) => {\n  showModalEmitter.emit('show-modal', callback);\n};\nconst CHUNK_SIZE = 1024 * 1024;\nconst MAX_UPLOAD_SIZE = 1024 * 1024 * 1024; // 1 GB\nexport const MediaBox: FC<{\n  setMedia: (params: { id: string; path: string }[]) => void;\n  standalone?: boolean;\n  type?: 'image' | 'video';\n  closeModal: () => void;\n}> = ({ type, standalone, setMedia }) => {\n  const [page, setPage] = useState(0);\n  const fetch = useFetch();\n  const modals = useModals();\n  const toaster = useToaster();\n  const loadMedia = useCallback(async () => {\n    return (await fetch(`/media?page=${page + 1}`)).json();\n  }, [page]);\n  const { data, mutate, isLoading } = useSWR(`get-media-${page}`, loadMedia);\n  const [selected, setSelected] = useState([]);\n  const t = useT();\n  const uploaderRef = useRef<any>(null);\n  const mediaDirectory = useMediaDirectory();\n  const [loading, setLoading] = useState(false);\n\n  const uppy = useUppyUploader({\n    allowedFileTypes:\n      type == 'image'\n        ? 'image/*'\n        : type == 'video'\n        ? 'video/mp4'\n        : 'image/*,video/mp4',\n    onUploadSuccess: async (arr) => {\n      await mutate();\n      if (standalone) {\n        return;\n      }\n      setSelected((prevSelected) => {\n        return [...prevSelected, ...arr];\n      });\n    },\n    onStart: () => setLoading(true),\n    onEnd: () => setLoading(false),\n  });\n\n  const addRemoveSelected = useCallback(\n    (media: any) => () => {\n      if (standalone) {\n        return;\n      }\n      const exists = selected.find((p: any) => p.id === media.id);\n      if (exists) {\n        setSelected(selected.filter((f: any) => f.id !== media.id));\n        return;\n      }\n      setSelected([...selected, media]);\n    },\n    [selected]\n  );\n\n  const addMedia = useCallback(async () => {\n    if (standalone) {\n      return;\n    }\n    // @ts-ignore\n    setMedia(selected);\n    modals.closeCurrent();\n  }, [selected]);\n\n  const addToUpload = useCallback(\n    async (e: ChangeEvent<HTMLInputElement>) => {\n      const files = Array.from(e.target.files || []);\n      const totalSize = files.reduce((acc, file) => acc + file.size, 0);\n\n      if (totalSize > MAX_UPLOAD_SIZE) {\n        toaster.show(\n          t(\n            'upload_size_limit_exceeded',\n            'Upload size limit exceeded. Maximum 1 GB per upload session.'\n          ),\n          'warning'\n        );\n        return;\n      }\n\n      setLoading(true);\n\n      // @ts-ignore\n      uppy.addFiles(files);\n    },\n    [toaster, t]\n  );\n\n  const dragAndDrop = useCallback(\n    async (event: ClipboardEvent<HTMLDivElement> | File[]) => {\n      // @ts-ignore\n      const clipboardItems = event.map((p) => ({\n        kind: 'file',\n        getAsFile: () => p,\n      }));\n      if (!clipboardItems) {\n        return;\n      }\n\n      const files: File[] = [];\n      // @ts-ignore\n      for (const item of clipboardItems) {\n        if (item.kind === 'file') {\n          const file = item.getAsFile();\n          if (file) {\n            files.push(file);\n          }\n        }\n      }\n\n      const totalSize = files.reduce((acc, file) => acc + file.size, 0);\n\n      if (totalSize > MAX_UPLOAD_SIZE) {\n        toaster.show(\n          t(\n            'upload_size_limit_exceeded',\n            'Upload size limit exceeded. Maximum 1 GB per upload session.'\n          ),\n          'warning'\n        );\n        return;\n      }\n\n      setLoading(true);\n\n      for (const file of files) {\n        uppy.addFile(file);\n      }\n    },\n    [toaster, t]\n  );\n\n  const maximize = useCallback(\n    (media: Media) => async (e: any) => {\n      e.stopPropagation();\n      modals.openModal({\n        title: '',\n        top: 10,\n        children: (\n          <div className=\"w-full h-full p-[50px]\">\n            {media.path.indexOf('mp4') > -1 ? (\n              <VideoFrame\n                autoplay={true}\n                url={mediaDirectory.set(media.path)}\n              />\n            ) : (\n              <img\n                width=\"100%\"\n                height=\"100%\"\n                className=\"w-full h-full max-h-[100%] max-w-[100%] object-cover\"\n                src={mediaDirectory.set(media.path)}\n                alt=\"media\"\n              />\n            )}\n          </div>\n        ),\n      });\n    },\n    []\n  );\n\n  const deleteImage = useCallback(\n    (media: Media) => async (e: any) => {\n      e.stopPropagation();\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_the_image',\n            'Are you sure you want to delete the image?'\n          )\n        ))\n      ) {\n        return;\n      }\n      await fetch(`/media/${media.id}`, {\n        method: 'DELETE',\n      });\n      mutate();\n    },\n    [mutate]\n  );\n\n  const btn = useMemo(() => {\n    return (\n      <button\n        disabled={loading}\n        onClick={() => uploaderRef?.current?.click()}\n        className=\"relative cursor-pointer bg-btnSimple changeColor flex gap-[8px] h-[44px] px-[18px] justify-center items-center rounded-[8px]\"\n      >\n        {loading ? (\n          <div className=\"absolute left-[50%] top-[50%] -translate-y-[50%] -translate-x-[50%]\">\n            <div className=\"animate-spin h-[20px] w-[20px] border-4 border-white border-t-transparent rounded-full\" />\n          </div>\n        ) : (\n          <PlusIcon size={14} />\n        )}\n        <div className={loading && 'invisible'}>{t('upload', 'Upload')}</div>\n      </button>\n    );\n  }, [t, loading]);\n\n  return (\n    <DropFiles disabled={loading} className=\"flex flex-col flex-1\" onDrop={dragAndDrop}>\n      <div className=\"flex flex-col flex-1\">\n        <div\n          className={clsx(\n            'flex',\n            !isLoading && !data?.results?.length && 'hidden'\n          )}\n        >\n          {!isLoading && !!data?.results?.length && (\n            <div className=\"flex-1 text-[14px] font-[600] whitespace-pre-line\">\n              {t(\n                'select_or_upload_pictures_max_1gb',\n                'Select or upload pictures (maximum 1 GB per upload).'\n              )}\n              {'\\n'}\n              {t(\n                'you_can_drag_drop_pictures',\n                'You can also drag & drop pictures.'\n              )}\n            </div>\n          )}\n          <input\n            type=\"file\"\n            ref={uploaderRef}\n            onChange={addToUpload}\n            className=\"hidden\"\n            multiple={true}\n          />\n          {!isLoading && !!data?.results?.length && btn}\n        </div>\n        <div className=\"w-full pointer-events-none relative mt-[5px] mb-[5px]\">\n          <div className=\"w-full h-[46px] overflow-hidden absolute left-0 bg-newBgColorInner uppyChange\">\n            <Dashboard\n              height={46}\n              uppy={uppy}\n              id={`uploader`}\n              showProgressDetails={true}\n              hideUploadButton={true}\n              hideRetryButton={true}\n              hidePauseResumeButton={true}\n              hideCancelButton={true}\n              hideProgressAfterFinish={true}\n            />\n          </div>\n          <div className=\"w-full h-[46px] uppyChange\" />\n        </div>\n        <div\n          className={clsx(\n            'flex-1 relative',\n            !isLoading &&\n              !data?.results?.length &&\n              'bg-newTextColor/[0.02] rounded-[12px]'\n          )}\n        >\n          <div\n            className={clsx(\n              'absolute -left-[3px] -top-[3px] withp3 h-full overflow-x-hidden overflow-y-auto scrollbar scrollbar-thumb-newColColor scrollbar-track-newBgColorInner',\n              !isLoading &&\n                !data?.results?.length &&\n                'flex justify-center items-center gap-[20px] flex-col'\n            )}\n          >\n            {!isLoading && !data?.results?.length && (\n              <>\n                <NoMediaIcon />\n                <div className=\"text-[20px] font-[600]\">\n                  {t(\n                    'you_dont_have_any_media_yet',\n                    \"You don't have any media yet\"\n                  )}\n                </div>\n                <div className=\"whitespace-pre-line text-newTextColor/[0.6] text-center\">\n                  {t(\n                    'select_or_upload_pictures_max_1gb',\n                    'Select or upload pictures (maximum 1 GB per upload).'\n                  )}{' '}\n                  {'\\n'}\n                  {t(\n                    'you_can_drag_drop_pictures',\n                    'You can also drag & drop pictures.'\n                  )}\n                </div>\n                <div className=\"forceChange\">{btn}</div>\n              </>\n            )}\n            {isLoading && (\n              <>\n                {[...new Array(16)].map((_, i) => (\n                  <div\n                    className={clsx(\n                      'px-[3px] py-[3px] float-left rounded-[6px] cursor-pointer w8-max aspect-square'\n                    )}\n                    key={i}\n                  >\n                    <div className=\"w-full h-full bg-newSep rounded-[6px] animate-pulse\" />\n                  </div>\n                ))}\n              </>\n            )}\n            {data?.results\n              ?.filter((f: any) => {\n                if (type === 'video') {\n                  return f.path.indexOf('mp4') > -1;\n                } else if (type === 'image') {\n                  return f.path.indexOf('mp4') === -1;\n                }\n                return true;\n              })\n              .map((media: any) => (\n                <div\n                  className={clsx(\n                    'group px-[3px] py-[3px] float-left rounded-[6px] w8-max aspect-square',\n                    !standalone && 'cursor-pointer'\n                  )}\n                  key={media.id}\n                >\n                  <div\n                    className={clsx(\n                      'w-full h-full rounded-[6px] border-[4px] relative',\n                      !!selected.find((p) => p.id === media.id)\n                        ? 'border-[#612BD3]'\n                        : 'border-transparent'\n                    )}\n                    onClick={addRemoveSelected(media)}\n                  >\n                    {!!selected.find((p: any) => p.id === media.id) ? (\n                      <div className=\"text-white flex z-[101] justify-center items-center text-[14px] font-[500] w-[24px] h-[24px] rounded-full bg-[#612BD3] absolute -bottom-[10px] -end-[10px]\">\n                        {selected.findIndex((z: any) => z.id === media.id) + 1}\n                      </div>\n                    ) : (\n                      <DeleteCircleIcon\n                        className=\"cursor-pointer hidden z-[100] group-hover:block absolute -top-[5px] -end-[5px]\"\n                        onClick={deleteImage(media)}\n                      />\n                    )}\n                    <div className=\"absolute bottom-[10px] end-[10px] z-[100]\">{media.originalName}</div>\n                    <div className=\"w-full h-full rounded-[6px] overflow-hidden relative\">\n                      <div className=\"absolute z-[20] left-[50%] top-[50%] -translate-x-[50%] -translate-y-[50%]\">\n                        <div\n                          onClick={maximize(media)}\n                          className=\"cursor-pointer p-[4px] bg-black/40 hidden group-hover:block hover:scale-150 transition-all\"\n                        >\n                          <svg\n                            width=\"30\"\n                            height=\"30\"\n                            viewBox=\"0 0 14 14\"\n                            fill=\"none\"\n                            xmlns=\"http://www.w3.org/2000/svg\"\n                          >\n                            <path\n                              d=\"M2 9H0V14H5V12H2V9ZM0 5H2V2H5V0H0V5ZM12 12H9V14H14V9H12V12ZM9 0V2H12V5H14V0H9Z\"\n                              fill=\"#F1F5F9\"\n                            />\n                          </svg>\n                        </div>\n                      </div>\n                      {media.path.indexOf('mp4') > -1 ? (\n                        <VideoFrame url={mediaDirectory.set(media.path)} />\n                      ) : (\n                        <img\n                          width=\"100%\"\n                          height=\"100%\"\n                          className=\"w-full h-full object-cover\"\n                          src={mediaDirectory.set(media.path)}\n                          alt=\"media\"\n                        />\n                      )}\n                    </div>\n                  </div>\n                </div>\n              ))}\n          </div>\n        </div>\n        {(data?.pages || 0) > 1 && (\n          <Pagination\n            current={page}\n            totalPages={data?.pages}\n            setPage={setPage}\n          />\n        )}\n        {!standalone && (\n          <div className=\"flex justify-end mt-[32px] gap-[8px]\">\n            <button\n              onClick={() => modals.closeCurrent()}\n              className=\"cursor-pointer h-[52px] px-[20px] items-center justify-center border border-newTextColor/10 flex rounded-[10px]\"\n            >\n              {t('cancel', 'Cancel')}\n            </button>\n            {!isLoading && !!data?.results?.length && (\n              <button\n                onClick={standalone ? () => {} : addMedia}\n                disabled={selected.length === 0}\n                className=\"cursor-pointer text-white disabled:opacity-80 disabled:cursor-not-allowed h-[52px] px-[20px] items-center justify-center bg-[#612BD3] flex rounded-[10px]\"\n              >\n                {t('add_selected_media', 'Add selected media')}\n              </button>\n            )}\n          </div>\n        )}\n      </div>\n    </DropFiles>\n  );\n};\nexport const MultiMediaComponent: FC<{\n  label: string;\n  description: string;\n  mediaNotAvailable?: boolean;\n  dummy: boolean;\n  allData: {\n    content: string;\n    id?: string;\n    image?: Array<{\n      id: string;\n      path: string;\n    }>;\n  }[];\n  value?: Array<{\n    path: string;\n    id: string;\n  }>;\n  text: string;\n  name: string;\n  error?: any;\n  onOpen?: () => void;\n  onClose?: () => void;\n  toolBar?: React.ReactNode;\n  information?: React.ReactNode;\n  onChange: (event: {\n    target: {\n      name: string;\n      value?: Array<{\n        id: string;\n        path: string;\n        alt?: string;\n        thumbnail?: string;\n        thumbnailTimestamp?: number;\n      }>;\n    };\n  }) => void;\n}> = (props) => {\n  const {\n    name,\n    error,\n    text,\n    onChange,\n    value,\n    allData,\n    dummy,\n    toolBar,\n    information,\n    mediaNotAvailable,\n  } = props;\n  const user = useUser();\n  const modals = useModals();\n  const t = useT();\n  useEffect(() => {\n    if (value) {\n      setCurrentMedia(value);\n    }\n  }, [value]);\n\n  const [currentMedia, setCurrentMedia] = useState(value);\n  const mediaDirectory = useMediaDirectory();\n  const changeMedia = useCallback(\n    (\n      m:\n        | {\n            path: string;\n            id: string;\n          }\n        | {\n            path: string;\n            id: string;\n          }[]\n    ) => {\n      const mediaArray = Array.isArray(m) ? m : [m];\n      const newMedia = [...(currentMedia || []), ...mediaArray];\n      setCurrentMedia(newMedia);\n      onChange({\n        target: {\n          name,\n          value: newMedia,\n        },\n      });\n    },\n    [currentMedia]\n  );\n  const showModal = useCallback(() => {\n    modals.openModal({\n      title: t('media_library', 'Media Library'),\n      askClose: false,\n      closeOnEscape: true,\n      fullScreen: true,\n      size: 'calc(100% - 80px)',\n      height: 'calc(100% - 80px)',\n      children: (close) => (\n        <MediaBox setMedia={changeMedia} closeModal={close} />\n      ),\n    });\n  }, [changeMedia, t]);\n\n  const clearMedia = useCallback(\n    (topIndex: number) => () => {\n      const newMedia = currentMedia?.filter((f, index) => index !== topIndex);\n      setCurrentMedia(newMedia);\n      onChange({\n        target: {\n          name,\n          value: newMedia,\n        },\n      });\n    },\n    [currentMedia]\n  );\n\n  const designMedia = useCallback(() => {\n    if (!!user?.tier?.ai && !dummy) {\n      modals.openModal({\n        askClose: false,\n        title: t('design_media', 'Design Media'),\n        size: '80%',\n        children: (close) => (\n          <Polonto setMedia={changeMedia} closeModal={close} />\n        ),\n      });\n    }\n  }, [changeMedia, t]);\n\n  return (\n    <>\n      <div className=\"b1 flex flex-col gap-[8px] rounded-bl-[8px] select-none w-full\">\n        <div className=\"flex gap-[10px] px-[12px]\">\n          {!!currentMedia && (\n            <ReactSortable\n              list={currentMedia}\n              setList={(value) =>\n                onChange({ target: { name: 'upload', value } })\n              }\n              className=\"flex gap-[10px] sortable-container\"\n              animation={200}\n              swap={true}\n              handle=\".dragging\"\n            >\n              {currentMedia.map((media, index) => (\n                <Fragment key={media.id}>\n                  <div className=\"cursor-pointer rounded-[5px] w-[40px] h-[40px] border-2 border-tableBorder relative flex transition-all\">\n                    <DragHandleIcon className=\"z-[20] dragging absolute pe-[1px] pb-[3px] -start-[4px] -top-[4px] cursor-move\" />\n\n                    <div className=\"w-full h-full relative group\">\n                      <div\n                        onClick={async () => {\n                          modals.openModal({\n                            title: t('media_settings', 'Media Settings'),\n                            children: (close) => (\n                              <MediaComponentInner\n                                media={media as any}\n                                onClose={close}\n                                onSelect={(value: any) => {\n                                  onChange({\n                                    target: {\n                                      name: 'upload',\n                                      value: currentMedia.map((p) => {\n                                        if (p.id === media.id) {\n                                          return {\n                                            ...p,\n                                            ...value,\n                                          };\n                                        }\n                                        return p;\n                                      }),\n                                    },\n                                  });\n                                }}\n                              />\n                            ),\n                          });\n                        }}\n                        className=\"absolute top-[50%] left-[50%] -translate-x-[50%] -translate-y-[50%] bg-black/80 rounded-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-[9]\"\n                      >\n                        <MediaSettingsIcon className=\"cursor-pointer relative z-[200]\" />\n                      </div>\n                      {media?.path?.indexOf('mp4') > -1 ? (\n                        <VideoFrame url={mediaDirectory.set(media?.path)} />\n                      ) : (\n                        <img\n                          className=\"w-full h-full object-cover rounded-[4px]\"\n                          src={mediaDirectory.set(media?.path)}\n                        />\n                      )}\n                    </div>\n\n                    <CloseCircleIcon\n                      onClick={clearMedia(index)}\n                      className=\"absolute -end-[4px] -top-[4px] z-[20] rounded-full bg-white\"\n                    />\n                  </div>\n                </Fragment>\n              ))}\n            </ReactSortable>\n          )}\n        </div>\n        <div className=\"flex gap-[8px] px-[12px] border-t border-newColColor w-full b1 text-textColor\">\n          {!mediaNotAvailable && (\n            <div className=\"flex py-[10px] b2 items-center gap-[4px]\">\n              <div\n                onClick={showModal}\n                className=\"cursor-pointer h-[30px] rounded-[6px] justify-center items-center flex bg-newColColor px-[8px]\"\n              >\n                <div className=\"flex gap-[8px] items-center\">\n                  <div>\n                    <InsertMediaIcon />\n                  </div>\n                  <div className=\"text-[10px] font-[600] maxMedia:hidden block\">\n                    {t('insert_media', 'Insert Media')}\n                  </div>\n                </div>\n              </div>\n              <div\n                onClick={designMedia}\n                className=\"cursor-pointer h-[30px] rounded-[6px] justify-center items-center flex bg-newColColor px-[8px]\"\n              >\n                <div className=\"flex gap-[5px] items-center\">\n                  <div>\n                    <DesignMediaIcon />\n                  </div>\n                  <div className=\"text-[10px] font-[600] iconBreak:hidden block\">\n                    {t('design_media', 'Design Media')}\n                  </div>\n                </div>\n              </div>\n\n              <ThirdPartyMedia allData={allData} onChange={changeMedia} />\n\n              {!!user?.tier?.ai && (\n                <>\n                  <AiImage value={text} onChange={changeMedia} />\n                  <AiVideo value={text} onChange={changeMedia} />\n                </>\n              )}\n            </div>\n          )}\n          {!mediaNotAvailable && (\n            <div className=\"text-newColColor h-full flex items-center\">\n              <VerticalDividerIcon />\n            </div>\n          )}\n          {!!toolBar && (\n            <div className=\"flex py-[10px] b2 items-center gap-[4px]\">\n              {toolBar}\n            </div>\n          )}\n          {information && (\n            <div className=\"flex-1 justify-end flex py-[10px] b2 items-center gap-[4px]\">\n              {information}\n            </div>\n          )}\n        </div>\n      </div>\n      <div className=\"text-[12px] text-red-400\">{error}</div>\n    </>\n  );\n};\nexport const MediaComponent: FC<{\n  label: string;\n  description: string;\n  value?: {\n    path: string;\n    id: string;\n  };\n  name: string;\n  onChange: (event: {\n    target: {\n      name: string;\n      value?: {\n        id: string;\n        path: string;\n      };\n    };\n  }) => void;\n  type?: 'image' | 'video';\n  width?: number;\n  height?: number;\n}> = (props) => {\n  const t = useT();\n\n  const { name, type, label, description, onChange, value, width, height } =\n    props;\n  const { getValues } = useSettings();\n  const user = useUser();\n  useEffect(() => {\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  const [currentMedia, setCurrentMedia] = useState(value);\n  const modals = useModals();\n  const mediaDirectory = useMediaDirectory();\n\n  const showDesignModal = useCallback(() => {\n    modals.openModal({\n      title: t('media_editor', 'Media Editor'),\n      askClose: false,\n      closeOnEscape: true,\n      fullScreen: true,\n      size: 'calc(100% - 80px)',\n      height: 'calc(100% - 80px)',\n      children: (close) => (\n        <Polonto\n          width={width}\n          height={height}\n          setMedia={changeMedia}\n          closeModal={close}\n        />\n      ),\n    });\n  }, [t]);\n  const changeMedia = useCallback((m: { path: string; id: string }[]) => {\n    setCurrentMedia(m[0]);\n    onChange({\n      target: {\n        name,\n        value: m[0],\n      },\n    });\n  }, []);\n  const showModal = useCallback(() => {\n    modals.openModal({\n      title: t('media_library', 'Media Library'),\n      askClose: false,\n      closeOnEscape: true,\n      fullScreen: true,\n      size: 'calc(100% - 80px)',\n      height: 'calc(100% - 80px)',\n      children: (close) => (\n        <MediaBox setMedia={changeMedia} closeModal={close} type={type} />\n      ),\n    });\n  }, [t]);\n  const clearMedia = useCallback(() => {\n    setCurrentMedia(undefined);\n    onChange({\n      target: {\n        name,\n        value: undefined,\n      },\n    });\n  }, [value]);\n  return (\n    <div className=\"flex flex-col gap-[8px]\">\n      <div className=\"text-[14px]\">{label}</div>\n      <div className=\"text-[12px]\">{description}</div>\n      {!!currentMedia && (\n        <div className=\"my-[20px] cursor-pointer w-[200px] h-[200px] border-2 border-tableBorder\">\n          <img\n            className=\"w-full h-full object-cover\"\n            src={currentMedia.path}\n            onClick={() => window.open(mediaDirectory.set(currentMedia.path))}\n          />\n        </div>\n      )}\n      <div className=\"flex gap-[5px]\">\n        <Button onClick={showModal}>{t('select', 'Select')}</Button>\n        <Button onClick={showDesignModal} className=\"!bg-customColor45\">\n          {t('editor', 'Editor')}\n        </Button>\n        <Button secondary={true} onClick={clearMedia}>\n          {t('clear', 'Clear')}\n        </Button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/media/new.uploader.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useState } from 'react';\n// @ts-ignore\nimport Uppy, { BasePlugin, UploadResult, UppyFile } from '@uppy/core';\n// @ts-ignore\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { getUppyUploadPlugin } from '@gitroom/react/helpers/uppy.upload';\nimport { Dashboard, FileInput, ProgressBar } from '@uppy/react';\n\n// Uppy styles\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport Compressor from '@uppy/compressor';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { uniqBy } from 'lodash';\n\nexport class CompressionWrapper<M = any, B = any> extends Compressor<any, any> {\n  override async prepareUpload(fileIDs: string[]) {\n    const { files } = this.uppy.getState();\n\n    // 1) Skip GIFs (and anything missing)\n    const filteredIDs = fileIDs.filter((id) => {\n      const f = files[id];\n      if (!f) return false;\n\n      const type = f.type ?? '';\n      const name = (f.name ?? '').toLowerCase();\n      const isGif = type === 'image/gif' || name.endsWith('.gif');\n\n      return !isGif;\n    });\n\n    // 2) Let @uppy/compressor do its work (convert/resize/etc)\n    return super.prepareUpload(filteredIDs);\n  }\n}\n\nexport function useUppyUploader(props: {\n  // @ts-ignore\n  onUploadSuccess: (result: UploadResult) => void;\n  onStart: () => void;\n  onEnd: () => void;\n  allowedFileTypes: string;\n}) {\n  const setLocked = useLaunchStore((state) => state.setLocked);\n  const toast = useToaster();\n  const { storageProvider, backendUrl, disableImageCompression, transloadit } =\n    useVariables();\n  const { onUploadSuccess, allowedFileTypes } = props;\n  const fetch = useFetch();\n  return useMemo(() => {\n    // Track file order to maintain original sequence after upload\n    let fileOrderIndex = 0;\n\n    const uppy2 = new Uppy({\n      autoProceed: true,\n      restrictions: {\n        // maxNumberOfFiles: 5,\n        // allowedFileTypes: allowedFileTypes.split(','),\n        maxFileSize: 1000000000, // Default 1GB, but we'll override with custom validation\n      },\n    });\n\n    // check for valid file types it can be something like this image/*,video/mp4.\n    // If it's an image, I need to replace image/* with image/png, image/jpeg, image/jpeg, image/gif (separately)\n    uppy2.addPreProcessor((fileIDs) => {\n      return new Promise<void>((resolve, reject) => {\n        const files = uppy2.getFiles();\n        const allowedTypes = allowedFileTypes\n          .split(',')\n          .map((type) => type.trim());\n\n        // Expand generic types to specific ones\n        const expandedTypes = allowedTypes.flatMap((type) => {\n          if (type === 'image/*') {\n            return [\n              'image/png',\n              'image/jpeg',\n              'image/jpg',\n              'image/gif',\n              'image/webp',\n            ];\n          }\n          if (type === 'video/*') {\n            return ['video/mp4', 'video/mpeg', 'video/quicktime'];\n          }\n          if (type === 'video/mp4' && transloadit && transloadit.length > 0) {\n            return ['video/mp4', 'video/mpeg', 'video/quicktime'];\n          }\n          return [type];\n        });\n\n        for (const file of files) {\n          if (fileIDs.includes(file.id)) {\n            const fileType = file.type;\n\n            // Check if file type is allowed\n            const isAllowed = expandedTypes.some((allowedType) => {\n              if (allowedType.endsWith('/*')) {\n                const baseType = allowedType.replace('/*', '/');\n                return fileType?.startsWith(baseType);\n              }\n              return fileType === allowedType;\n            });\n\n            if (!isAllowed) {\n              const error = new Error(\n                `File type \"${fileType}\" is not allowed for file \"${file.name}\". Allowed types: ${allowedFileTypes}`\n              );\n              uppy2.log(error.message, 'error');\n              uppy2.info(error.message, 'error', 5000);\n              toast.show(\n                `File type \"${fileType}\" is not allowed. Allowed types: ${allowedFileTypes}`,\n                'warning'\n              );\n              uppy2.removeFile(file.id);\n              return reject(error);\n            }\n          }\n        }\n\n        resolve();\n      });\n    });\n\n    uppy2.addPreProcessor((fileIDs) => {\n      return new Promise<void>((resolve, reject) => {\n        const files = uppy2.getFiles();\n\n        for (const file of files) {\n          if (fileIDs.includes(file.id)) {\n            const isImage = file.type?.startsWith('image/');\n            const isVideo = file.type?.startsWith('video/');\n\n            const maxImageSize = 30 * 1024 * 1024; // 30MB\n            const maxVideoSize = 1000 * 1024 * 1024; // 1GB\n\n            if (isImage && file.size > maxImageSize) {\n              const error = new Error(\n                `Image file \"${file.name}\" is too large. Maximum size allowed is 30MB.`\n              );\n              uppy2.log(error.message, 'error');\n              uppy2.info(error.message, 'error', 5000);\n              toast.show(\n                `Image file is too large. Maximum size allowed is 30MB.`\n              );\n              uppy2.removeFile(file.id); // Remove file from queue\n              return reject(error);\n            }\n\n            if (isVideo && file.size > maxVideoSize) {\n              const error = new Error(\n                `Video file \"${file.name}\" is too large. Maximum size allowed is 1GB.`\n              );\n              uppy2.log(error.message, 'error');\n              uppy2.info(error.message, 'error', 5000);\n              toast.show(\n                `Video file is too large. Maximum size allowed is 1GB.`\n              );\n              uppy2.removeFile(file.id); // Remove file from queue\n              return reject(error);\n            }\n          }\n        }\n\n        resolve();\n      });\n    });\n\n    const { plugin, options } = getUppyUploadPlugin(\n      transloadit.length > 0 ? 'transloadit' : storageProvider,\n      fetch,\n      backendUrl,\n      transloadit\n    );\n\n    uppy2.use(plugin, options);\n    if (!disableImageCompression) {\n      uppy2.use(CompressionWrapper, {\n        convertTypes: ['image/jpeg', 'image/png', 'image/webp'],\n        maxWidth: 1000,\n        maxHeight: 1000,\n        quality: 1,\n      });\n    }\n    // Set additional metadata when a file is added\n    uppy2.on('file-added', (file) => {\n      setLocked(true);\n      uppy2.setFileMeta(file.id, {\n        useCloudflare: storageProvider === 'cloudflare' ? 'true' : 'false', // Example of adding a custom field\n        addedOrder: fileOrderIndex++, // Track original order for sorting after upload\n        // Add more fields as needed\n      });\n    });\n    uppy2.on('error', (result) => {\n      uppy2.clear();\n      setLocked(false);\n      props.onEnd();\n      fileOrderIndex = 0;\n    });\n    uppy2.on('upload-start', () => {\n      props.onStart();\n    });\n    uppy2.on('complete', async (result) => {\n      console.log(result);\n      for (const file of [...result.successful]) {\n        uppy2.removeFile(file.id);\n      }\n\n      props.onEnd();\n      // Sort results by original add order to maintain file sequence\n      const sortedSuccessful = [...result.successful].sort((a, b) => {\n        const orderA = +((a.meta as any)?.addedOrder ?? 0);\n        const orderB = +((b.meta as any)?.addedOrder ?? 0);\n        return orderA - orderB;\n      });\n\n      if (storageProvider === 'local') {\n        setLocked(false);\n        fileOrderIndex = 0;\n        onUploadSuccess(sortedSuccessful.map((p) => p.response.body));\n        return;\n      }\n\n      if (transloadit.length > 0) {\n        // @ts-ignore\n        const allRes = result.transloadit[0].results;\n        const toSave = uniqBy<{ name: string; originalName: string; order: number }>(\n          // @ts-ignore\n          Object.values(allRes).flatMap((p: any[]) => {\n            return p.flatMap((item) => ({\n              name: item.url.split('/').pop(),\n              originalName: item.name || '',\n              order: +item.user_meta.addedOrder,\n            }));\n          }),\n          (item) => item.name\n        );\n\n        const loadAllMedia = (\n          await Promise.all(\n            toSave.map(async ({ name, originalName, order }) => ({\n              file: await (\n                await fetch('/media/save-media', {\n                  method: 'POST',\n                  body: JSON.stringify({\n                    name,\n                    originalName,\n                  }),\n                })\n              ).json(),\n              order,\n            }))\n          )\n        )\n          .sort((a, b) => {\n            return a.order - b.order;\n          })\n          .map((p) => p.file);\n\n        setLocked(false);\n        fileOrderIndex = 0;\n        onUploadSuccess(loadAllMedia);\n        return;\n      }\n\n      setLocked(false);\n      fileOrderIndex = 0;\n      onUploadSuccess(sortedSuccessful.map((p) => p.response.body.saved));\n    });\n    uppy2.on('upload-success', (file, response) => {\n      // @ts-ignore\n      uppy2.setFileState(file.id, {\n        // @ts-ignore\n        progress: uppy2.getState().files[file.id].progress,\n        // @ts-ignore\n        uploadURL: response.body.Location,\n        response: response,\n        isPaused: false,\n      });\n    });\n    return uppy2;\n  }, []);\n}\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/a.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\n\nexport const AComponent: FC<{\n  editor: any;\n  currentValue: string;\n}> = ({ editor }) => {\n  const mark = () => {\n    const previousUrl = editor?.getAttributes('link')?.href;\n    const url = window.prompt('URL', previousUrl);\n\n    // cancelled\n    if (url === null) {\n      return;\n    }\n\n    // empty\n    if (url === '') {\n      editor?.chain()?.focus()?.extendMarkRange('link')?.unsetLink()?.run();\n\n      return;\n    }\n\n    // update link\n    try {\n      editor\n        ?.chain()\n        ?.focus()\n        ?.extendMarkRange('link')\n        ?.setLink({ href: url })\n        ?.run();\n    } catch (e) {}\n    editor?.commands?.focus();\n  };\n  return (\n    <div\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content=\"Link\"\n      onClick={mark}\n      className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n      >\n        <g clip-path=\"url(#clip0_2452_193804)\">\n          <path\n            d=\"M6.6668 8.66599C6.9531 9.04875 7.31837 9.36545 7.73783 9.59462C8.1573 9.82379 8.62114 9.96007 9.0979 9.99422C9.57466 10.0284 10.0532 9.95957 10.501 9.79251C10.9489 9.62546 11.3555 9.36404 11.6935 9.02599L13.6935 7.02599C14.3007 6.39732 14.6366 5.55531 14.629 4.68132C14.6215 3.80733 14.2709 2.97129 13.6529 2.35326C13.0348 1.73524 12.1988 1.38467 11.3248 1.37708C10.4508 1.36948 9.60881 1.70547 8.98013 2.31266L7.83347 3.45266M9.33347 7.33266C9.04716 6.94991 8.68189 6.6332 8.26243 6.40403C7.84297 6.17486 7.37913 6.03858 6.90237 6.00444C6.4256 5.97029 5.94708 6.03908 5.49924 6.20614C5.0514 6.3732 4.64472 6.63461 4.3068 6.97266L2.3068 8.97266C1.69961 9.60133 1.36363 10.4433 1.37122 11.3173C1.37881 12.1913 1.72938 13.0274 2.3474 13.6454C2.96543 14.2634 3.80147 14.614 4.67546 14.6216C5.54945 14.6292 6.39146 14.2932 7.02013 13.686L8.16013 12.546\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </g>\n        <defs>\n          <clipPath id=\"clip0_2452_193804\">\n            <rect width=\"16\" height=\"16\" fill=\"white\" />\n          </clipPath>\n        </defs>\n      </svg>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/add.edit.modal.tsx",
    "content": "'use client';\nimport 'reflect-metadata';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport dayjs from 'dayjs';\nimport { FC, useEffect } from 'react';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { ManageModal } from '@gitroom/frontend/components/new-launch/manage.modal';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport { useShallow } from 'zustand/react/shallow';\nimport { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\n\nexport interface AddEditModalProps {\n  dummy?: boolean;\n  date: dayjs.Dayjs;\n  integrations: Integrations[];\n  allIntegrations?: Integrations[];\n  selectedChannels?: string[];\n  set?: any;\n  focusedChannel?: string;\n  addEditSets?: (data: any) => void;\n  reopenModal: () => void;\n  mutate: () => void;\n  padding?: string;\n  customClose?: () => void;\n  onlyValues?: Array<{\n    content: string;\n    id?: string;\n    image?: Array<{\n      id: string;\n      path: string;\n    }>;\n  }>;\n}\n\nexport const AddEditModal: FC<AddEditModalProps> = (props) => {\n  const { setAllIntegrations, setDate, setIsCreateSet, setDummy } =\n    useLaunchStore(\n      useShallow((state) => ({\n        setAllIntegrations: state.setAllIntegrations,\n        setDate: state.setDate,\n        setIsCreateSet: state.setIsCreateSet,\n        setDummy: state.setDummy,\n      }))\n    );\n\n  const integrations = useLaunchStore((state) => state.integrations);\n  useEffect(() => {\n    setDummy(!!props.dummy);\n    setDate(props.date || newDayjs());\n    setAllIntegrations(props.allIntegrations || []);\n    setIsCreateSet(!!props.addEditSets);\n  }, []);\n\n  if (!integrations.length) {\n    return null;\n  }\n\n  return <AddEditModalInner {...props} />;\n};\n\nexport const AddEditModalInner: FC<AddEditModalProps> = (props) => {\n  const existingData = useExistingData();\n  const { addOrRemoveSelectedIntegration, selectedIntegrations, integrations } =\n    useLaunchStore(\n      useShallow((state) => ({\n        integrations: state.integrations,\n        selectedIntegrations: state.selectedIntegrations,\n        addOrRemoveSelectedIntegration: state.addOrRemoveSelectedIntegration,\n      }))\n    );\n\n  useEffect(() => {\n    if (props?.set?.posts?.length) {\n      for (const post of props?.set?.posts) {\n        if (post.integration) {\n          const integration = integrations.find(\n            (i) => i.id === post.integration.id\n          );\n          addOrRemoveSelectedIntegration(integration, post.settings);\n        }\n      }\n    }\n\n    if (existingData.integration) {\n      const integration = integrations.find(\n        (i) => i.id === existingData.integration\n      );\n      addOrRemoveSelectedIntegration(integration, existingData.settings);\n    }\n\n    if (props?.selectedChannels?.length) {\n      for (const channel of props.selectedChannels) {\n        const integration = integrations.find((i) => i.id === channel);\n        if (integration) {\n          addOrRemoveSelectedIntegration(integration, {});\n        }\n      }\n    }\n  }, []);\n\n  if (existingData.integration && selectedIntegrations.length === 0) {\n    return null;\n  }\n\n  return <AddEditModalInnerInner {...props} />;\n};\n\nexport const AddEditModalInnerInner: FC<AddEditModalProps> = (props) => {\n  const existingData = useExistingData();\n  const {\n    reset,\n    addGlobalValue,\n    addInternalValue,\n    global,\n    setCurrent,\n    internal,\n    setTags,\n    setEditor,\n    setRepeater,\n  } = useLaunchStore(\n    useShallow((state) => ({\n      reset: state.reset,\n      addGlobalValue: state.addGlobalValue,\n      addInternalValue: state.addInternalValue,\n      setCurrent: state.setCurrent,\n      global: state.global,\n      internal: state.internal,\n      setTags: state.setTags,\n      setEditor: state.setEditor,\n      setRepeater: state.setRepeater,\n    }))\n  );\n\n  useEffect(() => {\n    if (existingData.integration) {\n      if (existingData?.posts?.[0]?.intervalInDays) {\n        setRepeater(existingData.posts[0].intervalInDays);\n      }\n      setTags(\n        // @ts-ignore\n        existingData?.posts?.[0]?.tags?.map((p: any) => ({\n          label: p.tag.name,\n          value: p.tag.name,\n        })) || []\n      );\n      addInternalValue(\n        0,\n        existingData.integration,\n        existingData.posts.map((post) => ({\n          delay: post.delay,\n          content:\n            post.content.indexOf('<p>') > -1\n              ? post.content\n              : post.content\n                  .split('\\n')\n                  .map((line: string) => `<p>${line}</p>`)\n                  .join(''),\n          id: post.id,\n          // @ts-ignore\n          media: post.image as any[],\n        }))\n      );\n      setCurrent(existingData.integration);\n    } else {\n      setEditor('normal');\n    }\n\n    if (props.focusedChannel) {\n      setCurrent(props.focusedChannel);\n    }\n\n    addGlobalValue(\n      0,\n      props.onlyValues?.length\n        ? props.onlyValues.map((p) => ({\n            content:\n              p.content.indexOf('<p>') > -1\n                ? p.content\n                : p.content\n                    .split('\\n')\n                    .map((line: string) => `<p>${line}</p>`)\n                    .join(''),\n            id: makeId(10),\n            media: p.image || [],\n          }))\n        : props.set?.posts?.length\n        ? props.set.posts[0].value.map((p: any) => ({\n            id: makeId(10),\n            content:\n              p.content.indexOf('<p>') > -1\n                ? p.content\n                : p.content\n                    .split('\\n')\n                    .map((line: string) => `<p>${line}</p>`)\n                    .join(''),\n            // @ts-ignore\n            media: p.media,\n          }))\n        : [\n            {\n              content: '',\n              id: makeId(10),\n              media: [],\n            },\n          ]\n    );\n\n    return () => {\n      reset();\n    };\n  }, []);\n\n  if (!global.length && !internal.length) {\n    return null;\n  }\n\n  return (\n    <>\n      <style>\n        {`#support-discord {display: none !important;}`}\n      </style>\n      <ManageModal {...props} />\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/add.post.button.tsx",
    "content": "'use client';\n\nimport { Button } from '@gitroom/react/form/button';\nimport React, { FC } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nexport const AddPostButton: FC<{\n  onClick: () => void;\n  num: number;\n  postComment: PostComment;\n}> = (props) => {\n  const { onClick, num } = props;\n  const t = useT();\n\n  return (\n    <div className=\"flex\">\n      <div\n        onClick={onClick}\n        className=\"select-none cursor-pointer h-[34px] rounded-[6px] flex bg-[#D82D7E] gap-[8px] justify-center items-center pl-[16px] pr-[20px] text-[13px] font-[600] mt-[12px]\"\n      >\n        <div>\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n            viewBox=\"0 0 16 16\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M8.00065 3.33301V12.6663M3.33398 7.99967H12.6673\"\n              stroke=\"white\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n        <div className=\"!text-white\">\n          {t(\n            ...(props.postComment === PostComment.ALL\n              ? ['add_comment_or_post', 'Add comment or post']\n              : props.postComment === PostComment.POST\n              ? ['add_post', 'Add post']\n              : ['add_comment', 'Add comment'])\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/bold.text.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport { Editor, Transforms } from 'slate';\nimport { ReactEditor } from 'slate-react';\nconst originalMap = {\n  a: '𝗮',\n  b: '𝗯',\n  c: '𝗰',\n  d: '𝗱',\n  e: '𝗲',\n  f: '𝗳',\n  g: '𝗴',\n  h: '𝗵',\n  i: '𝗶',\n  j: '𝗷',\n  k: '𝗸',\n  l: '𝗹',\n  m: '𝗺',\n  n: '𝗻',\n  o: '𝗼',\n  p: '𝗽',\n  q: '𝗾',\n  r: '𝗿',\n  s: '𝘀',\n  t: '𝘁',\n  u: '𝘂',\n  v: '𝘃',\n  w: '𝘄',\n  x: '𝘅',\n  y: '𝘆',\n  z: '𝘇',\n  A: '𝗔',\n  B: '𝗕',\n  C: '𝗖',\n  D: '𝗗',\n  E: '𝗘',\n  F: '𝗙',\n  G: '𝗚',\n  H: '𝗛',\n  I: '𝗜',\n  J: '𝗝',\n  K: '𝗞',\n  L: '𝗟',\n  M: '𝗠',\n  N: '𝗡',\n  O: '𝗢',\n  P: '𝗣',\n  Q: '𝗤',\n  R: '𝗥',\n  S: '𝗦',\n  T: '𝗧',\n  U: '𝗨',\n  V: '𝗩',\n  W: '𝗪',\n  X: '𝗫',\n  Y: '𝗬',\n  Z: '𝗭',\n  '1': '𝟭',\n  '2': '𝟮',\n  '3': '𝟯',\n  '4': '𝟰',\n  '5': '𝟱',\n  '6': '𝟲',\n  '7': '𝟳',\n  '8': '𝟴',\n  '9': '𝟵',\n  '0': '𝟬',\n};\nconst reverseMap = Object.fromEntries(\n  Object.entries(originalMap).map(([key, value]) => [value, key])\n);\nexport const BoldText: FC<{\n  editor: any;\n  currentValue: string;\n}> = ({ editor }) => {\n  const mark = () => {\n    editor?.commands?.unsetUnderline();\n    editor?.commands?.toggleBold();\n    editor?.commands?.focus();\n  };\n  return (\n    <div\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content=\"Bold Text\"\n      onClick={mark}\n      className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M4 8.00033H9.33333C10.8061 8.00033 12 6.80642 12 5.33366C12 3.8609 10.8061 2.66699 9.33333 2.66699H4V8.00033ZM4 8.00033H10C11.4728 8.00033 12.6667 9.19423 12.6667 10.667C12.6667 12.1398 11.4728 13.3337 10 13.3337H4V8.00033Z\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/bullets.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\n\nexport const Bullets: FC<{\n  editor: any;\n  currentValue: string;\n}> = ({ editor }) => {\n  const bullet = () => {\n    editor?.commands?.toggleBulletList();\n  };\n  return (\n    <div\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content=\"Bullets\"\n      onClick={bullet}\n      className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M14 8.00065L6 8.00065M14 4.00065L6 4.00065M14 12.0007L6 12.0007M3.33333 8.00065C3.33333 8.36884 3.03486 8.66732 2.66667 8.66732C2.29848 8.66732 2 8.36884 2 8.00065C2 7.63246 2.29848 7.33398 2.66667 7.33398C3.03486 7.33398 3.33333 7.63246 3.33333 8.00065ZM3.33333 4.00065C3.33333 4.36884 3.03486 4.66732 2.66667 4.66732C2.29848 4.66732 2 4.36884 2 4.00065C2 3.63246 2.29848 3.33398 2.66667 3.33398C3.03486 3.33398 3.33333 3.63246 3.33333 4.00065ZM3.33333 12.0007C3.33333 12.3688 3.03486 12.6673 2.66667 12.6673C2.29848 12.6673 2 12.3688 2 12.0007C2 11.6325 2.29848 11.334 2.66667 11.334C3.03486 11.334 3.33333 11.6325 3.33333 12.0007Z\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/delay.component.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useEffect, useState } from 'react';\nimport { DelayIcon, DropdownArrowIcon } from '@gitroom/frontend/components/ui/icons';\nimport clsx from 'clsx';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useClickOutside } from '@mantine/hooks';\n\nconst delayOptions = [\n  { value: 1, label: '1m' },\n  { value: 2, label: '2m' },\n  { value: 5, label: '5m' },\n  { value: 10, label: '10m' },\n  { value: 15, label: '15m' },\n  { value: 30, label: '30m' },\n  { value: 60, label: '1h' },\n  { value: 120, label: '2h' },\n];\n\nexport const DelayComponent: FC<{\n  currentIndex: number;\n  currentDelay: number;\n}> = ({ currentIndex, currentDelay }) => {\n  const t = useT();\n  const [isOpen, setIsOpen] = useState(false);\n  const [customValue, setCustomValue] = useState('');\n  \n  const isCustomDelay = currentDelay > 0 && !delayOptions.some((opt) => opt.value === currentDelay);\n\n  useEffect(() => {\n    if (isOpen && isCustomDelay) {\n      setCustomValue(String(currentDelay));\n    } else if (isOpen && !isCustomDelay) {\n      setCustomValue('');\n    }\n  }, [isOpen, isCustomDelay, currentDelay]);\n\n  const { current, setInternalDelay, setGlobalDelay } = useLaunchStore(\n    useShallow((state) => ({\n      current: state.current,\n      setGlobalDelay: state.setGlobalDelay,\n      setInternalDelay: state.setInternalDelay,\n    }))\n  );\n\n  const ref = useClickOutside(() => {\n    if (!isOpen) {\n      return;\n    }\n    setIsOpen(false);\n  });\n\n  const setDelay = useCallback(\n    (index: number) => (minutes: number) => {\n      if (current !== 'global') {\n        return setInternalDelay(current, index, minutes);\n      }\n\n      return setGlobalDelay(index, minutes);\n    },\n    [currentIndex, current]\n  );\n\n  const handleSelectDelay = useCallback(\n    (minutes: number) => {\n      setDelay(currentIndex)(minutes);\n      setIsOpen(false);\n    },\n    [currentIndex, setDelay]\n  );\n\n  const getCurrentDelayLabel = () => {\n    if (!currentDelay) return null;\n    const option = delayOptions.find((opt) => opt.value === currentDelay);\n    return option?.label || `${currentDelay} min`;\n  };\n\n  return (\n    <div ref={ref} className=\"relative\">\n      <div\n        onClick={() => setIsOpen(!isOpen)}\n        data-tooltip-id=\"tooltip\"\n        data-tooltip-content={\n          !currentDelay\n            ? t('delay_comment', 'Delay comment')\n            : `${t('delay_comment_by', 'Comment delayed by')} ${getCurrentDelayLabel()}`\n        }\n        className={clsx(\n          'cursor-pointer flex items-center gap-[4px]',\n          currentDelay > 0 && 'bg-[#D82D7E] text-white rounded-full'\n        )}\n      >\n        <DelayIcon />\n      </div>\n      {isOpen && (\n        <div className=\"z-[300] absolute end-0 top-[100%] w-[200px] bg-newBgColorInner p-[8px] menu-shadow translate-y-[10px] flex flex-col rounded-[8px]\">\n          <div className=\"grid grid-cols-4 gap-[4px]\">\n            {delayOptions.map((option) => (\n              <div\n                onClick={() => handleSelectDelay(option.value)}\n                key={option.value}\n                className={clsx(\n                  'h-[32px] flex items-center justify-center rounded-[4px] cursor-pointer hover:bg-newBgColor text-[13px]',\n                  currentDelay === option.value && 'bg-[#612BD3] text-white hover:bg-[#612BD3]'\n                )}\n              >\n                {option.label}\n              </div>\n            ))}\n          </div>\n          <div className=\"border-t border-newTextColor/10 mt-[8px] pt-[8px]\">\n            <div className=\"flex gap-[4px]\">\n              <input\n                type=\"number\"\n                min=\"1\"\n                value={customValue}\n                onChange={(e) => setCustomValue(e.target.value)}\n                onClick={(e) => e.stopPropagation()}\n                placeholder=\"Custom min\"\n                className={clsx(\n                  'flex-1 w-full h-[32px] px-[8px] rounded-[4px] bg-newBgColor border text-[13px] outline-none focus:border-[#612BD3]',\n                  isCustomDelay ? 'border-[#612BD3]' : 'border-newTextColor/10'\n                )}\n              />\n              <button\n                onClick={(e) => {\n                  e.stopPropagation();\n                  const value = parseInt(customValue, 10);\n                  if (value > 0) {\n                    handleSelectDelay(value);\n                    setCustomValue('');\n                  }\n                }}\n                className=\"h-[32px] px-[10px] rounded-[4px] bg-[#612BD3] text-white text-[12px] font-[600] hover:bg-[#612BD3]/80\"\n              >\n                Set\n              </button>\n            </div>\n          </div>\n          {currentDelay > 0 && (\n            <button\n              onClick={() => handleSelectDelay(0)}\n              className=\"mt-[8px] h-[32px] w-full rounded-[4px] text-[13px] text-red-400 hover:bg-red-400/10\"\n            >\n              Remove delay\n            </button>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/dummy.code.component.tsx",
    "content": "import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport React, { FC } from 'react';\nimport { Button } from '@gitroom/react/form/button';\nimport copy from 'copy-to-clipboard';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\n\nexport const DummyCodeComponent: FC<{ code: any }> = ({ code }) => {\n  const modal = useModals();\n  const toaster = useToaster();\n\n  return (\n    <div className=\"rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative w-full\">\n      <TopTitle title={`Output`}>\n        <Button\n          className=\"mr-[50px]\"\n          onClick={() => {\n            copy(JSON.stringify(code, null, 2));\n            toaster.show('Code copied to clipboard', 'success');\n          }}\n        >\n          Copy Code\n        </Button>\n      </TopTitle>\n      <button\n        className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n        type=\"button\"\n        onClick={() => modal.closeAll()}\n      >\n        <svg\n          viewBox=\"0 0 15 15\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n        >\n          <path\n            d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n            fill=\"currentColor\"\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n          ></path>\n        </svg>\n      </button>\n      <pre>{JSON.stringify(code, null, 2)}</pre>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/editor.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  ClipboardEvent,\n  forwardRef,\n  useImperativeHandle,\n} from 'react';\nimport clsx from 'clsx';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport EmojiPicker from 'emoji-picker-react';\nimport { Theme } from 'emoji-picker-react';\nimport { BoldText } from '@gitroom/frontend/components/new-launch/bold.text';\nimport { UText } from '@gitroom/frontend/components/new-launch/u.text';\nimport { SignatureBox } from '@gitroom/frontend/components/signature';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport {\n  SelectedIntegrations,\n  useLaunchStore,\n} from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { AddPostButton } from '@gitroom/frontend/components/new-launch/add.post.button';\nimport { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';\nimport { useDropzone } from 'react-dropzone';\nimport { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader';\nimport { Dashboard } from '@uppy/react';\nimport Link from '@tiptap/extension-link';\nimport {\n  useEditor,\n  EditorContent,\n  Extension,\n  mergeAttributes,\n} from '@tiptap/react';\nimport Document from '@tiptap/extension-document';\nimport Bold from '@tiptap/extension-bold';\nimport Text from '@tiptap/extension-text';\nimport Paragraph from '@tiptap/extension-paragraph';\nimport Underline from '@tiptap/extension-underline';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { History } from '@tiptap/extension-history';\nimport { BulletList, ListItem } from '@tiptap/extension-list';\nimport { Bullets } from '@gitroom/frontend/components/new-launch/bullets.component';\nimport Heading from '@tiptap/extension-heading';\nimport { HeadingComponent } from '@gitroom/frontend/components/new-launch/heading.component';\nimport Mention from '@tiptap/extension-mention';\nimport { suggestion } from '@gitroom/frontend/components/new-launch/mention.component';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { AComponent } from '@gitroom/frontend/components/new-launch/a.component';\nimport { Placeholder } from '@tiptap/extensions';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { InformationComponent } from '@gitroom/frontend/components/launches/information.component';\nimport {\n  LockIcon,\n  ConnectionLineIcon,\n  ResetIcon,\n  TrashIcon,\n  EmojiIcon,\n  DelayIcon,\n} from '@gitroom/frontend/components/ui/icons';\nimport { DelayComponent } from '@gitroom/frontend/components/new-launch/delay.component';\n\nconst MAX_UPLOAD_SIZE = 1024 * 1024 * 1024; // 1 GB\n\nconst InterceptBoldShortcut = Extension.create({\n  name: 'preventBoldWithUnderline',\n\n  addKeyboardShortcuts() {\n    return {\n      'Mod-b': () => {\n        // For example, toggle bold while removing underline\n        this?.editor?.commands?.unsetUnderline();\n        return this?.editor?.commands?.toggleBold();\n      },\n    };\n  },\n});\n\nconst InterceptUnderlineShortcut = Extension.create({\n  name: 'preventUnderlineWithUnderline',\n\n  addKeyboardShortcuts() {\n    return {\n      'Mod-u': () => {\n        // For example, toggle bold while removing underline\n        this?.editor?.commands?.unsetBold();\n        return this?.editor?.commands?.toggleUnderline();\n      },\n    };\n  },\n});\n\nexport const EditorWrapper: FC<{\n  totalPosts: number;\n  value: string;\n}> = () => {\n  const t = useT();\n  const {\n    setGlobalValueText,\n    setInternalValueText,\n    addRemoveInternal,\n    internal,\n    global,\n    current,\n    addInternalValue,\n    addGlobalValue,\n    setInternalValueMedia,\n    appendInternalValueMedia,\n    appendGlobalValueMedia,\n    setGlobalValueMedia,\n    changeOrderGlobal,\n    changeOrderInternal,\n    isCreateSet,\n    deleteGlobalValue,\n    deleteInternalValue,\n    setGlobalValue,\n    setInternalValue,\n    setInternalDelay,\n    setGlobalDelay,\n    internalFromAll,\n    totalChars,\n    postComment,\n    dummy,\n    editor,\n    loadedState,\n    setLoadedState,\n    selectedIntegration,\n    chars,\n    comments,\n  } = useLaunchStore(\n    useShallow((state) => ({\n      internal: state.internal.find((p) => p.integration.id === state.current),\n      internalFromAll: state.integrations.find((p) => p.id === state.current),\n      global: state.global,\n      comments: state.comments,\n      current: state.current,\n      addRemoveInternal: state.addRemoveInternal,\n      dummy: state.dummy,\n      setInternalValueText: state.setInternalValueText,\n      setGlobalValueText: state.setGlobalValueText,\n      addInternalValue: state.addInternalValue,\n      addGlobalValue: state.addGlobalValue,\n      setGlobalValueMedia: state.setGlobalValueMedia,\n      setInternalValueMedia: state.setInternalValueMedia,\n      changeOrderGlobal: state.changeOrderGlobal,\n      changeOrderInternal: state.changeOrderInternal,\n      isCreateSet: state.isCreateSet,\n      deleteGlobalValue: state.deleteGlobalValue,\n      deleteInternalValue: state.deleteInternalValue,\n      setGlobalValue: state.setGlobalValue,\n      setInternalValue: state.setInternalValue,\n      setGlobalDelay: state.setGlobalDelay,\n      setInternalDelay: state.setInternalDelay,\n      totalChars: state.totalChars,\n      appendInternalValueMedia: state.appendInternalValueMedia,\n      appendGlobalValueMedia: state.appendGlobalValueMedia,\n      postComment: state.postComment,\n      editor: state.editor,\n      loadedState: state.loaded,\n      setLoadedState: state.setLoaded,\n      selectedIntegration: state.selectedIntegrations,\n      chars: state.chars,\n    }))\n  );\n\n  const existingData = useExistingData();\n  const [loaded, setLoaded] = useState(true);\n\n  useEffect(() => {\n    if (loaded && loadedState) {\n      return;\n    }\n\n    setLoadedState(true);\n    setLoaded(true);\n  }, [loaded, loadedState]);\n\n  const canEdit = useMemo(() => {\n    return current === 'global' || !!internal;\n  }, [current, internal]);\n\n  const items = useMemo(() => {\n    if (internal) {\n      return internal.integrationValue;\n    }\n\n    return global;\n  }, [internal, global]);\n\n  const setValue = useCallback(\n    (value: string[]) => {\n      const newValue = value.map((p, index) => {\n        return {\n          id: makeId(10),\n          delay: 0,\n          ...(items?.[index]?.media\n            ? { media: items[index].media }\n            : { media: [] }),\n          content: p,\n        };\n      });\n      if (internal) {\n        return setInternalValue(current, newValue);\n      }\n\n      return setGlobalValue(newValue);\n    },\n    [internal, items]\n  );\n\n  useCopilotReadable({\n    description: 'Current content of posts',\n    value: items.map((p) => p.content),\n  });\n\n  useCopilotAction({\n    name: 'setPosts',\n    description: 'a thread of posts',\n    parameters: [\n      {\n        name: 'content',\n        type: 'string[]',\n        description: 'a thread of posts',\n      },\n    ],\n    handler: async ({ content }) => {\n      setValue(content);\n    },\n  });\n\n  const changeValue = useCallback(\n    (index: number) => (value: string) => {\n      if (internal) {\n        return setInternalValueText(current, index, value);\n      }\n\n      return setGlobalValueText(index, value);\n    },\n    [current, global, internal]\n  );\n\n  const changeImages = useCallback(\n    (index: number) => (value: any[]) => {\n      if (internal) {\n        return setInternalValueMedia(current, index, value);\n      }\n\n      return setGlobalValueMedia(index, value);\n    },\n    [current, global, internal]\n  );\n\n  const appendImages = useCallback(\n    (index: number) => (value: any[]) => {\n      if (internal) {\n        return appendInternalValueMedia(current, index, value);\n      }\n\n      return appendGlobalValueMedia(index, value);\n    },\n    [current, global, internal]\n  );\n\n  const changeOrder = useCallback(\n    (index: number) => (direction: 'up' | 'down') => {\n      if (internal) {\n        changeOrderInternal(current, index, direction);\n        return setLoaded(false);\n      }\n\n      changeOrderGlobal(index, direction);\n      setLoaded(false);\n    },\n    [changeOrderInternal, changeOrderGlobal, current, global, internal]\n  );\n\n  const goBackToGlobal = useCallback(async () => {\n    if (\n      await deleteDialog(\n        t(\n          'are_you_sure_go_back_to_global_mode',\n          'This action is irreversible. Are you sure you want to go back to global mode?'\n        ),\n        t('yes_go_back_to_global_mode', 'Yes, go back to global mode')\n      )\n    ) {\n      setLoaded(false);\n      addRemoveInternal(current);\n    }\n  }, [addRemoveInternal, current, t]);\n\n  const addValue = useCallback(\n    (index: number) => () => {\n      setTimeout(() => {\n        // scroll the the bottom\n        document.querySelector('#social-content').scrollTo({\n          top: document.querySelector('#social-content').scrollHeight,\n        });\n      }, 20);\n      if (internal) {\n        return addInternalValue(index, current, [\n          {\n            delay: 0,\n            content: '',\n            id: makeId(10),\n            media: [],\n          },\n        ]);\n      }\n\n      return addGlobalValue(index, [\n        {\n          delay: 0,\n          content: '',\n          id: makeId(10),\n          media: [],\n        },\n      ]);\n    },\n    [current, global, internal]\n  );\n\n  const deletePost = useCallback(\n    (index: number) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_delete_this_post',\n            'Are you sure you want to delete this post?'\n          ),\n          t('yes_delete_it', 'Yes, delete it!')\n        ))\n      ) {\n        return;\n      }\n\n      if (internal) {\n        deleteInternalValue(current, index);\n        return setLoaded(false);\n      }\n\n      deleteGlobalValue(index);\n      setLoaded(false);\n    },\n    [current, global, internal, t]\n  );\n\n  if (!loaded || !loadedState) {\n    return null;\n  }\n\n  return (\n    <div\n      className={clsx(\n        'relative flex-col gap-[20px] flex-1',\n        (items.length === 1 || !canEdit || !comments) && 'flex',\n        ((!canEdit && !isCreateSet) || !comments) &&\n          'bg-newSettings rounded-[12px]'\n      )}\n    >\n      {isCreateSet && current !== 'global' && (\n        <>\n          <div className=\"text-center absolute w-full h-full left-0 top-0 items-center justify-center flex z-[101] flex-col gap-[16px]\">\n            <div>\n              <div className=\"w-[54px] h-[54px] rounded-full absolute z-[101] flex justify-center items-center\">\n                <LockIcon />\n              </div>\n              <div className=\"w-[54px] h-[54px] rounded-full bg-newSettings opacity-80\" />\n            </div>\n            <div className=\"text-[14px] font-[600] text-white\">\n              {t(\n                'cant_edit_networks_when_creating_set',\n                \"You can't edit networks when creating a set\"\n              )}\n            </div>\n          </div>\n          <div className=\"absolute w-full h-full left-0 top-0 bg-newBackdrop opacity-60 z-[100] rounded-[12px]\" />\n        </>\n      )}\n      {!canEdit && !isCreateSet && (\n        <>\n          <div\n            onClick={() => {\n              setLoaded(false);\n              addRemoveInternal(current);\n            }}\n            className=\"text-center absolute w-full h-full p-[20px] left-0 top-0 items-center justify-center flex z-[101] flex-col gap-[16px]\"\n          >\n            <div>\n              <div className=\"w-[54px] h-[54px] rounded-full absolute z-[101] flex justify-center items-center\">\n                <LockIcon />\n              </div>\n              <div className=\"w-[54px] h-[54px] rounded-full bg-newSettings opacity-80\" />\n            </div>\n            <div className=\"text-[14px] font-[600] text-white\">\n              {t(\n                'click_to_exit_global_editing',\n                'Click this button to exit global editing and customize the post for this channel'\n              )}\n            </div>\n            <div>\n              <div className=\"text-white rounded-[8px] h-[44px] px-[20px] bg-[#D82D7E] cursor-pointer flex justify-center items-center\">\n                {t('edit_content', 'Edit content')}\n              </div>\n            </div>\n          </div>\n          <div className=\"absolute w-full h-full left-0 top-0 bg-newBackdrop opacity-60 z-[100] rounded-[12px]\" />\n        </>\n      )}\n      {items.map((g, index) => (\n        <div\n          key={g.id}\n          className={clsx(\n            'relative flex flex-col gap-[20px] flex-1 bg-newSettings',\n            index === 0 && 'rounded-t-[12px]',\n            (index === items.length - 1 || !comments) && 'rounded-b-[12px]',\n            !canEdit && !isCreateSet && 'blur-s',\n            ((!canEdit && index > 0) || (!comments && index > 0)) && 'hidden'\n          )}\n        >\n          <div className=\"flex gap-[5px] flex-1 w-full\">\n            <div className=\"flex-1 flex w-full\">\n              {index > 0 && (\n                <div className=\"flex justify-center pl-[12px] text-newSep\">\n                  <ConnectionLineIcon />\n                </div>\n              )}\n              <Editor\n                comments={comments}\n                editorType={editor}\n                allValues={items}\n                onChange={changeValue(index)}\n                key={index}\n                num={index}\n                totalPosts={global.length}\n                value={g.content}\n                pictures={g.media}\n                setImages={changeImages(index)}\n                autoComplete={canEdit}\n                validateChars={true}\n                identifier={internalFromAll?.identifier || 'global'}\n                totalChars={totalChars}\n                appendImages={appendImages(index)}\n                dummy={dummy}\n                selectedIntegration={selectedIntegration}\n                chars={chars}\n                childButton={\n                  <>\n                    {(canEdit && items.length - 1 === index) || !comments ? (\n                      <div className=\"flex items-center\">\n                        <div className=\"flex-1\">\n                          {comments && (\n                            <AddPostButton\n                              num={index}\n                              onClick={addValue(index)}\n                              postComment={postComment}\n                            />\n                          )}\n                        </div>\n                        {!!internal && !existingData?.integration && (\n                          <div\n                            className=\"mt-[12px] flex gap-[20px] items-center cursor-pointer select-none\"\n                            onClick={goBackToGlobal}\n                          >\n                            <div className=\"flex gap-[6px] items-center\">\n                              <div className=\"w-[8px] h-[8px] rounded-full bg-[#FC69FF]\" />\n                              <div className=\"text-[14px] font-[600]\">\n                                {t(\n                                  'editing_a_specific_network',\n                                  'Editing a Specific Network'\n                                )}\n                              </div>\n                            </div>\n                            <div className=\"flex gap-[6px] items-center\">\n                              <div>\n                                <ResetIcon />\n                              </div>\n                              <div className=\"text-[13px] font-[600]\">\n                                {t('back_to_global', 'Back to global')}\n                              </div>\n                            </div>\n                          </div>\n                        )}\n                      </div>\n                    ) : null}\n                  </>\n                }\n              />\n            </div>\n            {comments && (\n              <div className=\"flex flex-col items-center gap-[10px] pe-[12px]\">\n                <UpDownArrow\n                  isUp={index !== 0}\n                  isDown={index !== items.length - 1}\n                  onChange={changeOrder(index)}\n                />\n                {items.length > 1 && (\n                  <TrashIcon\n                    onClick={deletePost(index)}\n                    data-tooltip-id=\"tooltip\"\n                    data-tooltip-content={t(\n                      'delete_post_tooltip',\n                      'Delete Post'\n                    )}\n                    className=\"cursor-pointer text-[#FF3F3F]\"\n                  />\n                )}\n                {index > 0 && (\n                  <DelayComponent currentIndex={index} currentDelay={g.delay} />\n                )}\n              </div>\n            )}\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nexport const Editor: FC<{\n  editorType?: 'none' | 'normal' | 'markdown' | 'html';\n  totalPosts: number;\n  value: string;\n  num?: number;\n  pictures?: any[];\n  allValues?: any[];\n  onChange: (value: string) => void;\n  setImages?: (value: any[]) => void;\n  appendImages?: (value: any[]) => void;\n  autoComplete?: boolean;\n  validateChars?: boolean;\n  comments: boolean | 'no-media';\n  identifier?: string;\n  totalChars?: number;\n  selectedIntegration: SelectedIntegrations[];\n  dummy: boolean;\n  chars: Record<string, number>;\n  childButton?: React.ReactNode;\n}> = (props) => {\n  const {\n    editorType = 'normal',\n    allValues,\n    pictures,\n    setImages,\n    num,\n    identifier,\n    appendImages,\n    dummy,\n    chars,\n    childButton,\n    comments,\n  } = props;\n  const [id] = useState(makeId(10));\n  const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);\n  const t = useT();\n  const toaster = useToaster();\n  const editorRef = useRef<undefined | { editor: any }>();\n  const [loading, setLoading] = useState(false);\n\n  const uppy = useUppyUploader({\n    onUploadSuccess: (result: any) => {\n      appendImages(result);\n      uppy.clear();\n    },\n    allowedFileTypes: 'image/*,video/mp4',\n    onStart: () => {},\n    onEnd: () => setLoading(false),\n  });\n\n  const onDrop = useCallback(\n    (acceptedFiles: File[]) => {\n      const totalSize = acceptedFiles.reduce((acc, file) => acc + file.size, 0);\n\n      if (totalSize > MAX_UPLOAD_SIZE) {\n        toaster.show(\n          t(\n            'upload_size_limit_exceeded',\n            'Upload size limit exceeded. Maximum 1 GB per upload session.'\n          ),\n          'warning'\n        );\n        return;\n      }\n\n      setLoading(true);\n\n      for (const file of acceptedFiles) {\n        uppy.addFile(file);\n      }\n    },\n    [uppy, toaster, t]\n  );\n\n  const paste = useCallback(\n    async (event: ClipboardEvent | File[]) => {\n      if (num > 0 && comments === 'no-media') {\n        return;\n      }\n      // @ts-ignore\n      const clipboardItems = event.clipboardData?.items;\n      if (!clipboardItems) {\n        return;\n      }\n\n      const files: File[] = [];\n      // @ts-ignore\n      for (const item of clipboardItems) {\n        if (item.kind === 'file') {\n          const file = item.getAsFile();\n          if (file) {\n            files.push(file);\n          }\n        }\n      }\n\n      const totalSize = files.reduce((acc, file) => acc + file.size, 0);\n\n      if (totalSize > MAX_UPLOAD_SIZE) {\n        toaster.show(\n          t(\n            'upload_size_limit_exceeded',\n            'Upload size limit exceeded. Maximum 1 GB per upload session.'\n          ),\n          'warning'\n        );\n        return;\n      }\n\n      if (files.length > 0) {\n        setLoading(true);\n      }\n\n      for (const file of files) {\n        uppy.addFile(file);\n      }\n    },\n    [uppy, num, comments, toaster, t]\n  );\n\n  const { getRootProps, isDragActive } = useDropzone({\n    onDrop: (files) => {\n      if (loading) {\n        toaster.show(\n          'Upload current in progress, please wait and then try again.',\n          'warning'\n        );\n        return;\n      }\n      onDrop(files);\n    },\n    noDrag: num > 0 && comments === 'no-media',\n  });\n\n  const valueWithoutHtml = useMemo(() => {\n    return stripHtmlValidation('normal', props.value || '', true);\n  }, [props.value]);\n\n  const addText = useCallback(\n    (emoji: string) => {\n      editorRef?.current?.editor?.commands?.insertContent(emoji);\n      editorRef?.current?.editor?.commands?.focus();\n    },\n    [props.value, id]\n  );\n\n  const [loadedEditor, setLoadedEditor] = useState(editorType);\n  const [showEditor, setShowEditor] = useState(true);\n  useEffect(() => {\n    if (editorType === loadedEditor) {\n      return;\n    }\n    setLoadedEditor(editorType);\n    setShowEditor(false);\n  }, [editorType]);\n\n  useEffect(() => {\n    if (showEditor) {\n      return;\n    }\n    setTimeout(() => {\n      setShowEditor(true);\n    }, 20);\n  }, [showEditor]);\n\n  if (!showEditor) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex flex-col gap-[20px] flex-1\">\n      <div\n        className={clsx(\n          'relative flex-1 px-[12px] pt-[12px] pb-[12px] flex flex-col',\n          num > 0 && '!rounded-bs-[0]'\n        )}\n        id={id}\n      >\n        <div className=\"relative cursor-text flex flex-1 flex-col\">\n          <div {...getRootProps()} className=\"flex flex-1 flex-col\">\n            <div\n              className={clsx(\n                'absolute left-0 top-0 w-full h-full bg-black/70 z-[300] transition-all items-center justify-center flex text-white text-sm',\n                !isDragActive ? 'pointer-events-none opacity-0' : 'opacity-100'\n              )}\n            >\n              {t('drop_files_here_to_upload', 'Drop your files here to upload')}\n            </div>\n            <div className=\"px-[10px] pt-[10px] bg-newBgColorInner rounded-t-[6px] relative z-[99]\">\n              <OnlyEditor\n                value={props.value}\n                editorType={editorType}\n                onChange={props.onChange}\n                paste={paste}\n                ref={editorRef}\n              />\n            </div>\n            <div\n              className=\"bg-newBgColorInner flex-1\"\n              onClick={() => {\n                if (editorRef?.current?.editor?.isFocused) {\n                  return;\n                }\n                editorRef?.current?.editor?.commands?.focus('end');\n              }}\n            />\n            <div className=\"w-full pointer-events-none\">\n              <div className=\"w-full h-[46px] overflow-hidden absolute left-0 bg-newBgColorInner uppyChange\">\n                <Dashboard\n                  height={46}\n                  uppy={uppy}\n                  id={`prog-${num}`}\n                  showProgressDetails={true}\n                  hideUploadButton={true}\n                  hideRetryButton={true}\n                  hidePauseResumeButton={true}\n                  hideCancelButton={true}\n                  hideProgressAfterFinish={true}\n                />\n              </div>\n            </div>\n            <div\n              className=\"w-full h-[46px] bg-newBgColorInner cursor-text\"\n              onClick={() => {\n                if (editorRef?.current?.editor?.isFocused) {\n                  return;\n                }\n                editorRef?.current?.editor?.commands?.focus('end');\n              }}\n            />\n            <div className=\"flex bg-newBgColorInner rounded-b-[6px] cursor-default\">\n              {setImages && (\n                <MultiMediaComponent\n                  mediaNotAvailable={num > 0 && comments === 'no-media'}\n                  allData={allValues}\n                  text={valueWithoutHtml}\n                  label={t('attachments', 'Attachments')}\n                  description=\"\"\n                  value={props.pictures}\n                  dummy={dummy}\n                  name=\"image\"\n                  information={\n                    <InformationComponent\n                      isPicture={pictures?.length > 0}\n                      chars={chars}\n                      totalChars={valueWithoutHtml.length}\n                      totalAllowedChars={props.totalChars}\n                    />\n                  }\n                  toolBar={\n                    <div className=\"flex gap-[5px]\">\n                      <SignatureBox editor={editorRef?.current?.editor} />\n                      {editorType !== 'none' && (\n                        <>\n                          <UText\n                            editor={editorRef?.current?.editor}\n                            currentValue={props.value!}\n                          />\n                          <BoldText\n                            editor={editorRef?.current?.editor}\n                            currentValue={props.value!}\n                          />\n                        </>\n                      )}\n                      {(editorType === 'markdown' || editorType === 'html') &&\n                        identifier !== 'telegram' && (\n                          <>\n                            <AComponent\n                              editor={editorRef?.current?.editor}\n                              currentValue={props.value!}\n                            />\n                            <Bullets\n                              editor={editorRef?.current?.editor}\n                              currentValue={props.value!}\n                            />\n                            <HeadingComponent\n                              editor={editorRef?.current?.editor}\n                              currentValue={props.value!}\n                            />\n                          </>\n                        )}\n                      <div\n                        data-tooltip-id=\"tooltip\"\n                        data-tooltip-content={t('insert_emoji', 'Insert Emoji')}\n                        className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n                        onClick={() => setEmojiPickerOpen(!emojiPickerOpen)}\n                      >\n                        <EmojiIcon />\n                      </div>\n                      <div className=\"relative\">\n                        <div\n                          className={clsx(\n                            'absolute z-[500] -start-[50px]',\n                            num === 0 && allValues?.length > 1\n                              ? 'top-[35px]'\n                              : 'bottom-[35px]'\n                          )}\n                        >\n                          <EmojiPicker\n                            height={400}\n                            theme={\n                              (localStorage.getItem('mode') as Theme) ||\n                              Theme.DARK\n                            }\n                            onEmojiClick={(e) => {\n                              addText(e.emoji);\n                              setEmojiPickerOpen(false);\n                            }}\n                            open={emojiPickerOpen}\n                          />\n                        </div>\n                      </div>\n                    </div>\n                  }\n                  onChange={(value) => {\n                    setImages(value.target.value);\n                  }}\n                  onOpen={() => {}}\n                  onClose={() => {}}\n                />\n              )}\n            </div>\n            <div>{childButton}</div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport const OnlyEditor = forwardRef<\n  any,\n  {\n    editorType: 'none' | 'normal' | 'markdown' | 'html';\n    value: string;\n    onChange: (value: string) => void;\n    paste?: (event: ClipboardEvent | File[]) => void;\n  }\n>(({ editorType, value, onChange, paste }, ref) => {\n  const t = useT();\n  const fetch = useFetch();\n\n  const { internal } = useLaunchStore(\n    useShallow((state) => ({\n      internal: state.internal.find((p) => p.integration.id === state.current),\n    }))\n  );\n\n  const loadList = useCallback(\n    async (query: string) => {\n      if (query.length < 2) {\n        return [];\n      }\n\n      if (!internal?.integration.id) {\n        return [];\n      }\n\n      try {\n        const load = await fetch('/integrations/mentions', {\n          method: 'POST',\n          body: JSON.stringify({\n            name: 'mention',\n            id: internal.integration.id,\n            data: { query },\n          }),\n        });\n\n        const result = await load.json();\n        return result;\n      } catch (error) {\n        console.error('Error loading mentions:', error);\n        return [];\n      }\n    },\n    [internal, fetch]\n  );\n\n  const editor = useEditor({\n    extensions: [\n      Document,\n      Paragraph,\n      Text,\n      Underline,\n      Bold,\n      InterceptBoldShortcut,\n      InterceptUnderlineShortcut,\n      BulletList,\n      ListItem,\n      Placeholder.configure({\n        placeholder: t('write_something', 'Write something …'),\n        emptyEditorClass: 'is-editor-empty',\n      }),\n      ...(editorType === 'html' || editorType === 'markdown'\n        ? [\n            Link.configure({\n              openOnClick: false,\n              autolink: true,\n              defaultProtocol: 'https',\n              protocols: ['http', 'https'],\n              isAllowedUri: (url, ctx) => {\n                try {\n                  // prevent transforming plain emails like foo@bar.com into links\n                  const trimmed = String(url).trim();\n                  const emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n                  if (emailPattern.test(trimmed)) {\n                    return false;\n                  }\n\n                  // construct URL\n                  const parsedUrl = url.includes(':')\n                    ? new URL(url)\n                    : new URL(`${ctx.defaultProtocol}://${url}`);\n\n                  // use default validation\n                  if (!ctx.defaultValidate(parsedUrl.href)) {\n                    return false;\n                  }\n\n                  // disallowed protocols\n                  const disallowedProtocols = ['ftp', 'file', 'mailto'];\n                  const protocol = parsedUrl.protocol.replace(':', '');\n\n                  if (disallowedProtocols.includes(protocol)) {\n                    return false;\n                  }\n\n                  // only allow protocols specified in ctx.protocols\n                  const allowedProtocols = ctx.protocols.map((p) =>\n                    typeof p === 'string' ? p : p.scheme\n                  );\n\n                  if (!allowedProtocols.includes(protocol)) {\n                    return false;\n                  }\n\n                  // all checks have passed\n                  return true;\n                } catch {\n                  return false;\n                }\n              },\n              shouldAutoLink: (url) => {\n                try {\n                  // prevent auto-linking of plain emails like foo@bar.com\n                  const trimmed = String(url).trim();\n                  const emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n                  if (emailPattern.test(trimmed)) {\n                    return false;\n                  }\n\n                  // construct URL\n                  const parsedUrl = url.includes(':')\n                    ? new URL(url)\n                    : new URL(`https://${url}`);\n\n                  // only auto-link if the domain is not in the disallowed list\n                  const disallowedDomains = [\n                    'example-no-autolink.com',\n                    'another-no-autolink.com',\n                  ];\n                  const domain = parsedUrl.hostname;\n\n                  return !disallowedDomains.includes(domain);\n                } catch {\n                  return false;\n                }\n              },\n            }),\n          ]\n        : []),\n      ...(internal?.integration?.id\n        ? [\n            Mention.configure({\n              HTMLAttributes: {\n                class: 'mention',\n              },\n              renderHTML({ options, node }) {\n                return [\n                  'span',\n                  mergeAttributes(options.HTMLAttributes, {\n                    'data-mention-id': node.attrs.id || '',\n                    'data-mention-label': node.attrs.label || '',\n                  }),\n                  `@${node.attrs.label}`,\n                ];\n              },\n              suggestion: suggestion(loadList),\n            }),\n          ]\n        : []),\n      ...(editorType === 'html' || editorType === 'markdown'\n        ? [\n            Heading.configure({\n              levels: [1, 2, 3],\n            }),\n          ]\n        : []),\n      History.configure({\n        depth: 100, // default is 100\n        newGroupDelay: 100, // default is 500ms\n      }),\n    ],\n    content: value || '',\n    shouldRerenderOnTransaction: true,\n    immediatelyRender: false,\n    // @ts-ignore\n    onPaste: paste,\n    onUpdate: (innerProps) => {\n      onChange?.(innerProps.editor.getHTML());\n    },\n  });\n\n  useImperativeHandle(ref, () => ({\n    editor,\n  }));\n\n  return <EditorContent editor={editor} />;\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/finisher/thread.finisher.tsx",
    "content": "'use client';\n\nimport { Slider } from '@gitroom/react/form/slider';\nimport clsx from 'clsx';\nimport { Editor } from '@gitroom/frontend/components/new-launch/editor';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\n\nexport const ThreadFinisher = () => {\n  const integration = useIntegration();\n  const { register, watch, setValue } = useSettings();\n  const dummy = useLaunchStore((p) => p.dummy);\n  const t = useT();\n\n  register('active_thread_finisher', {\n    value: false,\n  });\n\n  register('thread_finisher', {\n    value: t('that_a_wrap', {\n      username:\n        integration.integration?.display || integration.integration?.name,\n    }),\n  });\n\n  const slider = watch('active_thread_finisher');\n  const value = watch('thread_finisher');\n\n  return (\n    <div className=\"flex flex-col gap-[10px] border-tableBorder border p-[15px] rounded-lg mb-5\">\n      <div className=\"flex items-center\">\n        <div className=\"flex-1\">Add a thread finisher</div>\n        <div>\n          <Slider\n            value={slider ? 'on' : 'off'}\n            onChange={(p) => setValue('active_thread_finisher', p === 'on')}\n            fill={true}\n          />\n        </div>\n      </div>\n      <div className=\"w-full mt-[20px]\">\n        <div\n          className={clsx(\n            !slider && 'relative opacity-25 pointer-events-none editor'\n          )}\n        >\n          <div>\n            <div className=\"flex gap-[4px]\">\n              <div className=\"flex-1 editor text-textColor\">\n                <Editor\n                  comments={true}\n                  chars={{}}\n                  selectedIntegration={[]}\n                  onChange={(val) => setValue('thread_finisher', val)}\n                  value={value}\n                  totalPosts={1}\n                  dummy={dummy}\n                />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/heading.component.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\n\nexport const HeadingComponent: FC<{\n  editor: any;\n  currentValue: string;\n}> = ({ editor }) => {\n  const setHeading = (level: number) => () => {\n    editor?.commands?.unsetUnderline();\n    editor?.commands?.unsetBold();\n    editor?.commands?.toggleHeading({ level });\n  };\n\n  return (\n    <div className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center group relative\">\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M3.9974 2.66602V13.3327M11.9974 2.66602V13.3327M5.33073 2.66602H2.66406M11.9974 7.99935L3.9974 7.99935M5.33073 13.3327H2.66406M13.3307 13.3327H10.6641M13.3307 2.66602H10.6641\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n      <div\n        data-tooltip-id=\"tooltip\"\n        data-tooltip-content=\"Title\"\n        className=\"flex p-[10px] gap-[5px] -left-[50%] rounded-[6px] bottom-[100%] opacity-0 pointer-events-none group-hover:pointer-events-auto group-hover:opacity-100 bg-newColColor border border-newColColor z-[100] absolute transition-all\"\n      >\n        <div onClick={setHeading(1)}>\n          <svg\n            width=\"20\"\n            height=\"16\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M22 0H2C1.46957 0 0.960859 0.210714 0.585786 0.585786C0.210714 0.960859 0 1.46957 0 2V22C0 22.5304 0.210714 23.0391 0.585786 23.4142C0.960859 23.7893 1.46957 24 2 24H22C22.5304 24 23.0391 23.7893 23.4142 23.4142C23.7893 23.0391 24 22.5304 24 22V2C24 1.46957 23.7893 0.960859 23.4142 0.585786C23.0391 0.210714 22.5304 0 22 0ZM14 16C14 16.2652 13.8946 16.5196 13.7071 16.7071C13.5196 16.8946 13.2652 17 13 17C12.7348 17 12.4804 16.8946 12.2929 16.7071C12.1054 16.5196 12 16.2652 12 16V12H5V16C5 16.2652 4.89464 16.5196 4.70711 16.7071C4.51957 16.8946 4.26522 17 4 17C3.73478 17 3.48043 16.8946 3.29289 16.7071C3.10536 16.5196 3 16.2652 3 16V6C3 5.73478 3.10536 5.48043 3.29289 5.29289C3.48043 5.10536 3.73478 5 4 5C4.26522 5 4.51957 5.10536 4.70711 5.29289C4.89464 5.48043 5 5.73478 5 6V10H12V6C12 5.73478 12.1054 5.48043 12.2929 5.29289C12.4804 5.10536 12.7348 5 13 5C13.2652 5 13.5196 5.10536 13.7071 5.29289C13.8946 5.48043 14 5.73478 14 6V16ZM21 18C21 18.2652 20.8946 18.5196 20.7071 18.7071C20.5196 18.8946 20.2652 19 20 19C19.7348 19 19.4804 18.8946 19.2929 18.7071C19.1054 18.5196 19 18.2652 19 18V9.875L17.555 10.8387C17.4457 10.9116 17.3231 10.9623 17.1942 10.9878C17.0653 11.0133 16.9326 11.0131 16.8038 10.9874C16.6749 10.9616 16.5524 10.9107 16.4433 10.8376C16.3341 10.7645 16.2404 10.6706 16.1675 10.5612C16.0946 10.4519 16.044 10.3293 16.0185 10.2004C15.993 10.0715 15.9931 9.93887 16.0189 9.81003C16.0447 9.68119 16.0956 9.55868 16.1687 9.44951C16.2418 9.34034 16.3357 9.24663 16.445 9.17375L19.445 7.17375C19.5952 7.07354 19.7697 7.01586 19.9501 7.00684C20.1304 6.99782 20.3098 7.03779 20.4692 7.12251C20.6287 7.20723 20.7622 7.33354 20.8557 7.48804C20.9491 7.64253 20.999 7.81945 21 8V18Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        </div>\n        <div onClick={setHeading(2)}>\n          <svg\n            width=\"20\"\n            height=\"16\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M22 0H2C1.46957 0 0.960859 0.210714 0.585786 0.585786C0.210714 0.960859 0 1.46957 0 2V22C0 22.5304 0.210714 23.0391 0.585786 23.4142C0.960859 23.7893 1.46957 24 2 24H22C22.5304 24 23.0391 23.7893 23.4142 23.4142C23.7893 23.0391 24 22.5304 24 22V2C24 1.46957 23.7893 0.960859 23.4142 0.585786C23.0391 0.210714 22.5304 0 22 0ZM12 16C12 16.2652 11.8946 16.5196 11.7071 16.7071C11.5196 16.8946 11.2652 17 11 17C10.7348 17 10.4804 16.8946 10.2929 16.7071C10.1054 16.5196 10 16.2652 10 16V12H5V16C5 16.2652 4.89464 16.5196 4.70711 16.7071C4.51957 16.8946 4.26522 17 4 17C3.73478 17 3.48043 16.8946 3.29289 16.7071C3.10536 16.5196 3 16.2652 3 16V6C3 5.73478 3.10536 5.48043 3.29289 5.29289C3.48043 5.10536 3.73478 5 4 5C4.26522 5 4.51957 5.10536 4.70711 5.29289C4.89464 5.48043 5 5.73478 5 6V10H10V6C10 5.73478 10.1054 5.48043 10.2929 5.29289C10.4804 5.10536 10.7348 5 11 5C11.2652 5 11.5196 5.10536 11.7071 5.29289C11.8946 5.48043 12 5.73478 12 6V16ZM20 19H15C14.8143 19 14.6322 18.9483 14.4743 18.8507C14.3163 18.753 14.1886 18.6133 14.1056 18.4472C14.0225 18.2811 13.9874 18.0952 14.004 17.9102C14.0207 17.7252 14.0886 17.5486 14.2 17.4L18.7 11.4C18.8229 11.2432 18.9133 11.0634 18.966 10.8713C19.0187 10.6791 19.0325 10.4784 19.0068 10.2808C18.981 10.0832 18.9161 9.89275 18.816 9.72052C18.7158 9.54829 18.5823 9.39774 18.4233 9.27768C18.2642 9.15761 18.0829 9.07042 17.8898 9.02121C17.6968 8.972 17.4958 8.96175 17.2987 8.99106C17.1016 9.02037 16.9124 9.08865 16.742 9.19191C16.5716 9.29518 16.4234 9.43136 16.3062 9.5925C16.1576 9.76796 16.0477 9.97286 15.9837 10.1938C15.9588 10.3235 15.9083 10.4471 15.8353 10.5572C15.7623 10.6674 15.6682 10.762 15.5584 10.8355C15.4486 10.9091 15.3253 10.9601 15.1956 10.9858C15.066 11.0114 14.9325 11.011 14.803 10.9848C14.6735 10.9585 14.5505 10.9068 14.441 10.8327C14.3316 10.7586 14.238 10.6636 14.1655 10.553C14.093 10.4425 14.0432 10.3187 14.0189 10.1888C13.9945 10.0589 13.9962 9.9255 14.0238 9.79625C14.129 9.27928 14.349 8.79256 14.6676 8.37206C14.9862 7.95155 15.3952 7.60801 15.8644 7.36682C16.3336 7.12562 16.851 6.99294 17.3784 6.97858C17.9058 6.96422 18.4296 7.06854 18.9113 7.28384C19.3929 7.49914 19.82 7.8199 20.161 8.22245C20.502 8.62499 20.7482 9.09901 20.8814 9.60948C21.0146 10.12 21.0314 10.6538 20.9306 11.1717C20.8297 11.6895 20.6138 12.1781 20.2987 12.6012L17 17H20C20.2652 17 20.5196 17.1054 20.7071 17.2929C20.8946 17.4804 21 17.7348 21 18C21 18.2652 20.8946 18.5196 20.7071 18.7071C20.5196 18.8946 20.2652 19 20 19Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        </div>\n        <div onClick={setHeading(3)}>\n          <svg\n            width=\"20\"\n            height=\"16\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M22 0H2C1.46957 0 0.960859 0.210714 0.585786 0.585786C0.210714 0.960859 0 1.46957 0 2V22C0 22.5304 0.210714 23.0391 0.585786 23.4142C0.960859 23.7893 1.46957 24 2 24H22C22.5304 24 23.0391 23.7893 23.4142 23.4142C23.7893 23.0391 24 22.5304 24 22V2C24 1.46957 23.7893 0.960859 23.4142 0.585786C23.0391 0.210714 22.5304 0 22 0ZM12 16C12 16.2652 11.8946 16.5196 11.7071 16.7071C11.5196 16.8946 11.2652 17 11 17C10.7348 17 10.4804 16.8946 10.2929 16.7071C10.1054 16.5196 10 16.2652 10 16V12H5V16C5 16.2652 4.89464 16.5196 4.70711 16.7071C4.51957 16.8946 4.26522 17 4 17C3.73478 17 3.48043 16.8946 3.29289 16.7071C3.10536 16.5196 3 16.2652 3 16V6C3 5.73478 3.10536 5.48043 3.29289 5.29289C3.48043 5.10536 3.73478 5 4 5C4.26522 5 4.51957 5.10536 4.70711 5.29289C4.89464 5.48043 5 5.73478 5 6V10H10V6C10 5.73478 10.1054 5.48043 10.2929 5.29289C10.4804 5.10536 10.7348 5 11 5C11.2652 5 11.5196 5.10536 11.7071 5.29289C11.8946 5.48043 12 5.73478 12 6V16ZM17 19C16.0158 19.0002 15.0661 18.6374 14.3325 17.9813C14.1349 17.8042 14.0157 17.5559 14.0012 17.291C13.9867 17.0262 14.078 16.7663 14.255 16.5688C14.432 16.3712 14.6803 16.252 14.9452 16.2375C15.2101 16.2229 15.4699 16.3142 15.6675 16.4912C15.9092 16.7073 16.1998 16.8613 16.5143 16.9401C16.8287 17.0188 17.1576 17.02 17.4726 16.9434C17.7876 16.8669 18.0793 16.7149 18.3225 16.5006C18.5657 16.2862 18.7532 16.016 18.8688 15.7132C18.9844 15.4103 19.0246 15.0839 18.986 14.762C18.9474 14.4402 18.8312 14.1325 18.6473 13.8655C18.4635 13.5985 18.2174 13.3803 17.9305 13.2295C17.6435 13.0787 17.3242 13 17 13C16.8143 13 16.6322 12.9483 16.4743 12.8507C16.3163 12.753 16.1886 12.6133 16.1056 12.4472C16.0225 12.2811 15.9874 12.0952 16.004 11.9102C16.0207 11.7252 16.0886 11.5486 16.2 11.4L18 9H15C14.7348 9 14.4804 8.89464 14.2929 8.70711C14.1054 8.51957 14 8.26522 14 8C14 7.73478 14.1054 7.48043 14.2929 7.29289C14.4804 7.10536 14.7348 7 15 7H20C20.1857 7 20.3678 7.05171 20.5257 7.14935C20.6837 7.24698 20.8114 7.38668 20.8944 7.55279C20.9775 7.71889 21.0126 7.90484 20.996 8.08981C20.9793 8.27477 20.9114 8.45143 20.8 8.6L18.7113 11.385C19.5321 11.7738 20.1962 12.4304 20.5943 13.2468C20.9924 14.0632 21.1008 14.9908 20.9017 15.877C20.7025 16.7632 20.2077 17.5552 19.4986 18.1228C18.7895 18.6904 17.9083 18.9998 17 19Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/manage.modal.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { AddEditModalProps } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { PicksSocialsComponent } from '@gitroom/frontend/components/new-launch/picks.socials.component';\nimport { EditorWrapper } from '@gitroom/frontend/components/new-launch/editor';\nimport { SelectCurrent } from '@gitroom/frontend/components/new-launch/select.current';\nimport { ShowAllProviders } from '@gitroom/frontend/components/new-launch/providers/show.all.providers';\nimport { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { DatePicker } from '@gitroom/frontend/components/launches/helpers/date.picker';\nimport { useShallow } from 'zustand/react/shallow';\nimport { RepeatComponent } from '@gitroom/frontend/components/launches/repeat.component';\nimport { TagsComponent } from '@gitroom/frontend/components/launches/tags.component';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { weightedLength } from '@gitroom/helpers/utils/count.length';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { capitalize } from 'lodash';\nimport { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer';\nimport { CopilotPopup } from '@copilotkit/react-ui';\nimport { DummyCodeComponent } from '@gitroom/frontend/components/new-launch/dummy.code.component';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport {\n  SettingsIcon,\n  ChevronDownIcon,\n  CloseIcon,\n  TrashIcon,\n  DropdownArrowSmallIcon,\n} from '@gitroom/frontend/components/ui/icons';\nimport { useHasScroll } from '@gitroom/frontend/components/ui/is.scroll.hook';\nimport { useShortlinkPreference } from '@gitroom/frontend/components/settings/shortlink-preference.component';\nimport dayjs from 'dayjs';\nimport { Button } from '@gitroom/react/form/button';\n\nfunction countCharacters(text: string, type: string): number {\n  if (type !== 'x') {\n    return text.length;\n  }\n  return weightedLength(text);\n}\n\nexport const ManageModal: FC<AddEditModalProps> = (props) => {\n  const t = useT();\n  const fetch = useFetch();\n  const ref = useRef(null);\n  const existingData = useExistingData();\n  const [loading, setLoading] = useState(false);\n  const toaster = useToaster();\n  const modal = useModals();\n  const [showSettings, setShowSettings] = useState(false);\n  const { data: shortlinkPreferenceData } = useShortlinkPreference();\n\n  const { addEditSets, mutate, customClose, dummy } = props;\n\n  const {\n    selectedIntegrations,\n    hide,\n    date,\n    setDate,\n    repeater,\n    setRepeater,\n    tags,\n    setTags,\n    integrations,\n    setSelectedIntegrations,\n    locked,\n    current,\n    activateExitButton,\n    setHide,\n  } = useLaunchStore(\n    useShallow((state) => ({\n      hide: state.hide,\n      setHide: state.setHide,\n      date: state.date,\n      setDate: state.setDate,\n      current: state.current,\n      repeater: state.repeater,\n      setRepeater: state.setRepeater,\n      tags: state.tags,\n      setTags: state.setTags,\n      selectedIntegrations: state.selectedIntegrations,\n      integrations: state.integrations,\n      setSelectedIntegrations: state.setSelectedIntegrations,\n      locked: state.locked,\n      activateExitButton: state.activateExitButton,\n    }))\n  );\n\n  useEffect(() => {\n    if (hide) {\n      setHide(false);\n    }\n  }, [hide]);\n\n  const currentIntegrationText = useMemo(() => {\n    if (current === 'global') {\n      return (\n        <div className=\"flex items-center gap-[10px]\">\n          <div className=\"relative\">\n            <SettingsIcon size={15} className=\"text-white\" />\n          </div>\n          <div>Settings</div>\n        </div>\n      );\n    }\n\n    const currentIntegration = integrations.find((p) => p.id === current)!;\n\n    return (\n      <div className=\"flex items-center gap-[10px]\">\n        <div className=\"relative\">\n          <img\n            src={`/icons/platforms/${currentIntegration.identifier}.png`}\n            className=\"w-[20px] h-[20px] rounded-[4px]\"\n            alt={currentIntegration.identifier}\n          />\n          <SettingsIcon\n            size={15}\n            className=\"text-white absolute -end-[5px] -bottom-[5px]\"\n          />\n        </div>\n        <div>\n          {currentIntegration.name} {t('channel_settings', 'Settings')}\n        </div>\n      </div>\n    );\n  }, [current]);\n\n  const changeCustomer = useCallback(\n    (customer: string) => {\n      const neededIntegrations = integrations.filter(\n        (p) => p?.customer?.id === customer\n      );\n      setSelectedIntegrations(\n        neededIntegrations.map((p) => ({\n          settings: {},\n          selectedIntegrations: p,\n        }))\n      );\n    },\n    [integrations]\n  );\n\n  const askClose = useCallback(async () => {\n    if (!activateExitButton || dummy) {\n      return;\n    }\n\n    if (\n      await deleteDialog(\n        t(\n          'are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost',\n          'Are you sure you want to close this modal? (all data will be lost)'\n        ),\n        t('yes_close_it', 'Yes, close it!')\n      )\n    ) {\n      if (customClose) {\n        customClose();\n        return;\n      }\n      modal.closeAll();\n    }\n  }, [activateExitButton, dummy]);\n\n  const deletePost = useCallback(async () => {\n    setLoading(true);\n    if (\n      !(await deleteDialog(\n        t(\n          'are_you_sure_you_want_to_delete_post',\n          'Are you sure you want to delete this post?'\n        ),\n        t('yes_delete_it', 'Yes, delete it!')\n      ))\n    ) {\n      setLoading(false);\n      return;\n    }\n    await fetch(`/posts/${existingData.group}`, {\n      method: 'DELETE',\n    });\n    mutate();\n    modal.closeAll();\n    return;\n  }, [existingData, mutate, modal]);\n\n  const schedule = useCallback(\n    (type: 'draft' | 'now' | 'schedule' | 'update') => async () => {\n      if (\n        (type === 'now' || type === 'schedule') &&\n        (existingData?.posts?.[0]?.state === 'PUBLISHED' ||\n          (existingData?.posts?.[0]?.state === 'QUEUE' &&\n            dayjs().isAfter(date.utc())))\n      ) {\n        const whatToDo = await new Promise((resolve) => {\n          modal.openModal({\n            title: 'What do you want to do?',\n            children: (\n              <div className=\"flex flex-col\">\n                <div className=\"text-[20px] mb-[20px]\">\n                  This post was already published, what do you want to do?\n                </div>\n                <div className=\"flex w-full gap-[10px]\">\n                  <div className=\"flex-1 flex\">\n                    <Button\n                      type=\"button\"\n                      className=\"flex-1\"\n                      onClick={() => resolve('update')}\n                    >\n                      Just update the post details\n                    </Button>\n                  </div>\n                  <div className=\"flex-1 flex\">\n                    <Button\n                      type=\"button\"\n                      className=\"flex-1\"\n                      onClick={() => resolve('republish')}\n                    >\n                      Republish the post\n                    </Button>\n                  </div>\n                </div>\n              </div>\n            ),\n          });\n        });\n\n        if (whatToDo === 'update') {\n          type = 'update';\n        }\n      }\n\n      setLoading(true);\n      const checkAllValid = await ref.current.checkAllValid();\n\n      const notEnoughChars = checkAllValid.filter((p: any) => {\n        return p.values.some((a: any) => {\n          return (\n            countCharacters(\n              stripHtmlValidation('normal', a.content, true),\n              p?.integration?.identifier || ''\n            ) === 0 && a.media?.length === 0\n          );\n        });\n      });\n\n      for (const item of notEnoughChars) {\n        toaster.show(\n          `${capitalize(item.integration.identifier.split('-')[0])} (${\n            item.integration.name\n          }):` +\n            ' ' +\n            t(\n              'post_needs_content_or_image',\n              'Your post should have at least one character or one image.'\n            ),\n          'warning'\n        );\n        setLoading(false);\n        item.preview();\n        return;\n      }\n\n      if (type !== 'draft') {\n        for (const item of checkAllValid) {\n          if (item.valid === false) {\n            toaster.show(\n              `${capitalize(item.integration.identifier.split('-')[0])} (${\n                item.integration.name\n              }): ${t('please_fix_your_settings', 'Please fix your settings')}`,\n              'warning'\n            );\n            item.fix();\n            setLoading(false);\n            setShowSettings(true);\n            return;\n          }\n\n          if (item.errors !== true) {\n            toaster.show(\n              `${capitalize(item.integration.identifier.split('-')[0])} (${\n                item.integration.name\n              }): ${item.errors}`,\n              'warning'\n            );\n            item.preview();\n            setLoading(false);\n            setShowSettings(false);\n            return;\n          }\n        }\n\n        const sliceNeeded = checkAllValid.filter((p: any) => {\n          return p.values.some((a: any) => {\n            const strip = stripHtmlValidation('normal', a.content, true);\n            const weightedLength = countCharacters(\n              strip,\n              p?.integration?.identifier || ''\n            );\n            const totalCharacters =\n              weightedLength > strip.length ? weightedLength : strip.length;\n\n            return totalCharacters > (p.maximumCharacters || 1000000);\n          });\n        });\n\n        for (const item of sliceNeeded) {\n          toaster.show(\n            `${item?.integration?.name} (${item?.integration?.identifier}) ${t(\n              'post_is_too_long',\n              'post is too long, please fix it'\n            )}`,\n            'warning'\n          );\n          item.preview();\n          setLoading(false);\n          return;\n        }\n      }\n\n      const shortlinkPreference = shortlinkPreferenceData?.shortlink || 'ASK';\n\n      let shortLink = false;\n\n      if (!dummy && shortlinkPreference !== 'NO') {\n        const shortLinkUrl = await (\n          await fetch('/posts/should-shortlink', {\n            method: 'POST',\n            body: JSON.stringify({\n              messages: checkAllValid.flatMap((p: any) =>\n                p.values.flatMap((a: any) => a.content)\n              ),\n            }),\n          })\n        ).json();\n\n        if (shortLinkUrl.ask) {\n          if (shortlinkPreference === 'YES') {\n            // Automatically shortlink without asking\n            shortLink = true;\n          } else {\n            // ASK: Show the dialog\n            shortLink = await deleteDialog(\n              t(\n                'shortlink_urls_question',\n                'Do you want to shortlink the URLs? it will let you get statistics over clicks'\n              ),\n              t('yes_shortlink_it', 'Yes, shortlink it!')\n            );\n          }\n        }\n      }\n\n      const group = existingData.group || makeId(10);\n      const data = {\n        type,\n        ...(repeater ? { inter: repeater } : {}),\n        tags,\n        shortLink,\n        date: date.utc().format('YYYY-MM-DDTHH:mm:ss'),\n        posts: checkAllValid.map((post: any) => ({\n          integration: {\n            id: post.integration.id,\n          },\n          group,\n          settings: { ...(post.settings || {}) },\n          value: post.values.map((value: any) => ({\n            ...(value.id ? { id: value.id } : {}),\n            content: value.content,\n            delay: value.delay || 0,\n            image:\n              (value?.media || []).map(\n                ({ id, path, alt, thumbnail, thumbnailTimestamp }: any) => ({\n                  id,\n                  path,\n                  alt,\n                  thumbnail,\n                  thumbnailTimestamp,\n                })\n              ) || [],\n          })),\n        })),\n      };\n\n      if (dummy) {\n        modal.openModal({\n          title: '',\n          children: <DummyCodeComponent code={data} />,\n          classNames: {\n            modal: 'w-[100%] bg-transparent text-textColor',\n          },\n          size: '100%',\n          withCloseButton: false,\n          closeOnEscape: true,\n          closeOnClickOutside: true,\n        });\n\n        setLoading(false);\n      }\n\n      if (!dummy) {\n        addEditSets\n          ? addEditSets(data)\n          : await fetch('/posts', {\n              method: 'POST',\n              body: JSON.stringify(data),\n            });\n\n        if (!addEditSets) {\n          mutate();\n          toaster.show(\n            !existingData.integration\n              ? t('added_successfully', 'Added successfully')\n              : t('updated_successfully', 'Updated successfully')\n          );\n        }\n        if (customClose) {\n          setTimeout(() => {\n            customClose();\n          }, 2000);\n        }\n\n        if (!addEditSets) {\n          modal.closeAll();\n        }\n      }\n    },\n    [ref, repeater, tags, date, addEditSets, dummy, shortlinkPreferenceData]\n  );\n\n  return (\n    <div className=\"w-full h-full flex-1 p-[40px] flex relative\">\n      <div className=\"flex flex-1 bg-newBgColorInner rounded-[20px] flex-col\">\n        <div className=\"flex-1 flex\">\n          <div className=\"flex flex-col flex-1 border-e border-newBorder\">\n            <div className=\"bg-newBgColor h-[65px] rounded-s-[20px] !rounded-b-[0] flex items-center px-[20px] text-[20px] font-[600]\">\n              {t('create_post_title', 'Create Post')}\n            </div>\n            <div className=\"flex-1 flex flex-col gap-[16px]\">\n              <div\n                className={clsx('flex-1 relative', showSettings && 'hidden')}\n              >\n                <div\n                  id=\"social-content\"\n                  className=\"gap-[32px] flex flex-col pe-[8px] pt-[20px] ps-[20px] absolute top-0 left-0 w-full h-full overflow-x-hidden overflow-y-scroll scrollbar scrollbar-thumb-newColColor scrollbar-track-newBgColorInner\"\n                >\n                  <div className=\"flex w-full\">\n                    <div className=\"flex flex-1\">\n                      <PicksSocialsComponent toolTip={true} />\n                    </div>\n                    <div>\n                      {!dummy && (\n                        <SelectCustomer\n                          onChange={changeCustomer}\n                          integrations={integrations}\n                        />\n                      )}\n                    </div>\n                  </div>\n                  <div className=\"flex flex-1 gap-[6px] flex-col\">\n                    <div>{!existingData.integration && <SelectCurrent />}</div>\n                    <div className=\"flex-1 flex\">\n                      {!hide && <EditorWrapper totalPosts={1} value=\"\" />}\n                    </div>\n                    <div\n                      id=\"social-empty\"\n                      className={clsx(\n                        'pb-[16px]'\n                        // current !== 'global' && 'hidden'\n                      )}\n                    />\n                  </div>\n                </div>\n              </div>\n              <div\n                id=\"wrapper-settings\"\n                className={clsx(\n                  'pb-[20px] px-[20px] select-none',\n                  showSettings && 'flex-1 flex pt-[20px]',\n                  current === 'global' && 'hidden'\n                )}\n              >\n                <div className=\"flex-1 flex flex-col rounded-[12px] gap-[12px] overflow-hidden bg-newSettings\">\n                  <div\n                    onClick={() => setShowSettings(!showSettings)}\n                    className={clsx(\n                      'bg-[#612BD3] rounded-[12px] flex items-center gap-[8px] cursor-pointer p-[12px]',\n                      showSettings ? '!rounded-b-none' : ''\n                    )}\n                  >\n                    <div className=\"flex-1 text-[14px] font-[600] text-white\">\n                      {currentIntegrationText}\n                    </div>\n                    <div>\n                      <ChevronDownIcon\n                        rotated={showSettings}\n                        className=\"text-white\"\n                      />\n                    </div>\n                  </div>\n                  <div\n                    className={clsx(\n                      !showSettings ? 'hidden' : 'flex-1',\n                      'text-[14px] text-textColor font-[500] relative'\n                    )}\n                  >\n                    <div className=\"absolute left-0 top-0 w-full h-full flex flex-col overflow-x-hidden overflow-y-auto scrollbar scrollbar-thumb-newBgColorInner scrollbar-track-newColColor\">\n                      <div\n                        id=\"social-settings\"\n                        className=\"flex flex-col gap-[20px] bg-newBgColor\"\n                      />\n                    </div>\n                  </div>\n                  <style>\n                    {`#social-settings [data-id=\"${current}\"] {display: block !important;}`}\n                  </style>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div className=\"w-[580px] flex flex-col\">\n            <div className=\"bg-newBgColor h-[65px] rounded-e-[20px] !rounded-b-[0] flex items-center px-[20px] text-[20px] font-[600]\">\n              <div className=\"flex-1\">{t('post_preview', 'Post Preview')}</div>\n              <div className=\"cursor-pointer\">\n                <CloseIcon onClick={askClose} className=\"text-[#A3A3A3]\" />\n              </div>\n            </div>\n            <div className=\"flex-1 relative\">\n              <Scrollable\n                scrollClasses=\"!pe-[20px]\"\n                className=\"absolute top-0 p-[20px] pe-[8px] left-0 w-full h-full overflow-x-hidden overflow-y-scroll scrollbar scrollbar-thumb-newColColor scrollbar-track-newBgColorInner\"\n              >\n                <ShowAllProviders ref={ref} />\n              </Scrollable>\n            </div>\n          </div>\n        </div>\n        <div className=\"select-none h-[84px] py-[20px] border-t border-newBorder flex items-center\">\n          <div className=\"flex-1 flex ps-[20px] gap-[8px]\">\n            {!dummy && (\n              <TagsComponent\n                name=\"tags\"\n                label={t('tags', 'Tags')}\n                initial={tags}\n                onChange={(e) => {\n                  setTags(e.target.value);\n                }}\n              />\n            )}\n\n            {!dummy && (\n              <RepeatComponent repeat={repeater} onChange={setRepeater} />\n            )}\n          </div>\n          <div className=\"pe-[20px] flex items-center justify-end gap-[8px]\">\n            {existingData?.integration && (\n              <button\n                onClick={deletePost}\n                className=\"cursor-pointer flex text-[#FF3F3F] gap-[8px] items-center text-[15px] font-[600]\"\n              >\n                <div>\n                  <TrashIcon />\n                </div>\n                <div>{t('delete_post', 'Delete Post')}</div>\n              </button>\n            )}\n            <DatePicker onChange={setDate} date={date} />\n            {!addEditSets && (\n              <button\n                disabled={\n                  selectedIntegrations.length === 0 || loading || locked\n                }\n                onClick={schedule('draft')}\n                className=\"relative cursor-pointer disabled:cursor-not-allowed px-[20px] h-[44px] bg-btnSimple justify-center items-center flex rounded-[8px] text-[15px] font-[600]\"\n              >\n                {loading && (\n                  <div className=\"absolute left-[50%] top-[50%] -translate-y-[50%] -translate-x-[50%]\">\n                    <div className=\"animate-spin h-[20px] w-[20px] border-4 border-textColor border-t-transparent rounded-full\" />\n                  </div>\n                )}\n                <div className={clsx(loading && 'invisible')}>\n                  {t('save_as_draft', 'Save as Draft')}\n                </div>\n              </button>\n            )}\n            {addEditSets && (\n              <button\n                className=\"text-white text-[15px] font-[600] min-w-[180px] btnSub disabled:cursor-not-allowed disabled:opacity-80 outline-none gap-[8px] flex justify-center items-center h-[44px] rounded-[8px] bg-[#612BD3] ps-[20px] pe-[16px]\"\n                disabled={\n                  selectedIntegrations.length === 0 || loading || locked\n                }\n                onClick={schedule('draft')}\n              >\n                Save Set\n              </button>\n            )}\n            {!addEditSets && (\n              <div className=\"group cursor-pointer relative\">\n                <button\n                  disabled={\n                    selectedIntegrations.length === 0 || loading || locked\n                  }\n                  onClick={schedule('schedule')}\n                  className=\"text-white relative min-w-[180px] btnSub disabled:cursor-not-allowed disabled:opacity-80 outline-none gap-[8px] flex justify-center items-center h-[44px] rounded-[8px] bg-[#612BD3] ps-[20px] pe-[16px]\"\n                >\n                  {loading && (\n                    <div className=\"absolute left-[50%] top-[50%] -translate-y-[50%] -translate-x-[50%]\">\n                      <div className=\"animate-spin h-[20px] w-[20px] border-4 border-white border-t-transparent rounded-full\" />\n                    </div>\n                  )}\n                  <div\n                    className={clsx(\n                      'text-[15px] font-[600]',\n                      loading && 'invisible'\n                    )}\n                  >\n                    {selectedIntegrations.length === 0\n                      ? t('check_circles_above', 'Check the circles above')\n                      : dummy\n                      ? t('create_output', 'Create output')\n                      : !existingData?.integration\n                      ? t('add_to_calendar', 'Add to calendar')\n                      : existingData?.posts?.[0]?.state === 'DRAFT'\n                      ? t('schedule', 'Schedule')\n                      : t('update', 'Update')}\n                  </div>\n                  {!dummy && (\n                    <div className=\"flex justify-center items-center h-[20px] w-[20px] pt-[4px] arrow-change\">\n                      <DropdownArrowSmallIcon className=\"group-hover:rotate-180 text-white\" />\n                    </div>\n                  )}\n                </button>\n\n                {!dummy && (\n                  <button\n                    onClick={schedule('now')}\n                    disabled={\n                      selectedIntegrations.length === 0 || loading || locked\n                    }\n                    className=\"rounded-[8px] z-[300] disabled:cursor-not-allowed disabled:opacity-80 hidden group-hover:flex absolute bottom-[100%] -left-[12px] p-[12px] w-[206px] bg-newBgColorInner\"\n                  >\n                    <div className=\"text-white rounded-[8px] bg-[#D82D7E] h-[44px] w-full flex justify-center items-center post-now\">\n                      {t('post_now', 'Post Now')}\n                    </div>\n                  </button>\n                )}\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n      <CopilotPopup\n        hitEscapeToClose={false}\n        clickOutsideToClose={true}\n        instructions={`\nYou are an assistant that help the user to schedule their social media posts,\nHere are the things you can do:\n- Add a new comment / post to the list of posts\n- Delete a comment / post from the list of posts\n- Add content to the comment / post\n- Activate or deactivate the comment / post\n\nPost content can be added using the addPostContentFor{num} function.\nAfter using the addPostFor{num} it will create a new addPostContentFor{num+ 1} function.\n`}\n        labels={{\n          title: t('your_assistant', 'Your Assistant'),\n          initial: t(\n            'assistant_initial_message',\n            'Hi! I can help you to refine your social media posts.'\n          ),\n        }}\n      />\n    </div>\n  );\n};\n\nconst Scrollable: FC<{\n  className: string;\n  scrollClasses: string;\n  children: ReactNode;\n}> = ({ className, scrollClasses, children }) => {\n  const ref = useRef();\n  const hasScroll = useHasScroll(ref);\n  return (\n    <div className={clsx(className, hasScroll && scrollClasses)} ref={ref}>\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/mention.component.tsx",
    "content": "'use client';\n\nimport React, { FC, useEffect, useImperativeHandle, useState } from 'react';\nimport { computePosition, flip, shift } from '@floating-ui/dom';\nimport { posToDOMRect, ReactRenderer } from '@tiptap/react';\n\n// Debounce utility for TipTap\nconst debounce = <T extends any[]>(\n  func: (...args: any[]) => Promise<T>,\n  wait: number\n) => {\n  let timeout: NodeJS.Timeout;\n  return (...args: any[]): Promise<T> => {\n    clearTimeout(timeout);\n    return new Promise((resolve) => {\n      timeout = setTimeout(async () => {\n        try {\n          const result = await func(...args);\n          resolve(result);\n        } catch (error) {\n          console.error('Debounced function error:', error);\n          resolve([] as T);\n        }\n      }, wait);\n    });\n  };\n};\n\nconst MentionList: FC = (props: any) => {\n  const [selectedIndex, setSelectedIndex] = useState(0);\n\n  const selectItem = (index: number) => {\n    const item = props.items[index];\n\n    if (item) {\n      props.command(item);\n    }\n  };\n\n  const upHandler = () => {\n    setSelectedIndex(\n      (selectedIndex + props.items.length - 1) % props.items.length\n    );\n  };\n\n  const downHandler = () => {\n    setSelectedIndex((selectedIndex + 1) % props.items.length);\n  };\n\n  const enterHandler = () => {\n    selectItem(selectedIndex);\n  };\n\n  useEffect(() => setSelectedIndex(0), [props.items]);\n\n  useImperativeHandle(props.ref, () => ({\n    onKeyDown: ({ event }: { event: any }) => {\n      if (event.key === 'ArrowUp') {\n        upHandler();\n        return true;\n      }\n\n      if (event.key === 'ArrowDown') {\n        downHandler();\n        return true;\n      }\n\n      if (event.key === 'Enter') {\n        enterHandler();\n        return true;\n      }\n\n      return false;\n    },\n  }));\n\n  if (props?.stop) {\n    return null;\n  }\n\n  return (\n    <div className=\"dropdown-menu bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto p-2\">\n      {props?.items?.none ? (\n        <div className=\"flex items-center justify-center p-2 text-gray-500\">\n          We don't have autocomplete for this social media\n        </div>\n      ) : props?.loading ? (\n        <div className=\"flex items-center justify-center p-2 text-gray-500\">\n          Loading...\n        </div>\n      ) : props?.items ? (\n        props.items.length === 0 ? (\n          <div className=\"p-2 text-gray-500 text-center\">No results found</div>\n        ) : (\n          props?.items?.map((item: any, index: any) => (\n            <button\n              className={`flex gap-[10px] w-full p-2 text-start rounded hover:bg-gray-100 ${\n                index === selectedIndex ? 'bg-blue-100' : ''\n              }`}\n              key={item.id || index}\n              onClick={() => selectItem(index)}\n            >\n              <img\n                src={item.image || '/no-picture.jpg'}\n                alt={item.label}\n                className=\"w-[30px] h-[30px] rounded-full object-cover\"\n              />\n              <div className=\"flex-1 text-gray-800\">{item.label}</div>\n            </button>\n          ))\n        )\n      ) : (\n        <div className=\"p-2 text-gray-500 text-center\">Loading...</div>\n      )}\n    </div>\n  );\n};\n\nconst updatePosition = (editor: any, element: any) => {\n  const virtualElement = {\n    getBoundingClientRect: () =>\n      posToDOMRect(\n        editor.view,\n        editor.state.selection.from,\n        editor.state.selection.to\n      ),\n  };\n\n  computePosition(virtualElement, element, {\n    placement: 'bottom-start',\n    strategy: 'absolute',\n    middleware: [shift(), flip()],\n  }).then(({ x, y, strategy }) => {\n    element.style.width = 'max-content';\n    element.style.position = strategy;\n    element.style.left = `${x}px`;\n    element.style.top = `${y}px`;\n    element.style.zIndex = '1000';\n  });\n};\n\nexport const suggestion = (\n  loadList: (\n    query: string\n  ) => Promise<{ image: string; label: string; id: string }[]>\n) => {\n  // Create debounced version of loadList once\n  const debouncedLoadList = debounce(loadList, 500);\n  let component: any;\n\n  return {\n    allowSpaces: true,\n    items: async ({ query }: { query: string }) => {\n      if (!query || query.length < 2) {\n        component.updateProps({ loading: true, stop: true });\n        return [];\n      }\n\n      try {\n        component.updateProps({ loading: true, stop: false });\n        const result = await debouncedLoadList(query);\n        return result;\n      } catch (error) {\n        return [];\n      }\n    },\n\n    render: () => {\n      let currentQuery = '';\n      let isLoadingQuery = false;\n\n      return {\n        onBeforeStart: (props: any) => {\n          component = new ReactRenderer(MentionList, {\n            props: {\n              ...props,\n              loading: true,\n            },\n            editor: props.editor,\n          });\n          component.updateProps({ ...props, loading: true, stop: false });\n          updatePosition(props.editor, component.element);\n        },\n        onStart: (props: any) => {\n          currentQuery = props.query || '';\n          isLoadingQuery = currentQuery.length >= 2;\n\n          if (!props.clientRect) {\n            return;\n          }\n\n          component.element.style.position = 'absolute';\n          component.element.style.zIndex = '1000';\n\n          const container =\n            document.querySelector('.mantine-Paper-root') || document.body;\n          container.appendChild(component.element);\n\n          updatePosition(props.editor, component.element);\n          component.updateProps({ ...props, loading: true });\n        },\n\n        onUpdate(props: any) {\n          const newQuery = props.query || '';\n          const queryChanged = newQuery !== currentQuery;\n          currentQuery = newQuery;\n\n          // If query changed and is valid, we're loading until results come in\n          if (queryChanged && newQuery.length >= 2) {\n            isLoadingQuery = true;\n          }\n\n          // If we have results, we're no longer loading\n          if (props.items && props.items.length > 0) {\n            isLoadingQuery = false;\n          }\n\n          // Show loading if we have a valid query but no results yet\n          const shouldShowLoading =\n            isLoadingQuery &&\n            newQuery.length >= 2 &&\n            (!props.items || props.items.length === 0);\n\n          component.updateProps({ ...props, loading: false, stop: false });\n\n          if (!props.clientRect) {\n            return;\n          }\n\n          updatePosition(props.editor, component.element);\n        },\n\n        onKeyDown(props: any) {\n          if (props.event.key === 'Escape') {\n            component.destroy();\n\n            return true;\n          }\n\n          return component.ref?.onKeyDown(props);\n        },\n\n        onExit() {\n          component.element.remove();\n          component.destroy();\n        },\n      };\n    },\n  };\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/modal.wrapper.component.tsx",
    "content": "import { FC, ReactNode, useEffect, useRef } from 'react';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const ModalWrapperComponent: FC<{\n  title: string;\n  children: ReactNode;\n  customClose?: () => void;\n  ask?: boolean;\n}> = ({ title, children, ask, customClose }) => {\n  const ref = useRef<HTMLDivElement>(null);\n  const modal = useModals();\n  const t = useT();\n  const closeModal = async () => {\n    if (\n      ask &&\n      !(await deleteDialog(\n        t(\n          'are_you_sure_you_want_to_close_the_window',\n          'Are you sure you want to close the window?'\n        ),\n        t('yes_close', 'Yes, close')\n      ))\n    ) {\n      return;\n    }\n\n    if (customClose) {\n      customClose();\n      return;\n    }\n\n    modal.closeAll();\n  };\n\n  useEffect(() => {\n    ref?.current?.scrollIntoView({\n      behavior: 'smooth',\n    });\n  }, []);\n\n  return (\n    <>\n      <div className=\"relative\">\n        <div className=\"absolute -top-[30px] left-0\" ref={ref} />\n      </div>\n      <div\n        className=\"p-[32px] flex flex-col text-newTextColor bg-newBgColorInner rounded-[24px]\"\n      >\n        <div className=\"flex items-start mb-[24px]\">\n          <div className=\"flex-1 text-[24px]\">{title}</div>\n          <div className=\"cursor-pointer\" onClick={closeModal}>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"21\"\n              height=\"21\"\n              viewBox=\"0 0 21 21\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M16.5 4.5L4.5 16.5M4.5 4.5L16.5 16.5\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div>{children}</div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/picks.socials.component.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport clsx from 'clsx';\nimport Image from 'next/image';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\n\nexport const PicksSocialsComponent: FC<{ toolTip?: boolean }> = ({\n  toolTip,\n}) => {\n  const exising = useExistingData();\n\n  const {\n    locked,\n    addOrRemoveSelectedIntegration,\n    integrations,\n    selectedIntegrations,\n  } = useLaunchStore(\n    useShallow((state) => ({\n      integrations: state.integrations,\n      selectedIntegrations: state.selectedIntegrations,\n      addOrRemoveSelectedIntegration: state.addOrRemoveSelectedIntegration,\n      locked: state.locked,\n    }))\n  );\n\n  return (\n    <div className={clsx('flex', locked && 'opacity-50 pointer-events-none')}>\n      <div className=\"flex flex-1\">\n        <div className=\"innerComponent flex-1 flex\">\n          <div className=\"flex flex-wrap gap-[12px] flex-1\">\n            {integrations\n              .filter((f) => {\n                if (exising.integration) {\n                  return f.id === exising.integration;\n                }\n                return !f.inBetweenSteps && !f.disabled;\n              })\n              .map((integration) => (\n                <div\n                  key={integration.id}\n                  className=\"flex gap-[8px] items-center\"\n                  {...(toolTip && {\n                    'data-tooltip-id': 'tooltip',\n                    'data-tooltip-content': integration.name,\n                  })}\n                >\n                  <div\n                    onClick={() => {\n                      if (exising.integration) {\n                        return;\n                      }\n                      addOrRemoveSelectedIntegration(integration, {});\n                    }}\n                    className={clsx(\n                      'cursor-pointer border-[2px] relative rounded-full flex justify-center items-center bg-fifth filter transition-all duration-500',\n                      selectedIntegrations.findIndex(\n                        (p) => p.integration.id === integration.id\n                      ) === -1\n                        ? 'grayscale border-transparent'\n                        : 'border-[#622FF6]'\n                    )}\n                  >\n                    <ImageWithFallback\n                      fallbackSrc=\"/no-picture.jpg\"\n                      src={integration.picture || '/no-picture.jpg'}\n                      className={clsx(\n                        'rounded-full transition-all min-w-[42px] border-[1.5px] min-h-[42px]',\n                        selectedIntegrations.findIndex(\n                          (p) => p.integration.id === integration.id\n                        ) === -1\n                          ? 'border-transparent'\n                          : 'border-[#000]'\n                      )}\n                      alt={integration.identifier}\n                      width={42}\n                      height={42}\n                    />\n                    {integration.identifier === 'youtube' ? (\n                      <img\n                        src=\"/icons/platforms/youtube.svg\"\n                        className=\"absolute z-10 bottom-0 -end-[5px] min-w-[16px]\"\n                        width={16}\n                      />\n                    ) : (\n                      <Image\n                        src={`/icons/platforms/${integration.identifier}.png`}\n                        className=\"rounded-[4px] absolute z-10 bottom-0 -end-[5px] min-w-[16px] min-h-[16px]\"\n                        alt={integration.identifier}\n                        width={16}\n                        height={16}\n                      />\n                    )}\n                  </div>\n                </div>\n              ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/bluesky/bluesky.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher';\n\nconst SettingsComponent = () => {\n  return <ThreadFinisher />;\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: SettingsComponent,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async (posts) => {\n    if (\n      posts?.some(\n        (p) => p?.some((a) => (a?.path?.indexOf?.('mp4') ?? -1) > -1) && (p?.length ?? 0) > 1\n      )\n    ) {\n      return 'You can only upload one video per post.';\n    }\n\n    if (posts?.some((p) => (p?.length ?? 0) > 4)) {\n      return 'There can be maximum 4 pictures in a post.';\n    }\n    return true;\n  },\n  maximumCharacters: 300,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/facebook/facebook.continue.tsx",
    "content": "'use client';\n\nimport { withContinueProvider } from '../with-continue-provider';\n\ninterface FacebookItem {\n  id: string;\n  username: string;\n  name: string;\n  picture: {\n    data: {\n      url: string;\n    };\n  };\n}\n\nexport const FacebookContinue = withContinueProvider<FacebookItem, string>({\n  endpoint: 'pages',\n  swrKey: 'load-facebook-pages',\n  titleKey: 'select_page',\n  titleDefault: 'Select Page:',\n  emptyStateMessages: [\n    {\n      key: 'we_couldn_t_find_any_business_connected_to_the_selected_pages',\n      text: \"We couldn't find any business connected to the selected pages.\",\n    },\n    {\n      key: 'we_recommend_you_to_connect_all_the_pages_and_all_the_businesses',\n      text: 'We recommend you to connect all the pages and all the businesses.',\n    },\n    {\n      key: 'please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again',\n      text: 'Please close this dialog, delete your integration and add a new channel again.',\n    },\n  ],\n  getItemId: (item) => item.id,\n  getSelectionValue: (item) => item.id,\n  transformSaveData: (selection) => ({ page: selection }),\n  isSelected: (item, selection) => selection === item.id,\n  renderItem: (item) => (\n    <>\n      <div>\n        <img className=\"w-full\" src={item.picture.data.url} alt=\"profile\" />\n      </div>\n      <div>{item.name}</div>\n    </>\n  ),\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/gmb/gmb.continue.tsx",
    "content": "'use client';\n\nimport { withContinueProvider } from '../with-continue-provider';\n\ninterface GmbItem {\n  id: string;\n  name: string;\n  accountName: string;\n  locationName: string;\n  picture?: {\n    data: {\n      url: string;\n    };\n  };\n}\n\ninterface GmbSelection {\n  id: string;\n  accountName: string;\n  locationName: string;\n}\n\nexport const GmbContinue = withContinueProvider<GmbItem, GmbSelection>({\n  endpoint: 'pages',\n  swrKey: 'load-gmb-locations',\n  titleKey: 'select_location',\n  titleDefault: 'Select Business Location:',\n  emptyStateMessages: [\n    {\n      key: 'gmb_no_locations_found',\n      text: \"We couldn't find any business locations connected to your account.\",\n    },\n    {\n      key: 'gmb_ensure_business_verified',\n      text: 'Please ensure your business is verified on Google My Business.',\n    },\n    {\n      key: 'gmb_try_again',\n      text: 'Please close this dialog, delete the integration and try again.',\n    },\n  ],\n  getItemId: (item) => item.id,\n  getSelectionValue: (item) => ({\n    id: item.id,\n    accountName: item.accountName,\n    locationName: item.locationName,\n  }),\n  transformSaveData: (selection) => selection,\n  isSelected: (item, selection) => selection?.id === item.id,\n  renderItem: (item) => (\n    <>\n      <div className=\"flex justify-center\">\n        {item.picture?.data?.url ? (\n          <img\n            className=\"w-[80px] h-[80px] object-cover rounded-[8px]\"\n            src={item.picture.data.url}\n            alt={item.name}\n          />\n        ) : (\n          <div className=\"w-[80px] h-[80px] bg-input rounded-[8px] flex items-center justify-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"40\"\n              height=\"40\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\" />\n              <circle cx=\"12\" cy=\"10\" r=\"3\" />\n            </svg>\n          </div>\n        )}\n      </div>\n      <div className=\"text-sm font-medium\">{item.name}</div>\n    </>\n  ),\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/instagram/instagram.continue.tsx",
    "content": "'use client';\n\nimport { withContinueProvider } from '../with-continue-provider';\n\ninterface InstagramItem {\n  id: string;\n  pageId: string;\n  username: string;\n  name: string;\n  picture: {\n    data: {\n      url: string;\n    };\n  };\n}\n\ninterface InstagramSelection {\n  id: string;\n  pageId: string;\n}\n\nexport const InstagramContinue = withContinueProvider<\n  InstagramItem,\n  InstagramSelection\n>({\n  endpoint: 'pages',\n  swrKey: 'load-instagram-pages',\n  titleKey: 'select_instagram_account',\n  titleDefault: 'Select Instagram Account:',\n  emptyStateMessages: [\n    {\n      key: 'we_couldn_t_find_any_business_connected_to_the_selected_pages',\n      text: \"We couldn't find any business connected to the selected pages.\",\n    },\n    {\n      key: 'we_recommend_you_to_connect_all_the_pages_and_all_the_businesses',\n      text: 'We recommend you to connect all the pages and all the businesses.',\n    },\n    {\n      key: 'please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again',\n      text: 'Please close this dialog, delete your integration and add a new channel again.',\n    },\n  ],\n  getItemId: (item) => item.id,\n  getSelectionValue: (item) => ({ id: item.id, pageId: item.pageId }),\n  transformSaveData: (selection) => selection,\n  isSelected: (item, selection) => selection?.id === item.id,\n  renderItem: (item) => (\n    <>\n      <div>\n        <img\n          className=\"w-full max-w-[156px]\"\n          src={item.picture.data.url}\n          alt=\"profile\"\n        />\n      </div>\n      <div>{item.name}</div>\n    </>\n  ),\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/linkedin/linkedin.continue.tsx",
    "content": "'use client';\n\nimport { withContinueProvider } from '../with-continue-provider';\n\ninterface LinkedinItem {\n  id: string;\n  pageId: string;\n  username: string;\n  name: string;\n  picture: string;\n}\n\ninterface LinkedinSelection {\n  id: string;\n  pageId: string;\n}\n\nexport const LinkedinContinue = withContinueProvider<\n  LinkedinItem,\n  LinkedinSelection\n>({\n  endpoint: 'companies',\n  swrKey: 'load-linkedin-pages',\n  titleKey: 'select_linkedin_page',\n  titleDefault: 'Select Linkedin Page:',\n  emptyStateMessages: [\n    {\n      key: 'we_couldn_t_find_any_business_connected_to_your_linkedin_page',\n      text: \"We couldn't find any business connected to your LinkedIn Page.\",\n    },\n    {\n      key: 'please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again',\n      text: 'Please close this dialog, create a new page, and add a new channel again.',\n    },\n  ],\n  getItemId: (item) => item.id,\n  getSelectionValue: (item) => ({ id: item.id, pageId: item.pageId }),\n  transformSaveData: (selection) => ({ page: selection.id }),\n  isSelected: (item, selection) => selection?.id === item.id,\n  renderItem: (item) => (\n    <>\n      <div>\n        <img className=\"w-full\" src={item.picture} alt=\"profile\" />\n      </div>\n      <div>{item.name}</div>\n    </>\n  ),\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/list.tsx",
    "content": "'use client';\n\nimport { InstagramContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/instagram/instagram.continue';\nimport { FacebookContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/facebook/facebook.continue';\nimport { LinkedinContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/linkedin/linkedin.continue';\nimport { GmbContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/gmb/gmb.continue';\nimport { YoutubeContinue } from '@gitroom/frontend/components/new-launch/providers/continue-provider/youtube/youtube.continue';\n\nexport const continueProviderList = {\n  instagram: InstagramContinue,\n  facebook: FacebookContinue,\n  'linkedin-page': LinkedinContinue,\n  gmb: GmbContinue,\n  youtube: YoutubeContinue,\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/with-continue-provider.tsx",
    "content": "'use client';\n\nimport { FC, ReactNode, useCallback, useMemo, useState } from 'react';\nimport useSWR from 'swr';\nimport clsx from 'clsx';\nimport { Button } from '@gitroom/react/form/button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\n\nconst SWR_OPTIONS = {\n  refreshWhenHidden: false,\n  refreshWhenOffline: false,\n  revalidateOnFocus: false,\n  revalidateIfStale: false,\n  revalidateOnMount: true,\n  revalidateOnReconnect: false,\n  refreshInterval: 0,\n};\n\nexport interface ContinueProviderProps {\n  onSave: (data: any) => Promise<void>;\n  existingId: string[];\n  initialData?: any[];\n  isSaving?: boolean;\n}\n\nexport interface EmptyStateMessage {\n  key: string;\n  text: string;\n}\n\nexport interface ContinueProviderConfig<TItem, TSelection> {\n  endpoint: string;\n  swrKey: string;\n  titleKey: string;\n  titleDefault: string;\n  emptyStateMessages: EmptyStateMessage[];\n  getSelectionValue: (item: TItem) => TSelection;\n  transformSaveData: (selection: TSelection) => any;\n  renderItem: (item: TItem, isSelected: boolean) => ReactNode;\n  isSelected: (item: TItem, selection: TSelection | null) => boolean;\n  getItemId: (item: TItem) => string;\n}\n\nexport function withContinueProvider<TItem, TSelection>(\n  config: ContinueProviderConfig<TItem, TSelection>\n): FC<ContinueProviderProps> {\n  const {\n    endpoint,\n    swrKey,\n    titleKey,\n    titleDefault,\n    emptyStateMessages,\n    getSelectionValue,\n    transformSaveData,\n    renderItem,\n    isSelected,\n    getItemId,\n  } = config;\n\n  return function ContinueProviderComponent(props: ContinueProviderProps) {\n    const { onSave, existingId, initialData, isSaving } = props;\n    const call = useCustomProviderFunction();\n    const t = useT();\n    const [selection, setSelection] = useState<TSelection | null>(null);\n\n    const loadData = useCallback(async () => {\n      // Skip fetch if initial data was provided\n      if (initialData) {\n        return initialData;\n      }\n      try {\n        return await call.get(endpoint);\n      } catch (e) {\n        // Handle error silently\n      }\n    }, [initialData]);\n\n    const { data, isLoading } = useSWR(\n      initialData ? null : swrKey,\n      loadData,\n      SWR_OPTIONS\n    );\n\n    const resolvedData = initialData || data;\n\n    const handleSelect = useCallback(\n      (item: TItem) => () => {\n        setSelection(getSelectionValue(item));\n      },\n      []\n    );\n\n    const handleSave = useCallback(async () => {\n      if (selection) {\n        await onSave(transformSaveData(selection));\n      }\n    }, [onSave, selection]);\n\n    const filteredData = useMemo(() => {\n      return (\n        (resolvedData as TItem[])?.filter(\n          (item) => !existingId.includes(getItemId(item))\n        ) || []\n      );\n    }, [resolvedData, existingId]);\n\n    if (!isLoading && !resolvedData?.length) {\n      return (\n        <div className=\"text-center flex flex-col justify-center items-center text-[18px] leading-[26px] h-[300px]\">\n          {emptyStateMessages.map((msg, index) => (\n            <span key={msg.key}>\n              {t(msg.key, msg.text)}\n              {index < emptyStateMessages.length - 1 && (\n                <>\n                  <br />\n                  <br />\n                </>\n              )}\n            </span>\n          ))}\n        </div>\n      );\n    }\n\n    return (\n      <div className=\"flex flex-col gap-[20px]\">\n        <div>{t(titleKey, titleDefault)}</div>\n        <div className=\"grid grid-cols-3 justify-items-center select-none cursor-pointer gap-[10px]\">\n          {filteredData.map((item) => (\n            <div\n              key={getItemId(item)}\n              className={clsx(\n                'flex flex-col w-full text-center gap-[10px] border border-input p-[10px] hover:bg-seventh rounded-[8px]',\n                isSelected(item, selection) && 'bg-seventh border-primary'\n              )}\n              onClick={handleSelect(item)}\n            >\n              {renderItem(item, isSelected(item, selection))}\n            </div>\n          ))}\n        </div>\n        <div>\n          <Button disabled={!selection || isSaving} loading={isSaving} onClick={handleSave}>\n            {t('save', 'Save')}\n          </Button>\n        </div>\n      </div>\n    );\n  };\n}\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/continue-provider/youtube/youtube.continue.tsx",
    "content": "'use client';\n\nimport { withContinueProvider } from '../with-continue-provider';\n\ninterface YoutubeItem {\n  id: string;\n  name: string;\n  username?: string;\n  subscriberCount?: string;\n  picture?: {\n    data: {\n      url: string;\n    };\n  };\n}\n\ninterface YoutubeSelection {\n  id: string;\n}\n\nexport const YoutubeContinue = withContinueProvider<\n  YoutubeItem,\n  YoutubeSelection\n>({\n  endpoint: 'pages',\n  swrKey: 'load-youtube-channels',\n  titleKey: 'select_channel',\n  titleDefault: 'Select YouTube Channel:',\n  emptyStateMessages: [\n    {\n      key: 'youtube_no_channels_found',\n      text: \"We couldn't find any YouTube channels connected to your account.\",\n    },\n    {\n      key: 'youtube_ensure_channel_exists',\n      text: 'Please ensure you have a YouTube channel created.',\n    },\n    {\n      key: 'youtube_try_again',\n      text: 'Please close this dialog, delete the integration and try again.',\n    },\n  ],\n  getItemId: (item) => item.id,\n  getSelectionValue: (item) => ({ id: item.id }),\n  transformSaveData: (selection) => selection,\n  isSelected: (item, selection) => selection?.id === item.id,\n  renderItem: (item) => (\n    <>\n      <div className=\"flex justify-center\">\n        {item.picture?.data?.url ? (\n          <img\n            className=\"w-[80px] h-[80px] object-cover rounded-full\"\n            src={item.picture.data.url}\n            alt={item.name}\n          />\n        ) : (\n          <div className=\"w-[80px] h-[80px] bg-input rounded-full flex items-center justify-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"40\"\n              height=\"40\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z\" />\n              <polygon points=\"9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02\" />\n            </svg>\n          </div>\n        )}\n      </div>\n      <div className=\"text-sm font-medium\">{item.name}</div>\n      {item.username && (\n        <div className=\"text-xs text-gray-500\">{item.username}</div>\n      )}\n      {item.subscriberCount && (\n        <div className=\"text-xs text-gray-400\">\n          {parseInt(item.subscriberCount).toLocaleString()} subscribers\n        </div>\n      )}\n    </>\n  ),\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/devto/devto.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto';\nimport { Input } from '@gitroom/react/form/input';\nimport { MediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { SelectOrganization } from '@gitroom/frontend/components/new-launch/providers/devto/select.organization';\nimport { DevtoTags } from '@gitroom/frontend/components/new-launch/providers/devto/devto.tags';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport clsx from 'clsx';\nimport { Canonical } from '@gitroom/react/form/canonical';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\n\nconst DevtoSettings: FC = () => {\n  const form = useSettings();\n  const { date } = useIntegration();\n  return (\n    <>\n      <Input label=\"Title\" {...form.register('title')} />\n      <Canonical\n        date={date}\n        label=\"Canonical Link\"\n        {...form.register('canonical')}\n      />\n      <MediaComponent\n        label=\"Cover picture\"\n        description=\"Add a cover picture\"\n        {...form.register('main_image')}\n      />\n      <div className=\"mt-[20px]\">\n        <SelectOrganization {...form.register('organization')} />\n      </div>\n      <div>\n        <DevtoTags\n          label=\"Tags (Maximum 4)\"\n          {...form.register('tags', {\n            value: [],\n          })}\n        />\n      </div>\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: DevtoSettings,\n  CustomPreviewComponent: undefined, // DevtoPreview,\n  dto: DevToSettingsDto,\n  checkValidity: undefined,\n  maximumCharacters: 100000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/devto/devto.tags.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useState } from 'react';\nimport { ReactTags } from 'react-tag-autocomplete';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const DevtoTags: FC<{\n  name: string;\n  label: string;\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, label } = props;\n  const form = useSettings();\n  const customFunc = useCustomProviderFunction();\n  const [tags, setTags] = useState<any[]>([]);\n  const { getValues } = useSettings();\n  const [tagValue, setTagValue] = useState<any[]>([]);\n  const onDelete = useCallback(\n    (tagIndex: number) => {\n      const modify = tagValue.filter((_, i) => i !== tagIndex);\n      setTagValue(modify);\n      form.setValue(name, modify);\n    },\n    [tagValue, name, form]\n  );\n  const onAddition = useCallback(\n    (newTag: any) => {\n      if (tagValue.length >= 4) {\n        return;\n      }\n      const modify = [...tagValue, newTag];\n      setTagValue(modify);\n      form.setValue(name, modify);\n    },\n    [tagValue, name, form]\n  );\n  useEffect(() => {\n    customFunc.get('tags').then((data) => setTags(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setTagValue(settings);\n    }\n  }, []);\n  if (!tags.length) {\n    return null;\n  }\n  return (\n    <div>\n      <div className={`text-[14px] mb-[6px]`}>{label}</div>\n      <ReactTags\n        suggestions={tags}\n        selected={tagValue}\n        onAdd={onAddition}\n        onDelete={onDelete}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/devto/select.organization.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const SelectOrganization: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('organizations').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select organization\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/discord/discord.channel.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const DiscordChannelSelect: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [publications, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('channels').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!publications.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Channel\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {publications.map((publication: any) => (\n        <option key={publication.id} value={publication.id}>\n          {publication.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/discord/discord.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC } from 'react';\nimport { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto';\nimport { DiscordChannelSelect } from '@gitroom/frontend/components/new-launch/providers/discord/discord.channel.select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nconst DiscordComponent: FC = () => {\n  const form = useSettings();\n  return (\n    <div>\n      <DiscordChannelSelect {...form.register('channel')} />\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: DiscordComponent,\n  CustomPreviewComponent: undefined,\n  dto: DiscordDto,\n  checkValidity: undefined,\n  maximumCharacters: 1980,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/dribbble/dribbble.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { DribbbleTeams } from '@gitroom/frontend/components/new-launch/providers/dribbble/dribbble.teams';\nimport { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';\nconst DribbbleSettings: FC = () => {\n  const { register, control } = useSettings();\n  return (\n    <div className=\"flex flex-col\">\n      <Input label={'Title'} {...register('title')} />\n      <DribbbleTeams {...register('team')} />\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: DribbbleSettings,\n  CustomPreviewComponent: undefined,\n  dto: DribbbleDto,\n  checkValidity: async ([firstItem, ...otherItems] = []) => {\n    const isMp4 = firstItem?.find((item) => (item?.path?.indexOf?.('mp4') ?? -1) > -1);\n    if (firstItem?.length !== 1) {\n      return 'Requires one item';\n    }\n    if (isMp4) {\n      return 'Does not support mp4 files';\n    }\n    const details = await new Promise<{\n      width: number;\n      height: number;\n    }>((resolve, reject) => {\n      const url = new Image();\n      url.onload = function () {\n        // @ts-ignore\n        resolve({ width: this.width, height: this.height });\n      };\n      url.src = firstItem?.[0]?.path;\n    });\n    if (\n      (details?.width === 400 && details?.height === 300) ||\n      (details?.width === 800 && details?.height === 600)\n    ) {\n      return true;\n    }\n    return 'Invalid image size. Requires 400x300 or 800x600 px images.';\n  },\n  maximumCharacters: 40000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/dribbble/dribbble.teams.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const DribbbleTeams: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState<undefined | any[]>();\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('teams').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs) {\n    return null;\n  }\n  if (!orgs.length) {\n    return <></>;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select a team\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/facebook/facebook.preview.tsx",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { FC } from 'react';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\n\nconst Icons = () => {\n  return (\n    <svg\n      width=\"31\"\n      height=\"16\"\n      viewBox=\"0 0 31 16\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.84344 15.1571 5.87827 16 8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0Z\"\n        fill=\"url(#paint0_linear_2511_139661)\"\n      />\n      <path\n        d=\"M12.162 7.338C12.338 7.461 12.5 7.583 12.5 8.012C12.5 8.442 12.271 8.616 12.026 8.737C12.1262 8.90028 12.1581 9.09637 12.115 9.283C12.038 9.627 11.723 9.894 11.443 9.973C11.564 10.167 11.602 10.358 11.458 10.593C11.273 10.888 11.112 11 10.4 11H7.5C6.512 11 6 10.454 6 10V7.665C6 6.435 7.467 5.39 7.467 4.535L7.361 3.47C7.356 3.405 7.369 3.246 7.419 3.2C7.499 3.121 7.72 3 8.054 3C8.272 3 8.417 3.041 8.588 3.123C9.169 3.4 9.32 4.101 9.32 4.665C9.32 4.936 8.906 5.748 8.85 6.029C8.85 6.029 9.717 5.837 10.729 5.83C11.79 5.824 12.478 6.02 12.478 6.672C12.478 6.933 12.259 7.195 12.162 7.338ZM3.6 7H4.4C4.55913 7 4.71174 7.06321 4.82426 7.17574C4.93679 7.28826 5 7.44087 5 7.6V11.4C5 11.5591 4.93679 11.7117 4.82426 11.8243C4.71174 11.9368 4.55913 12 4.4 12H3.6C3.44087 12 3.28826 11.9368 3.17574 11.8243C3.06321 11.7117 3 11.5591 3 11.4V7.6C3 7.44087 3.06321 7.28826 3.17574 7.17574C3.28826 7.06321 3.44087 7 3.6 7Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M23 0C20.8783 0 18.8434 0.842855 17.3431 2.34315C15.8429 3.84344 15 5.87827 15 8C15 10.1217 15.8429 12.1566 17.3431 13.6569C18.8434 15.1571 20.8783 16 23 16C25.1217 16 27.1566 15.1571 28.6569 13.6569C30.1571 12.1566 31 10.1217 31 8C31 5.87827 30.1571 3.84344 28.6569 2.34315C27.1566 0.842855 25.1217 0 23 0Z\"\n        fill=\"url(#paint1_linear_2511_139661)\"\n      />\n      <path\n        d=\"M25.473 4C23.275 4 23 5.824 23 5.824C23 5.824 22.726 4 20.528 4C18.414 4 17.798 6.222 18.056 7.41C18.736 10.55 23 12.75 23 12.75C23 12.75 27.265 10.55 27.945 7.41C28.202 6.222 27.585 4 25.473 4Z\"\n        fill=\"white\"\n      />\n      <defs>\n        <linearGradient\n          id=\"paint0_linear_2511_139661\"\n          x1=\"8\"\n          y1=\"0\"\n          x2=\"8\"\n          y2=\"16\"\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"#18AFFF\" />\n          <stop offset=\"1\" stopColor=\"#0062DF\" />\n        </linearGradient>\n        <linearGradient\n          id=\"paint1_linear_2511_139661\"\n          x1=\"23\"\n          y1=\"0\"\n          x2=\"23\"\n          y2=\"16\"\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"#FF6680\" />\n          <stop offset=\"1\" stopColor=\"#E61739\" />\n        </linearGradient>\n      </defs>\n    </svg>\n  );\n};\n\nexport const FacebookPreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n  return (\n    <div className=\"py-[15px] flex flex-col px-[15px] w-full gap-[20px] bg-bgFacebook rounded-[12px]\">\n      <div className=\"flex gap-[8px]\">\n        <div className=\"w-[36px] h-[36px]\">\n          <img\n            src={integration?.picture || '/no-picture.jpg'}\n            alt=\"social\"\n            className=\"rounded-full relative z-[2] w-[36px] h-[36px]\"\n          />\n        </div>\n        <div className=\"flex flex-col leading-[18px]\">\n          <div className=\"text-[14px] font-[500]\">{integration?.name}</div>\n          <div className=\"text-[12px] font-[400] text-[#A3A3A3] flex gap-[4px] items-center\">\n            <span>30m •</span>\n            <span>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"10\"\n                height=\"10\"\n                viewBox=\"0 0 10 10\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M5 10C4.30833 10 3.65833 9.86867 3.05 9.606C2.44167 9.34367 1.9125 8.9875 1.4625 8.5375C1.0125 8.0875 0.656333 7.55833 0.394 6.95C0.131333 6.34167 0 5.69167 0 5C0 4.30833 0.131333 3.65833 0.394 3.05C0.656333 2.44167 1.0125 1.9125 1.4625 1.4625C1.9125 1.0125 2.44167 0.656167 3.05 0.3935C3.65833 0.131167 4.30833 0 5 0C5.69167 0 6.34167 0.131167 6.95 0.3935C7.55833 0.656167 8.0875 1.0125 8.5375 1.4625C8.9875 1.9125 9.34367 2.44167 9.606 3.05C9.86867 3.65833 10 4.30833 10 5C10 5.69167 9.86867 6.34167 9.606 6.95C9.34367 7.55833 8.9875 8.0875 8.5375 8.5375C8.0875 8.9875 7.55833 9.34367 6.95 9.606C6.34167 9.86867 5.69167 10 5 10ZM4.5 8.975V8C4.225 8 3.98967 7.90217 3.794 7.7065C3.598 7.5105 3.5 7.275 3.5 7V6.5L1.1 4.1C1.075 4.25 1.052 4.4 1.031 4.55C1.01033 4.7 1 4.85 1 5C1 6.00833 1.33133 6.89167 1.994 7.65C2.65633 8.40833 3.49167 8.85 4.5 8.975ZM7.95 7.7C8.11667 7.51667 8.26667 7.31867 8.4 7.106C8.53333 6.89367 8.64383 6.67283 8.7315 6.4435C8.81883 6.2145 8.8855 5.97917 8.9315 5.7375C8.97717 5.49583 9 5.25 9 5C9 4.18333 8.773 3.4375 8.319 2.7625C7.86467 2.0875 7.25833 1.6 6.5 1.3V1.5C6.5 1.775 6.40217 2.01033 6.2065 2.206C6.0105 2.402 5.775 2.5 5.5 2.5H4.5V3.5C4.5 3.64167 4.45217 3.76033 4.3565 3.856C4.2605 3.952 4.14167 4 4 4H3V5H6C6.14167 5 6.2605 5.04783 6.3565 5.1435C6.45217 5.2395 6.5 5.35833 6.5 5.5V7H7C7.21667 7 7.4125 7.0645 7.5875 7.1935C7.7625 7.32283 7.88333 7.49167 7.95 7.7Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div\n        className=\"text-[14px] font-[400] whitespace-pre-line\"\n        dangerouslySetInnerHTML={{\n          __html: renderContent?.[0]?.text,\n        }}\n      />\n      {!!renderContent?.[0]?.images?.length && (\n        <div className=\"h-[280px] -mx-[15px] overflow-hidden flex\">\n          {renderContent?.[0]?.images.map((image, index) => (\n            <a\n              key={`image_${index}`}\n              className=\"flex-1\"\n              href={mediaDir.set(image.path)}\n              target=\"_blank\"\n            >\n              <VideoOrImage autoplay={true} src={mediaDir.set(image.path)} />\n            </a>\n          ))}\n        </div>\n      )}\n      <div className=\"flex text-textLinkedin text-[12px] font-[400] items-center\">\n        <div className=\"flex flex-1 gap-[10px] items-center\">\n          <Icons />\n          <div className=\"\">You & 12 other</div>\n        </div>\n        <div className=\"gap-[9px] items-center flex\">\n          <div>20 Comments</div>\n        </div>\n      </div>\n      <div className=\"pt-[8px] flex text-[14px] font-[700] px-[32px] justify-between border-t border-borderLinkedin text-textLinkedin\">\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M20.2983 12.23C20.5762 12.435 20.832 12.6383 20.832 13.3533C20.832 14.07 20.4705 14.36 20.0836 14.5617C20.2417 14.8338 20.2922 15.1606 20.2241 15.4717C20.1026 16.045 19.6052 16.49 19.1631 16.6217C19.3541 16.945 19.4141 17.2633 19.1868 17.655C18.8947 18.1467 18.6405 18.3333 17.5162 18.3333H12.9373C11.3773 18.3333 10.5689 17.4233 10.5689 16.6667V12.775C10.5689 10.725 12.8852 8.98333 12.8852 7.55833L12.7178 5.78333C12.7099 5.675 12.7305 5.41 12.8094 5.33333C12.9357 5.20167 13.2847 5 13.812 5C14.1562 5 14.3852 5.06833 14.6552 5.205C15.5726 5.66667 15.811 6.835 15.811 7.775C15.811 8.22667 15.1573 9.58 15.0689 10.0483C15.0689 10.0483 16.4378 9.72833 18.0357 9.71667C19.711 9.70667 20.7973 10.0333 20.7973 11.12C20.7973 11.555 20.4515 11.9917 20.2983 12.23ZM6.7794 11.6667H8.04256C8.29382 11.6667 8.53478 11.772 8.71245 11.9596C8.89011 12.1471 8.98993 12.4015 8.98993 12.6667V19C8.98993 19.2652 8.89011 19.5196 8.71245 19.7071C8.53478 19.8946 8.29382 20 8.04256 20H6.7794C6.52814 20 6.28718 19.8946 6.10951 19.7071C5.93184 19.5196 5.83203 19.2652 5.83203 19V12.6667C5.83203 12.4015 5.93184 12.1471 6.10951 11.9596C6.28718 11.772 6.52814 11.6667 6.7794 11.6667Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n          <div>Like</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <mask\n              id=\"mask0_2511_139673\"\n              style={{ maskType: 'alpha' }}\n              maskUnits=\"userSpaceOnUse\"\n              x=\"-1\"\n              y=\"2\"\n              width=\"21\"\n              height=\"21\"\n            >\n              <rect\n                x=\"-0.195312\"\n                y=\"2.88281\"\n                width=\"19.6964\"\n                height=\"19.6964\"\n                fill=\"currentColor\"\n              />\n            </mask>\n            <g mask=\"url(#mask0_2511_139673)\">\n              <path\n                d=\"M1.44531 20.9371V6.1648C1.44531 5.71343 1.60617 5.32689 1.92787 5.00518C2.24903 4.68402 2.6353 4.52344 3.08668 4.52344H16.2176C16.669 4.52344 17.0555 4.68402 17.3772 5.00518C17.6984 5.32689 17.859 5.71343 17.859 6.1648V16.013C17.859 16.4644 17.6984 16.8509 17.3772 17.1726C17.0555 17.4938 16.669 17.6544 16.2176 17.6544H4.72805L1.44531 20.9371ZM3.08668 16.9773L4.05098 16.013H16.2176V6.1648H3.08668V16.9773Z\"\n                fill=\"currentColor\"\n              />\n            </g>\n          </svg>\n          <div>Comments</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <path\n              d=\"M4.21168 20.2107C4.14582 20.2111 4.08035 20.2005 4.01793 20.1795C3.88741 20.1371 3.77445 20.0529 3.69651 19.94C3.61857 19.827 3.57998 19.6915 3.58668 19.5545C3.58668 19.4607 4.23043 10.3857 13.3304 9.6732V6.4107C13.3303 6.28645 13.3673 6.16498 13.4365 6.06181C13.5058 5.95864 13.6042 5.87846 13.7192 5.8315C13.8343 5.78455 13.9607 5.77295 14.0824 5.79819C14.2041 5.82343 14.3154 5.88436 14.4023 5.9732L20.5711 12.2732C20.6856 12.39 20.7497 12.5471 20.7497 12.7107C20.7497 12.8743 20.6856 13.0314 20.5711 13.1482L14.4023 19.4482C14.3154 19.537 14.2041 19.598 14.0824 19.6232C13.9607 19.6485 13.8343 19.6369 13.7192 19.5899C13.6042 19.5429 13.5058 19.4628 13.4365 19.3596C13.3673 19.2564 13.3303 19.135 13.3304 19.0107V15.8107C7.25543 16.042 4.76481 19.8732 4.73981 19.9201C4.68342 20.0091 4.60544 20.0825 4.5131 20.1333C4.42076 20.1841 4.31708 20.2107 4.21168 20.2107ZM14.5804 7.94195V10.2576C14.5805 10.4196 14.5177 10.5754 14.4052 10.692C14.2927 10.8086 14.1392 10.8769 13.9773 10.8826C8.08981 11.0982 5.98356 15.0201 5.22731 17.542C6.78981 16.192 9.57418 14.542 13.9179 14.542H13.9461C14.1118 14.542 14.2708 14.6078 14.388 14.725C14.5052 14.8422 14.5711 15.0012 14.5711 15.167V17.4826L19.2586 12.7138L14.5804 7.94195Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n          <div>Share</div>\n        </div>\n      </div>\n      {renderContent.length > 1 && (\n        <>\n          <div className=\"flex items-center\">\n            <div className=\"text-[14px] font-[700]\">Most relevant</div>\n            <div>\n              <svg\n                width=\"20\"\n                height=\"20\"\n                viewBox=\"0 0 20 20\"\n                fill=\"currentColor\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M12.5437 8L7.4563 8C7.05059 8 6.84741 8.56798 7.13429 8.90016L9.67799 11.8456C9.85583 12.0515 10.1442 12.0515 10.322 11.8456L12.8657 8.90016C13.1526 8.56798 12.9494 8 12.5437 8Z\"\n                  fill=\"#A3A3A3\"\n                />\n              </svg>\n            </div>\n          </div>\n          {renderContent.slice(1).map((value, index) => (\n            <div key={index} className=\"flex flex-col gap-[12px]\">\n              <div className=\"flex gap-[6px] leading-[17px]\">\n                <div className=\"h-[34px]\">\n                  <img\n                    src={integration?.picture || '/no-picture.jpg'}\n                    alt=\"social\"\n                    className=\"rounded-full relative z-[2] h-[34px] w-[34px]\"\n                  />\n                </div>\n                <div className=\"flex flex-col gap-[6px] min-w-[150px]\">\n                  <div className=\"flex flex-col gap-[4px] bg-bgCommentFacebook py-[8px] px-[12px] rounded-[12px]\">\n                    <div className=\"flex flex-col\">\n                      <div className=\"flex items-center gap-[6px]\">\n                        <div className=\"text-[13px] font-[500]\">\n                          {integration?.name}\n                        </div>\n                      </div>\n                    </div>\n                    <div\n                      className=\"whitespace-pre-line text-[14px] font-[400]\"\n                      dangerouslySetInnerHTML={{\n                        __html: value.text,\n                      }}\n                    />\n                    {!!value.images?.length && (\n                      <div className=\"h-[100px] mt-[12px] -mx-[15px] overflow-hidden flex\">\n                        {value.images.map((image, index) => (\n                          <a\n                            key={`image_${index}`}\n                            className=\"flex-1\"\n                            href={mediaDir.set(image.path)}\n                            target=\"_blank\"\n                          >\n                            <VideoOrImage\n                              autoplay={true}\n                              src={mediaDir.set(image.path)}\n                            />\n                          </a>\n                        ))}\n                      </div>\n                    )}\n                  </div>\n                  <div className=\"flex font-[400] text-[12px] text-textLinkedin items-center\">\n                    <div className=\"flex gap-[16px] flex-1\">\n                      <div className=\"font-[700]\">9h</div>\n                      <div className=\"font-[700]\">Like</div>\n                      <div className=\"font-[700]\">Reply</div>\n                    </div>\n                    <div className=\"flex gap-[4px]\">\n                      <div>2</div>\n                      <div>\n                        <svg\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          width=\"16\"\n                          height=\"16\"\n                          viewBox=\"0 0 16 16\"\n                          fill=\"none\"\n                        >\n                          <path\n                            d=\"M8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.84344 15.1571 5.87827 16 8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0Z\"\n                            fill=\"url(#paint0_linear_2511_139920)\"\n                          />\n                          <path\n                            d=\"M10.473 4C8.275 4 8 5.824 8 5.824C8 5.824 7.726 4 5.528 4C3.414 4 2.798 6.222 3.056 7.41C3.736 10.55 8 12.75 8 12.75C8 12.75 12.265 10.55 12.945 7.41C13.202 6.222 12.585 4 10.473 4Z\"\n                            fill=\"white\"\n                          />\n                          <defs>\n                            <linearGradient\n                              id=\"paint0_linear_2511_139920\"\n                              x1=\"8\"\n                              y1=\"0\"\n                              x2=\"8\"\n                              y2=\"16\"\n                              gradientUnits=\"userSpaceOnUse\"\n                            >\n                              <stop stopColor=\"#FF6680\" />\n                              <stop offset=\"1\" stopColor=\"#E61739\" />\n                            </linearGradient>\n                          </defs>\n                        </svg>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          ))}\n        </>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/facebook/facebook.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FacebookDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/facebook.dto';\nimport { Input } from '@gitroom/react/form/input';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { FacebookPreview } from '@gitroom/frontend/components/new-launch/providers/facebook/facebook.preview';\n\nexport const FacebookSettings = () => {\n  const { register } = useSettings();\n\n  return (\n    <Input\n      label={\n        'Embedded URL (only for text Post)'\n      }\n      {...register('url')}\n    />\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: FacebookSettings,\n  CustomPreviewComponent: FacebookPreview,\n  dto: FacebookDto,\n  checkValidity: undefined,\n  maximumCharacters: 63206,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/gmb/gmb.provider.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { GmbSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/gmb.settings.dto';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { Select } from '@gitroom/react/form/select';\nimport { useWatch } from 'react-hook-form';\n\nconst topicTypes = [\n  {\n    label: 'Standard Update',\n    value: 'STANDARD',\n  },\n  {\n    label: 'Event',\n    value: 'EVENT',\n  },\n  {\n    label: 'Offer',\n    value: 'OFFER',\n  },\n];\n\nconst callToActionTypes = [\n  {\n    label: 'None',\n    value: 'NONE',\n  },\n  {\n    label: 'Book',\n    value: 'BOOK',\n  },\n  {\n    label: 'Order Online',\n    value: 'ORDER',\n  },\n  {\n    label: 'Shop',\n    value: 'SHOP',\n  },\n  {\n    label: 'Learn More',\n    value: 'LEARN_MORE',\n  },\n  {\n    label: 'Sign Up',\n    value: 'SIGN_UP',\n  },\n  {\n    label: 'Get Offer',\n    value: 'GET_OFFER',\n  },\n  {\n    label: 'Call',\n    value: 'CALL',\n  },\n];\n\nconst GmbSettings: FC = () => {\n  const { register, control } = useSettings();\n  const topicType = useWatch({ control, name: 'topicType' });\n  const callToActionType = useWatch({ control, name: 'callToActionType' });\n\n  return (\n    <div className=\"flex flex-col gap-[10px]\">\n      <Select\n        label=\"Post Type\"\n        {...register('topicType', {\n          value: 'STANDARD',\n        })}\n      >\n        {topicTypes.map((t) => (\n          <option key={t.value} value={t.value}>\n            {t.label}\n          </option>\n        ))}\n      </Select>\n\n      <Select\n        label=\"Call to Action\"\n        {...register('callToActionType', {\n          value: 'NONE',\n        })}\n      >\n        {callToActionTypes.map((t) => (\n          <option key={t.value} value={t.value}>\n            {t.label}\n          </option>\n        ))}\n      </Select>\n\n      {callToActionType &&\n        callToActionType !== 'NONE' &&\n        callToActionType !== 'CALL' && (\n          <Input\n            label=\"Call to Action URL\"\n            placeholder=\"https://example.com\"\n            {...register('callToActionUrl')}\n          />\n        )}\n\n      {topicType === 'EVENT' && (\n        <div className=\"flex flex-col gap-[10px] mt-[10px] p-[15px] border border-input rounded-[8px]\">\n          <div className=\"text-[14px] font-medium mb-[5px]\">Event Details</div>\n          <Input\n            label=\"Event Title\"\n            placeholder=\"Event name\"\n            {...register('eventTitle')}\n          />\n          <div className=\"grid grid-cols-2 gap-[10px]\">\n            <Input\n              label=\"Start Date\"\n              type=\"date\"\n              {...register('eventStartDate')}\n            />\n            <Input label=\"End Date\" type=\"date\" {...register('eventEndDate')} />\n          </div>\n          <div className=\"grid grid-cols-2 gap-[10px]\">\n            <Input\n              label=\"Start Time (optional)\"\n              type=\"time\"\n              {...register('eventStartTime')}\n            />\n            <Input\n              label=\"End Time (optional)\"\n              type=\"time\"\n              {...register('eventEndTime')}\n            />\n          </div>\n        </div>\n      )}\n\n      {topicType === 'OFFER' && (\n        <div className=\"flex flex-col gap-[10px] mt-[10px] p-[15px] border border-input rounded-[8px]\">\n          <div className=\"text-[14px] font-medium mb-[5px]\">Offer Details</div>\n          <Input\n            label=\"Coupon Code (optional)\"\n            placeholder=\"SAVE20\"\n            {...register('offerCouponCode')}\n          />\n          <Input\n            label=\"Redeem Online URL (optional)\"\n            placeholder=\"https://example.com/redeem\"\n            {...register('offerRedeemUrl')}\n          />\n          <Input\n            label=\"Terms & Conditions (optional)\"\n            placeholder=\"Valid until...\"\n            {...register('offerTerms')}\n          />\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: GmbSettings,\n  CustomPreviewComponent: undefined,\n  dto: GmbSettingsDto,\n  checkValidity: async (items, settings: any) => {\n    // GMB posts can have text only, or text with one image\n    if ((items?.length ?? 0) > 0 && (items?.[0]?.length ?? 0) > 1) {\n      return 'Google My Business posts can only have one image';\n    }\n\n    // Check for video - GMB doesn't support video in local posts\n    if ((items?.length ?? 0) > 0 && (items?.[0]?.length ?? 0) > 0) {\n      const media = items?.[0]?.[0];\n      if ((media?.path?.indexOf?.('mp4') ?? -1) > -1) {\n        return 'Google My Business posts do not support video attachments';\n      }\n    }\n\n    // Event posts require a title\n    if (settings?.topicType === 'EVENT' && !settings?.eventTitle) {\n      return 'Event posts require an event title';\n    }\n\n    return true;\n  },\n  maximumCharacters: 1500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/hashnode/hashnode.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { HashnodePublications } from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.publications';\nimport { HashnodeTags } from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.tags';\nimport { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport clsx from 'clsx';\nimport { MediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { Canonical } from '@gitroom/react/form/canonical';\n\nconst HashnodeSettings: FC = () => {\n  const form = useSettings();\n  const { date } = useIntegration();\n  return (\n    <>\n      <Input label=\"Title\" {...form.register('title')} />\n      <Input label=\"Subtitle\" {...form.register('subtitle')} />\n      <Canonical\n        date={date}\n        label=\"Canonical Link\"\n        {...form.register('canonical')}\n      />\n      <MediaComponent\n        label=\"Cover picture\"\n        description=\"Add a cover picture\"\n        {...form.register('main_image')}\n      />\n      <div className=\"mt-[20px]\">\n        <HashnodePublications {...form.register('publication')} />\n      </div>\n      <div>\n        <HashnodeTags label=\"Tags\" {...form.register('tags')} />\n      </div>\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: HashnodeSettings,\n  CustomPreviewComponent: undefined, // HashnodePreview,\n  dto: HashnodeSettingsDto,\n  checkValidity: undefined,\n  maximumCharacters: 10000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/hashnode/hashnode.publications.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const HashnodePublications: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [publications, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('publications').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!publications.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select publication\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {(publications || []).map((publication: any) => (\n        <option key={publication.id} value={publication.id}>\n          {publication.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/hashnode/hashnode.tags.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { ReactTags } from 'react-tag-autocomplete';\n\nexport const HashnodeTags: FC<{\n  name: string;\n  label: string;\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, label } = props;\n  const customFunc = useCustomProviderFunction();\n  const [tags, setTags] = useState<any[]>([]);\n  const { getValues, formState: form } = useSettings();\n  const [tagValue, setTagValue] = useState<any[]>([]);\n  const onDelete = useCallback(\n    (tagIndex: number) => {\n      const modify = tagValue.filter((_, i) => i !== tagIndex);\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  const onAddition = useCallback(\n    (newTag: any) => {\n      if (tagValue.length >= 4) {\n        return;\n      }\n      const modify = [...tagValue, newTag];\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  useEffect(() => {\n    customFunc.get('tags').then((data) => setTags(data));\n    const settings = getValues()[props.name] || [];\n    if (settings) {\n      setTagValue(settings);\n    }\n  }, []);\n  const err = useMemo(() => {\n    if (!form || !form.errors[props?.name!]) return;\n    return form?.errors?.[props?.name!]?.message! as string;\n  }, [form?.errors?.[props?.name!]?.message]);\n  if (!tags.length) {\n    return null;\n  }\n\n  return (\n    <div>\n      <div className={`text-[14px] mb-[6px]`}>{label}</div>\n      <ReactTags\n        suggestions={tags || []}\n        selected={tagValue || []}\n        onAdd={onAddition}\n        onDelete={onDelete}\n      />\n      <div className=\"text-red-400 text-[12px]\">{err || <>&nbsp;</>}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/high.order.provider.tsx",
    "content": "'use client';\n\nimport React, {\n  FC,\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useMemo,\n} from 'react';\nimport { useForm, FormProvider } from 'react-hook-form';\nimport { IsOptional } from 'class-validator';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component';\nimport { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { InternalChannels } from '@gitroom/frontend/components/launches/internal.channels';\nimport { createPortal } from 'react-dom';\nimport clsx from 'clsx';\nimport Image from 'next/image';\n\nclass Empty {\n  @IsOptional()\n  empty: string;\n}\n\nexport enum PostComment {\n  ALL,\n  POST,\n  COMMENT,\n}\n\ninterface CharacterCondition {\n  format: 'no-pictures' | 'with-pictures';\n  type: 'post' | 'comment';\n  maximumCharacters: number;\n}\n\nexport const withProvider = function <T extends object>(params: {\n  comments?: boolean | 'no-media';\n  postComment: PostComment;\n  minimumCharacters: CharacterCondition[];\n  SettingsComponent: FC<{\n    values?: any;\n  }> | null;\n  CustomPreviewComponent?: FC<{\n    maximumCharacters?: number;\n  }>;\n  dto?: any;\n  checkValidity?: (\n    value: Array<\n      Array<{\n        path: string;\n        thumbnail?: string;\n      }>\n    >,\n    settings: T,\n    additionalSettings: any\n  ) => Promise<string | true>;\n  maximumCharacters?: number | ((settings: any) => number);\n}) {\n  const {\n    postComment,\n    SettingsComponent,\n    CustomPreviewComponent,\n    dto,\n    checkValidity,\n    maximumCharacters,\n  } = params;\n\n  return forwardRef((props: { id: string }, ref) => {\n    const t = useT();\n    const fetch = useFetch();\n    const {\n      current,\n      selectedIntegration,\n      setCurrent,\n      internal,\n      global,\n      date,\n      isGlobal,\n      tab,\n      setTotalChars,\n      justCurrent,\n      allIntegrations,\n      setPostComment,\n      setEditor,\n      dummy,\n      setChars,\n      setComments,\n      setHide,\n    } = useLaunchStore(\n      useShallow((state) => ({\n        date: state.date,\n        tab: state.tab,\n        global: state.global,\n        dummy: state.dummy,\n        internal: state.internal.find((p) => p.integration.id === props.id),\n        integrations: state.selectedIntegrations,\n        setHide: state.setHide,\n        allIntegrations: state.integrations,\n        justCurrent: state.current,\n        current: state.current === props.id,\n        isGlobal: state.current === 'global',\n        setCurrent: state.setCurrent,\n        setComments: state.setComments,\n        setTotalChars: state.setTotalChars,\n        setPostComment: state.setPostComment,\n        setEditor: state.setEditor,\n        setChars: state.setChars,\n        selectedIntegration: state.selectedIntegrations.find(\n          (p) => p.integration.id === props.id\n        ),\n      }))\n    );\n\n    useEffect(() => {\n      if (!setTotalChars) {\n        return;\n      }\n\n      setChars(\n        props.id,\n        typeof maximumCharacters === 'number'\n          ? maximumCharacters\n          : maximumCharacters(\n              JSON.parse(\n                selectedIntegration.integration.additionalSettings || '[]'\n              )\n            )\n      );\n\n      if (isGlobal) {\n        setComments(true);\n        setPostComment(PostComment.ALL);\n        setTotalChars(0);\n        setEditor('normal');\n      }\n\n      if (current) {\n        setComments(\n          typeof params.comments === 'undefined' ? true : params.comments\n        );\n        setEditor(selectedIntegration?.integration.editor);\n        setPostComment(postComment);\n        setTotalChars(\n          typeof maximumCharacters === 'number'\n            ? maximumCharacters\n            : maximumCharacters(\n                JSON.parse(\n                  selectedIntegration.integration.additionalSettings || '[]'\n                )\n              )\n        );\n      }\n    }, [justCurrent, current, isGlobal, setTotalChars]);\n\n    const getInternalPlugs = useCallback(async () => {\n      return (\n        await fetch(\n          `/integrations/${selectedIntegration.integration.identifier}/internal-plugs`\n        )\n      ).json();\n    }, [selectedIntegration.integration.identifier]);\n    const { data, isLoading } = useSWR(\n      `internal-${selectedIntegration.integration.identifier}`,\n      getInternalPlugs,\n      {\n        revalidateOnReconnect: true,\n      }\n    );\n\n    const value = useMemo(() => {\n      if (internal?.integrationValue?.length) {\n        return internal.integrationValue;\n      }\n\n      return global;\n    }, [internal, global, isGlobal]);\n\n    const form = useForm({\n      resolver: classValidatorResolver(dto || Empty),\n      ...(Object.keys(selectedIntegration.settings).length > 0\n        ? { values: { ...selectedIntegration.settings } }\n        : {}),\n      mode: 'all',\n      criteriaMode: 'all',\n      reValidateMode: 'onChange',\n    });\n\n    useImperativeHandle(\n      ref,\n      () => ({\n        isValid: async () => {\n          const settings = form.getValues();\n          return {\n            id: props.id,\n            identifier: selectedIntegration.integration.identifier,\n            integration: selectedIntegration.integration,\n            valid: await form.trigger(),\n            err: form.formState.errors,\n            errors: checkValidity\n              ? await checkValidity(\n                  value.map((p) => p.media || []),\n                  settings,\n                  JSON.parse(\n                    selectedIntegration.integration.additionalSettings || '[]'\n                  )\n                )\n              : true,\n            settings,\n            values: value,\n            maximumCharacters:\n              typeof maximumCharacters === 'number'\n                ? maximumCharacters\n                : maximumCharacters(\n                    JSON.parse(\n                      selectedIntegration.integration.additionalSettings || '[]'\n                    )\n                  ),\n            fix: () => {\n              setCurrent(props.id);\n              setHide(true);\n            },\n            preview: () => {\n              setCurrent(props.id);\n              setHide(true);\n            },\n          };\n        },\n        getValues: () => {\n          return {\n            id: props.id,\n            identifier: selectedIntegration.integration.identifier,\n            values: value,\n            settings: form.getValues(),\n          };\n        },\n        trigger: () => {\n          return form.trigger();\n        },\n      }),\n      [value]\n    );\n\n    return (\n      <IntegrationContext.Provider\n        value={{\n          date,\n          integration: selectedIntegration.integration,\n          allIntegrations,\n          value: value.map((p) => ({\n            id: p.id,\n            content: p.content,\n            image: p.media,\n          })),\n        }}\n      >\n        <FormProvider {...form}>\n          <div\n            className={clsx(\n              'border border-borderPreview rounded-[12px] shadow-previewShadow',\n              !current && 'hidden'\n            )}\n          >\n            {current &&\n              (tab === 0 ||\n                (!SettingsComponent && !data?.internalPlugs?.length)) &&\n              !value?.[0]?.content?.length && (\n                <div>\n                  {t(\n                    'start_writing_your_post',\n                    'Start writing your post for a preview'\n                  )}\n                </div>\n              )}\n            {current &&\n              (tab === 0 ||\n                (!SettingsComponent && !data?.internalPlugs?.length)) &&\n              !!value?.[0]?.content?.length &&\n              (CustomPreviewComponent ? (\n                <CustomPreviewComponent\n                  maximumCharacters={\n                    typeof maximumCharacters === 'number'\n                      ? maximumCharacters\n                      : maximumCharacters(\n                          JSON.parse(\n                            selectedIntegration.integration\n                              .additionalSettings || '[]'\n                          )\n                        )\n                  }\n                />\n              ) : (\n                <GeneralPreviewComponent\n                  maximumCharacters={\n                    typeof maximumCharacters === 'number'\n                      ? maximumCharacters\n                      : maximumCharacters(\n                          JSON.parse(\n                            selectedIntegration.integration\n                              .additionalSettings || '[]'\n                          )\n                        )\n                  }\n                />\n              ))}\n            {(SettingsComponent || !!data?.internalPlugs?.length) &&\n              createPortal(\n                <div data-id={props.id} className={isGlobal ? 'bg-newSettings pb-[12px] px-[12px]' : 'hidden bg-newSettings px-[12px] pb-[12px]'}>\n                  {isGlobal && (\n                    <style>{`#wrapper-settings {display: flex !important} #social-empty {display: block !important;}`}</style>\n                  )}\n                  {isGlobal && (\n                    <div className=\"flex py-[20px] items-center gap-[15px]\">\n                      <div className=\"relative\">\n                        <Image\n                          alt={selectedIntegration?.integration.name!}\n                          width={42}\n                          height={42}\n                          className=\"min-w-[42px] min-h-[42px] w-[42px] h-[42px] rounded-full\"\n                          src={selectedIntegration?.integration.picture}\n                        />\n                        <Image\n                          alt={selectedIntegration?.integration.identifier}\n                          width={16}\n                          height={16}\n                          className=\"rounded-[16px] min-w-[16px] min-h-[16px] w-[16px] h-[16px] absolute bottom-0 end-0\"\n                          src={`/icons/platforms/${selectedIntegration?.integration.identifier}.png`}\n                        />\n                      </div>\n                      <div className=\"text-[20px]\">{selectedIntegration?.integration.name}</div>\n                    </div>\n                  )}\n                  <SettingsComponent />\n                  {!!data?.internalPlugs?.length && !dummy && (\n                    <InternalChannels plugs={data?.internalPlugs} />\n                  )}\n                </div>,\n                document.querySelector('#social-settings') ||\n                  document.createElement('div')\n              )}\n            {current &&\n              !SettingsComponent &&\n              createPortal(\n                <style>{`#wrapper-settings {display: none !important;} #social-empty {display: block !important;}`}</style>,\n                document.querySelector('#social-settings') ||\n                  document.createElement('div')\n              )}\n          </div>\n        </FormProvider>\n      </IntegrationContext.Provider>\n    );\n  });\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/instagram/instagram.collaborators.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { Checkbox } from '@gitroom/react/form/checkbox';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';\nimport { InstagramCollaboratorsTags } from '@gitroom/frontend/components/new-launch/providers/instagram/instagram.tags';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { InstagramPreview } from '@gitroom/frontend/components/new-launch/providers/instagram/instagram.preview';\nconst postType = [\n  {\n    value: 'post',\n    label: 'Post / Reel',\n  },\n  {\n    value: 'story',\n    label: 'Story',\n  },\n];\n\nconst graduationStrategies = [\n  {\n    value: 'MANUAL',\n    label: 'Manual',\n  },\n  {\n    value: 'SS_PERFORMANCE',\n    label: 'Auto (based on performance)',\n  },\n];\nconst InstagramCollaborators: FC<{\n  values?: any;\n}> = (props) => {\n  const t = useT();\n  const { watch, register, formState, control } = useSettings();\n  const postCurrentType = watch('post_type');\n  const isTrialReel = watch('is_trial_reel');\n  return (\n    <>\n      <Select\n        label=\"Post Type\"\n        {...register('post_type', {\n          value: 'post',\n        })}\n      >\n        <option value=\"\">{t('select_post_type', 'Select Post Type...')}</option>\n        {postType.map((item) => (\n          <option key={item.value} value={item.value}>\n            {item.label}\n          </option>\n        ))}\n      </Select>\n\n      {postCurrentType !== 'story' && (\n        <InstagramCollaboratorsTags\n          label=\"Collaborators (max 3) - accounts can't be private\"\n          {...register('collaborators', {\n            value: [],\n          })}\n        />\n      )}\n\n      {postCurrentType === 'post' && (\n        <div className=\"mt-[18px] flex flex-col gap-[18px]\">\n          <Checkbox\n            {...register('is_trial_reel', {\n              value: false,\n            })}\n            label={t('trial_reel', 'Trial Reel (share only to non-followers first)')}\n          />\n\n          {isTrialReel && (\n            <Select\n              label=\"Graduation Strategy\"\n              {...register('graduation_strategy', {\n                value: 'MANUAL',\n              })}\n            >\n              {graduationStrategies.map((item) => (\n                <option key={item.value} value={item.value}>\n                  {item.label}\n                </option>\n              ))}\n            </Select>\n          )}\n        </div>\n      )}\n    </>\n  );\n};\nexport default withProvider<InstagramDto>({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: InstagramCollaborators,\n  CustomPreviewComponent: InstagramPreview,\n  dto: InstagramDto,\n  checkValidity: async ([firstPost, ...otherPosts] = [], settings) => {\n    if (!firstPost?.length) {\n      return 'Should have at least one media';\n    }\n    if (settings?.is_trial_reel) {\n      if ((firstPost?.length ?? 0) > 1) {\n        return 'Trial Reels can only have one video';\n      }\n      const hasVideo = firstPost?.some(\n        (f) => (f?.path?.indexOf?.('mp4') ?? -1) > -1\n      );\n      if (!hasVideo) {\n        return 'Trial Reels must be a video';\n      }\n    }\n    const checkVideosLength = await Promise.all(\n      firstPost\n        ?.filter((f) => (f?.path?.indexOf?.('mp4') ?? -1) > -1)\n        ?.flatMap((p) => p?.path)\n        ?.map((p) => {\n          return new Promise<number>((res) => {\n            const video = document.createElement('video');\n            video.preload = 'metadata';\n            video.src = p;\n            video.addEventListener('loadedmetadata', () => {\n              res(video.duration);\n            });\n          });\n        }) ?? []\n    );\n    for (const video of checkVideosLength) {\n      if (video > 60 && settings?.post_type === 'story') {\n        return 'Stories should be maximum 60 seconds';\n      }\n      if (video > 180 && settings?.post_type === 'post') {\n        return 'Reel should be maximum 180 seconds';\n      }\n    }\n    return true;\n  },\n  maximumCharacters: 2200,\n  comments: 'no-media'\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/instagram/instagram.preview.tsx",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { FC } from 'react';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\nimport { SliderComponent } from '@gitroom/frontend/components/third-parties/slider.component';\n\nexport const InstagramPreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      `<strong class=\"text-[15px] font-[600]\">${integration?.name} </strong>` +\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n  return (\n    <div className=\"py-[10px] flex flex-col px-[15px] w-full gap-[10px] bg-bgInstagram rounded-[12px]\">\n      <div className=\"flex gap-[10px] items-center\">\n        <div className=\"w-[36px] h-[36px]\">\n          <img\n            src={integration?.picture || '/no-picture.jpg'}\n            alt=\"social\"\n            className=\"rounded-full relative z-[2] w-[36px] h-[36px]\"\n          />\n        </div>\n        <div className=\"flex flex-col leading-[18px]\">\n          <div className=\"text-[15px] font-[600]\">{integration?.name}</div>\n        </div>\n      </div>\n      {!!renderContent?.[0]?.images?.length ? (\n        <SliderComponent\n          className=\"h-[585px] rounded-[8px] overflow-hidden\"\n          list={renderContent?.[0]?.images.map((image, index) => (\n            <a\n              key={`image_${index}`}\n              className=\"flex-1\"\n              href={mediaDir.set(image.path)}\n              target=\"_blank\"\n            >\n              <VideoOrImage autoplay={true} src={mediaDir.set(image.path)} />\n            </a>\n          ))}\n        />\n      ) : (\n        <div\n          style={{ background: 'url(/no-video-youtube.png)' }}\n          className=\"!bg-cover w-full aspect-[calc(16/9)] rounded-[8px] overflow-hidden\"\n        />\n      )}\n      <div\n        className=\"text-[14px] font-[400] whitespace-pre-line\"\n        dangerouslySetInnerHTML={{\n          __html: renderContent?.[0]?.text,\n        }}\n      />\n      <div className=\"py-[8px] text-textColor flex text-[14px] font-[700] gap-[10.5px]\">\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"22\"\n            height=\"20\"\n            viewBox=\"0 0 22 20\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M10.7232 18.2722C10.7232 18.2722 0.792969 12.7112 0.792969 5.95866C0.792969 4.76493 1.20656 3.60807 1.96337 2.68491C2.72018 1.76175 3.77346 1.12932 4.94401 0.895206C6.11455 0.661097 7.33006 0.839776 8.38371 1.40084C9.43737 1.96191 10.2641 2.87071 10.7232 3.97261V3.97261C11.1823 2.87071 12.0091 1.96191 13.0627 1.40084C14.1164 0.839776 15.3319 0.661097 16.5024 0.895206C17.673 1.12932 18.7263 1.76175 19.4831 2.68491C20.2399 3.60807 20.6535 4.76493 20.6535 5.95866C20.6535 12.7112 10.7232 18.2722 10.7232 18.2722Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.58884\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n          <div>121</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"26\"\n            height=\"26\"\n            viewBox=\"0 0 26 26\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M4.5067 17.576C3.3239 15.5805 2.91017 13.2218 3.34318 10.9428C3.7762 8.66377 5.02618 6.6212 6.85846 5.1985C8.69075 3.7758 10.9793 3.07083 13.2946 3.21592C15.6098 3.36102 17.7924 4.34621 19.4328 5.98653C21.0731 7.62686 22.0583 9.80951 22.2034 12.1247C22.3485 14.44 21.6435 16.7285 20.2208 18.5608C18.7981 20.3931 16.7555 21.6431 14.4765 22.0761C12.1975 22.5091 9.83884 22.0954 7.84327 20.9126V20.9126L4.54642 21.846C4.41135 21.8855 4.26814 21.888 4.13179 21.8531C3.99545 21.8182 3.871 21.7473 3.77149 21.6478C3.67197 21.5483 3.60106 21.4238 3.56619 21.2875C3.53131 21.1512 3.53375 21.0079 3.57326 20.8729L4.5067 17.576Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.58884\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n          <div>32</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center flex-1\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"26\"\n            height=\"26\"\n            viewBox=\"0 0 26 26\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M20.884 3.56475L2.37397 8.77813C2.21641 8.82121 2.07595 8.91181 1.97174 9.0376C1.86752 9.16339 1.80462 9.31824 1.79159 9.48107C1.77857 9.6439 1.81605 9.80679 1.89894 9.94754C1.98183 10.0883 2.1061 10.2001 2.25481 10.2677L10.7551 14.2894C10.9216 14.3665 11.0553 14.5003 11.1325 14.6668L15.1542 23.1671C15.2218 23.3158 15.3336 23.44 15.4743 23.5229C15.6151 23.6058 15.778 23.6433 15.9408 23.6303C16.1036 23.6172 16.2585 23.5543 16.3843 23.4501C16.5101 23.3459 16.6007 23.2055 16.6437 23.0479L21.8571 4.53791C21.8966 4.40284 21.8991 4.25962 21.8642 4.12328C21.8293 3.98694 21.7584 3.86249 21.6589 3.76297C21.5594 3.66346 21.4349 3.59255 21.2986 3.55767C21.1622 3.5228 21.019 3.52524 20.884 3.56475V3.56475Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.58884\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M11.0117 14.4084L15.5002 9.91992\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.58884\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"26\"\n            height=\"26\"\n            viewBox=\"0 0 26 26\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M19.0662 22.2443L12.7108 18.2722L6.35547 22.2443V4.76708C6.35547 4.55638 6.43917 4.35432 6.58815 4.20534C6.73713 4.05635 6.9392 3.97266 7.14989 3.97266H18.2718C18.4825 3.97266 18.6845 4.05635 18.8335 4.20534C18.9825 4.35432 19.0662 4.55638 19.0662 4.76708V22.2443Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.58884\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n      </div>\n      {renderContent.length > 1 && (\n        <>\n          {renderContent.slice(1).map((value, index) => (\n            <div key={index} className=\"flex flex-col gap-[12px]\">\n              <div className=\"flex gap-[10px] leading-[17px]\">\n                <div className=\"h-[34px]\">\n                  <img\n                    src={integration?.picture || '/no-picture.jpg'}\n                    alt=\"social\"\n                    className=\"rounded-full relative z-[2] h-[34px] w-[34px]\"\n                  />\n                </div>\n                <div className=\"flex flex-col gap-[6px] flex-1\">\n                  <div className=\"flex gap-[4px] py-[8px]\">\n                    <div\n                      className=\"whitespace-pre-line text-[14px] font-[400] flex-1\"\n                      dangerouslySetInnerHTML={{\n                        __html: value.text,\n                      }}\n                    />\n                    <div>\n                      <svg\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                        width=\"20\"\n                        height=\"20\"\n                        viewBox=\"0 0 20 20\"\n                        fill=\"none\"\n                      >\n                        <path\n                          d=\"M10 16.875C10 16.875 2.1875 12.5 2.1875 7.18751C2.1875 6.24836 2.51289 5.33821 3.1083 4.61193C3.70371 3.88564 4.53236 3.38808 5.45328 3.2039C6.37419 3.01971 7.33047 3.16029 8.15943 3.6017C8.98838 4.04311 9.63879 4.7581 10 5.62501V5.62501C10.3612 4.7581 11.0116 4.04311 11.8406 3.6017C12.6695 3.16029 13.6258 3.01971 14.5467 3.2039C15.4676 3.38808 16.2963 3.88564 16.8917 4.61193C17.4871 5.33821 17.8125 6.24836 17.8125 7.18751C17.8125 12.5 10 16.875 10 16.875Z\"\n                          stroke=\"currentColor\"\n                          strokeWidth=\"1.58884\"\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                  <div className=\"flex font-[400] text-[12px] text-textLinkedin items-center\">\n                    <div className=\"flex gap-[16px] flex-1\">\n                      <div className=\"font-[700]\">30m</div>\n                      <div className=\"font-[700]\">8 Likes</div>\n                      <div className=\"font-[700]\">Reply</div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          ))}\n        </>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/instagram/instagram.tags.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { ReactTags } from 'react-tag-autocomplete';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const InstagramCollaboratorsTags: FC<{\n  name: string;\n  label: string;\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, label } = props;\n  const { getValues } = useSettings();\n  const { integration } = useIntegration();\n  const [tagValue, setTagValue] = useState<any[]>([]);\n  const [suggestions, setSuggestions] = useState<string>('');\n  const t = useT();\n\n  const onDelete = useCallback(\n    (tagIndex: number) => {\n      const modify = tagValue.filter((_, i) => i !== tagIndex);\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  const onAddition = useCallback(\n    (newTag: any) => {\n      if (tagValue.length >= 3) {\n        return;\n      }\n      const modify = [...tagValue, newTag];\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  useEffect(() => {\n    const settings = getValues()[props.name];\n    if (settings) {\n      setTagValue(settings);\n    }\n  }, []);\n  const suggestionsArray = useMemo(() => {\n    return [\n      ...tagValue,\n      {\n        label: suggestions,\n        value: suggestions,\n      },\n    ].filter((f) => f.label);\n  }, [suggestions, tagValue]);\n  return (\n    <div>\n      <div>\n        <div className={clsx(`text-[14px] mb-[6px]`)}>{label}</div>\n        <ReactTags\n          placeholderText={t('add_a_tag', 'Add a tag')}\n          suggestions={suggestionsArray}\n          selected={tagValue}\n          onAdd={onAddition}\n          onInput={setSuggestions}\n          onDelete={onDelete}\n        />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/kick/kick.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\n\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  comments: 'no-media',\n  minimumCharacters: [],\n  SettingsComponent: undefined,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: undefined,\n  maximumCharacters: 500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/lemmy/lemmy.provider.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useFieldArray } from 'react-hook-form';\nimport { Button } from '@gitroom/react/form/button';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { Subreddit } from './subreddit';\nimport { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/lemmy.dto';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst LemmySettings: FC = () => {\n  const { register, control } = useSettings();\n  const { fields, append, remove } = useFieldArray({\n    control,\n    // control props comes from useForm (optional: if you are using FormContext)\n    name: 'subreddit', // unique name for your Field Array\n  });\n  const t = useT();\n\n  const addField = useCallback(() => {\n    append({});\n  }, [fields, append]);\n  const deleteField = useCallback(\n    (index: number) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_this_subreddit',\n            'Are you sure you want to delete this Subreddit?'\n          )\n        ))\n      )\n        return;\n      remove(index);\n    },\n    [fields, remove]\n  );\n  return (\n    <>\n      <div className=\"flex flex-col gap-[20px] mb-[20px]\">\n        {fields.map((field, index) => (\n          <div key={field.id} className=\"flex flex-col relative\">\n            <div\n              onClick={deleteField(index)}\n              className=\"absolute -start-[10px] justify-center items-center flex -top-[10px] w-[20px] h-[20px] bg-red-600 rounded-full text-textColor\"\n            >\n              x\n            </div>\n            <Subreddit {...register(`subreddit.${index}.value`)} />\n          </div>\n        ))}\n      </div>\n      <Button onClick={addField}>{t('add_community', 'Add Community')}</Button>\n      {fields.length === 0 && (\n        <div className=\"text-red-500 text-[12px] mt-[10px]\">\n          {t(\n            'please_add_at_least_one_subreddit',\n            'Please add at least one Subreddit'\n          )}\n        </div>\n      )}\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: LemmySettings,\n  CustomPreviewComponent: undefined,\n  dto: LemmySettingsDto,\n  checkValidity: async (items) => {\n    const [firstItems] = items ?? [];\n    if (\n      firstItems?.length &&\n      (firstItems?.[0]?.path?.indexOf?.('png') ?? -1) === -1 &&\n      (firstItems?.[0]?.path?.indexOf?.('jpg') ?? -1) === -1 &&\n      (firstItems?.[0]?.path?.indexOf?.('jpef') ?? -1) === -1 &&\n      (firstItems?.[0]?.path?.indexOf?.('gif') ?? -1) === -1\n    ) {\n      return 'You can set only one picture for a cover';\n    }\n    return true;\n  },\n  maximumCharacters: 10000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/lemmy/subreddit.tsx",
    "content": "'use client';\n\nimport { FC, FormEvent, useCallback, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Input } from '@gitroom/react/form/input';\nimport { useDebouncedCallback } from 'use-debounce';\nimport { useWatch } from 'react-hook-form';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const Subreddit: FC<{\n  onChange: (event: {\n    target: {\n      name: string;\n      value: {\n        id: string;\n        subreddit: string;\n        title: string;\n        name: string;\n        url: string;\n        body: string;\n        media: any[];\n      };\n    };\n  }) => void;\n  name: string;\n}> = (props) => {\n  const { onChange, name } = props;\n  const state = useSettings();\n  const split = name.split('.');\n  const [loading, setLoading] = useState(false);\n  // @ts-ignore\n  const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value;\n  const [results, setResults] = useState([]);\n  const func = useCustomProviderFunction();\n  const value = useWatch({\n    name,\n  });\n  const [searchValue, setSearchValue] = useState('');\n  const setResult = (result: { id: string; name: string }) => async () => {\n    setLoading(true);\n    setSearchValue('');\n    onChange({\n      target: {\n        name,\n        value: {\n          id: String(result.id),\n          subreddit: result.name,\n          title: '',\n          name: '',\n          url: '',\n          body: '',\n          media: [],\n        },\n      },\n    });\n    setLoading(false);\n  };\n  const setTitle = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            title: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setURL = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            url: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const search = useDebouncedCallback(\n    useCallback(async (e: FormEvent<HTMLInputElement>) => {\n      // @ts-ignore\n      setResults([]);\n      // @ts-ignore\n      if (!e.target.value) {\n        return;\n      }\n      // @ts-ignore\n      const results = await func.get('subreddits', { word: e.target.value });\n      // @ts-ignore\n      setResults(results);\n    }, []),\n    500\n  );\n  return (\n    <div className=\"bg-primary p-[20px]\">\n      {value?.subreddit ? (\n        <>\n          <Input\n            error={errors?.subreddit?.message}\n            disableForm={true}\n            value={value.subreddit}\n            readOnly={true}\n            label=\"Community\"\n            name=\"subreddit\"\n          />\n          <Input\n            error={errors?.title?.message}\n            value={value.title}\n            disableForm={true}\n            label=\"Title\"\n            name=\"title\"\n            onChange={setTitle}\n          />\n          <Input\n            error={errors?.url?.message}\n            value={value.url}\n            label=\"URL\"\n            name=\"url\"\n            disableForm={true}\n            onChange={setURL}\n          />\n        </>\n      ) : (\n        <div className=\"relative\">\n          <Input\n            placeholder=\"Community\"\n            name=\"search\"\n            label=\"Search Community\"\n            readOnly={loading}\n            value={searchValue}\n            error={errors?.message}\n            disableForm={true}\n            onInput={async (e) => {\n              // @ts-ignore\n              setSearchValue(e.target.value);\n              await search(e);\n            }}\n          />\n          {!!results.length && !loading && (\n            <div className=\"z-[400] w-full absolute bg-input -mt-[20px] outline-none border-fifth border cursor-pointer\">\n              {results.map((r: { id: string; name: string }) => (\n                <div\n                  onClick={setResult(r)}\n                  key={r.id}\n                  className=\"px-[16px] py-[5px] hover:bg-secondary\"\n                >\n                  {r.name}\n                </div>\n              ))}\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/linkedin/linkedin.preview.tsx",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { FC } from 'react';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\n\nconst Icons = () => {\n  return (\n    <svg\n      width={71}\n      height={22}\n      viewBox=\"0 0 71 22\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className=\"text-bgLinkedin\"\n    >\n      <rect\n        x={1.0625}\n        y={1.27539}\n        width={18.7109}\n        height={18.7109}\n        rx={9.35544}\n        stroke=\"currentColor\"\n        strokeWidth={2.12624}\n      />\n      <path\n        d=\"M10.4444 19.7508C15.682 19.7508 19.9279 15.6014 19.9279 10.4828C19.9279 5.36425 15.682 1.21484 10.4444 1.21484C5.20684 1.21484 0.960938 5.36425 0.960938 10.4828C0.960938 15.6014 5.20684 19.7508 10.4444 19.7508Z\"\n        fill=\"#1485BD\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M15.7769 9.48947H15.0318C14.964 9.48947 14.8286 9.23791 14.4899 8.88043C13.9886 8.35083 13.4331 7.6756 13.0402 7.30488C12.0654 6.47806 11.2686 5.46989 10.6965 4.33913C10.3713 3.66389 10.3442 3.35938 9.68038 3.35938C9.41819 3.39246 9.17864 3.52159 9.01014 3.72067C8.84164 3.91975 8.75676 4.17392 8.77267 4.43181C8.77267 4.61717 8.86751 5.26593 8.90815 5.49101C9.19833 6.52618 9.65567 7.50947 10.2629 8.40379H10.9945H5.19604C5.03181 8.40143 4.86888 8.43263 4.71771 8.49541C4.56654 8.55818 4.43047 8.65114 4.31821 8.76832C4.20596 8.88551 4.12001 9.02432 4.06587 9.17587C4.01173 9.32742 3.9906 9.48835 4.00383 9.64835C4.02471 9.95855 4.16748 10.2489 4.40229 10.4586C4.63709 10.6683 4.94577 10.7812 5.26378 10.7737H5.48055C5.34176 10.7772 5.20503 10.8073 5.07816 10.8624C4.95129 10.9175 4.83678 10.9965 4.74116 11.0948C4.64554 11.1932 4.57069 11.309 4.52089 11.4357C4.47108 11.5623 4.44731 11.6973 4.45091 11.8329C4.45326 12.0997 4.55728 12.3561 4.74266 12.5519C4.92804 12.7478 5.18147 12.8691 5.45345 12.8921C5.28789 13.0159 5.16238 13.1837 5.09194 13.3755C5.0215 13.5672 5.00913 13.7747 5.05632 13.9731C5.10351 14.1716 5.20824 14.3525 5.35797 14.4942C5.50771 14.6359 5.69609 14.7325 5.90053 14.7722C5.77072 14.9924 5.72737 15.2513 5.7786 15.5004C5.84797 15.745 5.99897 15.9599 6.2076 16.111C6.41624 16.2621 6.67055 16.3408 6.93017 16.3345H10.0326C10.4391 16.3345 10.8441 16.2842 11.2384 16.1889L13.1622 15.6328H15.7363C17.1181 15.5798 17.4839 9.48947 15.7769 9.48947Z\"\n        fill=\"#E6F7FF\"\n      />\n      <path\n        d=\"M9.66218 8.40402H5.16429C4.99306 8.40245 4.82349 8.43696 4.66712 8.50518C4.51076 8.5734 4.37127 8.67374 4.25814 8.79938C4.14501 8.92501 4.06091 9.07298 4.01154 9.23323C3.96218 9.39347 3.94872 9.56223 3.97208 9.72801C3.99629 10.0359 4.14049 10.3229 4.37495 10.5298C4.6094 10.7368 4.9162 10.8479 5.23203 10.8402H5.4488C5.31001 10.8436 5.17328 10.8737 5.04641 10.9288C4.91954 10.9839 4.80503 11.0629 4.70941 11.1613C4.61379 11.2596 4.53894 11.3754 4.48914 11.5021C4.43933 11.6287 4.41556 11.7637 4.41916 11.8994C4.42415 12.1667 4.52871 12.4231 4.71326 12.6205C4.89781 12.818 5.14969 12.9428 5.4217 12.9718C5.29214 13.0711 5.18712 13.1977 5.11442 13.3421C5.04172 13.4865 5.0032 13.645 5.00172 13.8059C5.0024 14.0543 5.09018 14.2949 5.25043 14.4876C5.41069 14.6804 5.63372 14.8136 5.88233 14.8651C5.75158 15.0898 5.70828 15.3531 5.7604 15.6065C5.83228 15.8487 5.98435 16.0606 6.19275 16.2092C6.40116 16.3577 6.65411 16.4345 6.91196 16.4274H10.0144C10.4209 16.4274 10.8259 16.3771 11.2202 16.2818L13.144 15.6595H15.7181C17.0728 15.6595 17.4386 9.51617 15.7181 9.51617C15.4699 9.52953 15.2211 9.52953 14.9729 9.51617C14.9052 9.51617 14.7697 9.26461 14.431 8.90714C13.9297 8.37754 13.3743 7.7023 12.9814 7.33158C12.0197 6.49714 11.2369 5.48461 10.6783 4.3526C10.5707 4.10277 10.4244 3.87057 10.2447 3.66412C10.0191 3.47868 9.72796 3.38726 9.43402 3.40955C9.14008 3.43183 8.86686 3.56604 8.67318 3.78328C8.53278 3.93167 8.41835 4.10165 8.33449 4.2864C8.27968 4.41424 8.23881 4.54738 8.21256 4.68359C8.17621 5.05687 8.20832 5.4335 8.30739 5.79575C8.3941 6.17706 8.51738 6.5491 8.67318 6.90791C8.92788 7.37528 9.22865 7.81881 9.56734 8.2319C9.60914 8.25707 9.6422 8.29399 9.66218 8.33782\"\n        stroke=\"#004B7C\"\n        strokeWidth={0.847356}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <rect\n        x={17.6484}\n        y={1.27539}\n        width={18.7109}\n        height={18.7109}\n        rx={9.35544}\n        stroke=\"currentColor\"\n        strokeWidth={2.12624}\n      />\n      <mask\n        id=\"mask0_2511_140139\"\n        style={{\n          maskType: 'alpha',\n        }}\n        maskUnits=\"userSpaceOnUse\"\n        x={17}\n        y={1}\n        width={20}\n        height={19}\n      >\n        <path\n          d=\"M36.1844 10.5941C36.1844 5.47558 32.0349 1.32617 26.9164 1.32617C21.7978 1.32617 17.6484 5.47558 17.6484 10.5941C17.6484 15.7127 21.7978 19.8621 26.9164 19.8621C32.0349 19.8621 36.1844 15.7127 36.1844 10.5941Z\"\n          fill=\"white\"\n        />\n      </mask>\n      <g mask=\"url(#mask0_2511_140139)\">\n        <path\n          d=\"M36.1844 10.5941C36.1844 5.47558 32.0349 1.32617 26.9164 1.32617C21.7978 1.32617 17.6484 5.47558 17.6484 10.5941C17.6484 15.7127 21.7978 19.8621 26.9164 19.8621C32.0349 19.8621 36.1844 15.7127 36.1844 10.5941Z\"\n          fill=\"#D8D8D8\"\n        />\n        <path\n          d=\"M26.9242 19.864C32.0428 19.864 36.1922 15.7146 36.1922 10.5961C36.1922 5.47753 32.0428 1.32812 26.9242 1.32812C21.8057 1.32812 17.6562 5.47753 17.6562 10.5961C17.6562 15.7146 21.8057 19.864 26.9242 19.864Z\"\n          fill=\"#6DAE4F\"\n        />\n        <path\n          d=\"M26.9279 1.3279C29.3859 1.3279 31.7432 2.30434 33.4813 4.04242C35.2194 5.7805 36.1959 8.13785 36.1959 10.5959C36.1959 13.0539 35.2194 15.4112 33.4813 17.1493C31.7432 18.8874 29.3859 19.8638 26.9279 19.8638C24.4699 19.8638 22.1125 18.8874 20.3745 17.1493C18.6364 15.4112 17.6599 13.0539 17.6599 10.5959C17.6599 8.13785 18.6364 5.7805 20.3745 4.04242C22.1125 2.30434 24.4699 1.3279 26.9279 1.3279ZM26.9279 0.00390625C24.1187 0.00390625 21.4246 1.11984 19.4382 3.10622C17.4519 5.09259 16.3359 7.7867 16.3359 10.5959C16.3359 13.405 17.4519 16.0991 19.4382 18.0855C21.4246 20.0719 24.1187 21.1878 26.9279 21.1878C29.7371 21.1878 32.4312 20.0719 34.4175 18.0855C36.4039 16.0991 37.5198 13.405 37.5198 10.5959C37.5198 7.7867 36.4039 5.09259 34.4175 3.10622C32.4312 1.11984 29.7371 0.00390625 26.9279 0.00390625V0.00390625Z\"\n          fill=\"#1B1F23\"\n        />\n        <path\n          d=\"M32.3916 12.217C32.3675 11.1767 32.2073 10.144 31.915 9.14529C31.1136 8.50117 30.5044 7.64931 30.1541 6.68266C29.9422 5.98095 29.7966 5.72939 29.1743 5.71615C29.0477 5.72767 28.9246 5.76441 28.8123 5.82421C28.7001 5.88402 28.601 5.96568 28.5208 6.06438C28.4406 6.16308 28.381 6.27683 28.3454 6.39893C28.3098 6.52103 28.2991 6.64902 28.3137 6.77534C28.3217 7.0931 28.3481 7.41218 28.3931 7.72862C28.5745 8.59584 28.8301 9.44716 29.1611 10.2707L23.2031 5.74263C23.1037 5.64582 22.9851 5.57091 22.855 5.52273C22.7249 5.47455 22.5862 5.45417 22.4477 5.46291C22.3092 5.47164 22.1741 5.50929 22.0511 5.57343C21.9281 5.63757 21.8199 5.72678 21.7335 5.83531C21.6678 5.94514 21.6248 6.06707 21.6073 6.19384C21.5897 6.32061 21.5978 6.44962 21.6312 6.57317C21.6646 6.69673 21.7225 6.8123 21.8014 6.913C21.8804 7.0137 21.9789 7.09747 22.0909 7.1593L25.0699 9.41009L25.8511 10.1515L21.1244 6.5635C21.0297 6.46795 20.9155 6.39386 20.7897 6.34623C20.6638 6.29859 20.5292 6.27854 20.3949 6.28741C20.2607 6.29629 20.1299 6.33388 20.0114 6.39766C19.8929 6.46144 19.7895 6.54992 19.7081 6.65711C19.6268 6.7643 19.5694 6.88771 19.5398 7.01899C19.5103 7.15027 19.5093 7.28636 19.5368 7.41807C19.5644 7.54978 19.62 7.67403 19.6997 7.78242C19.7794 7.89082 19.8815 7.98083 19.999 8.04638L22.978 10.2972L24.7654 11.6212L20.8993 8.72162C20.8 8.62481 20.6814 8.5499 20.5513 8.50172C20.4212 8.45354 20.2824 8.43316 20.144 8.44189C20.0055 8.45063 19.8704 8.48828 19.7474 8.55242C19.6244 8.61656 19.5161 8.70577 19.4297 8.81429C19.0193 9.34389 19.3503 9.88673 19.7739 10.2177L22.7529 12.4553L24.249 13.5807L21.2701 11.3299C21.1783 11.23 21.0668 11.1502 20.9425 11.0956C20.8183 11.0411 20.6841 11.0129 20.5485 11.0129C20.4128 11.0129 20.2786 11.0411 20.1544 11.0956C20.0302 11.1502 19.9187 11.23 19.8269 11.3299C19.7456 11.4431 19.6891 11.5722 19.661 11.7088C19.633 11.8453 19.6339 11.9862 19.6639 12.1223C19.6939 12.2585 19.7522 12.3868 19.835 12.4989C19.9178 12.611 20.0233 12.7044 20.1447 12.773L25.573 16.745C26.3595 17.3368 28.3137 17.8479 29.1743 17.0998\"\n          stroke=\"#165209\"\n          strokeWidth={1.27103}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M33.034 13.1164L32.4779 12.6266C32.4056 11.6521 32.1776 10.6955 31.8026 9.79321C31.0275 8.88392 30.4786 7.80414 30.2006 6.6421C29.9888 5.94038 29.8431 5.68882 29.2209 5.67558C29.0942 5.6871 28.9711 5.72384 28.8589 5.78365C28.7467 5.84345 28.6475 5.92511 28.5673 6.02381C28.4871 6.12252 28.4275 6.23627 28.3919 6.35837C28.3564 6.48047 28.3456 6.60845 28.3603 6.73478C28.3682 7.05254 28.3947 7.37162 28.4397 7.68805C28.592 8.52349 28.8369 9.3404 29.1679 10.1242L28.9163 9.97856L23.2099 5.59614C23.1105 5.49934 22.992 5.42443 22.8619 5.37625C22.7318 5.32807 22.593 5.30769 22.4545 5.31642C22.3161 5.32515 22.181 5.3628 22.058 5.42695C21.9349 5.49109 21.8267 5.5803 21.7403 5.68882C21.6633 5.80203 21.6112 5.93032 21.5875 6.06518C21.5637 6.20004 21.569 6.3384 21.6028 6.4711C21.6366 6.60379 21.6982 6.72779 21.7835 6.83487C21.8688 6.94196 21.976 7.02969 22.0978 7.09226L25.0106 9.32981L25.8579 10.005L21.1312 6.41702C21.0328 6.31945 20.9149 6.24359 20.7853 6.19431C20.6557 6.14504 20.5173 6.12345 20.3788 6.13093C20.2404 6.13842 20.105 6.17481 19.9815 6.23776C19.858 6.30072 19.749 6.38884 19.6616 6.49646C19.5861 6.60948 19.535 6.73697 19.5115 6.87082C19.4879 7.00466 19.4925 7.14195 19.5249 7.27394C19.5572 7.40593 19.6167 7.52974 19.6995 7.6375C19.7823 7.74526 19.8867 7.83462 20.0059 7.89989L22.9848 10.1507L24.7722 11.4747L20.9062 8.58837C20.8087 8.48888 20.6912 8.4113 20.5613 8.36083C20.4315 8.31035 20.2925 8.28813 20.1534 8.29565C20.0143 8.30317 19.8784 8.34025 19.7548 8.40442C19.6312 8.4686 19.5227 8.55839 19.4365 8.66781C19.359 8.78019 19.3062 8.90772 19.2815 9.042C19.2569 9.17627 19.2609 9.31425 19.2935 9.44684C19.326 9.57943 19.3862 9.70363 19.4702 9.81126C19.5542 9.91888 19.6601 10.0075 19.7808 10.0712L22.7598 12.3088L24.2559 13.4342L21.2769 11.1966C21.1874 11.093 21.0766 11.0098 20.9521 10.9528C20.8276 10.8958 20.6923 10.8663 20.5553 10.8663C20.4184 10.8663 20.283 10.8958 20.1585 10.9528C20.034 11.0098 19.9232 11.093 19.8337 11.1966C19.7527 11.3088 19.6963 11.4369 19.6684 11.5724C19.6404 11.7079 19.6415 11.8479 19.6715 11.983C19.7015 12.1181 19.7598 12.2453 19.8425 12.3562C19.9253 12.4671 20.0306 12.5593 20.1515 12.6266L25.5799 16.7442C25.9693 17.0372 26.4142 17.2482 26.8876 17.3642C27.3609 17.4803 27.8529 17.4991 28.3338 17.4194C29.0461 17.8325 29.6684 17.8325 30.2006 17.4194C30.9977 16.7985 30.7355 16.7428 31.326 15.9233C31.9943 15.0516 32.5669 14.1106 33.034 13.1164Z\"\n          fill=\"#DCF0CB\"\n        />\n        <path\n          d=\"M32.3916 12.217C32.3675 11.1767 32.2073 10.144 31.915 9.14529C31.1136 8.50117 30.5044 7.64931 30.1541 6.68266C29.9422 5.98095 29.7966 5.72939 29.1743 5.71615C29.0477 5.72767 28.9246 5.76441 28.8123 5.82421C28.7001 5.88402 28.601 5.96568 28.5208 6.06438C28.4406 6.16308 28.381 6.27683 28.3454 6.39893C28.3098 6.52103 28.2991 6.64902 28.3137 6.77534C28.3217 7.0931 28.3481 7.41219 28.3931 7.72862C28.5745 8.59584 28.8301 9.44716 29.1611 10.2707L23.2031 5.74263C23.1037 5.64582 22.9851 5.57091 22.855 5.52273C22.7249 5.47455 22.5862 5.45417 22.4477 5.46291C22.3092 5.47164 22.1741 5.50929 22.0511 5.57343C21.9281 5.63757 21.8199 5.72678 21.7335 5.83531C21.6678 5.94514 21.6248 6.06707 21.6073 6.19384C21.5897 6.32061 21.5978 6.44962 21.6312 6.57317C21.6646 6.69673 21.7225 6.8123 21.8014 6.913C21.8804 7.0137 21.9789 7.09747 22.0909 7.1593L25.0699 9.41009L25.8511 10.1515L21.1244 6.5635C21.0297 6.46795 20.9155 6.39386 20.7897 6.34623C20.6638 6.29859 20.5292 6.27854 20.3949 6.28741C20.2607 6.29629 20.1299 6.33388 20.0114 6.39766C19.8929 6.46145 19.7895 6.54992 19.7081 6.65711C19.6268 6.7643 19.5694 6.88771 19.5398 7.01899C19.5103 7.15027 19.5093 7.28636 19.5368 7.41807C19.5644 7.54978 19.62 7.67403 19.6997 7.78242C19.7794 7.89082 19.8815 7.98083 19.999 8.04638L22.978 10.2972L24.7654 11.6212L20.8993 8.72162C20.8 8.62481 20.6814 8.5499 20.5513 8.50172C20.4212 8.45354 20.2824 8.43316 20.144 8.44189C20.0055 8.45063 19.8704 8.48828 19.7474 8.55242C19.6244 8.61656 19.5161 8.70577 19.4297 8.81429C19.0193 9.34389 19.3503 9.88673 19.7739 10.2177L22.7529 12.4553L24.249 13.5807L21.2701 11.3299C21.1783 11.23 21.0668 11.1502 20.9425 11.0956C20.8183 11.0411 20.6841 11.0129 20.5485 11.0129C20.4128 11.0129 20.2786 11.0411 20.1544 11.0956C20.0302 11.1502 19.9187 11.23 19.8269 11.3299C19.7456 11.4431 19.6891 11.5722 19.661 11.7088C19.633 11.8453 19.6339 11.9862 19.6639 12.1223C19.6939 12.2585 19.7522 12.3868 19.835 12.4989C19.9178 12.611 20.0233 12.7044 20.1447 12.773L25.573 16.745C26.3595 17.3368 28.2025 17.7274 29.1743 17.5659\"\n          stroke=\"#165209\"\n          strokeWidth={0.423678}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M35.8765 15.0818C35.6087 14.7952 35.4015 14.4574 35.2675 14.0888C35.2675 13.7975 35.188 13.5195 35.1351 13.2282C35.0937 12.7201 34.9302 12.2296 34.6584 11.7983C33.891 10.8839 33.343 9.806 33.0564 8.64718C32.8446 7.94546 32.6989 7.6939 32.0766 7.68066C31.95 7.69218 31.8269 7.72892 31.7147 7.78873C31.6024 7.84853 31.5033 7.93019 31.4231 8.02889C31.3429 8.12759 31.2833 8.24134 31.2477 8.36345C31.2122 8.48555 31.2014 8.61353 31.216 8.73986C31.2253 9.05762 31.2518 9.37537 31.2955 9.69313C31.5338 10.9112 31.7854 11.9174 31.8118 11.9836L26.1319 7.73362C26.0335 7.63488 25.9152 7.55825 25.7849 7.50887C25.6546 7.45949 25.5152 7.43848 25.3761 7.44726C25.237 7.45603 25.1014 7.49438 24.9783 7.55974C24.8552 7.62511 24.7474 7.71598 24.6623 7.8263C24.596 7.93533 24.5524 8.05654 24.5338 8.18276C24.5153 8.30898 24.5223 8.43763 24.5544 8.56109C24.5865 8.68456 24.6431 8.80032 24.7207 8.90153C24.7984 9.00274 24.8956 9.08733 25.0065 9.15029L27.9855 11.4011L28.8858 12.1425L24.1194 8.5545C24.022 8.455 23.9044 8.37743 23.7746 8.32695C23.6448 8.27648 23.5057 8.25426 23.3666 8.26178C23.2276 8.26929 23.0917 8.30638 22.9681 8.37055C22.8445 8.43472 22.736 8.52452 22.6498 8.63394C22.5723 8.74632 22.5194 8.87385 22.4948 9.00812C22.4701 9.1424 22.4742 9.28038 22.5067 9.41297C22.5392 9.54556 22.5995 9.66976 22.6835 9.77738C22.7675 9.88501 22.8733 9.97361 22.994 10.0374L25.973 12.2882L27.7604 13.6122L23.8811 10.7126C23.782 10.6168 23.6638 10.5431 23.5343 10.4961C23.4047 10.4491 23.2667 10.43 23.1292 10.44C22.9918 10.45 22.858 10.4889 22.7366 10.5541C22.6152 10.6193 22.5089 10.7094 22.4247 10.8185C22.0143 11.3481 22.332 11.891 22.769 12.222L25.7479 14.4463L27.2308 15.5717L24.1989 13.3209C24.013 13.1594 23.7724 13.0749 23.5264 13.0848C23.2803 13.0946 23.0473 13.198 22.8749 13.3738C22.7938 13.4823 22.7361 13.6064 22.7055 13.7384C22.6748 13.8703 22.6718 14.0071 22.6967 14.1402C22.7216 14.2733 22.7738 14.3999 22.85 14.5118C22.9263 14.6237 23.0249 14.7186 23.1397 14.7905L28.4356 18.7625C28.8779 19.0935 29.3889 19.3199 29.9318 19.4245L30.6997 19.5436C31.1035 19.6032 31.4941 19.733 31.8516 19.9276C32.0987 20.0483 32.3822 20.0719 32.646 19.9938C33.5771 19.6654 34.3917 19.0717 34.9894 18.2859C35.5492 17.5121 35.8713 16.5919 35.9162 15.6379C35.9498 15.4521 35.9362 15.2609 35.8765 15.0818Z\"\n          stroke=\"#165209\"\n          strokeWidth={1.27103}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M35.9319 15.0166L35.3361 14.5664C35.2702 13.591 35.042 12.6334 34.6609 11.7331C33.8911 10.8202 33.3429 9.74178 33.0589 8.58197C32.847 7.88025 32.7014 7.62869 32.0791 7.61545C31.9525 7.62697 31.8294 7.66371 31.7172 7.72351C31.6049 7.78332 31.5058 7.86498 31.4256 7.96368C31.3454 8.06238 31.2858 8.17613 31.2502 8.29823C31.2146 8.42033 31.2039 8.54832 31.2185 8.67464C31.2278 8.9924 31.2543 9.31016 31.298 9.62792C31.5363 10.8592 31.7878 11.8655 31.8143 11.9184L26.1344 7.66841C26.035 7.5716 25.9164 7.49669 25.7863 7.44851C25.6562 7.40034 25.5175 7.37996 25.379 7.38869C25.2405 7.39742 25.1054 7.43507 24.9824 7.49921C24.8594 7.56335 24.7512 7.65256 24.6648 7.76109C24.2543 8.30393 24.5853 8.83352 25.009 9.16452L27.988 11.4021L28.8883 12.1435L24.1219 8.48929C24.0225 8.39248 23.904 8.31757 23.7739 8.26939C23.6438 8.22121 23.505 8.20083 23.3665 8.20956C23.2281 8.2183 23.093 8.25595 22.97 8.32009C22.8469 8.38423 22.7387 8.47344 22.6523 8.58197C22.2418 9.11156 22.5728 9.6544 22.9965 9.9854L25.9755 12.2229L27.7629 13.5469L23.8836 10.6474C23.7854 10.5509 23.6679 10.4762 23.5389 10.4281C23.4098 10.3801 23.2721 10.3597 23.1347 10.3685C22.9973 10.3772 22.8633 10.4148 22.7413 10.4789C22.6194 10.5429 22.5124 10.6319 22.4272 10.7401C22.3475 10.8518 22.2929 10.9793 22.2671 11.1141C22.2412 11.2488 22.2448 11.3875 22.2774 11.5207C22.3101 11.654 22.3712 11.7786 22.4564 11.8861C22.5417 11.9936 22.6491 12.0814 22.7714 12.1435L25.7504 14.3811L27.2333 15.5065L24.2014 13.2689C24.0184 13.1011 23.7765 13.0125 23.5284 13.0224C23.2804 13.0324 23.0463 13.14 22.8774 13.3219C22.7849 13.4307 22.7177 13.5586 22.6805 13.6965C22.6434 13.8343 22.6372 13.9787 22.6626 14.1192C22.6879 14.2598 22.744 14.3929 22.827 14.5092C22.9099 14.6254 23.0175 14.7218 23.1422 14.7915L28.4381 18.7635C29.2411 19.2423 30.1518 19.511 31.0861 19.5446C31.4251 19.7518 31.7792 19.9333 32.1453 20.0875C33.2076 19.8872 34.1541 19.2909 34.7933 18.4192C35.4911 17.4176 35.8864 16.2365 35.9319 15.0166Z\"\n          fill=\"#DDF6D1\"\n        />\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M35.8765 15.0818C35.6087 14.7952 35.4015 14.4574 35.2675 14.0888C35.2675 13.7975 35.188 13.5195 35.1351 13.2282C35.0937 12.7201 34.9302 12.2296 34.6584 11.7983C33.891 10.8839 33.343 9.806 33.0564 8.64718C32.8446 7.94546 32.6989 7.6939 32.0766 7.68066C31.95 7.69218 31.8269 7.72892 31.7147 7.78873C31.6024 7.84853 31.5033 7.93019 31.4231 8.02889C31.3429 8.12759 31.2833 8.24134 31.2477 8.36345C31.2122 8.48555 31.2014 8.61353 31.216 8.73986C31.2253 9.05762 31.2518 9.37537 31.2955 9.69313C31.5338 10.9112 31.7854 11.9174 31.8118 11.9836L26.1319 7.73362C26.0335 7.63488 25.9152 7.55825 25.7849 7.50887C25.6546 7.45949 25.5152 7.43848 25.3761 7.44726C25.237 7.45603 25.1014 7.49438 24.9783 7.55974C24.8552 7.62511 24.7474 7.71598 24.6623 7.8263C24.596 7.93533 24.5524 8.05654 24.5338 8.18276C24.5153 8.30898 24.5223 8.43763 24.5544 8.56109C24.5865 8.68456 24.6431 8.80032 24.7207 8.90153C24.7984 9.00274 24.8956 9.08733 25.0065 9.15029L27.9855 11.4011L28.8858 12.1425L24.1194 8.5545C24.022 8.455 23.9044 8.37743 23.7746 8.32695C23.6448 8.27648 23.5057 8.25426 23.3666 8.26178C23.2276 8.26929 23.0917 8.30638 22.9681 8.37055C22.8445 8.43472 22.736 8.52452 22.6498 8.63394C22.5723 8.74632 22.5194 8.87385 22.4948 9.00812C22.4701 9.1424 22.4742 9.28038 22.5067 9.41297C22.5392 9.54556 22.5995 9.66976 22.6835 9.77738C22.7675 9.88501 22.8733 9.97361 22.994 10.0374L25.973 12.2882L27.7604 13.6122L23.8811 10.7126C23.782 10.6168 23.6638 10.5431 23.5343 10.4961C23.4047 10.4491 23.2667 10.43 23.1292 10.44C22.9918 10.45 22.858 10.4889 22.7366 10.5541C22.6152 10.6193 22.5089 10.7094 22.4247 10.8185C22.0143 11.3481 22.332 11.891 22.769 12.222L25.7479 14.4463L27.2308 15.5717L24.1989 13.3209C24.013 13.1594 23.7724 13.0749 23.5264 13.0848C23.2803 13.0946 23.0473 13.198 22.8749 13.3738C22.7938 13.4823 22.7361 13.6064 22.7055 13.7384C22.6748 13.8703 22.6718 14.0071 22.6967 14.1402C22.7216 14.2733 22.7738 14.3999 22.85 14.5118C22.9263 14.6237 23.0249 14.7186 23.1397 14.7905L28.4356 18.7625C28.8779 19.0935 29.3889 19.3199 29.9318 19.4245L30.6997 19.5436C31.1035 19.6032 31.4941 19.733 31.8516 19.9276C32.0987 20.0483 32.3822 20.0719 32.646 19.9938C33.5771 19.6654 34.3917 19.0717 34.9894 18.2859C35.5492 17.5121 35.8713 16.5919 35.9162 15.6379C35.9498 15.4521 35.9362 15.2609 35.8765 15.0818Z\"\n          stroke=\"#165209\"\n          strokeWidth={0.423678}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n        <path\n          d=\"M26.3078 2.16684L25.4485 4.23227M28.0171 3.73578L27.0506 4.95385L28.0171 3.73578ZM23.6016 2.15625L23.6876 3.73578L23.6016 2.15625Z\"\n          stroke=\"#165209\"\n          strokeWidth={0.847356}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </g>\n      <rect\n        x={34.2305}\n        y={1.27539}\n        width={18.7109}\n        height={18.7109}\n        rx={9.35544}\n        stroke=\"currentColor\"\n        strokeWidth={2.12624}\n      />\n      <path\n        d=\"M43.6358 19.8621C48.8734 19.8621 53.1193 15.7127 53.1193 10.5941C53.1193 5.47558 48.8734 1.32617 43.6358 1.32617C38.3982 1.32617 34.1523 5.47558 34.1523 10.5941C34.1523 15.7127 38.3982 19.8621 43.6358 19.8621Z\"\n        fill=\"#DF704D\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M43.2395 6.62305C42.9098 6.29999 42.5181 6.04368 42.0869 5.8688C41.6556 5.69391 41.1933 5.60389 40.7264 5.60389C40.2595 5.60389 39.7972 5.69391 39.3659 5.8688C38.9347 6.04368 38.543 6.29999 38.2133 6.62305C37.8794 6.94891 37.6146 7.33589 37.4339 7.76186C37.2532 8.18782 37.1602 8.64442 37.1602 9.10554C37.1602 9.56666 37.2532 10.0233 37.4339 10.4492C37.6146 10.8752 37.8794 11.2622 38.2133 11.588L43.6324 16.884L49.0516 11.588C49.3854 11.2622 49.6503 10.8752 49.831 10.4492C50.0117 10.0233 50.1047 9.56666 50.1047 9.10554C50.1047 8.64442 50.0117 8.18782 49.831 7.76186C49.6503 7.33589 49.3854 6.94891 49.0516 6.62305C48.724 6.29895 48.3337 6.04188 47.9034 5.86684C47.473 5.6918 47.0113 5.6023 46.5452 5.60358C46.0789 5.60081 45.6167 5.68963 45.1862 5.86476C44.7556 6.03988 44.3655 6.29775 44.0389 6.62305L43.6324 7.02025L43.2395 6.62305Z\"\n        fill=\"#FFF3F0\"\n        stroke=\"#77280C\"\n        strokeWidth={0.847356}\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M43.2451 6.62256C42.9173 6.2963 42.5262 6.03715 42.0947 5.86025C41.6632 5.68335 41.1999 5.59224 40.732 5.59224C40.2641 5.59224 39.8008 5.68335 39.3693 5.86025C38.9379 6.03715 38.5467 6.2963 38.2189 6.62256C37.5651 7.27782 37.1992 8.15677 37.1992 9.07195C37.1992 9.98713 37.5651 10.8661 38.2189 11.5213L43.638 16.8173L49.0572 11.5213C49.7109 10.8661 50.0768 9.98713 50.0768 9.07195C50.0768 8.15677 49.7109 7.27782 49.0572 6.62256C48.7303 6.29666 48.3404 6.03753 47.9101 5.86024C47.4798 5.68295 47.0178 5.59103 46.5508 5.58984C46.0836 5.58955 45.6211 5.68079 45.1906 5.85817C44.7601 6.03555 44.3704 6.29547 44.0444 6.62256L43.638 6.95356L43.2451 6.62256Z\"\n        stroke=\"#77280C\"\n        strokeWidth={0.847356}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <rect\n        x={50.8164}\n        y={1.27539}\n        width={18.7109}\n        height={18.7109}\n        rx={9.35544}\n        stroke=\"currentColor\"\n        strokeWidth={2.12624}\n      />\n      <path\n        d=\"M60.1436 19.8621C65.3812 19.8621 69.6271 15.7127 69.6271 10.5941C69.6271 5.47558 65.3812 1.32617 60.1436 1.32617C54.9061 1.32617 50.6602 5.47558 50.6602 10.5941C50.6602 15.7127 54.9061 19.8621 60.1436 19.8621Z\"\n        fill=\"#F5BB5C\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M61.2469 17.7454H59.0792C58.8852 17.7454 58.6991 17.6701 58.5619 17.536C58.4247 17.4019 58.3477 17.2201 58.3477 17.0304V15.2695H61.9785V17.0304C61.9785 17.2201 61.9014 17.4019 61.7642 17.536C61.627 17.6701 61.4409 17.7454 61.2469 17.7454Z\"\n        fill=\"#FFE1B2\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M58.3634 15.6129V15.2687C58.3634 14.8186 58.2889 14.3724 58.1466 13.9447C57.9568 13.57 57.7152 13.2225 57.4286 12.912C56.9295 12.5112 56.5265 12.0082 56.2479 11.4386C55.9694 10.869 55.8221 10.2466 55.8164 9.61526C55.8164 8.48808 56.2746 7.40707 57.0902 6.61004C57.9057 5.813 59.0119 5.36523 60.1653 5.36523C61.3187 5.36523 62.4248 5.813 63.2404 6.61004C64.0559 7.40707 64.5141 8.48808 64.5141 9.61526C64.5025 10.2604 64.3475 10.8953 64.0597 11.476C63.772 12.0567 63.3584 12.5693 62.8477 12.9782C62.9426 12.912 62.7394 13.0576 62.6039 13.243C62.4705 13.435 62.3656 13.6445 62.2923 13.8653C62.1543 14.2935 62.0857 14.7403 62.0891 15.1893V15.5335\"\n        fill=\"#FCF0DE\"\n      />\n      <path\n        d=\"M59.4061 6.33203C58.7742 6.43607 58.1941 6.73813 57.7533 7.19263C57.2911 7.63134 56.9708 8.19297 56.832 8.8079\"\n        stroke=\"#1B1F23\"\n        strokeWidth={1.69471}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M61.2469 17.7454H59.0792C58.8852 17.7454 58.6991 17.6701 58.5619 17.536C58.4247 17.4019 58.3477 17.2201 58.3477 17.0304V15.2695H61.9785V17.0304C61.9785 17.2201 61.9014 17.4019 61.7642 17.536C61.627 17.6701 61.4409 17.7454 61.2469 17.7454V17.7454Z\"\n        stroke=\"#5D3B01\"\n        strokeWidth={0.847356}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M60.1336 1.98828V3.65651M58.3453 15.6122V15.2679C58.3489 14.8189 58.2803 14.3722 58.1421 13.9439C58.0538 13.722 57.9353 13.5127 57.7898 13.3217C57.645 13.1142 57.4769 12.9232 57.2886 12.7524C57.1078 12.5934 56.9402 12.4207 56.7873 12.236C56.16 11.4894 55.8155 10.5541 55.8118 9.58801C55.8118 8.46083 56.27 7.37982 57.0856 6.58279C57.9012 5.78576 59.0073 5.33799 60.1607 5.33799C61.3141 5.33799 62.4202 5.78576 63.2358 6.58279C64.0514 7.37982 64.5096 8.46083 64.5096 9.58801C64.4935 10.5542 64.1451 11.4869 63.5206 12.236C63.2903 12.514 63.0329 12.7656 62.7483 12.9907C62.6513 13.0768 62.5607 13.1697 62.4774 13.2687C62.3481 13.4612 62.2478 13.6707 62.1793 13.891C62.0413 14.3193 61.9728 14.766 61.9761 15.215V15.5592L58.3453 15.6122ZM55.5273 3.49234L56.3538 4.53962L55.5273 3.49234ZM64.7602 3.48704L63.9406 4.53962L64.7602 3.48704Z\"\n        stroke=\"#5D3B01\"\n        strokeWidth={0.847356}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n};\n\nconst LinkedinIconSmall = () => {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <rect width=\"16\" height=\"16\" rx=\"2.77787\" fill=\"#E9A53F\" />\n      <g clipPath=\"url(#clip0_2511_140522)\">\n        <path\n          d=\"M3.33203 4.5751C3.33203 4.27479 3.43714 4.02704 3.64735 3.83186C3.85755 3.63666 4.13083 3.53906 4.46717 3.53906C4.7975 3.53906 5.06476 3.63515 5.26897 3.82735C5.47918 4.02555 5.58428 4.2838 5.58428 4.60213C5.58428 4.89041 5.48218 5.13065 5.27798 5.32285C5.06777 5.52104 4.79149 5.62014 4.44915 5.62014H4.44014C4.10981 5.62014 3.84254 5.52104 3.63834 5.32285C3.43413 5.12465 3.33203 4.8754 3.33203 4.5751ZM3.44915 12.458L3.44915 6.43996H5.44915L5.44915 12.458H3.44915ZM6.55726 12.458H8.55726V9.09762C8.55726 8.8874 8.58128 8.72524 8.62933 8.61113C8.71341 8.40693 8.84104 8.23425 9.01221 8.09312C9.18338 7.95197 9.39809 7.8814 9.65635 7.8814C10.329 7.8814 10.6654 8.33485 10.6654 9.24176V12.458H12.6654L12.6654 9.00753C12.6654 8.11864 12.4552 7.44447 12.0347 6.98501C11.6143 6.52555 11.0588 6.29582 10.3681 6.29582C9.59329 6.29582 8.98969 6.62915 8.55726 7.29582V7.31384H8.54825L8.55726 7.29582V6.43996H6.55726C6.56926 6.63215 6.57527 7.22975 6.57527 8.23276C6.57527 9.23575 6.56926 10.6442 6.55726 12.458Z\"\n          fill=\"white\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0_2511_140522\">\n          <rect\n            x=\"3.33203\"\n            y=\"3.33203\"\n            width=\"9.33333\"\n            height=\"9.33333\"\n            fill=\"white\"\n          />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n};\nexport const LinkedinPreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n  return (\n    <div className=\"py-[15px] flex flex-col px-[15px] w-full gap-[20px] bg-bgLinkedin rounded-[12px]\">\n      <div className=\"flex gap-[8px]\">\n        <div className=\"w-[48px] h-[48px]\">\n          <img\n            src={integration?.picture || '/no-picture.jpg'}\n            alt=\"social\"\n            className=\"rounded-full relative z-[2] w-[48px] h-[48px]\"\n          />\n        </div>\n        <div className=\"flex flex-col leading-[16px]\">\n          <div className=\"text-[14px] font-[500]\">{integration?.name}</div>\n          <div className=\"text-[12px] font-[400] text-[#A3A3A3]\">\n            2,871 followers\n          </div>\n          <div className=\"text-[12px] font-[400] text-[#A3A3A3] flex gap-[4px] items-center\">\n            <span>30m •</span>\n            <span>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"10\"\n                height=\"10\"\n                viewBox=\"0 0 10 10\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M5 10C4.30833 10 3.65833 9.86867 3.05 9.606C2.44167 9.34367 1.9125 8.9875 1.4625 8.5375C1.0125 8.0875 0.656333 7.55833 0.394 6.95C0.131333 6.34167 0 5.69167 0 5C0 4.30833 0.131333 3.65833 0.394 3.05C0.656333 2.44167 1.0125 1.9125 1.4625 1.4625C1.9125 1.0125 2.44167 0.656167 3.05 0.3935C3.65833 0.131167 4.30833 0 5 0C5.69167 0 6.34167 0.131167 6.95 0.3935C7.55833 0.656167 8.0875 1.0125 8.5375 1.4625C8.9875 1.9125 9.34367 2.44167 9.606 3.05C9.86867 3.65833 10 4.30833 10 5C10 5.69167 9.86867 6.34167 9.606 6.95C9.34367 7.55833 8.9875 8.0875 8.5375 8.5375C8.0875 8.9875 7.55833 9.34367 6.95 9.606C6.34167 9.86867 5.69167 10 5 10ZM4.5 8.975V8C4.225 8 3.98967 7.90217 3.794 7.7065C3.598 7.5105 3.5 7.275 3.5 7V6.5L1.1 4.1C1.075 4.25 1.052 4.4 1.031 4.55C1.01033 4.7 1 4.85 1 5C1 6.00833 1.33133 6.89167 1.994 7.65C2.65633 8.40833 3.49167 8.85 4.5 8.975ZM7.95 7.7C8.11667 7.51667 8.26667 7.31867 8.4 7.106C8.53333 6.89367 8.64383 6.67283 8.7315 6.4435C8.81883 6.2145 8.8855 5.97917 8.9315 5.7375C8.97717 5.49583 9 5.25 9 5C9 4.18333 8.773 3.4375 8.319 2.7625C7.86467 2.0875 7.25833 1.6 6.5 1.3V1.5C6.5 1.775 6.40217 2.01033 6.2065 2.206C6.0105 2.402 5.775 2.5 5.5 2.5H4.5V3.5C4.5 3.64167 4.45217 3.76033 4.3565 3.856C4.2605 3.952 4.14167 4 4 4H3V5H6C6.14167 5 6.2605 5.04783 6.3565 5.1435C6.45217 5.2395 6.5 5.35833 6.5 5.5V7H7C7.21667 7 7.4125 7.0645 7.5875 7.1935C7.7625 7.32283 7.88333 7.49167 7.95 7.7Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div\n        className=\"text-[14px] font-[400] whitespace-pre-line\"\n        dangerouslySetInnerHTML={{\n          __html: renderContent?.[0]?.text,\n        }}\n      />\n      {!!renderContent?.[0]?.images?.length && (\n        <div className=\"h-[280px] -mx-[15px] overflow-hidden flex\">\n          {renderContent?.[0]?.images.map((image, index) => (\n            <a\n              key={`image_${index}`}\n              className=\"flex-1\"\n              href={mediaDir.set(image.path)}\n              target=\"_blank\"\n            >\n              <VideoOrImage autoplay={true} src={mediaDir.set(image.path)} />\n            </a>\n          ))}\n        </div>\n      )}\n      <div className=\"flex text-textLinkedin text-[12px] font-[400] items-center\">\n        <div className=\"flex flex-1 gap-[11px] items-center\">\n          <Icons />\n          <div className=\"\">88</div>\n        </div>\n        <div className=\"gap-[9px] items-center flex\">\n          <div>4 Comments</div>\n          <div>\n            <div className=\"w-[3px] h-[3px] bg-[#565C65] rounded-full\" />\n          </div>\n          <div>8 Reposts</div>\n        </div>\n      </div>\n      <div className=\"pt-[8px] flex text-[14px] font-[700] px-[32px] justify-between border-t border-borderLinkedin text-textLinkedin\">\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"21\"\n            viewBox=\"0 0 20 21\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M6.39692 20.41C5.39692 20.41 4.35692 20.02 3.75692 19.41C3.37692 19.03 3.13692 18.5 3.00692 17.79C2.67692 17.57 2.39692 17.28 2.17692 16.95C1.89692 16.53 1.71692 15.99 1.65692 15.29C0.916917 14.74 0.476917 14.06 0.366917 13.24C0.276917 12.56 0.406917 11.86 0.746917 11.15C0.336917 10.66 0.0969174 10.05 0.0169174 9.35C-0.163083 7.53 1.10692 6.16 3.17692 5.95C3.53692 5.91 3.89692 5.89 4.25692 5.89C4.65692 5.89 5.05692 5.91 5.45692 5.96C5.26692 5.39 5.14692 4.86 5.10692 4.4C4.97692 3.09 5.31692 1.98 6.11692 1.11C6.71692 0.45 7.63692 0 8.41692 0C8.54692 0 8.67692 0.01 8.78692 0.04C9.61692 0.22 10.2069 0.78 10.6269 1.79C10.8369 2.29 10.9269 2.69 11.0669 3.46C11.1669 4.05 11.2269 4.29 11.3169 4.57C11.6069 5.44 12.3869 6.36 13.3469 6.98C14.1069 7.47 14.9369 7.86 15.7969 8.14H17.9169C18.3569 8.14 18.7269 8.29 19.0069 8.56C19.3169 8.86 19.4669 9.28 19.4269 9.75V17.05C19.4269 17.53 19.2269 17.93 18.8769 18.18C18.6169 18.36 18.3069 18.45 17.9569 18.45H14.9069C12.2269 19.08 10.4069 19.54 9.54692 19.82C8.22692 20.24 7.62692 20.36 6.64692 20.43C6.56692 20.43 6.47692 20.43 6.39692 20.43V20.41ZM4.27692 7.68C3.97692 7.68 3.67692 7.69 3.36692 7.72C2.27692 7.83 1.72692 8.34 1.80692 9.16C1.85692 9.65 2.03692 9.99 2.39692 10.23C2.58692 10.37 2.71692 10.57 2.76692 10.8C2.80692 11.03 2.76692 11.27 2.63692 11.46C2.25692 12.03 2.09692 12.54 2.15692 12.99C2.20692 13.38 2.46692 13.7 2.97692 13.99C3.10692 14.07 3.21692 14.18 3.29692 14.31C3.37692 14.44 3.41692 14.59 3.42692 14.74C3.44692 15.49 3.58692 15.8 3.67692 15.92C3.82692 16.14 3.99692 16.29 4.21692 16.38C4.36692 16.44 4.48692 16.55 4.58692 16.68C4.67692 16.81 4.73692 16.97 4.75692 17.12C4.81692 17.79 4.97692 18.04 5.05692 18.12C5.31692 18.39 5.90692 18.59 6.41692 18.59H6.54692C7.37692 18.53 7.81692 18.44 9.02692 18.05C9.96692 17.75 11.8069 17.29 14.6469 16.62V9.55C13.7869 9.23 13.0469 8.86 12.3969 8.44C11.0969 7.6 10.0669 6.35 9.64692 5.09C9.50692 4.69 9.44692 4.38 9.32692 3.72C9.20692 3.01 9.13692 2.75 9.00692 2.43C8.76692 1.85 8.60692 1.78 8.43692 1.74C8.24692 1.74 7.78692 1.92 7.46692 2.26C7.00692 2.77 6.83692 3.36 6.91692 4.18C6.97692 4.84 7.25692 5.69 7.73692 6.72C7.80692 6.87 7.83692 7.05 7.81692 7.22C7.79692 7.39 7.72692 7.55 7.61692 7.68C7.50692 7.81 7.36692 7.91 7.19692 7.96C7.10692 7.99 7.01692 8 6.91692 8C6.83692 8 6.76692 8 6.68692 7.97C5.89692 7.76 5.08692 7.65 4.27692 7.65V7.68ZM17.6469 16.63V9.9H16.4169V16.63H17.6469Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n          <div>Like</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"21\"\n            height=\"19\"\n            viewBox=\"0 0 21 19\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M7 1H14C17.3137 1 20 3.68629 20 7C20 8.96497 19.0567 10.7096 17.5938 11.8057L17.5762 11.8184L17.5596 11.832L11.5 16.7891V13H7C3.68629 13 1 10.3137 1 7C1 3.68629 3.68629 1 7 1Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n            />\n          </svg>\n          <div>Comments</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"18\"\n            height=\"25\"\n            viewBox=\"0 0 18 25\"\n            fill=\"none\"\n          >\n            <g clip-path=\"url(#clip0_2511_140348)\">\n              <path\n                d=\"M14.69 4.17L10.2 0H7.56L11.08 3.27H3.61C1.62 3.27 0 4.89 0 6.88V15.94H1.8V6.88C1.8 5.88 2.61 5.07 3.61 5.07H11.08L7.58 8.32H10.22L14.69 4.17Z\"\n                fill=\"currentColor\"\n              />\n              <path\n                d=\"M3.14062 20.1098L7.63062 24.2798H10.2706L6.75062 21.0098H14.2206C16.2106 21.0098 17.8306 19.3898 17.8306 17.3998V8.33984H16.0306V17.3998C16.0306 18.3998 15.2206 19.2098 14.2206 19.2098H6.75062L10.2506 15.9598H7.61063L3.14062 20.1098Z\"\n                fill=\"currentColor\"\n              />\n            </g>\n            <defs>\n              <clipPath id=\"clip0_2511_140348\">\n                <rect width=\"17.83\" height=\"24.28\" fill=\"white\" />\n              </clipPath>\n            </defs>\n          </svg>\n          <div>Repost</div>\n        </div>\n        <div className=\"flex gap-[4px] items-center\">\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"21\"\n            height=\"21\"\n            viewBox=\"0 0 21 21\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M21 0L0 7.2L7.2 10.8L16.2 4.8L9.6 13.8L13.8 21L21 0Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n          <div>Send</div>\n        </div>\n      </div>\n      {renderContent.length > 1 && (\n        <>\n          {renderContent.slice(1).map((value, index) => (\n            <div key={index} className=\"flex flex-col gap-[12px]\">\n              <div className=\"flex gap-[6px] leading-[17px]\">\n                <div className=\"h-[34px]\">\n                  <img\n                    src={integration?.picture || '/no-picture.jpg'}\n                    alt=\"social\"\n                    className=\"rounded-full relative z-[2] h-[34px] w-[34px]\"\n                  />\n                </div>\n                <div className=\"flex flex-col gap-[12px]\">\n                  <div className=\"flex flex-col\">\n                    <div className=\"flex items-center gap-[6px]\">\n                      <div className=\"text-[13px] font-[500]\">\n                        {integration?.name}\n                      </div>\n                      <div>\n                        <LinkedinIconSmall />\n                      </div>\n                      <div className=\"text-[12px] font-[400]\">• 1st</div>\n                    </div>\n                    <div className=\"text-[12px] font-[400] text-textLinkedin\">\n                      Founder\n                    </div>\n                  </div>\n                  <div\n                    className=\"whitespace-pre-line text-[14px] font-[400]\"\n                    dangerouslySetInnerHTML={{\n                      __html: value.text,\n                    }}\n                  />\n                  <div className=\"flex gap-[6px] font-[400] text-[12px] text-textLinkedin items-center\">\n                    <div className=\"font-[700]\">Like</div>\n                    <div>•</div>\n                    <div>\n                      <svg\n                        width=\"21\"\n                        height=\"22\"\n                        viewBox=\"0 0 21 22\"\n                        fill=\"none\"\n                        xmlns=\"http://www.w3.org/2000/svg\"\n                      >\n                        <rect\n                          x=\"1.0625\"\n                          y=\"1.27734\"\n                          width=\"18.7109\"\n                          height=\"18.7109\"\n                          rx=\"9.35544\"\n                          stroke=\"white\"\n                          strokeWidth=\"2.12624\"\n                        />\n                        <path\n                          d=\"M10.4444 19.7547C15.682 19.7547 19.9279 15.6053 19.9279 10.4867C19.9279 5.36816 15.682 1.21875 10.4444 1.21875C5.20684 1.21875 0.960938 5.36816 0.960938 10.4867C0.960938 15.6053 5.20684 19.7547 10.4444 19.7547Z\"\n                          fill=\"#1485BD\"\n                        />\n                        <path\n                          fill-rule=\"evenodd\"\n                          clip-rule=\"evenodd\"\n                          d=\"M15.7769 9.49337H15.0318C14.964 9.49337 14.8286 9.24182 14.4899 8.88434C13.9886 8.35474 13.4331 7.6795 13.0402 7.30878C12.0654 6.48197 11.2686 5.4738 10.6965 4.34304C10.3713 3.6678 10.3442 3.36328 9.68038 3.36328C9.41819 3.39637 9.17864 3.5255 9.01014 3.72458C8.84164 3.92366 8.75676 4.17783 8.77267 4.43572C8.77267 4.62108 8.86751 5.26983 8.90815 5.49491C9.19833 6.53009 9.65567 7.51338 10.2629 8.4077H10.9945H5.19604C5.03181 8.40533 4.86888 8.43654 4.71771 8.49931C4.56654 8.56209 4.43047 8.65505 4.31821 8.77223C4.20596 8.88941 4.12001 9.02823 4.06587 9.17978C4.01173 9.33132 3.9906 9.49226 4.00383 9.65225C4.02471 9.96246 4.16748 10.2528 4.40229 10.4625C4.63709 10.6722 4.94577 10.7852 5.26378 10.7776H5.48055C5.34176 10.7811 5.20503 10.8112 5.07816 10.8663C4.95129 10.9214 4.83678 11.0004 4.74116 11.0988C4.64554 11.1971 4.57069 11.3129 4.52089 11.4396C4.47108 11.5662 4.44731 11.7012 4.45091 11.8368C4.45326 12.1036 4.55728 12.36 4.74266 12.5558C4.92804 12.7517 5.18147 12.873 5.45345 12.896C5.28789 13.0198 5.16238 13.1876 5.09194 13.3794C5.0215 13.5711 5.00913 13.7786 5.05632 13.977C5.10351 14.1755 5.20824 14.3564 5.35797 14.4981C5.50771 14.6398 5.69609 14.7364 5.90053 14.7761C5.77072 14.9963 5.72737 15.2552 5.7786 15.5043C5.84797 15.7489 5.99897 15.9638 6.2076 16.1149C6.41624 16.266 6.67055 16.3447 6.93017 16.3384H10.0326C10.4391 16.3384 10.8441 16.2881 11.2384 16.1928L13.1622 15.6367H15.7363C17.1181 15.5837 17.4839 9.49337 15.7769 9.49337Z\"\n                          fill=\"#E6F7FF\"\n                        />\n                        <path\n                          d=\"M9.66218 8.40793H5.16429C4.99306 8.40636 4.82349 8.44086 4.66712 8.50909C4.51076 8.57731 4.37127 8.67765 4.25814 8.80328C4.14501 8.92891 4.06091 9.07689 4.01154 9.23713C3.96218 9.39738 3.94872 9.56613 3.97208 9.73192C3.99629 10.0398 4.14049 10.3268 4.37495 10.5337C4.6094 10.7407 4.9162 10.8518 5.23203 10.8441H5.4488C5.31001 10.8475 5.17328 10.8776 5.04641 10.9327C4.91954 10.9878 4.80503 11.0668 4.70941 11.1652C4.61379 11.2635 4.53894 11.3794 4.48914 11.506C4.43933 11.6326 4.41556 11.7676 4.41916 11.9033C4.42415 12.1706 4.52871 12.427 4.71326 12.6245C4.89781 12.8219 5.14969 12.9468 5.4217 12.9757C5.29214 13.075 5.18712 13.2016 5.11442 13.346C5.04172 13.4904 5.0032 13.6489 5.00172 13.8098C5.0024 14.0582 5.09018 14.2988 5.25043 14.4915C5.41069 14.6843 5.63372 14.8175 5.88233 14.869C5.75158 15.0937 5.70828 15.357 5.7604 15.6105C5.83228 15.8526 5.98435 16.0645 6.19275 16.2131C6.40116 16.3616 6.65411 16.4384 6.91196 16.4313H10.0144C10.4209 16.4313 10.8259 16.381 11.2202 16.2857L13.144 15.6634H15.7181C17.0728 15.6634 17.4386 9.52008 15.7181 9.52008C15.4699 9.53344 15.2211 9.53344 14.9729 9.52008C14.9052 9.52008 14.7697 9.26852 14.431 8.91104C13.9297 8.38145 13.3743 7.70621 12.9814 7.33549C12.0197 6.50104 11.2369 5.48852 10.6783 4.3565C10.5707 4.10668 10.4244 3.87448 10.2447 3.66803C10.0191 3.48258 9.72796 3.39116 9.43402 3.41345C9.14008 3.43574 8.86686 3.56995 8.67318 3.78719C8.53278 3.93558 8.41835 4.10556 8.33449 4.2903C8.27968 4.41815 8.23881 4.55128 8.21256 4.6875C8.17621 5.06078 8.20832 5.43741 8.30739 5.79966C8.3941 6.18097 8.51738 6.55301 8.67318 6.91181C8.92788 7.37918 9.22865 7.82272 9.56734 8.23581C9.60914 8.26098 9.6422 8.29789 9.66218 8.34173\"\n                          stroke=\"#004B7C\"\n                          strokeWidth=\"0.847356\"\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                        />\n                      </svg>\n                    </div>\n                    <div>19</div>\n                    <div>|</div>\n                    <div className=\"font-[700]\">Reply</div>\n                    <div>•</div>\n                    <div>1 reply</div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          ))}\n        </>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/linkedin/linkedin.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { Checkbox } from '@gitroom/react/form/checkbox';\nimport { Input } from '@gitroom/react/form/input';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto';\nimport { LinkedinPreview } from '@gitroom/frontend/components/new-launch/providers/linkedin/linkedin.preview';\n\nconst LinkedInSettings = () => {\n  const t = useT();\n  const { watch, register, formState, control } = useSettings();\n  const isCarousel = watch('post_as_images_carousel');\n\n  return (\n    <div className=\"mb-[20px]\">\n      <Checkbox\n        variant=\"hollow\"\n        label={t('post_as_images_carousel', 'Post as images carousel')}\n        {...register('post_as_images_carousel', {\n          value: false,\n        })}\n      />\n      {isCarousel && (\n        <div className=\"mt-[10px]\">\n          <Input\n            label={t('carousel_name', 'Carousel slide name')}\n            placeholder=\"slides\"\n            {...register('carousel_name')}\n          />\n        </div>\n      )}\n    </div>\n  );\n};\nexport default withProvider<LinkedinDto>({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: LinkedInSettings,\n  CustomPreviewComponent: LinkedinPreview,\n  dto: LinkedinDto,\n  checkValidity: async (posts, vals) => {\n    const [firstPost, ...restPosts] = posts ?? [];\n\n    if (\n      vals?.post_as_images_carousel &&\n      ((firstPost?.length ?? 0) < 2 ||\n        firstPost?.some((p) => (p?.path?.indexOf?.('mp4') ?? -1) > -1))\n    ) {\n      return 'Carousel can only be created with 2 or more images and no videos.';\n    }\n\n    if (\n      (firstPost?.length ?? 0) > 1 &&\n      firstPost?.some((p) => (p?.path?.indexOf?.('mp4') ?? -1) > -1)\n    ) {\n      return 'Can have maximum 1 media when selecting a video.';\n    }\n    if (restPosts?.some((p) => (p?.length ?? 0) > 0)) {\n      return 'Comments can only contain text.';\n    }\n    return true;\n  },\n  maximumCharacters: 3000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/listmonk/listmonk.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';\nimport { Input } from '@gitroom/react/form/input';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { SelectList } from '@gitroom/frontend/components/new-launch/providers/listmonk/select.list';\nimport { SelectTemplates } from '@gitroom/frontend/components/new-launch/providers/listmonk/select.templates';\n\nconst SettingsComponent = () => {\n  const form = useSettings();\n\n  return (\n    <>\n      <Input label=\"Subject\" {...form.register('subject')} />\n      <Input label=\"Preview\" {...form.register('preview')} />\n      <SelectList {...form.register('list')} />\n      <SelectTemplates {...form.register('template')} />\n    </>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: SettingsComponent,\n  CustomPreviewComponent: undefined,\n  dto: ListmonkDto,\n  checkValidity: undefined,\n  maximumCharacters: 300000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/listmonk/select.list.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const SelectList: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('list').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select List\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/listmonk/select.templates.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const SelectTemplates: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('templates').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Template\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/mastodon/mastodon.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: null,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: undefined,\n  maximumCharacters: 500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/medium/fonts/stylesheet.css",
    "content": "/* Generated by Font Squirrel (http://www.fontsquirrel.com) on July 10, 2013 */\n\n@font-face {\n  font-family: 'charterbold_italic';\n  src: url('charter_bold_italic-webfont.eot');\n  src: url('charter_bold_italic-webfont.eot?#iefix') format('embedded-opentype'),\n    url('charter_bold_italic-webfont.woff') format('woff');\n  font-weight: normal;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: 'charterbold';\n  src: url('charter_bold-webfont.eot');\n  src: url('charter_bold-webfont.eot?#iefix') format('embedded-opentype'),\n    url('charter_bold-webfont.woff') format('woff');\n  font-weight: normal;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: 'charteritalic';\n  src: url('charter_italic-webfont.eot');\n  src: url('charter_italic-webfont.eot?#iefix') format('embedded-opentype'),\n    url('charter_italic-webfont.woff') format('woff');\n  font-weight: normal;\n  font-style: normal;\n}\n\n@font-face {\n  font-family: 'charterregular';\n  src: url('charter_regular-webfont.eot');\n  src: url('charter_regular-webfont.eot?#iefix') format('embedded-opentype'),\n    url('charter_regular-webfont.woff') format('woff');\n  font-weight: normal;\n  font-style: normal;\n}\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/medium/medium.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { MediumPublications } from '@gitroom/frontend/components/new-launch/providers/medium/medium.publications';\nimport { MediumTags } from '@gitroom/frontend/components/new-launch/providers/medium/medium.tags';\nimport { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { Canonical } from '@gitroom/react/form/canonical';\n\nconst MediumSettings: FC = () => {\n  const form = useSettings();\n  const { date } = useIntegration();\n  return (\n    <>\n      <Input label=\"Title\" {...form.register('title')} />\n      <Input label=\"Subtitle\" {...form.register('subtitle')} />\n      <Canonical\n        date={date}\n        label=\"Canonical Link\"\n        {...form.register('canonical')}\n      />\n      <div>\n        <MediumPublications {...form.register('publication')} />\n      </div>\n      <div>\n        <MediumTags label=\"Topics\" {...form.register('tags')} />\n      </div>\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: MediumSettings,\n  CustomPreviewComponent: undefined, //MediumPreview,\n  dto: MediumSettingsDto,\n  checkValidity: undefined,\n  maximumCharacters: 100000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/medium/medium.publications.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const MediumPublications: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n\n  const customFunc = useCustomProviderFunction();\n  const [publications, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('publications').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!publications.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select publication\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {publications.map((publication: any) => (\n        <option key={publication.id} value={publication.id}>\n          {publication.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/medium/medium.tags.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { ReactTags } from 'react-tag-autocomplete';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const MediumTags: FC<{\n  name: string;\n  label: string;\n  onChange: (event: {\n    target: {\n      value: any[];\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, label } = props;\n  const { getValues } = useSettings();\n  const [tagValue, setTagValue] = useState<any[]>([]);\n  const [suggestions, setSuggestions] = useState<string>('');\n  const t = useT();\n\n  const onDelete = useCallback(\n    (tagIndex: number) => {\n      const modify = tagValue.filter((_, i) => i !== tagIndex);\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  const onAddition = useCallback(\n    (newTag: any) => {\n      if (tagValue.length >= 3) {\n        return;\n      }\n      const modify = [...tagValue, newTag];\n      setTagValue(modify);\n      onChange({\n        target: {\n          value: modify,\n          name,\n        },\n      });\n    },\n    [tagValue]\n  );\n  useEffect(() => {\n    const settings = getValues()[props.name];\n    if (settings) {\n      setTagValue(settings);\n    }\n  }, []);\n  const suggestionsArray = useMemo(() => {\n    return [\n      ...tagValue,\n      {\n        label: suggestions,\n        value: suggestions,\n      },\n    ].filter((f) => f.label);\n  }, [suggestions, tagValue]);\n  return (\n    <div>\n      <div className={`text-[14px] mb-[6px]`}>{label}</div>\n      <ReactTags\n        placeholderText={t('add_a_tag', 'Add a tag')}\n        suggestions={suggestionsArray}\n        selected={tagValue}\n        onAdd={onAddition}\n        onInput={setSuggestions}\n        onDelete={onDelete}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/mewe/mewe.group.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const MeweGroupSelect: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [groups, setGroups] = useState([]);\n  const { getValues } = useSettings();\n  const [currentGroup, setCurrentGroup] = useState<string | undefined>();\n\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentGroup(event.target.value);\n    onChange(event);\n  };\n\n  useEffect(() => {\n    customFunc.get('groups').then((data) => setGroups(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentGroup(settings);\n    }\n  }, []);\n\n  if (!groups.length) {\n    return null;\n  }\n\n  return (\n    <Select\n      name={name}\n      label=\"Select Group\"\n      onChange={onChangeInner}\n      value={currentGroup}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {groups.map((group: any) => (\n        <option key={group.id} value={group.id}>\n          {group.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/mewe/mewe.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC } from 'react';\nimport { MeweDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/mewe.dto';\nimport { MeweGroupSelect } from '@gitroom/frontend/components/new-launch/providers/mewe/mewe.group.select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Select } from '@gitroom/react/form/select';\nimport { useWatch } from 'react-hook-form';\n\nconst MeweComponent: FC = () => {\n  const form = useSettings();\n  const postType = useWatch({ control: form.control, name: 'postType' });\n\n  return (\n    <div>\n      <Select\n        label=\"Post To\"\n        {...form.register('postType')}\n      >\n        <option value=\"timeline\">My Timeline</option>\n        <option value=\"group\">Group</option>\n      </Select>\n      {postType === 'group' && (\n        <MeweGroupSelect {...form.register('group')} />\n      )}\n    </div>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  comments: false,\n  minimumCharacters: [],\n  SettingsComponent: MeweComponent,\n  CustomPreviewComponent: undefined,\n  dto: MeweDto,\n  checkValidity: undefined,\n  maximumCharacters: 63206,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/moltbook/moltbook.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { MoltbookDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/moltbook.dto';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nconst MoltbookSettings: FC = () => {\n  const form = useSettings();\n  const t = useT();\n\n  return (\n    <div>\n      <Input\n        label={t('submolt', 'Submolt')}\n        placeholder=\"general\"\n        {...form.register('submolt')}\n      />\n    </div>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: MoltbookSettings,\n  CustomPreviewComponent: undefined,\n  dto: MoltbookDto,\n  maximumCharacters: 300,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/nostr/nostr.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: null,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async () => {\n    return true;\n  },\n  maximumCharacters: 100000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/pinterest/pinterest.board.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const PinterestBoard: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState<undefined | any[]>();\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('boards').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs) {\n    return null;\n  }\n  if (!orgs.length) {\n    return 'No boards found, you have to create a board first';\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select board\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/pinterest/pinterest.preview.tsx",
    "content": "import { FC } from 'react';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\n\nexport const PinterestPreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n\n  return (\n    <div className=\"absolute left-0 top-0 gap-[10px] w-full h-full flex flex-col p-[16px] bg-bgYoutube\">\n      <div className=\"h-[40px] items-center flex\">\n        <div className=\"flex gap-[16px] flex-1 items-center\">\n          <div className=\"flex gap-[8px] items-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"22\"\n              height=\"20\"\n              viewBox=\"0 0 22 20\"\n              fill=\"none\"\n            >\n              <path\n                fillRule=\"evenodd\"\n                clipRule=\"evenodd\"\n                d=\"M10.7432 2.88777C8.7438 0.550353 5.40975 -0.0784043 2.90469 2.06197C0.399644 4.20234 0.0469677 7.78093 2.0142 10.3124C3.64982 12.4171 8.59977 16.8561 10.2221 18.2928C10.4036 18.4535 10.4944 18.5339 10.6002 18.5655C10.6926 18.593 10.7937 18.593 10.8861 18.5655C10.9919 18.5339 11.0827 18.4535 11.2642 18.2928C12.8865 16.8561 17.8365 12.4171 19.4721 10.3124C21.4393 7.78093 21.1297 4.17982 18.5816 2.06197C16.0335 -0.0558897 12.7425 0.550353 10.7432 2.88777Z\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n            <div>80</div>\n          </div>\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"21\"\n              height=\"19\"\n              viewBox=\"0 0 21 19\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M19.358 9.25C19.358 13.9444 15.5524 17.75 10.858 17.75C9.78124 17.75 8.75123 17.5498 7.80318 17.1845C7.62984 17.1178 7.54318 17.0844 7.47426 17.0685C7.40647 17.0529 7.3574 17.0463 7.28788 17.0437C7.21721 17.041 7.13967 17.049 6.98459 17.065L1.86356 17.5944C1.37532 17.6448 1.1312 17.6701 0.987197 17.5822C0.861771 17.5057 0.776342 17.3779 0.753601 17.2328C0.727493 17.0661 0.844148 16.8502 1.07746 16.4184L2.71312 13.3908C2.84782 13.1415 2.91517 13.0168 2.94568 12.8969C2.9758 12.7786 2.98309 12.6932 2.97345 12.5714C2.96369 12.4481 2.90959 12.2876 2.80139 11.9666C2.51387 11.1136 2.35802 10.2 2.35802 9.25C2.35802 4.55558 6.1636 0.75 10.858 0.75C15.5524 0.75 19.358 4.55558 19.358 9.25Z\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"20\"\n              height=\"20\"\n              viewBox=\"0 0 20 20\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M18.75 9.75V13.95C18.75 15.6302 18.75 16.4702 18.423 17.112C18.1354 17.6765 17.6765 18.1354 17.112 18.423C16.4702 18.75 15.6302 18.75 13.95 18.75H5.55C3.86984 18.75 3.02976 18.75 2.38803 18.423C1.82354 18.1354 1.3646 17.6765 1.07698 17.112C0.75 16.4702 0.75 15.6302 0.75 13.95V9.75M13.75 4.75L9.75 0.75M9.75 0.75L5.75 4.75M9.75 0.75V12.75\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"18\"\n              height=\"4\"\n              viewBox=\"0 0 18 4\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M8.75 2.75C9.30228 2.75 9.75 2.30228 9.75 1.75C9.75 1.19772 9.30228 0.75 8.75 0.75C8.19772 0.75 7.75 1.19772 7.75 1.75C7.75 2.30228 8.19772 2.75 8.75 2.75Z\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n              <path\n                d=\"M15.75 2.75C16.3023 2.75 16.75 2.30228 16.75 1.75C16.75 1.19772 16.3023 0.75 15.75 0.75C15.1977 0.75 14.75 1.19772 14.75 1.75C14.75 2.30228 15.1977 2.75 15.75 2.75Z\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n              <path\n                d=\"M1.75 2.75C2.30228 2.75 2.75 2.30228 2.75 1.75C2.75 1.19772 2.30228 0.75 1.75 0.75C1.19772 0.75 0.75 1.19772 0.75 1.75C0.75 2.30228 1.19772 2.75 1.75 2.75Z\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n        </div>\n        <div className=\"h-full flex rounded-[12px] text-[16px] font-[600] w-[100px] bg-[#E70024] text-white justify-center items-center\">\n          Save\n        </div>\n      </div>\n      <div\n        style={{ background: 'url(/no-video-youtube.png)' }}\n        className=\"!bg-cover w-full aspect-[calc(16/9)] rounded-[20px] overflow-hidden\"\n      >\n        {!!renderContent?.[0]?.images?.[0]?.path && (\n          <VideoOrImage\n            imageClassName=\"w-full aspect-[calc(16/9)]\"\n            videoClassName=\"w-full aspect-[calc(16/9)] bg-black\"\n            autoplay={true}\n            src={mediaDir.set(renderContent?.[0]?.images?.[0]?.path || '')}\n          />\n        )}\n      </div>\n      <div\n        className=\"mt-[13px] whitespace-pre-line\"\n        dangerouslySetInnerHTML={{ __html: renderContent?.[0]?.text || '' }}\n      ></div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/pinterest/pinterest.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { PinterestBoard } from '@gitroom/frontend/components/new-launch/providers/pinterest/pinterest.board';\nimport { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';\nimport { Input } from '@gitroom/react/form/input';\nimport { ColorPicker } from '@gitroom/react/form/color.picker';\nimport { PinterestPreview } from '@gitroom/frontend/components/new-launch/providers/pinterest/pinterest.preview';\nconst PinterestSettings: FC = () => {\n  const { register, control } = useSettings();\n  return (\n    <div className=\"flex flex-col\">\n      <Input label={'Title'} {...register('title')} />\n      <Input label={'Link'} {...register('link')} />\n      <PinterestBoard {...register('board')} />\n      <ColorPicker\n        label=\"Select Pin Color\"\n        name=\"dominant_color\"\n        enabled={false}\n        canBeCancelled={true}\n      />\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  comments: false,\n  SettingsComponent: PinterestSettings,\n  CustomPreviewComponent: PinterestPreview,\n  dto: PinterestSettingsDto,\n  checkValidity: async ([firstItem, ...otherItems] = []) => {\n    const isMp4 = firstItem?.find((item) => (item?.path?.indexOf?.('mp4') ?? -1) > -1);\n    const isPicture = firstItem?.find(\n      (item) => (item?.path?.indexOf?.('mp4') ?? -1) === -1\n    );\n    if ((firstItem?.length ?? 0) === 0) {\n      return 'Requires at least one media';\n    }\n    if (isMp4 && firstItem?.length !== 2 && !isPicture) {\n      return 'If posting a video you have to also include a cover image as second media';\n    }\n    if (isMp4 && (firstItem?.length ?? 0) > 2) {\n      return 'If posting a video you can only have two media items';\n    }\n\n    if (\n      (firstItem?.length ?? 0) > 1 &&\n      firstItem?.every((p) => (p?.path?.indexOf?.('mp4') ?? -1) == -1)\n    ) {\n      const loadAll: Array<{\n        width: number;\n        height: number;\n      }> = (await Promise.all(\n        firstItem?.map((p) => {\n          return new Promise((resolve, reject) => {\n            const url = new Image();\n            url.onload = function () {\n              // @ts-ignore\n              resolve({ width: this.width, height: this.height });\n            };\n            url.src = p?.path;\n          });\n        }) ?? []\n      )) as any;\n      const checkAllTheSameWidthHeight = loadAll?.every((p, i, arr) => {\n        return p?.width === arr?.[0]?.width && p?.height === arr?.[0]?.height;\n      });\n      if (!checkAllTheSameWidthHeight) {\n        return 'Requires all images to have the same width and height';\n      }\n    }\n    return true;\n  },\n  maximumCharacters: 500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { Subreddit } from '@gitroom/frontend/components/new-launch/providers/reddit/subreddit';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useFieldArray, useWatch } from 'react-hook-form';\nimport { Button } from '@gitroom/react/form/button';\nimport {\n  RedditSettingsDto,\n  RedditSettingsValueDto,\n} from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto';\nimport clsx from 'clsx';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport Image from 'next/image';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting';\nconst RenderRedditComponent: FC<{\n  type: string;\n  images?: Array<{\n    id: string;\n    path: string;\n  }>;\n}> = (props) => {\n  const { value: topValue } = useIntegration();\n  const showMedia = useMediaDirectory();\n  const t = useT();\n\n  const { type, images } = props;\n  const [firstPost] = topValue;\n  switch (type) {\n    case 'self':\n      return (\n        <div\n          dangerouslySetInnerHTML={{ __html: firstPost?.content }}\n          style={{\n            whiteSpace: 'pre-wrap',\n            fontSize: '14px',\n          }}\n        />\n      );\n    case 'link':\n      return (\n        <div className=\"h-[375px] bg-primary rounded-[16px] flex justify-center items-center\">\n          {t('link', 'Link')}\n        </div>\n      );\n    case 'media':\n      return (\n        <div className=\"h-[375px] bg-primary rounded-[16px] flex justify-center items-center\">\n          {!!images?.length &&\n            images.map((image, index) => (\n              <a\n                key={`image_${index}`}\n                href={showMedia.set(image.path)}\n                className=\"flex-1 h-full\"\n                target=\"_blank\"\n              >\n                <img\n                  className=\"w-full h-full object-cover\"\n                  src={showMedia.set(image.path)}\n                />\n              </a>\n            ))}\n        </div>\n      );\n  }\n  return <></>;\n};\nconst RedditPreview: FC = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const settings = useWatch({\n    name: 'subreddit',\n  }) as Array<RedditSettingsValueDto>;\n  const [, ...restOfPosts] = useFormatting(topValue, {\n    removeMarkdown: true,\n    saveBreaklines: true,\n    specialFunc: (text: string) => {\n      return text.slice(0, 280);\n    },\n  });\n  if (!settings || !settings.length) {\n    return <>Please add at least one Subreddit from the settings</>;\n  }\n  return (\n    <div className=\"flex flex-col gap-[40px] w-full\">\n      {settings\n        .filter(({ value }) => value?.subreddit)\n        .map(({ value }, index) => (\n          <div\n            key={index}\n            className={clsx(\n              `bg-customColor37 w-full p-[10px] flex flex-col border-tableBorder border`\n            )}\n          >\n            <div className=\"flex flex-col\">\n              <div className=\"flex flex-row gap-[8px]\">\n                <div className=\"w-[40px] h-[40px] bg-white rounded-full\" />\n                <div className=\"flex flex-col\">\n                  <div className=\"text-[12px] font-[700]\">\n                    {value.subreddit}\n                  </div>\n                  <div className=\"text-[12px]\">{integration?.name}</div>\n                </div>\n              </div>\n              <div className=\"font-[600] text-[24px] mb-[16px]\">\n                {value.title}\n              </div>\n              <div\n                className={clsx(\n                  restOfPosts.length && 'mt-[40px] flex flex-col gap-[20px]'\n                )}\n              >\n                {restOfPosts.map((p, index) => (\n                  <div className=\"flex gap-[8px]\" key={index}>\n                    <div className=\"w-[32px] h-[32px] relative\">\n                      <Image\n                        width={48}\n                        height={48}\n                        src={integration?.picture!}\n                        alt=\"x\"\n                        className=\"rounded-full w-full h-full relative z-[2]\"\n                      />\n                      <Image\n                        width={24}\n                        height={24}\n                        src={`/icons/platforms/${integration?.identifier!}.png`}\n                        alt=\"x\"\n                        className=\"rounded-full absolute -end-[5px] -bottom-[5px] z-[2]\"\n                      />\n                    </div>\n                    <div className=\"flex-1 flex flex-col leading-[16px] w-full pe-[64px] pb-[8px] rounded-[8px]\">\n                      <div className=\"text-[14px] font-[600]\">\n                        {integration?.name}\n                      </div>\n                      <div\n                        dangerouslySetInnerHTML={{ __html: p.text }}\n                        style={{\n                          whiteSpace: 'pre-wrap',\n                        }}\n                      />\n                    </div>\n                  </div>\n                ))}\n              </div>\n            </div>\n          </div>\n        ))}\n    </div>\n  );\n};\nconst RedditSettings: FC = () => {\n  const { register, control } = useSettings();\n  const { fields, append, remove } = useFieldArray({\n    control,\n    // control props comes from useForm (optional: if you are using FormContext)\n    name: 'subreddit', // unique name for your Field Array\n  });\n  const t = useT();\n\n  const addField = useCallback(() => {\n    append({});\n  }, [fields, append]);\n  const deleteField = useCallback(\n    (index: number) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_this_subreddit',\n            'Are you sure you want to delete this Subreddit?'\n          )\n        ))\n      )\n        return;\n      remove(index);\n    },\n    [fields, remove]\n  );\n  return (\n    <>\n      <div className=\"flex flex-col gap-[20px] mb-[20px]\">\n        {fields.map((field, index) => (\n          <div key={field.id} className=\"flex flex-col relative\">\n            <div\n              onClick={deleteField(index)}\n              className=\"absolute -start-[10px] justify-center items-center flex -top-[10px] w-[20px] h-[20px] bg-red-600 rounded-full text-textColor\"\n            >\n              x\n            </div>\n            <Subreddit {...register(`subreddit.${index}.value`)} />\n          </div>\n        ))}\n      </div>\n      <Button onClick={addField}>{t('add_subreddit', 'Add Subreddit')}</Button>\n      {fields.length === 0 && (\n        <div className=\"text-red-500 text-[12px] mt-[10px]\">\n          {t(\n            'please_add_at_least_one_subreddit',\n            'Please add at least one Subreddit'\n          )}\n        </div>\n      )}\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: RedditSettings,\n  CustomPreviewComponent: undefined,\n  dto: RedditSettingsDto,\n  checkValidity: async (posts, settings: any) => {\n    if (\n      settings?.subreddit?.some(\n        (p: any, index: number) =>\n          p?.value?.type === 'media' && posts?.[0]?.length !== 1\n      )\n    ) {\n      return 'When posting a media post, you must attached exactly one media file.';\n    }\n\n    if (\n      posts?.some((p) =>\n        p?.some((a) => !a?.thumbnail && (a?.path?.indexOf?.('mp4') ?? -1) > -1)\n      )\n    ) {\n      return 'You must attach a thumbnail to your video post.';\n    }\n\n    return true;\n  },\n  maximumCharacters: 10000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx",
    "content": "'use client';\n\nimport { FC, FormEvent, useCallback, useMemo, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Input } from '@gitroom/react/form/input';\nimport { useDebouncedCallback } from 'use-debounce';\nimport { Button } from '@gitroom/react/form/button';\nimport clsx from 'clsx';\nimport { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { useWatch } from 'react-hook-form';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Canonical } from '@gitroom/react/form/canonical';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nexport const RenderOptions: FC<{\n  options: Array<'self' | 'link' | 'media'>;\n  onClick: (current: 'self' | 'link' | 'media') => void;\n  value: 'self' | 'link' | 'media';\n}> = (props) => {\n  const { options, onClick, value } = props;\n  const mapValues = useMemo(() => {\n    return options?.map((p) => ({\n      children: (\n        <>\n          {p === 'self'\n            ? 'Post'\n            : p === 'link'\n            ? 'Link'\n            : p === 'media'\n            ? 'Media'\n            : ''}\n        </>\n      ),\n      id: p,\n      onClick: () => onClick(p),\n    })) || [];\n  }, [options]);\n  return (\n    <div className=\"flex\">\n      {mapValues.map((p) => (\n        <Button\n          className={clsx('flex-1', p.id !== value && 'bg-secondary')}\n          key={p.id}\n          {...p}\n        />\n      ))}\n    </div>\n  );\n};\nexport const Subreddit: FC<{\n  onChange: (event: {\n    target: {\n      name: string;\n      value: {\n        id: string;\n        name: string;\n      };\n    };\n  }) => void;\n  name: string;\n}> = (props) => {\n  const { onChange, name } = props;\n  const state = useSettings();\n  const t = useT();\n\n  const { date } = useIntegration();\n  const dummy = useLaunchStore((state) => state.dummy);\n  const split = name.split('.');\n  const [loading, setLoading] = useState(false);\n  // @ts-ignore\n  const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value;\n  const [results, setResults] = useState([]);\n  const func = useCustomProviderFunction();\n  const value = useWatch({\n    name,\n  });\n  const [searchValue, setSearchValue] = useState('');\n  const setResult = (result: { id: string; name: string }) => async () => {\n    setLoading(true);\n    setSearchValue('');\n    const restrictions = await func.get('restrictions', {\n      subreddit: result.name,\n    });\n    onChange({\n      target: {\n        name,\n        value: {\n          ...restrictions,\n          type: restrictions.allow[0],\n          media: [],\n        },\n      },\n    });\n    setLoading(false);\n  };\n  const setTitle = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            title: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setType = useCallback(\n    (e: string) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            type: e,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setMedia = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            media: e.target.value.map((p: any) => p),\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setURL = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            url: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setFlair = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            flair: value.flairs.find((p: any) => p.id === e.target.value),\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const search = useDebouncedCallback(\n    useCallback(async (e: FormEvent<HTMLInputElement>) => {\n      // @ts-ignore\n      setResults([]);\n      // @ts-ignore\n      if (!e.target.value) {\n        return;\n      }\n      // @ts-ignore\n      const results = await func.get('subreddits', { word: e.target.value });\n      // @ts-ignore\n      setResults(results);\n    }, []),\n    500\n  );\n  return (\n    <div className=\"bg-primary p-[20px]\">\n      {value?.subreddit ? (\n        <>\n          <Input\n            error={errors?.subreddit?.message}\n            disableForm={true}\n            value={value.subreddit}\n            readOnly={true}\n            label=\"Subreddit\"\n            name=\"subreddit\"\n          />\n          <div className=\"mb-[12px]\">\n            <RenderOptions\n              value={value.type}\n              options={value.allow}\n              onClick={setType}\n            />\n          </div>\n          <Input\n            error={errors?.title?.message}\n            value={value.title}\n            disableForm={true}\n            label=\"Title\"\n            name=\"title\"\n            onChange={setTitle}\n          />\n          <Select\n            error={errors?.flair?.message}\n            onChange={setFlair}\n            value={value?.flair?.id}\n            disableForm={true}\n            label=\"Flair\"\n            name=\"flair\"\n          >\n            <option value=\"\">{t('select_flair', '--Select Flair--')}</option>\n            {value?.flairs?.map((f: any) => (\n              <option key={f.name} value={f.id}>\n                {f.name}\n              </option>\n            ))}\n          </Select>\n          {value.type === 'link' && (\n            <Canonical\n              date={date}\n              error={errors?.url?.message}\n              value={value.url}\n              label=\"URL\"\n              name=\"url\"\n              disableForm={true}\n              onChange={setURL}\n            />\n          )}\n        </>\n      ) : (\n        <div className=\"relative\">\n          <Input\n            placeholder=\"/r/selfhosted\"\n            name=\"search\"\n            label=\"Search Subreddit\"\n            readOnly={loading}\n            value={searchValue}\n            error={errors?.message}\n            disableForm={true}\n            onInput={async (e) => {\n              // @ts-ignore\n              setSearchValue(e.target.value);\n              await search(e);\n            }}\n          />\n          {!!results.length && !loading && (\n            <div className=\"z-[400] w-full absolute bg-input -mt-[20px] outline-none border-fifth border cursor-pointer\">\n              {results.map((r: { id: string; name: string }) => (\n                <div\n                  onClick={setResult(r)}\n                  key={r.id}\n                  className=\"px-[16px] py-[5px] hover:bg-secondary\"\n                >\n                  {r.name}\n                </div>\n              ))}\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/show.all.providers.tsx",
    "content": "'use client';\n\nimport DevtoProvider from '@gitroom/frontend/components/new-launch/providers/devto/devto.provider';\nimport XProvider from '@gitroom/frontend/components/new-launch/providers/x/x.provider';\nimport LinkedinProvider from '@gitroom/frontend/components/new-launch/providers/linkedin/linkedin.provider';\nimport RedditProvider from '@gitroom/frontend/components/new-launch/providers/reddit/reddit.provider';\nimport MediumProvider from '@gitroom/frontend/components/new-launch/providers/medium/medium.provider';\nimport HashnodeProvider from '@gitroom/frontend/components/new-launch/providers/hashnode/hashnode.provider';\nimport FacebookProvider from '@gitroom/frontend/components/new-launch/providers/facebook/facebook.provider';\nimport InstagramProvider from '@gitroom/frontend/components/new-launch/providers/instagram/instagram.collaborators';\nimport YoutubeProvider from '@gitroom/frontend/components/new-launch/providers/youtube/youtube.provider';\nimport TiktokProvider from '@gitroom/frontend/components/new-launch/providers/tiktok/tiktok.provider';\nimport PinterestProvider from '@gitroom/frontend/components/new-launch/providers/pinterest/pinterest.provider';\nimport DribbbleProvider from '@gitroom/frontend/components/new-launch/providers/dribbble/dribbble.provider';\nimport ThreadsProvider from '@gitroom/frontend/components/new-launch/providers/threads/threads.provider';\nimport DiscordProvider from '@gitroom/frontend/components/new-launch/providers/discord/discord.provider';\nimport SlackProvider from '@gitroom/frontend/components/new-launch/providers/slack/slack.provider';\nimport KickProvider from '@gitroom/frontend/components/new-launch/providers/kick/kick.provider';\nimport TwitchProvider from '@gitroom/frontend/components/new-launch/providers/twitch/twitch.provider';\nimport MastodonProvider from '@gitroom/frontend/components/new-launch/providers/mastodon/mastodon.provider';\nimport BlueskyProvider from '@gitroom/frontend/components/new-launch/providers/bluesky/bluesky.provider';\nimport LemmyProvider from '@gitroom/frontend/components/new-launch/providers/lemmy/lemmy.provider';\nimport WarpcastProvider from '@gitroom/frontend/components/new-launch/providers/warpcast/warpcast.provider';\nimport TelegramProvider from '@gitroom/frontend/components/new-launch/providers/telegram/telegram.provider';\nimport NostrProvider from '@gitroom/frontend/components/new-launch/providers/nostr/nostr.provider';\nimport VkProvider from '@gitroom/frontend/components/new-launch/providers/vk/vk.provider';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useShallow } from 'zustand/react/shallow';\nimport React, { FC, forwardRef, useEffect, useImperativeHandle } from 'react';\nimport { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component';\nimport { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { Button } from '@gitroom/react/form/button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport WordpressProvider from '@gitroom/frontend/components/new-launch/providers/wordpress/wordpress.provider';\nimport ListmonkProvider from '@gitroom/frontend/components/new-launch/providers/listmonk/listmonk.provider';\nimport GmbProvider from '@gitroom/frontend/components/new-launch/providers/gmb/gmb.provider';\nimport MoltbookProvider from '@gitroom/frontend/components/new-launch/providers/moltbook/moltbook.provider';\nimport SkoolProvider from '@gitroom/frontend/components/new-launch/providers/skool/skool.provider';\nimport WhopProvider from '@gitroom/frontend/components/new-launch/providers/whop/whop.provider';\nimport MeweProvider from '@gitroom/frontend/components/new-launch/providers/mewe/mewe.provider';\n\nexport const Providers = [\n  {\n    identifier: 'devto',\n    component: DevtoProvider,\n  },\n  {\n    identifier: 'x',\n    component: XProvider,\n  },\n  {\n    identifier: 'linkedin',\n    component: LinkedinProvider,\n  },\n  {\n    identifier: 'linkedin-page',\n    component: LinkedinProvider,\n  },\n  {\n    identifier: 'reddit',\n    component: RedditProvider,\n  },\n  {\n    identifier: 'medium',\n    component: MediumProvider,\n  },\n  {\n    identifier: 'hashnode',\n    component: HashnodeProvider,\n  },\n  {\n    identifier: 'facebook',\n    component: FacebookProvider,\n  },\n  {\n    identifier: 'instagram',\n    component: InstagramProvider,\n  },\n  {\n    identifier: 'instagram-standalone',\n    component: InstagramProvider,\n  },\n  {\n    identifier: 'youtube',\n    component: YoutubeProvider,\n  },\n  {\n    identifier: 'tiktok',\n    component: TiktokProvider,\n  },\n  {\n    identifier: 'pinterest',\n    component: PinterestProvider,\n  },\n  {\n    identifier: 'dribbble',\n    component: DribbbleProvider,\n  },\n  {\n    identifier: 'threads',\n    component: ThreadsProvider,\n  },\n  {\n    identifier: 'discord',\n    component: DiscordProvider,\n  },\n  {\n    identifier: 'slack',\n    component: SlackProvider,\n  },\n  {\n    identifier: 'kick',\n    component: KickProvider,\n  },\n  {\n    identifier: 'twitch',\n    component: TwitchProvider,\n  },\n  {\n    identifier: 'mastodon',\n    component: MastodonProvider,\n  },\n  {\n    identifier: 'bluesky',\n    component: BlueskyProvider,\n  },\n  {\n    identifier: 'lemmy',\n    component: LemmyProvider,\n  },\n  {\n    identifier: 'wrapcast',\n    component: WarpcastProvider,\n  },\n  {\n    identifier: 'telegram',\n    component: TelegramProvider,\n  },\n  {\n    identifier: 'nostr',\n    component: NostrProvider,\n  },\n  {\n    identifier: 'vk',\n    component: VkProvider,\n  },\n  {\n    identifier: 'wordpress',\n    component: WordpressProvider,\n  },\n  {\n    identifier: 'listmonk',\n    component: ListmonkProvider,\n  },\n  {\n    identifier: 'gmb',\n    component: GmbProvider,\n  },\n  {\n    identifier: 'moltbook',\n    component: MoltbookProvider,\n  },\n  {\n    identifier: 'skool',\n    component: SkoolProvider,\n  },\n  {\n    identifier: 'whop',\n    component: WhopProvider,\n  },\n  {\n    identifier: 'mewe',\n    component: MeweProvider,\n  },\n];\nexport const ShowAllProviders = forwardRef((props, ref) => {\n  const { date, current, global, selectedIntegrations, allIntegrations } =\n    useLaunchStore(\n      useShallow((state) => ({\n        date: state.date,\n        selectedIntegrations: state.selectedIntegrations,\n        allIntegrations: state.integrations,\n        current: state.current,\n        global: state.global,\n      }))\n    );\n\n  const t = useT();\n\n  useImperativeHandle(ref, () => ({\n    checkAllValid: async () => {\n      return Promise.all(\n        selectedIntegrations.map(async (p) => await p.ref?.current.isValid())\n      );\n    },\n    getAllValues: async () => {\n      return Promise.all(\n        selectedIntegrations.map(async (p) => await p.ref?.current.getValues())\n      );\n    },\n    triggerAll: () => {\n      return selectedIntegrations.map(\n        async (p) => await p.ref?.current.trigger()\n      );\n    },\n  }));\n\n  return (\n    <div className=\"w-full flex flex-col flex-1\">\n      {current === 'global' && (\n        <IntegrationContext.Provider\n          value={{\n            date,\n            integration:\n              selectedIntegrations?.[0]?.integration || allIntegrations?.[0],\n            allIntegrations: selectedIntegrations.map((p) => p.integration),\n            value: global.map((p) => ({\n              id: p.id,\n              content: p.content,\n              image: p.media,\n            })),\n          }}\n        >\n          {global?.[0]?.content?.length === 0 ? (\n            <div>\n              {t(\n                'start_writing_your_post',\n                'Start writing your post for a preview'\n              )}\n            </div>\n          ) : (\n            <div className=\"border border-borderPreview rounded-[12px] shadow-previewShadow\">\n              <GeneralPreviewComponent maximumCharacters={100000000} />\n            </div>\n          )}\n        </IntegrationContext.Provider>\n      )}\n      {selectedIntegrations.map((integration) => {\n        const { component: ProviderComponent } = Providers.find(\n          (provider) =>\n            provider.identifier === integration.integration.identifier\n        ) || {\n          component: Empty,\n        };\n\n        return (\n          <ProviderComponent\n            ref={integration.ref}\n            key={integration.integration.id}\n            id={integration.integration.id}\n          />\n        );\n      })}\n    </div>\n  );\n});\n\nexport const Empty: FC = () => {\n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/skool/skool.group.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const SkoolGroupSelect: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [groups, setGroups] = useState([]);\n  const { getValues } = useSettings();\n  const [currentGroup, setCurrentGroup] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentGroup(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('groups').then((data) => setGroups(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentGroup(settings);\n    }\n  }, []);\n  if (!groups.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Group\"\n      onChange={onChangeInner}\n      value={currentGroup}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {groups.map((group: any) => (\n        <option key={group.id} value={group.id}>\n          {group.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/skool/skool.label.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const SkoolLabelSelect: FC<{\n  name: string;\n  groupId: string | undefined;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, groupId } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [labels, setLabels] = useState([]);\n  const { getValues } = useSettings();\n  const [currentLabel, setCurrentLabel] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentLabel(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    if (!groupId) {\n      setLabels([]);\n      setCurrentLabel(undefined);\n      return;\n    }\n    customFunc.get('label', { id: groupId }).then((data) => setLabels(data));\n  }, [groupId]);\n  useEffect(() => {\n    const settings = getValues()[name];\n    if (settings) {\n      setCurrentLabel(settings);\n    }\n  }, []);\n  if (!groupId || !labels.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Label\"\n      onChange={onChangeInner}\n      value={currentLabel}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {labels.map((label: any) => (\n        <option key={label.id} value={label.id}>\n          {label.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/skool/skool.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment, withProvider\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC, useState } from 'react';\nimport { SkoolDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/skool.dto';\nimport { SkoolGroupSelect } from '@gitroom/frontend/components/new-launch/providers/skool/skool.group.select';\nimport { SkoolLabelSelect } from '@gitroom/frontend/components/new-launch/providers/skool/skool.label.select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nconst SkoolComponent: FC = () => {\n  const form = useSettings();\n  const [selectedGroup, setSelectedGroup] = useState<string | undefined>(\n    form.getValues().group\n  );\n  const groupRegister = form.register('group');\n  const onGroupChange = (event: { target: { value: string; name: string } }) => {\n    setSelectedGroup(event.target.value);\n    groupRegister.onChange(event);\n  };\n  return (\n    <div>\n      <Input label=\"Title\" {...form.register('title')} />\n      <SkoolGroupSelect {...groupRegister} onChange={onGroupChange} />\n      <SkoolLabelSelect {...form.register('label')} groupId={selectedGroup} />\n    </div>\n  );\n};\nexport default withProvider({\n  minimumCharacters: [],\n  SettingsComponent: SkoolComponent,\n  CustomPreviewComponent: undefined,\n  dto: SkoolDto,\n  checkValidity: undefined,\n  maximumCharacters: 50000,\n  postComment: PostComment.ALL,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/slack/slack.channel.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const SlackChannelSelect: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [publications, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('channels').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!publications.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Channel\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {publications.map((publication: any) => (\n        <option key={publication.id} value={publication.id}>\n          {publication.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/slack/slack.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC } from 'react';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { SlackChannelSelect } from '@gitroom/frontend/components/new-launch/providers/slack/slack.channel.select';\nimport { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto';\nconst SlackComponent: FC = () => {\n  const form = useSettings();\n  return (\n    <div>\n      <SlackChannelSelect {...form.register('channel')} />\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: SlackComponent,\n  CustomPreviewComponent: undefined,\n  dto: SlackDto,\n  checkValidity: undefined,\n  maximumCharacters: 400000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/telegram/telegram.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: null,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async () => {\n    return true;\n  },\n  maximumCharacters: 4096,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/threads/threads.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher';\nconst SettingsComponent = () => {\n  return <ThreadFinisher />;\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: SettingsComponent,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async ([firstPost, ...otherPosts] = [], settings) => {\n    const checkVideosLength = await Promise.all(\n      firstPost\n        ?.filter((f) => (f?.path?.indexOf?.('mp4') ?? -1) > -1)\n        ?.flatMap((p) => p?.path)\n        ?.map((p) => {\n          return new Promise<number>((res) => {\n            const video = document.createElement('video');\n            video.preload = 'metadata';\n            video.src = p;\n            video.addEventListener('loadedmetadata', () => {\n              res(video.duration);\n            });\n          });\n        }) ?? []\n    );\n\n    for (const video of checkVideosLength) {\n      if (video > 300) {\n        return 'Video should be maximum 300 seconds (5 minutes)';\n      }\n    }\n\n    return true;\n  },\n  maximumCharacters: 500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/tiktok/tiktok.preview.tsx",
    "content": "import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { FC, ReactNode } from 'react';\nimport { SliderComponent } from '@gitroom/frontend/components/third-parties/slider.component';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\n\nconst TikTokItem: FC<{ icon: ReactNode; num: string }> = ({ icon, num }) => {\n  return (\n    <div className=\"flex items-center flex-col\">\n      <div className=\"w-[29px] h-[29px] rounded-full bg-bgTiktokItem flex justify-center items-center text-bgTiktokItemIcon\">\n        {icon}\n      </div>\n      <div className=\"text-[8px] font-[700] text-bgTiktokItemIcon\">{num}</div>\n    </div>\n  );\n};\nexport const TiktokPreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n  return (\n    <div className=\"p-[15px] absolute left-0 top-0 w-full h-full flex justify-center bg-newBgColorInner\">\n      <div className=\"relative\">\n        <SliderComponent\n          list={renderContent?.[0]?.images.map((image, index) => (\n            <a\n              key={`image_${index}`}\n              className=\"flex-1\"\n              href={mediaDir.set(image.path)}\n              target=\"_blank\"\n            >\n              <VideoOrImage autoplay={true} src={mediaDir.set(image.path)} />\n            </a>\n          ))}\n          className=\"h-full bg-black aspect-[calc(9/16)] rounded-[3px] overflow-hidden\"\n        />\n        <div className=\"absolute pointer-events-none w-full h-full start-0 top-0 px-[12px] py-[25px] justify-end items-start text-white flex flex-col\">\n          <div className=\"text-[14px] font-[500]\">@{integration?.name}</div>\n          <div className=\"text-[13px] font-[400] whitespace-pre-line line-clamp-6 w-full\"\n            dangerouslySetInnerHTML={{ __html: renderContent?.[0]?.text || '' }}\n          />\n        </div>\n      </div>\n      <div className=\"flex flex-col justify-end gap-[10px] ml-[18px]\">\n        <div className=\"relative\">\n          <img\n            src={integration?.picture || '/no-picture.jpg'}\n            alt=\"social\"\n            className=\"rounded-full z-[2] w-[29px] h-[29px]\"\n          />\n          <div className=\"absolute left-[50%] -translate-x-[50%] bottom-0 translate-y-[50%] z-[1]\">\n            <svg\n              width=\"13\"\n              height=\"13\"\n              viewBox=\"0 0 13 13\"\n              fill=\"none\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n            >\n              <circle cx=\"6.42665\" cy=\"6.42665\" r=\"6.42665\" fill=\"#EA4359\" />\n              <path\n                d=\"M8.53954 5.78529H7.06717V4.31274C7.06717 3.95875 6.78003 3.67188 6.42627 3.67188C6.07226 3.67188 5.78538 3.95876 5.78538 4.31274V5.78529H4.31277C3.95876 5.78529 3.67188 6.07218 3.67188 6.42615C3.67188 6.78015 3.95878 7.06702 4.31277 7.06702H5.78538V8.53956C5.78538 8.89356 6.07228 9.18043 6.42627 9.18043C6.78027 9.18043 7.06717 8.89354 7.06717 8.53956V7.06702H8.53954C8.89355 7.06702 9.18043 6.78013 9.18043 6.42615C9.18043 6.07216 8.89353 5.78529 8.53954 5.78529Z\"\n                fill=\"white\"\n              />\n            </svg>\n          </div>\n        </div>\n        <TikTokItem\n          num=\"1.3M\"\n          icon={\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"14\"\n              height=\"13\"\n              viewBox=\"0 0 14 13\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M6.73271 12.3432C6.54684 12.3432 6.36164 12.274 6.20938 12.1348C5.66959 11.6478 5.14626 11.1786 4.67368 10.7786C3.32385 9.57933 2.14275 8.55317 1.33269 7.54467C0.421822 6.41466 0 5.33697 0 4.17236C0 3.02468 0.371068 1.98152 1.06313 1.21694C1.75518 0.434621 2.7168 0 3.76278 0C4.53921 0 5.24773 0.260773 5.87189 0.747687C6.19221 1.00846 6.47958 1.30386 6.73268 1.66922C6.98577 1.30386 7.27247 1.00846 7.59346 0.747687C8.21762 0.243805 8.92615 0 9.70257 0C10.7486 0 11.6937 0.434621 12.4022 1.21694C13.0943 1.98159 13.4654 3.04235 13.4654 4.17236C13.4654 5.35467 13.0435 6.41466 12.1327 7.54467C11.3226 8.55313 10.1415 9.57853 8.79167 10.7786C8.33624 11.1786 7.79644 11.6478 7.25597 12.1348C7.10372 12.274 6.91852 12.3432 6.73264 12.3432H6.73271Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          }\n        />\n        <TikTokItem\n          num=\"10.7M\"\n          icon={\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"15\"\n              height=\"14\"\n              viewBox=\"0 0 15 14\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M7.03906 0C10.9263 0.000164123 14.0771 2.70127 14.0771 6.0332C14.0771 6.9371 13.8434 7.79378 13.4277 8.56348C13.4272 8.56492 13.4274 8.56696 13.4268 8.56836C12.6717 10.1862 11.4147 11.5178 9.84277 12.3643L8.0918 13.3076C7.64292 13.5491 7.10605 13.1896 7.1582 12.6826L7.22168 12.0615C7.16098 12.0629 7.10014 12.0664 7.03906 12.0664C3.15189 12.0664 0.000323506 9.365 0 6.0332C0 2.70117 3.15169 0 7.03906 0ZM3.41895 5.22852C2.86382 5.22876 2.41406 5.67919 2.41406 6.23438C2.41423 6.78942 2.86392 7.23901 3.41895 7.23926C3.97418 7.23926 4.42464 6.78957 4.4248 6.23438C4.4248 5.67904 3.97428 5.22852 3.41895 5.22852ZM7.03711 5.22852C6.48177 5.22852 6.03125 5.67904 6.03125 6.23438C6.03143 6.78956 6.48188 7.23926 7.03711 7.23926C7.59219 7.23908 8.04181 6.78945 8.04199 6.23438C8.04199 5.67915 7.5923 5.22869 7.03711 5.22852ZM10.6582 5.22852C10.1029 5.22852 9.65234 5.67904 9.65234 6.23438C9.65251 6.78957 10.103 7.23926 10.6582 7.23926C11.2133 7.23914 11.6629 6.7895 11.6631 6.23438C11.6631 5.67911 11.2134 5.22864 10.6582 5.22852Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          }\n        />\n        <TikTokItem\n          num=\"1.2M\"\n          icon={\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"11\"\n              height=\"12\"\n              viewBox=\"0 0 11 12\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M8.09766 0C9.37507 0.000192409 10.4129 1.04397 10.4189 2.31543V10.7666C10.4189 11.852 9.6448 12.3079 8.69727 11.7803L5.77051 10.1543C5.46465 9.98038 4.96034 9.98042 4.64844 10.1543L1.72168 11.7803C0.77405 12.3021 8.98228e-05 11.8461 0 10.7666V2.31543C0 1.04385 1.03786 0 2.31543 0H8.09766Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          }\n        />\n        <TikTokItem\n          num=\"1.2M\"\n          icon={\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"15\"\n              height=\"12\"\n              viewBox=\"0 0 15 12\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M14.4733 6.3949L9.33173 11.5364C9.12172 11.7465 8.80634 11.8091 8.5316 11.6955C8.25755 11.5819 8.07853 11.314 8.07853 11.0173V8.82547C3.18623 8.99211 1.26785 10.7872 1.24864 10.8064H1.24795C1.0159 11.0323 0.662667 11.0791 0.378972 10.9221C0.0952775 10.7644 -0.0520695 10.4401 0.0167829 10.1234C0.0319314 10.0538 1.57984 3.43187 8.07847 2.96368V0.734176C8.07847 0.437402 8.2575 0.169555 8.53155 0.055929C8.80629 -0.0576845 9.12166 0.00497537 9.33167 0.214988L14.4732 5.35653C14.6109 5.49425 14.6887 5.68084 14.6887 5.87571C14.6887 6.07058 14.6109 6.25718 14.4732 6.39489L14.4733 6.3949Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          }\n        />\n        <div>\n          <img\n            src={integration?.picture || '/no-picture.jpg'}\n            alt=\"social\"\n            className=\"rounded-full relative z-[2] w-[29px] h-[29px]\"\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/tiktok/tiktok.provider.tsx",
    "content": "'use client';\n\nimport {\n  FC,\n  useMemo,\n} from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Select } from '@gitroom/react/form/select';\nimport { Checkbox } from '@gitroom/react/form/checkbox';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { Input } from '@gitroom/react/form/input';\nimport { TiktokPreview } from '@gitroom/frontend/components/new-launch/providers/tiktok/tiktok.preview';\n\nconst TikTokSettings: FC<{\n  values?: any;\n}> = (props) => {\n  const { watch, register } = useSettings();\n  const { value } = useIntegration();\n  const t = useT();\n\n  const isTitle = useMemo(() => {\n    return value?.[0]?.image?.some((p) => (p?.path?.indexOf?.('mp4') ?? -1) === -1);\n  }, [value]);\n\n  const disclose = watch('disclose');\n  const brand_organic_toggle = watch('brand_organic_toggle');\n  const brand_content_toggle = watch('brand_content_toggle');\n  const content_posting_method = watch('content_posting_method');\n  const isUploadMode = content_posting_method === 'UPLOAD';\n\n  const privacyLevel = [\n    {\n      value: 'PUBLIC_TO_EVERYONE',\n      label: t('public_to_everyone', 'Public to everyone'),\n    },\n    {\n      value: 'MUTUAL_FOLLOW_FRIENDS',\n      label: t('mutual_follow_friends', 'Mutual follow friends'),\n    },\n    {\n      value: 'FOLLOWER_OF_CREATOR',\n      label: t('follower_of_creator', 'Follower of creator'),\n    },\n    {\n      value: 'SELF_ONLY',\n      label: t('self_only', 'Self only'),\n    },\n  ];\n  const contentPostingMethod = [\n    {\n      value: 'DIRECT_POST',\n      label: t(\n        'post_content_directly_to_tiktok',\n        'Post content directly to TikTok'\n      ),\n    },\n    {\n      value: 'UPLOAD',\n      label: t(\n        'upload_content_to_tiktok_without_posting',\n        'Upload content to TikTok without posting it'\n      ),\n    },\n  ];\n  const yesNo = [\n    {\n      value: 'yes',\n      label: t('yes', 'Yes'),\n    },\n    {\n      value: 'no',\n      label: t('no', 'No'),\n    },\n  ];\n\n  return (\n    <div className=\"flex flex-col\">\n      {/*<CheckTikTokValidity picture={props?.values?.[0]?.image?.[0]?.path} />*/}\n      {isTitle && <Input label=\"Title\" {...register('title')} maxLength={89} />}\n      <Select\n        label={t('label_who_can_see_this_video', 'Who can see this video?')}\n        disabled={isUploadMode}\n        {...register('privacy_level', {\n          value: 'PUBLIC_TO_EVERYONE',\n        })}\n      >\n        <option value=\"\">{t('select', 'Select')}</option>\n        {privacyLevel.map((item) => (\n          <option key={item.value} value={item.value}>\n            {item.label}\n          </option>\n        ))}\n      </Select>\n      <div className=\"text-[14px] mt-[10px] mb-[18px] text-balance\">\n        {t(\n          'choose_upload_without_posting_description',\n          `Choose upload without posting if you want to review and edit your content within TikTok's app before publishing.\n        This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.`\n        )}\n      </div>\n      <Select\n        label={t('label_content_posting_method', 'Content posting method')}\n        {...register('content_posting_method', {\n          value: 'DIRECT_POST',\n        })}\n      >\n        <option value=\"\">{t('select', 'Select')}</option>\n        {contentPostingMethod.map((item) => (\n          <option key={item.value} value={item.value}>\n            {item.label}\n          </option>\n        ))}\n      </Select>\n      {isUploadMode && <div className=\"-mt-[23px] mb-[23px] text-red-600\">After posting you fill find a notification inside your Inbox about your post (not content studio)</div>}\n      <Select\n        label={t('label_auto_add_music', 'Auto add music')}\n        {...register('autoAddMusic', {\n          value: 'no',\n        })}\n      >\n        <option value=\"\">{t('select', 'Select')}</option>\n        {yesNo.map((item) => (\n          <option key={item.value} value={item.value}>\n            {item.label}\n          </option>\n        ))}\n      </Select>\n      <div className=\"text-[14px] mt-[10px] mb-[24px] text-balance\">\n        {t(\n          'this_feature_available_only_for_photos',\n          'This feature available only for photos, it will add a default music that\\n        you can change later.'\n        )}\n      </div>\n      <hr className=\"mb-[15px] border-tableBorder\" />\n      <div className=\"text-[14px] mb-[10px]\">\n        {t('allow_user_to', 'Allow User To:')}\n      </div>\n      <div className=\"flex gap-[40px]\">\n        <Checkbox\n          label={t('label_comments', 'Comments')}\n          variant=\"hollow\"\n          disabled={isUploadMode}\n          {...register('comment', {\n            value: true,\n          })}\n        />\n        <Checkbox\n          variant=\"hollow\"\n          label={t('label_duet', 'Duet')}\n          disabled={isUploadMode}\n          {...register('duet', {\n            value: false,\n          })}\n        />\n        <Checkbox\n          label={t('label_stitch', 'Stitch')}\n          variant=\"hollow\"\n          disabled={isUploadMode}\n          {...register('stitch', {\n            value: false,\n          })}\n        />\n      </div>\n      <hr className=\"my-[15px] mb-[25px] border-tableBorder\" />\n      <div className=\"flex flex-col gap-[20px]\">\n        <Checkbox\n          label={t('video_made_with_ai', 'Video made with AI')}\n          variant=\"hollow\"\n          {...register('video_made_with_ai', {\n            value: false,\n          })}\n        />\n        <Checkbox\n          variant=\"hollow\"\n          label={t('label_disclose_video_content', 'Disclose Video Content')}\n          disabled={isUploadMode}\n          {...register('disclose', {\n            value: false,\n          })}\n        />\n        {disclose && (\n          <div className=\"bg-tableBorder p-[10px] mt-[10px] rounded-[10px] flex gap-[20px] items-center\">\n            <div>\n              <svg\n                width=\"24\"\n                height=\"24\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M22.201 17.6335L14.0026 3.39569C13.7977 3.04687 13.5052 2.75764 13.1541 2.55668C12.803 2.35572 12.4055 2.25 12.001 2.25C11.5965 2.25 11.199 2.35572 10.8479 2.55668C10.4968 2.75764 10.2043 3.04687 9.99944 3.39569L1.80101 17.6335C1.60388 17.9709 1.5 18.3546 1.5 18.7454C1.5 19.1361 1.60388 19.5199 1.80101 19.8572C2.00325 20.2082 2.29523 20.499 2.64697 20.6998C2.99871 20.9006 3.39755 21.0043 3.80257 21.0001H20.1994C20.6041 21.0039 21.0026 20.9001 21.354 20.6993C21.7054 20.4985 21.997 20.2079 22.1991 19.8572C22.3965 19.52 22.5007 19.1364 22.5011 18.7456C22.5014 18.3549 22.3978 17.9711 22.201 17.6335ZM11.251 9.75006C11.251 9.55115 11.33 9.36038 11.4707 9.21973C11.6113 9.07908 11.8021 9.00006 12.001 9.00006C12.1999 9.00006 12.3907 9.07908 12.5313 9.21973C12.672 9.36038 12.751 9.55115 12.751 9.75006V13.5001C12.751 13.699 12.672 13.8897 12.5313 14.0304C12.3907 14.171 12.1999 14.2501 12.001 14.2501C11.8021 14.2501 11.6113 14.171 11.4707 14.0304C11.33 13.8897 11.251 13.699 11.251 13.5001V9.75006ZM12.001 18.0001C11.7785 18.0001 11.561 17.9341 11.376 17.8105C11.191 17.6868 11.0468 17.5111 10.9616 17.3056C10.8765 17.1 10.8542 16.8738 10.8976 16.6556C10.941 16.4374 11.0482 16.2369 11.2055 16.0796C11.3628 15.9222 11.5633 15.8151 11.7815 15.7717C11.9998 15.7283 12.226 15.7505 12.4315 15.8357C12.6371 15.9208 12.8128 16.065 12.9364 16.25C13.06 16.4351 13.126 16.6526 13.126 16.8751C13.126 17.1734 13.0075 17.4596 12.7965 17.6706C12.5855 17.8815 12.2994 18.0001 12.001 18.0001Z\"\n                  fill=\"white\"\n                />\n              </svg>\n            </div>\n            <div>\n              {t(\n                'your_video_will_be_labeled_promotional',\n                'Your video will be labeled \"Promotional Content\".'\n              )}\n              <br />\n              {t(\n                'this_cannot_be_changed_once_posted',\n                'This cannot be changed once your video is posted.'\n              )}\n            </div>\n          </div>\n        )}\n        <div className=\"text-[14px] my-[10px] text-balance\">\n          {t(\n            'turn_on_to_disclose_video_promotes',\n            'Turn on to disclose that this video promotes goods or services in\\n          exchange for something of value. You video could promote yourself, a\\n          third party, or both.'\n          )}\n        </div>\n      </div>\n      <div className={clsx(!disclose && 'invisible h-0 overflow-hidden', 'mt-[20px]')}>\n        <Checkbox\n          variant=\"hollow\"\n          label={t('label_your_brand', 'Your brand')}\n          disabled={isUploadMode}\n          {...register('brand_organic_toggle', {\n            value: false,\n          })}\n        />\n        <div className=\"text-balance my-[10px] text-[14px]\">\n          {t(\n            'you_are_promoting_yourself',\n            'You are promoting yourself or your own brand.'\n          )}\n          <br />\n          {t(\n            'this_video_will_be_classified_brand_organic',\n            'This video will be classified as Brand Organic.'\n          )}\n        </div>\n        <Checkbox\n          variant=\"hollow\"\n          label={t('label_branded_content', 'Branded content')}\n          disabled={isUploadMode}\n          {...register('brand_content_toggle', {\n            value: false,\n          })}\n        />\n        <div className=\"text-balance my-[10px] text-[14px]\">\n          {t(\n            'you_are_promoting_another_brand',\n            'You are promoting another brand or a third party.'\n          )}\n          <br />\n          {t(\n            'this_video_will_be_classified_branded_content',\n            'This video will be classified as Branded Content.'\n          )}\n        </div>\n        {(brand_organic_toggle || brand_content_toggle) && (\n          <div className=\"my-[10px] text-[14px] text-balance\">\n            {t(\n              'by_posting_you_agree_to_tiktoks',\n              \"By posting, you agree to TikTok's\"\n            )}\n            {[\n              brand_organic_toggle || brand_content_toggle ? (\n                <a\n                  target=\"_blank\"\n                  className=\"text-[#B69DEC] hover:underline\"\n                  href=\"https://www.tiktok.com/legal/page/global/music-usage-confirmation/en\"\n                >\n                  {t('music_usage_confirmation', 'Music Usage Confirmation')}\n                </a>\n              ) : undefined,\n              brand_content_toggle ? <> {t('and', 'and')} </> : undefined,\n              brand_content_toggle ? (\n                <a\n                  target=\"_blank\"\n                  className=\"text-[#B69DEC] hover:underline\"\n                  href=\"https://www.tiktok.com/legal/page/global/bc-policy/en\"\n                >\n                  {t('branded_content_policy', 'Branded Content Policy')}\n                </a>\n              ) : undefined,\n            ].filter((f) => f)}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: TikTokSettings,\n  comments: false,\n  CustomPreviewComponent: TiktokPreview,\n  dto: TikTokDto,\n  checkValidity: async (items) => {\n    const [firstItems] = items ?? [];\n    if ((firstItems?.length ?? 0) === 0) {\n      return 'No video / images selected';\n    }\n    if (\n      (firstItems?.length ?? 0) > 1 &&\n      firstItems?.some((p) => (p?.path?.indexOf?.('mp4') ?? -1) > -1)\n    ) {\n      return 'Only pictures are supported when selecting multiple items';\n    } else if (\n      firstItems?.length !== 1 &&\n      (firstItems?.[0]?.path?.indexOf?.('mp4') ?? -1) > -1\n    ) {\n      return 'You need one media';\n    }\n    return true;\n  },\n  maximumCharacters: 2000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/twitch/twitch.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { TwitchDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/twitch.dto';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Select } from '@gitroom/react/form/select';\nimport { useWatch } from 'react-hook-form';\n\nconst messageTypes = [\n  {\n    label: 'Chat Message',\n    value: 'message',\n  },\n  {\n    label: 'Announcement',\n    value: 'announcement',\n  },\n];\n\nconst announcementColors = [\n  {\n    label: 'Primary (Default)',\n    value: 'primary',\n  },\n  {\n    label: 'Blue',\n    value: 'blue',\n  },\n  {\n    label: 'Green',\n    value: 'green',\n  },\n  {\n    label: 'Orange',\n    value: 'orange',\n  },\n  {\n    label: 'Purple',\n    value: 'purple',\n  },\n];\n\nconst TwitchSettings: FC = () => {\n  const { register, control } = useSettings();\n  const messageType = useWatch({\n    control,\n    name: 'messageType',\n  });\n\n  return (\n    <div className=\"flex flex-col\">\n      <Select\n        label=\"Message Type\"\n        {...register('messageType', {\n          value: 'message',\n        })}\n      >\n        {messageTypes.map((t) => (\n          <option key={t.value} value={t.value}>\n            {t.label}\n          </option>\n        ))}\n      </Select>\n      {messageType === 'announcement' && (\n        <Select\n          label=\"Announcement Color\"\n          {...register('announcementColor', {\n            value: 'primary',\n          })}\n        >\n          {announcementColors.map((c) => (\n            <option key={c.value} value={c.value}>\n              {c.label}\n            </option>\n          ))}\n        </Select>\n      )}\n    </div>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  comments: 'no-media',\n  minimumCharacters: [],\n  SettingsComponent: TwitchSettings,\n  CustomPreviewComponent: undefined,\n  dto: TwitchDto,\n  checkValidity: undefined,\n  maximumCharacters: 500,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/vk/vk.provider.tsx",
    "content": "'use client';\n'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: null,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async (posts) => {\n    return true;\n  },\n  maximumCharacters: 2048,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/warpcast/subreddit.tsx",
    "content": "'use client';\n\nimport { FC, FormEvent, useCallback, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Input } from '@gitroom/react/form/input';\nimport { useDebouncedCallback } from 'use-debounce';\nimport { useWatch } from 'react-hook-form';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const Subreddit: FC<{\n  onChange: (event: {\n    target: {\n      name: string;\n      value: {\n        id: string;\n        subreddit: string;\n        title: string;\n        name: string;\n        url: string;\n        body: string;\n        media: any[];\n      };\n    };\n  }) => void;\n  name: string;\n}> = (props) => {\n  const { onChange, name } = props;\n  const state = useSettings();\n  const split = name.split('.');\n  const [loading, setLoading] = useState(false);\n  // @ts-ignore\n  const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value;\n  const [results, setResults] = useState([]);\n  const func = useCustomProviderFunction();\n  const value = useWatch({\n    name,\n  });\n  const [searchValue, setSearchValue] = useState('');\n  const setResult = (result: { id: string; name: string }) => async () => {\n    setLoading(true);\n    setSearchValue('');\n    onChange({\n      target: {\n        name,\n        value: {\n          id: String(result.id),\n          subreddit: result.name,\n          title: '',\n          name: '',\n          url: '',\n          body: '',\n          media: [],\n        },\n      },\n    });\n    setLoading(false);\n  };\n  const setTitle = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            title: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const setURL = useCallback(\n    (e: any) => {\n      onChange({\n        target: {\n          name,\n          value: {\n            ...value,\n            url: e.target.value,\n          },\n        },\n      });\n    },\n    [value]\n  );\n  const search = useDebouncedCallback(\n    useCallback(async (e: FormEvent<HTMLInputElement>) => {\n      // @ts-ignore\n      setResults([]);\n      // @ts-ignore\n      if (!e.target.value) {\n        return;\n      }\n      // @ts-ignore\n      const results = await func.get('subreddits', { word: e.target.value });\n      // @ts-ignore\n      setResults(results);\n    }, []),\n    500\n  );\n  return (\n    <div className=\"bg-primary p-[20px]\">\n      {value?.subreddit ? (\n        <>\n          <Input\n            error={errors?.subreddit?.message}\n            disableForm={true}\n            value={value.subreddit}\n            readOnly={true}\n            label=\"Channel\"\n            name=\"subreddit\"\n          />\n        </>\n      ) : (\n        <div className=\"relative\">\n          <Input\n            placeholder=\"Channel\"\n            name=\"search\"\n            label=\"Search Channel\"\n            readOnly={loading}\n            value={searchValue}\n            error={errors?.message}\n            disableForm={true}\n            onInput={async (e) => {\n              // @ts-ignore\n              setSearchValue(e.target.value);\n              await search(e);\n            }}\n          />\n          {!!results.length && !loading && (\n            <div className=\"z-[400] w-full absolute bg-input -mt-[20px] outline-none border-fifth border cursor-pointer\">\n              {results.map((r: { id: string; name: string }) => (\n                <div\n                  onClick={setResult(r)}\n                  key={r.id}\n                  className=\"px-[16px] py-[5px] hover:bg-secondary\"\n                >\n                  {r.name}\n                </div>\n              ))}\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/warpcast/warpcast.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { FC, useCallback } from 'react';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useFieldArray } from 'react-hook-form';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { Button } from '@gitroom/react/form/button';\nimport { Subreddit } from './subreddit';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst WrapcastProvider: FC = () => {\n  const { register, control } = useSettings();\n  const { fields, append, remove } = useFieldArray({\n    control,\n    // control props comes from useForm (optional: if you are using FormContext)\n    name: 'subreddit', // unique name for your Field Array\n  });\n  const t = useT();\n\n  const addField = useCallback(() => {\n    append({});\n  }, [fields, append]);\n  const deleteField = useCallback(\n    (index: number) => async () => {\n      if (\n        !(await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete_this_subreddit',\n            'Are you sure you want to delete this Subreddit?'\n          )\n        ))\n      )\n        return;\n      remove(index);\n    },\n    [fields, remove]\n  );\n  return (\n    <>\n      <div className=\"flex flex-col gap-[20px] mb-[20px]\">\n        {fields.map((field, index) => (\n          <div key={field.id} className=\"flex flex-col relative\">\n            <div\n              onClick={deleteField(index)}\n              className=\"absolute -start-[10px] justify-center items-center flex -top-[10px] w-[20px] h-[20px] bg-red-600 rounded-full text-textColor\"\n            >\n              x\n            </div>\n            <Subreddit {...register(`subreddit.${index}.value`)} />\n          </div>\n        ))}\n      </div>\n      <Button onClick={addField}>{t('add_channel', 'Add Channel')}</Button>\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: WrapcastProvider,\n  CustomPreviewComponent: undefined,\n  dto: undefined,\n  checkValidity: async (list) => {\n    if (\n      list?.some((item) => item?.some((field) => (field?.path?.indexOf?.('mp4') ?? -1) > -1))\n    ) {\n      return 'Can only accept images';\n    }\n    return true;\n  },\n  maximumCharacters: 800,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/whop/whop.company.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const WhopCompanySelect: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [companies, setCompanies] = useState([]);\n  const { getValues } = useSettings();\n  const [currentCompany, setCurrentCompany] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentCompany(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('companies').then((data) => setCompanies(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentCompany(settings);\n    }\n  }, []);\n  if (!companies.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Company\"\n      onChange={onChangeInner}\n      value={currentCompany}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {companies.map((company: any) => (\n        <option key={company.id} value={company.id}>\n          {company.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/whop/whop.experience.select.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { Select } from '@gitroom/react/form/select';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const WhopExperienceSelect: FC<{\n  name: string;\n  companyId: string | undefined;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name, companyId } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [experiences, setExperiences] = useState([]);\n  const { getValues } = useSettings();\n  const [currentExperience, setCurrentExperience] = useState<\n    string | undefined\n  >();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentExperience(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    if (!companyId) {\n      setExperiences([]);\n      setCurrentExperience(undefined);\n      return;\n    }\n    customFunc\n      .get('experiences', { id: companyId })\n      .then((data) => setExperiences(data));\n  }, [companyId]);\n  useEffect(() => {\n    const settings = getValues()[name];\n    if (settings) {\n      setCurrentExperience(settings);\n    }\n  }, []);\n  if (!companyId || !experiences.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select Forum\"\n      onChange={onChangeInner}\n      value={currentExperience}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {experiences.map((experience: any) => (\n        <option key={experience.id} value={experience.id}>\n          {experience.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/whop/whop.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { WhopDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/whop.dto';\nimport { Input } from '@gitroom/react/form/input';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { WhopCompanySelect } from '@gitroom/frontend/components/new-launch/providers/whop/whop.company.select';\nimport { WhopExperienceSelect } from '@gitroom/frontend/components/new-launch/providers/whop/whop.experience.select';\nimport { FC, useState } from 'react';\n\nconst WhopSettings: FC = () => {\n  const form = useSettings();\n  const [selectedCompany, setSelectedCompany] = useState<string | undefined>(\n    form.getValues().company\n  );\n  const companyRegister = form.register('company');\n  const onCompanyChange = (event: {\n    target: { value: string; name: string };\n  }) => {\n    setSelectedCompany(event.target.value);\n    companyRegister.onChange(event);\n  };\n\n  return (\n    <div>\n      <WhopCompanySelect {...companyRegister} onChange={onCompanyChange} />\n      <WhopExperienceSelect\n        {...form.register('experience')}\n        companyId={selectedCompany}\n      />\n      <Input label=\"Title (optional)\" {...form.register('title')} />\n    </div>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: WhopSettings,\n  CustomPreviewComponent: undefined,\n  dto: WhopDto,\n  checkValidity: undefined,\n  maximumCharacters: 50000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/wordpress/wordpress.post.type.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useState } from 'react';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nexport const WordpressPostType: FC<{\n  name: string;\n  onChange: (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => void;\n}> = (props) => {\n  const { onChange, name } = props;\n  const t = useT();\n  const customFunc = useCustomProviderFunction();\n  const [orgs, setOrgs] = useState([]);\n  const { getValues } = useSettings();\n  const [currentMedia, setCurrentMedia] = useState<string | undefined>();\n  const onChangeInner = (event: {\n    target: {\n      value: string;\n      name: string;\n    };\n  }) => {\n    setCurrentMedia(event.target.value);\n    onChange(event);\n  };\n  useEffect(() => {\n    customFunc.get('postTypes').then((data) => setOrgs(data));\n    const settings = getValues()[props.name];\n    if (settings) {\n      setCurrentMedia(settings);\n    }\n  }, []);\n  if (!orgs.length) {\n    return null;\n  }\n  return (\n    <Select\n      name={name}\n      label=\"Select type\"\n      onChange={onChangeInner}\n      value={currentMedia}\n    >\n      <option value=\"\">{t('select_1', '--Select--')}</option>\n      {orgs.map((org: any) => (\n        <option key={org.id} value={org.id}>\n          {org.name}\n        </option>\n      ))}\n    </Select>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/wordpress/wordpress.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { Input } from '@gitroom/react/form/input';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { WordpressPostType } from '@gitroom/frontend/components/new-launch/providers/wordpress/wordpress.post.type';\nimport { MediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';\n\nconst WordpressSettings: FC = () => {\n  const form = useSettings();\n  return (\n    <>\n      <Input label=\"Title\" {...form.register('title')} />\n      <WordpressPostType {...form.register('type')} />\n      <MediaComponent\n        label=\"Cover picture\"\n        description=\"Add a cover picture\"\n        {...form.register('main_image')}\n      />\n    </>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  minimumCharacters: [],\n  SettingsComponent: WordpressSettings,\n  CustomPreviewComponent: undefined, // WordpressPreview,\n  dto: WordpressDto,\n  checkValidity: undefined,\n  maximumCharacters: 100000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/x/x.provider.tsx",
    "content": "'use client';\n\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { ThreadFinisher } from '@gitroom/frontend/components/new-launch/finisher/thread.finisher';\nimport { Select } from '@gitroom/react/form/select';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { XDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/x.dto';\nimport { Input } from '@gitroom/react/form/input';\n\nconst whoCanReply = [\n  {\n    label: 'Everyone',\n    value: 'everyone',\n  },\n  {\n    label: 'Accounts you follow',\n    value: 'following',\n  },\n  {\n    label: 'Mentioned accounts',\n    value: 'mentionedUsers',\n  },\n  {\n    label: 'Subscribers',\n    value: 'subscribers',\n  },\n  {\n    label: 'Verified accounts',\n    value: 'verified',\n  },\n];\n\nconst SettingsComponent = () => {\n  const t = useT();\n  const { register, watch, setValue } = useSettings();\n\n  return (\n    <>\n      <Select\n        label={t(\n          'label_who_can_reply_to_this_post',\n          'Who can reply to this post?'\n        )}\n        className=\"mb-5\"\n        hideErrors={true}\n        {...register('who_can_reply_post', {\n          value: 'everyone',\n        })}\n      >\n        {whoCanReply.map((item) => (\n          <option key={item.value} value={item.value}>\n            {item.label}\n          </option>\n        ))}\n      </Select>\n\n      <Input\n        label={\n          'Post to a community, URL (Ex: https://x.com/i/communities/1493446837214187523)'\n        }\n        {...register('community')}\n      />\n\n      <ThreadFinisher />\n    </>\n  );\n};\n\nexport default withProvider({\n  postComment: PostComment.POST,\n  minimumCharacters: [],\n  SettingsComponent: SettingsComponent,\n  CustomPreviewComponent: undefined,\n  dto: XDto,\n  checkValidity: async (posts, settings, additionalSettings: any) => {\n    const premium =\n      additionalSettings?.find((p: any) => p?.title === 'Verified')?.value ||\n      false;\n    if (posts?.some((p) => (p?.length ?? 0) > 4)) {\n      return 'There can be maximum 4 pictures in a post.';\n    }\n    if (\n      posts?.some(\n        (p) => p?.some((m) => (m?.path?.indexOf?.('mp4') ?? -1) > -1) && (p?.length ?? 0) > 1\n      )\n    ) {\n      return 'There can be maximum 1 video in a post.';\n    }\n    for (const load of posts?.flatMap((p) => p?.flatMap((a) => a?.path)) ?? []) {\n      if ((load?.indexOf?.('mp4') ?? -1) > -1) {\n        const isValid = await checkVideoDuration(load, premium);\n        if (!isValid) {\n          return 'Video duration must be less than or equal to 140 seconds.';\n        }\n      }\n    }\n    return true;\n  },\n  maximumCharacters: (settings) => {\n    if (settings?.[0]?.value) {\n      return 4000;\n    }\n    return 280;\n  },\n});\nconst checkVideoDuration = async (\n  url: string,\n  isPremium = false\n): Promise<boolean> => {\n  return new Promise((resolve, reject) => {\n    const video = document.createElement('video');\n    video.src = url;\n    video.preload = 'metadata';\n    video.onloadedmetadata = () => {\n      // Check if the duration is less than or equal to 140 seconds\n      const duration = video.duration;\n      if ((!isPremium && duration <= 140) || isPremium) {\n        resolve(true); // Video duration is acceptable\n      } else {\n        resolve(false); // Video duration exceeds 140 seconds\n      }\n    };\n    video.onerror = () => {\n      reject(new Error('Failed to load video metadata.'));\n    };\n  });\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/youtube/youtube.preview.tsx",
    "content": "import { FC } from 'react';\nimport { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { textSlicer } from '@gitroom/helpers/utils/count.length';\nimport { VideoOrImage } from '@gitroom/react/helpers/video.or.image';\n\nexport const YoutubePreview: FC<{\n  maximumCharacters?: number;\n}> = (props) => {\n  const { value: topValue, integration } = useIntegration();\n  const current = useLaunchStore((state) => state.current);\n  const mediaDir = useMediaDirectory();\n\n  const renderContent = topValue.map((p) => {\n    const newContent = stripHtmlValidation(\n      'normal',\n      p.content.replace(\n        /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n        (match, match1, match2) => {\n          return `[[[${match2}]]]`;\n        }\n      ),\n      true\n    );\n\n    const { start, end } = textSlicer(\n      integration?.identifier || '',\n      props.maximumCharacters || 10000,\n      newContent\n    );\n\n    const finalValue =\n      newContent\n        .slice(start, end)\n        .replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n          return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n        }) +\n      `<mark class=\"bg-red-500\" data-tooltip-id=\"tooltip\" data-tooltip-content=\"This text will be cropped\">` +\n      newContent.slice(end).replace(/\\[\\[\\[([.\\s\\S]*?)]]]/, (match, match1) => {\n        return `<span class=\"font-bold font-[arial]\" style=\"color: #ae8afc\">${match1}</span>`;\n      }) +\n      `</mark>`;\n\n    return { text: finalValue, images: p.image };\n  });\n\n  return (\n    <div className=\"absolute left-0 top-0 gap-[12px] w-full h-full flex flex-col p-[16px] bg-bgYoutube\">\n      <div\n        style={{ background: 'url(/no-video-youtube.png)' }}\n        className=\"!bg-cover w-full aspect-[calc(16/9)] rounded-[4px] overflow-hidden\"\n      >\n        {!!renderContent?.[0]?.images?.[0]?.path && (\n          <VideoOrImage\n            imageClassName=\"w-full aspect-[calc(16/9)]\"\n            videoClassName=\"w-full aspect-[calc(16/9)] bg-black\"\n            autoplay={true}\n            src={mediaDir.set(renderContent?.[0]?.images?.[0]?.path || '')}\n          />\n        )}\n      </div>\n      <div className=\"flex items-center\">\n        <div className=\"flex flex-1 gap-[17px] items-center\">\n          <div>\n            <img\n              src={integration?.picture || '/no-picture.jpg'}\n              alt=\"social\"\n              className=\"rounded-full z-[2] w-[40px] h-[40px]\"\n            />\n          </div>\n          <div className=\"flex flex-col\">\n            <div className=\"text-[14px] font-[500]\">{integration?.name}</div>\n            <div className=\"text-[10px] font-[400]\">16.7M subscribers</div>\n          </div>\n          <div>\n            <div className=\"h-[32px] text-[12px] text-newBgColor font-[500] px-[14px] flex justify-center items-center bg-youtubeButton rounded-[16px]\">\n              Subscribe\n            </div>\n          </div>\n        </div>\n        <div className=\"gap-[4px] flex items-center text-youtubeSvg\">\n          <div className=\"bg-youtubeBgAction text-[13px] px-[9px] h-[32px] rounded-[16px] flex items-center\">\n            <svg\n              className=\"mr-[9px]\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"15\"\n              viewBox=\"0 0 16 15\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M13.7503 6.1035H10.0621L11.3874 1.79617C11.6664 0.898087 10.934 0 9.92255 0C9.41684 0 8.92855 0.209263 8.59722 0.566754L3.48772 6.1035H0V14.8228H3.48772H4.35965H12.5819C13.5062 14.8228 14.3084 14.2386 14.4915 13.419L15.6598 8.18742C15.8953 7.10622 14.9797 6.1035 13.7503 6.1035ZM3.48772 13.9509H0.871929V6.97543H3.48772V13.9509ZM14.8054 7.99559L13.637 13.2272C13.5498 13.6457 13.1051 13.9509 12.5819 13.9509H4.35965V6.44356L9.24245 1.15967C9.40812 0.976561 9.66098 0.871929 9.92255 0.871929C10.1493 0.871929 10.3585 0.967842 10.4719 1.13351C10.5329 1.2207 10.6027 1.36021 10.5503 1.54331L9.22501 5.85065L8.87624 6.97543H10.0533H13.7416C14.0991 6.97543 14.4391 7.12366 14.6397 7.37652C14.753 7.50731 14.8664 7.72529 14.8054 7.99559Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n            <div className=\"mr-[14px]\">205K</div>\n            <div className=\"h-[20px] w-[1px] bg-[#A0A0A0] mr-[12px]\" />\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"15\"\n              viewBox=\"0 0 16 15\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M12.2092 0H11.3373H3.11498C2.18201 0 1.38856 0.584193 1.20545 1.40381L0.0370652 6.63538C-0.198356 7.71657 0.71717 8.71929 1.94659 8.71929H5.63485L4.30952 13.0266C4.0305 13.9247 4.76292 14.8228 5.77436 14.8228C6.28008 14.8228 6.76836 14.6135 7.09969 14.256L12.2092 8.71929H15.6969V0H12.2092ZM6.45446 13.6631C6.2888 13.8462 6.03594 13.9509 5.77436 13.9509C5.54766 13.9509 5.33839 13.855 5.22504 13.6893C5.16401 13.6021 5.09425 13.4626 5.14657 13.2795L6.4719 8.97215L6.82067 7.84736H5.63485H1.94659C1.5891 7.84736 1.24905 7.69913 1.0485 7.44628C0.943871 7.31549 0.830521 7.0975 0.891556 6.81849L2.05994 1.58691C2.14713 1.1771 2.59182 0.871929 3.11498 0.871929H11.3373V8.37924L6.45446 13.6631ZM14.825 7.84736H12.2092V0.871929H14.825V7.84736Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          </div>\n          <div className=\"h-[32px] w-[32px] rounded-full bg-youtubeBgAction flex justify-center items-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"18\"\n              height=\"16\"\n              viewBox=\"0 0 18 16\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M11.3351 2.29317L16.2702 7.84736L11.3351 13.4016V10.4632V9.59122H10.4632C7.01031 9.59122 4.23758 10.4632 1.96184 12.2855C3.56619 8.73673 6.4174 6.70514 10.5852 6.09479L11.3351 5.98143V5.23158V2.29317ZM10.4632 0V5.23158C3.67954 6.21686 0.967841 10.7509 0 15.6947C2.42396 12.2332 5.61522 10.4632 10.4632 10.4632V15.6947L17.4386 7.84736L10.4632 0Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          </div>\n          <div className=\"h-[32px] w-[32px] rounded-full bg-youtubeBgAction flex justify-center items-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"14\"\n              height=\"3\"\n              viewBox=\"0 0 14 3\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M2.61579 1.30789C2.61579 2.03159 2.03159 2.61579 1.30789 2.61579C0.584193 2.61579 0 2.03159 0 1.30789C0 0.584193 0.584193 0 1.30789 0C2.03159 0 2.61579 0.584193 2.61579 1.30789ZM6.53947 0C5.81577 0 5.23158 0.584193 5.23158 1.30789C5.23158 2.03159 5.81577 2.61579 6.53947 2.61579C7.26317 2.61579 7.84736 2.03159 7.84736 1.30789C7.84736 0.584193 7.26317 0 6.53947 0ZM11.771 0C11.0473 0 10.4632 0.584193 10.4632 1.30789C10.4632 2.03159 11.0473 2.61579 11.771 2.61579C12.4947 2.61579 13.0789 2.03159 13.0789 1.30789C13.0789 0.584193 12.4947 0 11.771 0Z\"\n                fill=\"currentColor\"\n              />\n            </svg>\n          </div>\n        </div>\n      </div>\n      <div\n        className=\"bg-youtubeBgAction rounded-[12px] p-[12px] text-[12px] font-[400] whitespace-pre-line\"\n        dangerouslySetInnerHTML={{ __html: renderContent?.[0]?.text }}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/providers/youtube/youtube.provider.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport {\n  PostComment,\n  withProvider,\n} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';\nimport { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';\nimport { Input } from '@gitroom/react/form/input';\nimport { MediumTags } from '@gitroom/frontend/components/new-launch/providers/medium/medium.tags';\nimport { MediaComponent } from '@gitroom/frontend/components/media/media.component';\nimport { Select } from '@gitroom/react/form/select';\nimport { YoutubePreview } from '@gitroom/frontend/components/new-launch/providers/youtube/youtube.preview';\nconst type = [\n  {\n    label: 'Public',\n    value: 'public',\n  },\n  {\n    label: 'Private',\n    value: 'private',\n  },\n  {\n    label: 'Unlisted',\n    value: 'unlisted',\n  },\n];\n\nconst madeForKids = [\n  {\n    label: 'No',\n    value: 'no',\n  },\n  {\n    label: 'Yes',\n    value: 'yes',\n  },\n];\nconst YoutubeSettings: FC = () => {\n  const { register, control } = useSettings();\n  return (\n    <div className=\"flex flex-col\">\n      <Input label=\"Title\" {...register('title')} maxLength={100} />\n      <Select\n        label=\"Type\"\n        {...register('type', {\n          value: 'public',\n        })}\n      >\n        {type.map((t) => (\n          <option key={t.value} value={t.value}>\n            {t.label}\n          </option>\n        ))}\n      </Select>\n      <Select\n        label=\"Made for kids\"\n        {...register('selfDeclaredMadeForKids', {\n          value: 'no',\n        })}\n      >\n        {madeForKids.map((t) => (\n          <option key={t.value} value={t.value}>\n            {t.label}\n          </option>\n        ))}\n      </Select>\n      <MediumTags label=\"Tags\" {...register('tags')} />\n      <div className=\"mt-[20px]\">\n        <MediaComponent\n          type=\"image\"\n          width={1280}\n          height={720}\n          label=\"Thumbnail\"\n          description=\"Thumbnail picture (optional)\"\n          {...register('thumbnail')}\n        />\n      </div>\n    </div>\n  );\n};\nexport default withProvider({\n  postComment: PostComment.COMMENT,\n  comments: false,\n  minimumCharacters: [],\n  SettingsComponent: YoutubeSettings,\n  CustomPreviewComponent: YoutubePreview,\n  dto: YoutubeSettingsDto,\n  checkValidity: async (items) => {\n    const [firstItems] = items ?? [];\n    if (items?.[0]?.length !== 1) {\n      return 'You need one media';\n    }\n    if ((firstItems?.[0]?.path?.indexOf?.('mp4') ?? -1) === -1) {\n      return 'Item must be a video';\n    }\n    return true;\n  },\n  maximumCharacters: 5000,\n});\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/select.current.tsx",
    "content": "'use client';\n\nimport { FC, RefObject, useCallback, useEffect, useRef, useState } from 'react';\nimport {\n  SelectedIntegrations,\n  useLaunchStore,\n} from '@gitroom/frontend/components/new-launch/store';\nimport clsx from 'clsx';\nimport Image from 'next/image';\nimport { useShallow } from 'zustand/react/shallow';\nimport { GlobalIcon } from '@gitroom/frontend/components/ui/icons';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport {\n  useDecisionModal,\n  useModals,\n} from '@gitroom/frontend/components/layout/new-modal';\n\nexport function useHasScroll(ref: RefObject<HTMLElement>): boolean {\n  const [hasHorizontalScroll, setHasHorizontalScroll] = useState(false);\n\n  useEffect(() => {\n    if (!ref.current) return;\n\n    const checkScroll = () => {\n      const el = ref.current;\n      if (el) {\n        setHasHorizontalScroll(el.scrollWidth > el.clientWidth);\n      }\n    };\n\n    checkScroll(); // initial check\n\n    const resizeObserver = new ResizeObserver(checkScroll);\n    resizeObserver.observe(ref.current);\n\n    const mutationObserver = new MutationObserver(checkScroll);\n    mutationObserver.observe(ref.current, {\n      childList: true,\n      subtree: true,\n      characterData: true,\n    });\n\n    return () => {\n      resizeObserver.disconnect();\n      mutationObserver.disconnect();\n    };\n  }, [ref]);\n\n  return hasHorizontalScroll;\n}\n\nexport const SelectCurrent: FC = () => {\n  const modals = useDecisionModal();\n  const {\n    selectedIntegrations,\n    current,\n    setCurrent,\n    locked,\n    setHide,\n    addOrRemoveSelectedIntegration,\n  } = useLaunchStore(\n    useShallow((state) => ({\n      selectedIntegrations: state.selectedIntegrations,\n      addOrRemoveSelectedIntegration: state.addOrRemoveSelectedIntegration,\n      current: state.current,\n      setCurrent: state.setCurrent,\n      locked: state.locked,\n      setHide: state.setHide,\n    }))\n  );\n\n  const contentRef = useRef<HTMLDivElement>(null);\n  const hasScroll = useHasScroll(contentRef);\n\n  const removeSocial = useCallback(\n    (sIntegration: Integrations) => async (e: any) => {\n      e.stopPropagation();\n      e.preventDefault();\n      const open = await modals.open({\n        title: 'Remove Social Account',\n        description:\n          'Are you sure you want to remove this social from scheduling?',\n      });\n\n      if (!open) {\n        return;\n      }\n\n      addOrRemoveSelectedIntegration(sIntegration, {});\n    },\n    []\n  );\n\n  return (\n    <>\n      <div className=\"select-none left-0 absolute w-full z-[100] px-[20px]\">\n        <div\n          ref={contentRef}\n          className={clsx(\n            'flex gap-[6px] w-full overflow-x-auto scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',\n            locked && 'opacity-50 pointer-events-none'\n          )}\n        >\n          <div\n            onClick={() => {\n              setHide(true);\n              setCurrent('global');\n            }}\n            className={clsx(\n              'cursor-pointer flex gap-[8px] rounded-[8px] w-[40px] h-[40px] justify-center items-center bg-newBgLineColor',\n              current !== 'global'\n                ? 'text-[#A3A3A3]'\n                : 'border border-[#FC69FF] text-[#FC69FF]'\n            )}\n          >\n            <div>\n              <GlobalIcon />\n            </div>\n          </div>\n          {selectedIntegrations.map(({ integration }) => (\n            <div\n              onClick={() => {\n                setHide(true);\n                setCurrent(integration.id);\n              }}\n              key={integration.id}\n              className={clsx(\n                'border cursor-pointer relative flex gap-[8px] w-[40px] h-[40px] rounded-[8px] items-center bg-newBgLineColor justify-center',\n                current === integration.id\n                  ? 'border-[#FC69FF] text-[#FC69FF]'\n                  : 'border-transparent'\n              )}\n            >\n              <div\n                onClick={removeSocial(integration)}\n                className=\"absolute justify-center items-center flex w-[8px] h-[8px] -top-[1px] -start-[3px] bg-red-500 rounded-full text-white text-[8px]\"\n              >\n                X\n              </div>\n              <IsGlobal id={integration.id} />\n              <div\n                {...{\n                  'data-tooltip-id': 'tooltip',\n                  'data-tooltip-content': integration.name,\n                }}\n                className={clsx(\n                  'relative w-full h-full rounded-full flex justify-center items-center filter transition-all duration-500'\n                )}\n              >\n                <Image\n                  src={integration.picture || '/no-picture.jpg'}\n                  className=\"rounded-full min-w-[26px]\"\n                  alt={integration.identifier}\n                  width={26}\n                  height={26}\n                  onError={(e) => {\n                    e.currentTarget.src = '/no-picture.jpg';\n                    e.currentTarget.srcset = '/no-picture.jpg';\n                  }}\n                />\n                {integration.identifier === 'youtube' ? (\n                  <img\n                    src=\"/icons/platforms/youtube.svg\"\n                    className=\"absolute z-10 bottom-[2px] end-[2px] min-w-[12px]\"\n                    width={12}\n                  />\n                ) : (\n                  <Image\n                    src={`/icons/platforms/${integration.identifier}.png`}\n                    className=\"min-w-[12px] min-h-[12px] rounded-[3px] absolute z-10 bottom-[6px] end-[6px]\"\n                    alt={integration.identifier}\n                    width={12}\n                    height={12}\n                  />\n                )}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className={clsx(hasScroll ? 'h-[55px]' : 'h-[40px]')} />\n    </>\n  );\n};\n\nexport const IsGlobal: FC<{ id: string }> = ({ id }) => {\n  const t = useT();\n  const { isInternal } = useLaunchStore(\n    useShallow((state) => ({\n      isInternal: !!state.internal.find((p) => p.integration.id === id),\n    }))\n  );\n\n  if (!isInternal) {\n    return null;\n  }\n\n  return (\n    <div\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content={t(\n        'no_longer_global_mode',\n        'No longer in global mode'\n      )}\n      className=\"w-[8px] h-[8px] bg-[#FC69FF] -top-[1px] -end-[3px] absolute rounded-full\"\n    />\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/store.ts",
    "content": "'use client';\n\nimport { create } from 'zustand';\nimport dayjs from 'dayjs';\nimport { Integrations } from '@gitroom/frontend/components/launches/calendar.context';\nimport { createRef, RefObject } from 'react';\nimport { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\n\ninterface Values {\n  id: string;\n  content: string;\n  delay: number;\n  media: { id: string; path: string; thumbnail?: string }[];\n}\n\nexport interface Internal {\n  integration: Integrations;\n  integrationValue: Values[];\n}\n\nexport interface SelectedIntegrations {\n  settings: any;\n  integration: Integrations;\n  ref?: RefObject<any>;\n}\n\ninterface StoreState {\n  editor: undefined | 'none' | 'normal' | 'markdown' | 'html';\n  loaded: boolean;\n  date: dayjs.Dayjs;\n  postComment: PostComment;\n  dummy: boolean;\n  repeater?: number;\n  isCreateSet: boolean;\n  totalChars: number;\n  activateExitButton: boolean;\n  tags: { label: string; value: string }[];\n  tab: 0 | 1;\n  current: string;\n  comments: boolean | 'no-media';\n  locked: boolean;\n  hide: boolean;\n  setLocked: (locked: boolean) => void;\n  integrations: Integrations[];\n  selectedIntegrations: SelectedIntegrations[];\n  global: Values[];\n  internal: Internal[];\n  addGlobalValue: (index: number, value: Values[]) => void;\n  setGlobalDelay: (index: number, minutes: number) => void;\n  setInternalDelay: (\n    integrationId: string,\n    index: number,\n    minutes: number\n  ) => void;\n  addInternalValue: (\n    index: number,\n    integrationId: string,\n    value: Values[]\n  ) => void;\n  setGlobalValue: (value: Values[]) => void;\n  setInternalValue: (integrationId: string, value: Values[]) => void;\n  deleteGlobalValue: (index: number) => void;\n  deleteInternalValue: (integrationId: string, index: number) => void;\n  addRemoveInternal: (integrationId: string) => void;\n  changeOrderGlobal: (index: number, direction: 'up' | 'down') => void;\n  changeOrderInternal: (\n    integrationId: string,\n    index: number,\n    direction: 'up' | 'down'\n  ) => void;\n  setGlobalValueText: (index: number, content: string) => void;\n  setGlobalValueMedia: (\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  setInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  addGlobalValueMedia: (\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  removeGlobalValueMedia: (index: number, mediaIndex: number) => void;\n  setInternalValueText: (\n    integrationId: string,\n    index: number,\n    content: string\n  ) => void;\n  addInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  removeInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    mediaIndex: number\n  ) => void;\n  setAllIntegrations: (integrations: Integrations[]) => void;\n  setCurrent: (current: string) => void;\n  addOrRemoveSelectedIntegration: (\n    integration: Integrations,\n    settings: any\n  ) => void;\n  reset: () => void;\n  setSelectedIntegrations: (\n    params: { selectedIntegrations: Integrations; settings: any }[]\n  ) => void;\n  setTab: (tab: 0 | 1) => void;\n  setHide: (hide: boolean) => void;\n  setDate: (date: dayjs.Dayjs) => void;\n  setRepeater: (repeater: number) => void;\n  setTags: (tags: { label: string; value: string }[]) => void;\n  setIsCreateSet: (isCreateSet: boolean) => void;\n  setTotalChars?: (totalChars: number) => void;\n  appendInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  appendGlobalValueMedia: (\n    index: number,\n    media: { id: string; path: string }[]\n  ) => void;\n  setPostComment: (postComment: PostComment) => void;\n  setActivateExitButton?: (activateExitButton: boolean) => void;\n  setDummy: (dummy: boolean) => void;\n  setEditor: (editor: 'none' | 'normal' | 'markdown' | 'html') => void;\n  setLoaded?: (loaded: boolean) => void;\n  setChars: (id: string, chars: number) => void;\n  chars: Record<string, number>;\n  setComments: (comments: boolean | 'no-media') => void;\n}\n\nconst initialState = {\n  editor: undefined as undefined,\n  loaded: true,\n  dummy: false,\n  comments: true,\n  activateExitButton: true,\n  date: newDayjs(),\n  postComment: PostComment.ALL,\n  tags: [] as { label: string; value: string }[],\n  totalChars: 0,\n  tab: 0 as 0,\n  isCreateSet: false,\n  current: 'global',\n  locked: false,\n  hide: false,\n  integrations: [] as Integrations[],\n  selectedIntegrations: [] as SelectedIntegrations[],\n  global: [] as Values[],\n  internal: [] as Internal[],\n  chars: {},\n};\n\nexport const useLaunchStore = create<StoreState>()((set) => ({\n  ...initialState,\n  setCurrent: (current: string) =>\n    set((state) => ({\n      current: current,\n    })),\n  addOrRemoveSelectedIntegration: (\n    integration: Integrations,\n    settings: any\n  ) => {\n    set((state) => {\n      const existing = state.selectedIntegrations.find(\n        (i) => i.integration.id === integration.id\n      );\n\n      if (existing) {\n        const selectedList = state.selectedIntegrations.filter(\n          (s, index) => s.integration.id !== existing.integration.id\n        );\n\n        return {\n          ...(existing.integration.id === state.current\n            ? { current: 'global' }\n            : {}),\n          loaded: false,\n          selectedIntegrations: selectedList,\n          ...(selectedList.length === 0\n            ? {\n                current: 'global',\n                editor: 'normal',\n              }\n            : {}),\n        };\n      }\n\n      return {\n        selectedIntegrations: [\n          ...state.selectedIntegrations,\n          { integration, settings, ref: createRef() },\n        ],\n      };\n    });\n  },\n  addGlobalValue: (index: number, value: Values[]) =>\n    set((state) => {\n      if (!state.global.length) {\n        return { global: value };\n      }\n\n      return {\n        global: state.global.reduce((acc, item, i) => {\n          acc.push(item);\n          if (i === index) {\n            acc.push(...value);\n          }\n          return acc;\n        }, []),\n      };\n    }),\n  // Add value after index, similar to addGlobalValue, but for a speciic integration (index starts from 0)\n  addInternalValue: (index: number, integrationId: string, value: Values[]) =>\n    set((state) => {\n      const integrationIndex = state.internal.findIndex(\n        (i) => i.integration.id === integrationId\n      );\n\n      if (integrationIndex === -1) {\n        return {\n          internal: [\n            ...state.internal,\n            {\n              integration: state.selectedIntegrations.find(\n                (i) => i.integration.id === integrationId\n              )!.integration,\n              integrationValue: value,\n            },\n          ],\n        };\n      }\n\n      const updatedIntegration = state.internal[integrationIndex];\n      const newValues = updatedIntegration.integrationValue.reduce(\n        (acc, item, i) => {\n          acc.push(item);\n          if (i === index) {\n            acc.push(...value);\n          }\n          return acc;\n        },\n        [] as Values[]\n      );\n\n      return {\n        internal: state.internal.map((i, idx) =>\n          idx === integrationIndex ? { ...i, integrationValue: newValues } : i\n        ),\n      };\n    }),\n  deleteGlobalValue: (index: number) =>\n    set((state) => {\n      // Preserve the IDs at their current positions\n      const ids = state.global.map((item) => item.id);\n\n      // Get remaining data (content, delay, media) after filtering out deleted index\n      const remainingData = state.global\n        .filter((_, i) => i !== index)\n        .map(({ id, ...rest }) => rest);\n\n      // Reconstruct with preserved IDs\n      return {\n        global: remainingData.map((data, i) => ({\n          id: ids[i],\n          ...data,\n        })),\n      };\n    }),\n  deleteInternalValue: (integrationId: string, index: number) =>\n    set((state) => {\n      return {\n        internal: state.internal.map((item) => {\n          if (item.integration.id === integrationId) {\n            // Preserve the IDs at their current positions\n            const ids = item.integrationValue.map((v) => v.id);\n\n            // Get remaining data after filtering out deleted index\n            const remainingData = item.integrationValue\n              .filter((_, idx) => idx !== index)\n              .map(({ id, ...rest }) => rest);\n\n            return {\n              ...item,\n              integrationValue: remainingData.map((data, i) => ({\n                id: ids[i],\n                ...data,\n              })),\n            };\n          }\n          return item;\n        }),\n      };\n    }),\n  addRemoveInternal: (integrationId: string) =>\n    set((state) => {\n      const integration = state.selectedIntegrations.find(\n        (i) => i.integration.id === integrationId\n      );\n      const findIntegrationIndex = state.internal.findIndex(\n        (i) => i.integration.id === integrationId\n      );\n\n      if (findIntegrationIndex > -1) {\n        return {\n          internal: state.internal.filter(\n            (i) => i.integration.id !== integrationId\n          ),\n        };\n      }\n\n      return {\n        internal: [\n          ...state.internal,\n          {\n            integration: integration.integration,\n            integrationValue: state.global.slice(0).map((p) => p),\n          },\n        ],\n      };\n    }),\n  changeOrderGlobal: (index: number, direction: 'up' | 'down') =>\n    set((state) => {\n      const targetIndex = direction === 'up' ? index - 1 : index + 1;\n\n      if (targetIndex < 0 || targetIndex >= state.global.length) {\n        return { global: state.global };\n      }\n\n      const currentItem = state.global[index];\n      const targetItem = state.global[targetIndex];\n\n      return {\n        global: state.global.map((item, i) => {\n          if (i === index) {\n            return {\n              id: item.id,\n              content: targetItem.content,\n              delay: targetItem.delay,\n              media: targetItem.media,\n            };\n          }\n          if (i === targetIndex) {\n            return {\n              id: item.id,\n              content: currentItem.content,\n              delay: currentItem.delay,\n              media: currentItem.media,\n            };\n          }\n          return item;\n        }),\n      };\n    }),\n  changeOrderInternal: (\n    integrationId: string,\n    index: number,\n    direction: 'up' | 'down'\n  ) =>\n    set((state) => {\n      return {\n        internal: state.internal.map((item) => {\n          if (item.integration.id === integrationId) {\n            const targetIndex = direction === 'up' ? index - 1 : index + 1;\n\n            if (targetIndex < 0 || targetIndex >= item.integrationValue.length) {\n              return item;\n            }\n\n            const currentValue = item.integrationValue[index];\n            const targetValue = item.integrationValue[targetIndex];\n\n            return {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) => {\n                if (i === index) {\n                  return {\n                    id: v.id,\n                    content: targetValue.content,\n                    delay: targetValue.delay,\n                    media: targetValue.media,\n                  };\n                }\n                if (i === targetIndex) {\n                  return {\n                    id: v.id,\n                    content: currentValue.content,\n                    delay: currentValue.delay,\n                    media: currentValue.media,\n                  };\n                }\n                return v;\n              }),\n            };\n          }\n\n          return item;\n        }),\n      };\n    }),\n  setGlobalValueText: (index: number, content: string) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index ? { ...item, content } : item\n      ),\n    })),\n  setInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) => {\n    return set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index ? { ...v, media } : v\n              ),\n            }\n          : item\n      ),\n    }));\n  },\n  setGlobalValueMedia: (index: number, media: { id: string; path: string }[]) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index ? { ...item, media } : item\n      ),\n    })),\n  addGlobalValueMedia: (index: number, media: { id: string; path: string }[]) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index ? { ...item, media: [...item.media, ...media] } : item\n      ),\n    })),\n  removeGlobalValueMedia: (index: number, mediaIndex: number) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index\n          ? {\n              ...item,\n              media: item.media.filter((_, idx) => idx !== mediaIndex),\n            }\n          : item\n      ),\n    })),\n  setInternalValueText: (\n    integrationId: string,\n    index: number,\n    content: string\n  ) => {\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index ? { ...v, content } : v\n              ),\n            }\n          : item\n      ),\n    }));\n  },\n  addInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) =>\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index ? { ...v, media: [...v.media, ...media] } : v\n              ),\n            }\n          : item\n      ),\n    })),\n  removeInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    mediaIndex: number\n  ) =>\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index\n                  ? {\n                      ...v,\n                      media: v.media.filter((_, idx) => idx !== mediaIndex),\n                    }\n                  : v\n              ),\n            }\n          : item\n      ),\n    })),\n  reset: () =>\n    set((state) => ({\n      ...state,\n      ...initialState,\n    })),\n  setAllIntegrations: (integrations: Integrations[]) =>\n    set((state) => ({\n      integrations: integrations,\n    })),\n  setTab: (tab: 0 | 1) =>\n    set((state) => ({\n      tab: tab,\n    })),\n  setLocked: (locked: boolean) =>\n    set((state) => ({\n      locked: locked,\n    })),\n  setHide: (hide: boolean) =>\n    set((state) => ({\n      hide: hide,\n    })),\n  setDate: (date: dayjs.Dayjs) =>\n    set((state) => ({\n      date,\n    })),\n  setRepeater: (repeater: number) =>\n    set((state) => ({\n      repeater,\n    })),\n  setTags: (tags: { label: string; value: string }[]) =>\n    set((state) => ({\n      tags,\n    })),\n  setIsCreateSet: (isCreateSet: boolean) =>\n    set((state) => ({\n      isCreateSet,\n    })),\n  setSelectedIntegrations: (\n    params: { selectedIntegrations: Integrations; settings: any }[]\n  ) =>\n    set((state) => ({\n      selectedIntegrations: params.map((p) => ({\n        integration: p.selectedIntegrations,\n        settings: p.settings,\n        ref: createRef(),\n      })),\n    })),\n  setGlobalValue: (value: Values[]) =>\n    set((state) => ({\n      global: value,\n    })),\n  setInternalValue: (integrationId: string, value: Values[]) =>\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? { ...item, integrationValue: value }\n          : item\n      ),\n    })),\n  setTotalChars: (totalChars: number) =>\n    set((state) => ({\n      totalChars,\n    })),\n  appendInternalValueMedia: (\n    integrationId: string,\n    index: number,\n    media: { id: string; path: string }[]\n  ) =>\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index\n                  ? { ...v, media: [...(v?.media || []), ...media] }\n                  : v\n              ),\n            }\n          : item\n      ),\n    })),\n  appendGlobalValueMedia: (\n    index: number,\n    media: { id: string; path: string }[]\n  ) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index\n          ? { ...item, media: [...(item?.media || []), ...media] }\n          : item\n      ),\n    })),\n  setPostComment: (postComment: PostComment) =>\n    set((state) => ({\n      postComment,\n    })),\n  setActivateExitButton: (activateExitButton: boolean) =>\n    set((state) => ({\n      activateExitButton,\n    })),\n  setDummy: (dummy: boolean) =>\n    set((state) => ({\n      dummy,\n    })),\n  setEditor: (editor: 'none' | 'normal' | 'markdown' | 'html') =>\n    set((state) => ({\n      editor,\n    })),\n  setLoaded: (loaded: boolean) =>\n    set((state) => ({\n      loaded,\n    })),\n  setChars: (id: string, chars: number) =>\n    set((state) => ({\n      chars: {\n        ...state.chars,\n        [id]: chars,\n      },\n    })),\n  setComments: (comments: boolean | 'no-media') =>\n    set((state) => ({\n      comments,\n    })),\n  setGlobalDelay: (index: number, minutes: number) =>\n    set((state) => ({\n      global: state.global.map((item, i) =>\n        i === index ? { ...item, delay: minutes } : item\n      ),\n    })),\n  setInternalDelay: (integrationId: string, index: number, minutes: number) =>\n    set((state) => ({\n      internal: state.internal.map((item) =>\n        item.integration.id === integrationId\n          ? {\n              ...item,\n              integrationValue: item.integrationValue.map((v, i) =>\n                i === index ? { ...v, delay: minutes } : v\n              ),\n            }\n          : item\n      ),\n    })),\n}));\n"
  },
  {
    "path": "apps/frontend/src/components/new-launch/u.text.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport { Editor, Transforms } from 'slate';\nimport { ReactEditor } from 'slate-react';\nconst underlineMap = {\n  a: 'a̲',\n  b: 'b̲',\n  c: 'c̲',\n  d: 'd̲',\n  e: 'e̲',\n  f: 'f̲',\n  g: 'g̲',\n  h: 'h̲',\n  i: 'i̲',\n  j: 'j̲',\n  k: 'k̲',\n  l: 'l̲',\n  m: 'm̲',\n  n: 'n̲',\n  o: 'o̲',\n  p: 'p̲',\n  q: 'q̲',\n  r: 'r̲',\n  s: 's̲',\n  t: 't̲',\n  u: 'u̲',\n  v: 'v̲',\n  w: 'w̲',\n  x: 'x̲',\n  y: 'y̲',\n  z: 'z̲',\n  A: 'A̲',\n  B: 'B̲',\n  C: 'C̲',\n  D: 'D̲',\n  E: 'E̲',\n  F: 'F̲',\n  G: 'G̲',\n  H: 'H̲',\n  I: 'I̲',\n  J: 'J̲',\n  K: 'K̲',\n  L: 'L̲',\n  M: 'M̲',\n  N: 'N̲',\n  O: 'O̲',\n  P: 'P̲',\n  Q: 'Q̲',\n  R: 'R̲',\n  S: 'S̲',\n  T: 'T̲',\n  U: 'U̲',\n  V: 'V̲',\n  W: 'W̲',\n  X: 'X̲',\n  Y: 'Y̲',\n  Z: 'Z̲',\n  '1': '1̲',\n  '2': '2̲',\n  '3': '3̲',\n  '4': '4̲',\n  '5': '5̲',\n  '6': '6̲',\n  '7': '7̲',\n  '8': '8̲',\n  '9': '9̲',\n  '0': '0̲',\n};\nconst reverseMap = Object.fromEntries(\n  Object.entries(underlineMap).map(([key, value]) => [value, key])\n);\nexport const UText: FC<{\n  editor: any;\n  currentValue: string;\n}> = ({ editor }) => {\n  const mark = () => {\n    editor?.commands?.unsetBold();\n    editor?.commands?.toggleUnderline();\n    editor?.commands?.focus();\n  };\n  return (\n    <div\n      data-tooltip-id=\"tooltip\"\n      data-tooltip-content=\"Underline\"\n      onClick={mark}\n      className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n    >\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M11.9993 2.66699V7.33366C11.9993 9.5428 10.2085 11.3337 7.99935 11.3337C5.79021 11.3337 3.99935 9.5428 3.99935 7.33366V2.66699M2.66602 14.0003H13.3327\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        />\n      </svg>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/billing.after.tsx",
    "content": "import { BillingComponent } from '@gitroom/frontend/components/billing/billing.component';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { Logo } from '@gitroom/frontend/components/new-layout/logo';\nimport { LogoutComponent } from '@gitroom/frontend/components/layout/logout.component';\nimport React from 'react';\nimport { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector';\n\nexport const BillingAfter = () => {\n  const user = useUser();\n  const { isGeneral, billingEnabled } = useVariables();\n  const t = useT();\n  return (\n    <div className=\"flex-1 rounded-3xl px-0 py-[17px] flex flex-col max-w-[1440px] mx-auto\">\n      <div>\n        <OrganizationSelector asOpenSelect={true} />\n      </div>\n      <div className=\"flex justify-center mb-[10px]\">\n        <Logo />\n      </div>\n      <div className=\"text-center mb-[20px] text-xl [@media(max-width:1024px)]:text-xl\">\n        <h1 className=\"text-3xl [@media(max-width:1024px)]:text-xl\">\n          {t(\n            'join_10000_entrepreneurs_who_use_postiz',\n            'Join 10,000+ Entrepreneurs Who Use Postiz'\n          )}\n          <br />\n          {t(\n            'to_manage_all_your_social_media_channels',\n            'To Manage All Your Social Media Channels'\n          )}\n        </h1>\n        <br />\n        {user?.allowTrial && (\n          <div className=\"table mx-auto\">\n            <div className=\"flex gap-[5px] items-center\">\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                    fill=\"#06ff00\"\n                  />\n                </svg>\n              </div>\n              <div>{t('100_no_risk_trial', '100% no-risk trial')}</div>\n            </div>\n            <div className=\"flex gap-[5px] items-center\">\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                    fill=\"#06ff00\"\n                  />\n                </svg>\n              </div>\n              <div>\n                {t(\n                  'pay_nothing_for_the_first_7_days',\n                  'Pay nothing for the first 7 days'\n                )}\n              </div>\n            </div>\n            <div className=\"flex gap-[5px] items-center\">\n              <div>\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                >\n                  <path\n                    d=\"M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z\"\n                    fill=\"#06ff00\"\n                  />\n                </svg>\n              </div>\n              <div>\n                {t('cancel_anytime_hassle_free', 'Cancel anytime, from settings')}\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n      <BillingComponent />\n      <div className=\"flex justify-center items-center mt-[20px]\">\n        <LogoutComponent />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/layout.component.tsx",
    "content": "'use client';\n\nimport React, { ReactNode, useCallback } from 'react';\nimport { Logo } from '@gitroom/frontend/components/new-layout/logo';\nimport { Plus_Jakarta_Sans } from 'next/font/google';\nconst ModeComponent = dynamic(\n  () => import('@gitroom/frontend/components/layout/mode.component'),\n  {\n    ssr: false,\n  }\n);\n\nimport clsx from 'clsx';\nimport dynamic from 'next/dynamic';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useSearchParams } from 'next/navigation';\nimport useSWR from 'swr';\nimport { CheckPayment } from '@gitroom/frontend/components/layout/check.payment';\nimport { ToolTip } from '@gitroom/frontend/components/layout/top.tip';\nimport { ShowMediaBoxModal } from '@gitroom/frontend/components/media/media.component';\nimport { ShowLinkedinCompany } from '@gitroom/frontend/components/launches/helpers/linkedin.component';\nimport { MediaSettingsLayout } from '@gitroom/frontend/components/launches/helpers/media.settings.component';\nimport { Toaster } from '@gitroom/react/toaster/toaster';\nimport { ShowPostSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector';\nimport { NewSubscription } from '@gitroom/frontend/components/layout/new.subscription';\nimport { Support } from '@gitroom/frontend/components/layout/support';\nimport { ContinueProvider } from '@gitroom/frontend/components/layout/continue.provider';\nimport { ContextWrapper } from '@gitroom/frontend/components/layout/user.context';\nimport { CopilotKit } from '@copilotkit/react-core';\nimport { MantineWrapper } from '@gitroom/react/helpers/mantine.wrapper';\nimport { Impersonate } from '@gitroom/frontend/components/layout/impersonate';\nimport { Title } from '@gitroom/frontend/components/layout/title';\nimport { TopMenu } from '@gitroom/frontend/components/layout/top.menu';\nimport { LanguageComponent } from '@gitroom/frontend/components/layout/language.component';\nimport { ChromeExtensionComponent } from '@gitroom/frontend/components/layout/chrome.extension.component';\nimport NotificationComponent from '@gitroom/frontend/components/notifications/notification.component';\nimport { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector';\nimport { StreakComponent } from '@gitroom/frontend/components/layout/streak.component';\nimport { PreConditionComponent } from '@gitroom/frontend/components/layout/pre-condition.component';\nimport { AttachToFeedbackIcon } from '@gitroom/frontend/components/new-layout/sentry.feedback.component';\nimport { FirstBillingComponent } from '@gitroom/frontend/components/billing/first.billing.component';\n\nconst jakartaSans = Plus_Jakarta_Sans({\n  weight: ['600', '500', '700'],\n  style: ['normal', 'italic'],\n  subsets: ['latin'],\n});\n\nexport const LayoutComponent = ({ children }: { children: ReactNode }) => {\n  const fetch = useFetch();\n\n  const { backendUrl, billingEnabled, isGeneral } = useVariables();\n\n  // Feedback icon component attaches Sentry feedback to a top-bar icon when DSN is present\n  const searchParams = useSearchParams();\n  const load = useCallback(async (path: string) => {\n    return await (await fetch(path)).json();\n  }, []);\n  const { data: user, mutate } = useSWR('/user/self', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    refreshWhenOffline: false,\n    refreshWhenHidden: false,\n  });\n\n  if (!user) return null;\n\n  return (\n    <ContextWrapper user={user}>\n      <CopilotKit\n        credentials=\"include\"\n        runtimeUrl={backendUrl + '/copilot/chat'}\n        showDevConsole={false}\n      >\n        <MantineWrapper>\n          <ToolTip />\n          <Toaster />\n          <CheckPayment check={searchParams.get('check') || ''} mutate={mutate}>\n            <ShowMediaBoxModal />\n            <ShowLinkedinCompany />\n            <MediaSettingsLayout />\n            <ShowPostSelector />\n            <PreConditionComponent />\n            <NewSubscription />\n            <ContinueProvider />\n            <div\n              className={clsx(\n                'flex flex-col min-h-screen min-w-screen text-newTextColor p-[12px]',\n                jakartaSans.className\n              )}\n            >\n              <div>{user?.admin ? <Impersonate /> : <div />}</div>\n              {user.tier === 'FREE' && isGeneral && billingEnabled ? (\n                <FirstBillingComponent />\n              ) : (\n                <div className=\"flex-1 flex gap-[8px]\">\n                  <Support />\n                  <div className=\"flex flex-col bg-newBgColorInner w-[80px] rounded-[12px]\">\n                    <div\n                      className={clsx(\n                        'fixed h-full w-[64px] start-[17px] flex flex-1 top-0',\n                        user?.admin && 'pt-[60px] max-h-[1000px]:w-[500px]'\n                      )}\n                    >\n                      <div className=\"flex flex-col h-full gap-[32px] flex-1 py-[12px]\">\n                        <Logo />\n                        <TopMenu />\n                      </div>\n                    </div>\n                  </div>\n                  <div className=\"flex-1 bg-newBgLineColor rounded-[12px] overflow-hidden flex flex-col gap-[1px] blurMe\">\n                    <div className=\"flex bg-newBgColorInner h-[80px] px-[20px] items-center\">\n                      <div className=\"text-[24px] font-[600] flex flex-1\">\n                        <Title />\n                      </div>\n                      <div className=\"flex gap-[20px] text-textItemBlur\">\n                        <StreakComponent />\n                        <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />\n                        <OrganizationSelector />\n                        <div className=\"hover:text-newTextColor\">\n                          <ModeComponent />\n                        </div>\n                        <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />\n                        <LanguageComponent />\n                        <ChromeExtensionComponent />\n                        <div className=\"w-[1px] h-[20px] bg-blockSeparator\" />\n                        <AttachToFeedbackIcon />\n                        <NotificationComponent />\n                      </div>\n                    </div>\n                    <div className=\"flex flex-1 gap-[1px]\">{children}</div>\n                  </div>\n                </div>\n              )}\n            </div>\n          </CheckPayment>\n        </MantineWrapper>\n      </CopilotKit>\n    </ContextWrapper>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/layout.media.component.tsx",
    "content": "'use client';\n\nimport { MediaBox } from '@gitroom/frontend/components/media/media.component';\n\nexport const MediaLayoutComponent = () => {\n  return (\n    <div className=\"bg-newBgColorInner p-[20px] flex flex-1 flex-col gap-[15px] transition-all\">\n      <MediaBox setMedia={() => {}} closeModal={() => {}} standalone={true} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/logo.tsx",
    "content": "'use client';\n\nexport const Logo = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"60\"\n      height=\"60\"\n      viewBox=\"0 0 60 60\"\n      fill=\"none\"\n      className=\"mt-[8px] min-w-[60px] min-h-[60px]\"\n    >\n      <path\n        d=\"M12.8816 11.4648C12.9195 12.0781 12.9731 12.7698 13.0342 13.5594L15.2408 42.0719C15.4874 45.2588 15.6107 46.8522 16.3251 48.0214C16.9535 49.0498 17.8913 49.853 19.0042 50.3156C20.2694 50.8416 21.8629 50.7183 25.0497 50.4717L46.8877 48.7817C48.9537 48.6218 50.3501 48.5137 51.3952 48.2628C51.0447 49.0858 50.5039 49.8189 49.8121 50.3992C48.7623 51.2797 47.205 51.6389 44.0905 52.3574L22.7476 57.2805C19.633 57.9989 18.0757 58.3581 16.7463 58.0264C15.5769 57.7346 14.5299 57.0801 13.7554 56.1566C12.8749 55.1069 12.5156 53.5496 11.7972 50.435L5.36942 22.569C4.651 19.4544 4.29178 17.8972 4.62351 16.5677C4.91531 15.3983 5.56982 14.3513 6.49325 13.5768C7.54303 12.6963 9.10032 12.3371 12.2149 11.6186L12.8816 11.4648Z\"\n        fill=\"#612BD3\"\n      />\n      <path\n        d=\"M47.0122 2.5752C47.9217 2.57909 48.5299 2.67533 49.0386 2.88672C50.0099 3.29052 50.829 3.99206 51.3774 4.88965C51.6647 5.35982 51.8537 5.94554 51.9976 6.84375C52.1427 7.7505 52.2327 8.91127 52.3569 10.5166L54.564 39.0283C54.6882 40.6337 54.7769 41.7946 54.7729 42.7129C54.7691 43.6226 54.6729 44.2305 54.4614 44.7393C54.0576 45.7105 53.3561 46.5287 52.4585 47.0771C51.9883 47.3644 51.4027 47.5534 50.5044 47.6973C49.5977 47.8424 48.4376 47.9334 46.8325 48.0576L24.9937 49.748C23.3886 49.8723 22.2282 49.961 21.3101 49.957C20.4003 49.9531 19.7925 49.8561 19.2837 49.6445C18.3124 49.2407 17.4933 48.5402 16.9448 47.6426C16.6576 47.1724 16.4685 46.5867 16.3247 45.6885C16.1795 44.7817 16.0896 43.621 15.9653 42.0156L13.7583 13.5029C13.6341 11.8979 13.5454 10.7375 13.5493 9.81934C13.5532 8.90971 13.6494 8.30172 13.8608 7.79297C14.2646 6.82169 14.9662 6.00253 15.8638 5.4541C16.3339 5.16692 16.9197 4.97778 17.8179 4.83398C18.7246 4.68882 19.8854 4.59884 21.4907 4.47461L43.3286 2.78418C44.9336 2.65997 46.094 2.57129 47.0122 2.5752Z\"\n        stroke=\"#131019\"\n        strokeWidth=\"1.45254\"\n      />\n      <path\n        d=\"M21.5681 5.49237L43.4061 3.80233C45.0283 3.67679 46.1402 3.59211 47.007 3.59582C47.8534 3.59945 48.3108 3.69053 48.6454 3.82963C49.4173 4.15056 50.0679 4.70763 50.5037 5.421C50.6927 5.7302 50.853 6.16816 50.9868 7.00389C51.1239 7.85984 51.2113 8.97152 51.3368 10.5937L53.5434 39.1062C53.6689 40.7284 53.7536 41.8403 53.7499 42.7071C53.7463 43.5535 53.6552 44.0109 53.5161 44.3455C53.1952 45.1174 52.6381 45.7679 51.9247 46.2038C51.6155 46.3927 51.1776 46.5531 50.3418 46.6869C49.4859 46.824 48.3742 46.9114 46.752 47.0369L24.914 48.7269C23.2918 48.8525 22.1799 48.9372 21.3131 48.9335C20.4667 48.9298 20.0093 48.8387 19.6747 48.6996C18.9028 48.3787 18.2522 47.8216 17.8164 47.1083C17.6274 46.7991 17.4671 46.3611 17.3333 45.5254C17.1962 44.6694 17.1088 43.5578 16.9833 41.9356L14.7767 13.4231C14.6512 11.8009 14.5665 10.689 14.5702 9.82217C14.5738 8.97581 14.6649 8.51838 14.804 8.1838C15.1249 7.41186 15.682 6.76133 16.3954 6.32545C16.7046 6.13653 17.1425 5.97616 17.9783 5.84235C18.8342 5.70531 19.9459 5.61791 21.5681 5.49237Z\"\n        fill=\"white\"\n      />\n      <path\n        d=\"M31.0188 12.0956L31.2277 14.7969C31.6025 14.3332 32.0909 13.9331 32.6929 13.5967C33.2931 13.2362 34.0374 13.0217 34.9259 12.953C35.7423 12.8899 36.5227 12.9865 37.2672 13.243C38.0357 13.4976 38.723 13.9517 39.3291 14.6054C39.9574 15.2332 40.4832 16.0983 40.9067 17.2009C41.3301 18.3035 41.604 19.6592 41.7284 21.268C41.8175 22.4205 41.7985 23.5814 41.6715 24.7507C41.5685 25.9182 41.3002 26.9776 40.8665 27.929C40.4328 28.8805 39.806 29.6777 38.9861 30.3209C38.1884 30.9381 37.1532 31.2959 35.8806 31.3943C34.9681 31.4649 34.2385 31.4005 33.6917 31.2012C33.143 30.9779 32.7236 30.7084 32.4335 30.3927L33.1102 39.145L28.0238 40.8427L25.8323 12.4966L31.0188 12.0956ZM33.9095 28.3944C34.5338 28.3462 35.0463 28.1012 35.4469 27.6596C35.8457 27.194 36.1392 26.6157 36.3273 25.9248C36.5395 25.2321 36.662 24.4738 36.6949 23.6499C36.75 22.8002 36.746 21.9672 36.6829 21.1508C36.5808 19.8301 36.38 18.7949 36.0804 18.0451C35.8049 17.2934 35.4972 16.7495 35.1572 16.4135C34.8153 16.0534 34.4846 15.8374 34.165 15.7655C33.8694 15.6918 33.6376 15.6614 33.4695 15.6744C33.0373 15.7078 32.6292 15.8963 32.2451 16.2401C31.8592 16.5598 31.5934 17.0272 31.4477 17.6423L32.2246 27.6913C32.3595 27.8741 32.5665 28.0514 32.8455 28.2231C33.1226 28.3707 33.4773 28.4278 33.9095 28.3944Z\"\n        fill=\"#131019\"\n      />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/menu-item.tsx",
    "content": "'use client';\nimport { FC, ReactNode } from 'react';\nimport { usePathname } from 'next/navigation';\nimport clsx from 'clsx';\nimport Link from 'next/link';\n\nexport const MenuItem: FC<{ label: string; icon: ReactNode; path: string; onClick?: () => void }> = ({\n  label,\n  icon,\n  path,\n  onClick,\n}) => {\n  const currentPath = usePathname();\n  const isActive = currentPath.indexOf(path) === 0;\n\n  const className = clsx(\n    'w-full minCustom:h-[54px] custom:h-[30px] py-[8px] px-[6px] gap-[4px] flex flex-col custom:flex-row text-[10px] font-[600] items-center minCustom:justify-center rounded-[12px] hover:text-textItemFocused hover:bg-boxFocused',\n    isActive ? 'text-textItemFocused bg-boxFocused' : 'text-textItemBlur'\n  );\n\n  if (onClick) {\n    return (\n      <button onClick={onClick} className={className}>\n        <div className=\"custom:hidden\">{icon}</div>\n        <div className=\"text-[10px]\">{label}</div>\n      </button>\n    );\n  }\n\n  return (\n    <Link\n      prefetch={true}\n      href={path}\n      {...path.indexOf('http') === 0 && { target: '_blank' }}\n      className={className}\n    >\n      <div className=\"custom:hidden\">{icon}</div>\n      <div className=\"text-[10px]\">{label}</div>\n    </Link>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/new-layout/sentry.feedback.component.tsx",
    "content": "'use client';\n\nimport { FC, useEffect, useRef, useState } from 'react';\nimport * as Sentry from '@sentry/nextjs';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\n\nexport const AttachToFeedbackIcon: FC = () => {\n  const { sentryDsn } = useVariables();\n  const [feedback, setFeedback] = useState<any>();\n  const buttonRef = useRef<HTMLButtonElement | null>(null);\n\n  useEffect(() => {\n    if (!sentryDsn) return;\n    try {\n      const fb = (Sentry as any).getFeedback?.();\n      setFeedback(fb);\n    } catch (e) {\n      setFeedback(undefined);\n    }\n  }, [sentryDsn]);\n\n  useEffect(() => {\n    if (feedback && buttonRef.current) {\n      const unsubscribe = feedback.attachTo(buttonRef.current);\n      return unsubscribe;\n    }\n    return () => {};\n  }, [feedback]);\n\n  if (!sentryDsn) return null;\n\n  return (\n    <button\n      ref={buttonRef}\n      type=\"button\"\n      aria-label=\"Feedback\"\n      className=\"hover:text-newTextColor\"\n    >\n      <svg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 32 32\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M27 10H23V6C23 5.46957 22.7893 4.96086 22.4142 4.58579C22.0391 4.21071 21.5304 4 21 4H5C4.46957 4 3.96086 4.21071 3.58579 4.58579C3.21071 4.96086 3 5.46957 3 6V22C3.00059 22.1881 3.05423 22.3723 3.15478 22.5313C3.25532 22.6903 3.39868 22.8177 3.56839 22.8989C3.7381 22.9801 3.92728 23.0118 4.11418 22.9903C4.30108 22.9689 4.47814 22.8951 4.625 22.7775L9 19.25V23C9 23.5304 9.21071 24.0391 9.58579 24.4142C9.96086 24.7893 10.4696 25 11 25H22.6987L27.375 28.7775C27.5519 28.9206 27.7724 28.9991 28 29C28.2652 29 28.5196 28.8946 28.7071 28.7071C28.8946 28.5196 29 28.2652 29 28V12C29 11.4696 28.7893 10.9609 28.4142 10.5858C28.0391 10.2107 27.5304 10 27 10ZM8.31875 17.2225L5 19.9062V6H21V17H8.9475C8.71863 17 8.4967 17.0786 8.31875 17.2225ZM27 25.9062L23.6812 23.2225C23.5043 23.0794 23.2838 23.0009 23.0562 23H11V19H21C21.5304 19 22.0391 18.7893 22.4142 18.4142C22.7893 18.0391 23 17.5304 23 17V12H27V25.9062Z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    </button>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/notifications/notification.component.tsx",
    "content": "'use client';\n\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { FC, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport { useClickAway } from '@uidotdev/usehooks';\nimport ReactLoading from 'react-loading';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nfunction replaceLinks(text: string) {\n  const urlRegex =\n    /(\\bhttps?:\\/\\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;\n  return text.replace(\n    urlRegex,\n    '<a class=\"cursor-pointer underline font-bold\" target=\"_blank\" href=\"$1\">$1</a>'\n  );\n}\nexport const ShowNotification: FC<{\n  notification: {\n    createdAt: string;\n    content: string;\n  };\n  lastReadNotification: string;\n}> = (props) => {\n  const { notification } = props;\n  const [newNotification] = useState(\n    new Date(notification.createdAt) > new Date(props.lastReadNotification)\n  );\n  return (\n    <div\n      className={clsx(\n        `text-textColor px-[16px] py-[10px] border-b border-tableBorder last:border-b-0 transition-colors overflow-hidden text-ellipsis`,\n        newNotification && 'font-bold bg-seventh animate-newMessages'\n      )}\n      dangerouslySetInnerHTML={{\n        __html: replaceLinks(notification.content),\n      }}\n    />\n  );\n};\nexport const NotificationOpenComponent = () => {\n  const fetch = useFetch();\n  const loadNotifications = useCallback(async () => {\n    return await (await fetch('/notifications/list')).json();\n  }, []);\n  const t = useT();\n\n  const { data, isLoading } = useSWR('notifications', loadNotifications);\n  return (\n    <div\n      id=\"notification-popup\"\n      className=\"opacity-0 animate-normalFadeDown mt-[10px] absolute w-[420px] min-h-[200px] top-[100%] end-0 bg-third text-textColor rounded-[16px] flex flex-col border border-tableBorder z-[600]\"\n    >\n      <div\n        className={`p-[16px] border-b border-tableBorder font-bold`}\n      >\n        {t('notifications', 'Notifications')}\n      </div>\n\n      <div className=\"flex flex-col\">\n        {isLoading && (\n          <div className=\"flex-1 flex justify-center pt-12\">\n            <ReactLoading type=\"spin\" color=\"#fff\" width={36} height={36} />\n          </div>\n        )}\n        {!isLoading && !data.notifications.length && (\n          <div className=\"text-center p-[16px] text-textColor flex-1 flex justify-center items-center mt-[20px]\">\n            {t('no_notifications', 'No notifications')}\n          </div>\n        )}\n        {!isLoading &&\n          data.notifications.map(\n            (\n              notification: {\n                createdAt: string;\n                content: string;\n              },\n              index: number\n            ) => (\n              <ShowNotification\n                notification={notification}\n                lastReadNotification={data.lastReadNotifications}\n                key={`notifications_${index}`}\n              />\n            )\n          )}\n      </div>\n    </div>\n  );\n};\nconst NotificationComponent = () => {\n  const fetch = useFetch();\n  const [show, setShow] = useState(false);\n  const loadNotifications = useCallback(async () => {\n    return await (await fetch('/notifications')).json();\n  }, []);\n  const { data, mutate } = useSWR('notifications-list', loadNotifications);\n  const changeShow = useCallback(() => {\n    mutate(\n      {\n        ...data,\n        total: 0,\n      },\n      {\n        revalidate: false,\n      }\n    );\n    setShow(!show);\n  }, [show, data]);\n  const ref = useClickAway<HTMLDivElement>(() => setShow(false));\n  return (\n    <div className=\"relative cursor-pointer select-none\" ref={ref}>\n      <div onClick={changeShow}>\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          className=\"hover:text-newTextColor\"\n        >\n          <path\n            d=\"M14 21H10M18 8C18 6.4087 17.3679 4.88258 16.2427 3.75736C15.1174 2.63214 13.5913 2 12 2C10.4087 2 8.8826 2.63214 7.75738 3.75736C6.63216 4.88258 6.00002 6.4087 6.00002 8C6.00002 11.0902 5.22049 13.206 4.34968 14.6054C3.61515 15.7859 3.24788 16.3761 3.26134 16.5408C3.27626 16.7231 3.31488 16.7926 3.46179 16.9016C3.59448 17 4.19261 17 5.38887 17H18.6112C19.8074 17 20.4056 17 20.5382 16.9016C20.6852 16.7926 20.7238 16.7231 20.7387 16.5408C20.7522 16.3761 20.3849 15.7859 19.6504 14.6054C18.7795 13.206 18 11.0902 18 8Z\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n          {data && data.total > 0 && (\n            <circle\n              cx=\"17.0625\"\n              cy=\"5\"\n              r=\"4\"\n              fill=\"#FF3EA2\"\n              stroke=\"#1A1919\"\n              strokeWidth=\"2\"\n            />\n          )}\n        </svg>\n      </div>\n      {show && <NotificationOpenComponent />}\n    </div>\n  );\n};\nexport default NotificationComponent;\n"
  },
  {
    "path": "apps/frontend/src/components/onboarding/github.onboarding.tsx",
    "content": "import { FC, useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { GithubComponent } from '@gitroom/frontend/components/settings/github.component';\nexport const GithubOnboarding: FC = () => {\n  const fetch = useFetch();\n  const load = useCallback(async (path: string) => {\n    const { github } = await (await fetch('/settings/github')).json();\n    if (!github) {\n      return false;\n    }\n    const emptyOnes = github.find((p: { login: string }) => !p.login);\n    const { organizations } = emptyOnes\n      ? await (await fetch(`/settings/organizations/${emptyOnes.id}`)).json()\n      : {\n          organizations: [],\n        };\n    return {\n      github,\n      organizations,\n    };\n  }, []);\n  const { isLoading: isLoadingSettings, data: loadAll } = useSWR(\n    'load-all',\n    load\n  );\n  if (!loadAll) {\n    return null;\n  }\n  return (\n    <GithubComponent\n      github={loadAll.github}\n      organizations={loadAll.organizations}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/onboarding/onboarding.modal.tsx",
    "content": "'use client';\n\nimport React, { FC, useCallback, useMemo, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { orderBy } from 'lodash';\nimport clsx from 'clsx';\nimport Image from 'next/image';\nimport { AddProviderComponent } from '@gitroom/frontend/components/launches/add.provider.component';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\n\ninterface OnboardingModalProps {\n  onClose: () => void;\n}\n\nexport const OnboardingModal: FC<OnboardingModalProps> = ({ onClose }) => {\n  const [step, setStep] = useState(1);\n  const modals = useModals();\n  const t = useT();\n\n  return (\n    <div className=\"w-full min-h-full flex-1 p-[40px] flex relative\">\n      <style>\n        {`#support-discord {display: none}`}\n      </style>\n      <div className=\"flex flex-1 bg-newBgColorInner rounded-[20px] flex-col relative\">\n        <button\n          className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n          type=\"button\"\n          onClick={modals.closeAll}\n        >\n          <svg\n            viewBox=\"0 0 15 15\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n          >\n            <path\n              d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n              fill=\"currentColor\"\n              fillRule=\"evenodd\"\n              clipRule=\"evenodd\"\n            ></path>\n          </svg>\n        </button>\n        <div className=\"flex-1 flex p-[40px]\">\n          <div className=\"flex flex-col gap-[24px] flex-1\">\n            {/* Step indicators */}\n            <div className=\"flex items-center justify-center gap-[16px]\">\n              <div className=\"flex items-center gap-[8px]\">\n                <div\n                  className={clsx(\n                    'w-[32px] h-[32px] rounded-full flex items-center justify-center text-[14px] font-semibold transition-colors',\n                    step === 1\n                      ? 'bg-boxFocused text-textItemFocused'\n                      : 'bg-newTableHeader'\n                  )}\n                >\n                  1\n                </div>\n                <span\n                  className={clsx(\n                    'text-[14px]',\n                    step === 1 ? 'font-medium' : 'text-textColor'\n                  )}\n                >\n                  {t('connect_channels', 'Connect Channels')}\n                </span>\n              </div>\n              <div className=\"w-[40px] h-[2px] bg-boxFocused\" />\n              <div className=\"flex items-center gap-[8px]\">\n                <div\n                  className={clsx(\n                    'w-[32px] h-[32px] rounded-full flex items-center justify-center text-[14px] font-semibold transition-colors',\n                    step === 2\n                      ? 'bg-boxFocused text-textItemFocused'\n                      : 'bg-newTableHeader'\n                  )}\n                >\n                  2\n                </div>\n                <span\n                  className={clsx(\n                    'text-[14px]',\n                    step === 2 ? 'font-medium' : 'text-textColor'\n                  )}\n                >\n                  {t('watch_tutorial', 'Watch Tutorial')}\n                </span>\n              </div>\n            </div>\n\n            {/* Step content */}\n            {step === 1 && (\n              <OnboardingStep1\n                onNext={() => setStep(2)}\n                onSkip={() => setStep(2)}\n              />\n            )}\n            {step === 2 && (\n              <OnboardingStep2 onBack={() => setStep(1)} onFinish={onClose} />\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst OnboardingStep1: FC<{ onNext: () => void; onSkip: () => void }> = ({\n  onNext,\n  onSkip,\n}) => {\n  const fetch = useFetch();\n  const t = useT();\n\n  const getIntegrations = useCallback(async () => {\n    return (await fetch('/integrations')).json();\n  }, []);\n\n  const load = useCallback(async (path: string) => {\n    const list = (await (await fetch(path)).json()).integrations;\n    return list;\n  }, []);\n\n  const { data: integrations } = useSWR('/integrations/list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n\n  const sortedIntegrations = useMemo(() => {\n    return orderBy(\n      integrations,\n      ['type', 'disabled', 'identifier'],\n      ['desc', 'asc', 'asc']\n    );\n  }, [integrations]);\n\n  const { data } = useSWR('get-all-integrations-onboarding', getIntegrations);\n\n  return (\n    <div className=\"flex flex-col gap-[24px]\">\n      <div className=\"flex gap-[4px] flex-col text-center\">\n        <div className=\"text-[24px] font-semibold\">\n          {t('connect_your_channels', 'Connect Your Channels')}\n        </div>\n        <div className=\"text-[14px] text-customColor18\">\n          {t(\n            'connect_social_media_to_start',\n            'Connect your social media accounts to start scheduling posts'\n          )}\n        </div>\n      </div>\n\n      {/* Connected channels */}\n      {sortedIntegrations.length > 0 && (\n        <div className=\"bg-newTableHeader rounded-[8px] p-[16px]\">\n          <div className=\"text-[14px] font-medium mb-[12px]\">\n            {t('connected_channels', 'Connected Channels')} (\n            {sortedIntegrations.length})\n          </div>\n          <div className=\"flex flex-wrap gap-[12px]\">\n            {sortedIntegrations.map((integration: any) => (\n              <div\n                key={integration.id}\n                className=\"flex items-center gap-[8px] bg-customColor47/30 rounded-[8px] px-[12px] py-[8px]\"\n              >\n                <div className=\"relative w-[28px] h-[28px]\">\n                  <Image\n                    src={integration.picture}\n                    className=\"rounded-full\"\n                    alt={integration.identifier}\n                    width={28}\n                    height={28}\n                  />\n                  <Image\n                    src={`/icons/platforms/${integration.identifier}.png`}\n                    className=\"rounded-full absolute -bottom-[3px] -end-[3px] border border-fifth\"\n                    alt={integration.identifier}\n                    width={14}\n                    height={14}\n                  />\n                </div>\n                <span className=\"text-[13px]\">{integration.name}</span>\n              </div>\n            ))}\n          </div>\n        </div>\n      )}\n\n      {/* Available platforms - using AddProviderComponent */}\n      <div className=\"flex flex-col gap-[12px]\">\n        <div className=\"text-[14px] font-medium\">\n          {t('click_channel_to_add', 'Click a channel to add it')}\n        </div>\n        {data && (\n          <AddProviderComponent\n            invite={false}\n            social={data.social || []}\n            article={data.article || []}\n            onboarding={true}\n          />\n        )}\n      </div>\n\n      {/* Action buttons */}\n      <div className=\"flex justify-end pt-[24px] mt-[8px]\">\n        <button\n          onClick={onNext}\n          className=\"group flex items-center gap-[12px] bg-gradient-to-r from-[#622aff] to-[#8b5cf6] hover:from-[#7c3aff] hover:to-[#9d6eff] text-white font-semibold px-[32px] py-[14px] rounded-[12px] text-[16px] transition-all shadow-lg shadow-purple-500/25 hover:shadow-purple-500/40\"\n        >\n          {sortedIntegrations.length > 0\n            ? t('continue', 'Continue')\n            : t('continue_without_channels', 'Continue without channels')}\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"group-hover:translate-x-1 transition-transform\"\n          >\n            <path d=\"M5 12h14\" />\n            <path d=\"m12 5 7 7-7 7\" />\n          </svg>\n        </button>\n      </div>\n    </div>\n  );\n};\n\nconst OnboardingStep2: FC<{ onBack: () => void; onFinish: () => void }> = ({\n  onBack,\n  onFinish,\n}) => {\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col gap-[24px] flex-1\">\n      <div className=\"flex gap-[4px] flex-col text-center\">\n        <div className=\"text-[24px] font-semibold\">\n          {t('watch_tutorial_title', 'Learn How to Use Postiz')}\n        </div>\n        <div className=\"text-[14px] text-customColor18\">\n          {t(\n            'watch_tutorial_description',\n            'Watch this short video to learn how to get the most out of Postiz'\n          )}\n        </div>\n      </div>\n\n      {/* YouTube Video Embed */}\n      <div className=\"relative flex-1 rounded-[12px] overflow-hidden\">\n        <div className=\"absolute left-0 top-0 w-full h-full flex justify-center\">\n          <iframe\n            className=\"h-full aspect-video\"\n            src=\"https://www.youtube.com/embed/BdsCVvEYgHU?si=vvhaZJ8I5oXXvVJS?autoplay=1\"\n            title=\"Postiz Tutorial\"\n            allow=\"autoplay\"\n            allowFullScreen\n          />\n        </div>\n      </div>\n\n      {/* Action buttons */}\n      <div className=\"flex justify-between pt-[24px] mt-[8px]\">\n        <button\n          onClick={onBack}\n          className=\"group flex items-center gap-[8px] bg-transparent border-2 border-boxFocused font-medium px-[24px] py-[12px] rounded-[12px] text-[15px] transition-all\"\n        >\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"18\"\n            height=\"18\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"group-hover:-translate-x-1 transition-transform\"\n          >\n            <path d=\"m12 19-7-7 7-7\" />\n            <path d=\"M19 12H5\" />\n          </svg>\n          {t('back', 'Back')}\n        </button>\n        <button\n          onClick={onFinish}\n          className=\"group flex items-center gap-[12px] bg-gradient-to-r from-[#10b981] to-[#059669] hover:from-[#34d399] hover:to-[#10b981] text-white font-semibold px-[32px] py-[14px] rounded-[12px] text-[16px] transition-all shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40\"\n        >\n          {t('get_started', 'Get Started')}\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"20\"\n            height=\"20\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"group-hover:scale-110 transition-transform\"\n          >\n            <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\" />\n            <polyline points=\"22 4 12 14.01 9 11.01\" />\n          </svg>\n        </button>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/onboarding/onboarding.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect, useRef } from 'react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { OnboardingModal } from '@gitroom/frontend/components/onboarding/onboarding.modal';\n\nexport const Onboarding: FC = () => {\n  const query = useSearchParams();\n  const modal = useModals();\n  const router = useRouter();\n  const modalOpen = useRef(false);\n  const t = useT();\n\n  const handleClose = useCallback(() => {\n    modal.closeAll();\n    router.push('/launches');\n  }, [modal, router]);\n\n  useEffect(() => {\n    const onboarding = query.get('onboarding');\n    if (!onboarding) {\n      if (modalOpen.current) {\n        modalOpen.current = false;\n        modal.closeAll();\n      }\n      return;\n    }\n    if (modalOpen.current) {\n      return;\n    }\n    modalOpen.current = true;\n    modal.openModal({\n      // title: t('onboarding', 'Welcome to Postiz'),\n      withCloseButton: true,\n      closeOnEscape: false,\n      removeLayout: true,\n      askClose: true,\n      fullScreen: true,\n      onClose: handleClose,\n      children: <OnboardingModal onClose={handleClose} />,\n    });\n  }, [query, handleClose, t]);\n  \n  return null;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/platform-analytics/platform.analytics.tsx",
    "content": "'use client';\n\nimport useSWR from 'swr';\nimport { useCallback, useMemo, useState } from 'react';\nimport { capitalize, orderBy } from 'lodash';\nimport clsx from 'clsx';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\nimport Image from 'next/image';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { RenderAnalytics } from '@gitroom/frontend/components/platform-analytics/render.analytics';\nimport { Select } from '@gitroom/react/form/select';\nimport { Button } from '@gitroom/react/form/button';\nimport { useRouter } from 'next/navigation';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport useCookie from 'react-use-cookie';\nimport { SVGLine } from '@gitroom/frontend/components/launches/launches.component';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nconst allowedIntegrations = [\n  'facebook',\n  'instagram',\n  'instagram-standalone',\n  'linkedin-page',\n  'tiktok',\n  'youtube',\n  'gmb',\n  'pinterest',\n  'threads',\n  'x',\n];\nexport const PlatformAnalytics = () => {\n  const fetch = useFetch();\n  const t = useT();\n  const router = useRouter();\n  const { disableXAnalytics } = useVariables();\n\n  const [current, setCurrent] = useState(0);\n  const [key, setKey] = useState(7);\n  const [refresh, setRefresh] = useState(false);\n  const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0');\n  const toaster = useToaster();\n  const load = useCallback(async () => {\n    const int = (\n      await (await fetch('/integrations/list')).json()\n    ).integrations.filter((f: any) => {\n      if (f.identifier === 'x' && disableXAnalytics) {\n        return false;\n      }\n      return true;\n    });\n    return int.filter((f: any) => allowedIntegrations.includes(f.identifier));\n  }, []);\n  const { data, isLoading } = useSWR('analytics-list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n  const sortedIntegrations = useMemo(() => {\n    return orderBy(\n      data,\n      ['type', 'disabled', 'identifier'],\n      ['desc', 'asc', 'asc']\n    );\n  }, [data]);\n  const currentIntegration = useMemo(() => {\n    return sortedIntegrations[current];\n  }, [current, sortedIntegrations]);\n  const options = useMemo(() => {\n    if (!currentIntegration) {\n      return [];\n    }\n    const arr = [];\n    if (\n      [\n        'facebook',\n        'instagram',\n        'instagram-standalone',\n        'linkedin-page',\n        'pinterest',\n        'youtube',\n        'threads',\n        'gmb',\n        'x',\n        'tiktok',\n      ].indexOf(currentIntegration.identifier) !== -1\n    ) {\n      arr.push({\n        key: 7,\n        value: t('7_days', '7 Days'),\n      });\n    }\n    if (\n      [\n        'facebook',\n        'instagram',\n        'instagram-standalone',\n        'linkedin-page',\n        'pinterest',\n        'youtube',\n        'threads',\n        'gmb',\n        'x',\n        'tiktok',\n      ].indexOf(currentIntegration.identifier) !== -1\n    ) {\n      arr.push({\n        key: 30,\n        value: t('30_days', '30 Days'),\n      });\n    }\n    if (\n      ['facebook', 'linkedin-page', 'pinterest', 'youtube', 'x', 'gmb'].indexOf(\n        currentIntegration.identifier\n      ) !== -1\n    ) {\n      arr.push({\n        key: 90,\n        value: t('90_days', '90 Days'),\n      });\n    }\n    return arr;\n  }, [currentIntegration]);\n  const keys = useMemo(() => {\n    if (!currentIntegration) {\n      return 7;\n    }\n    if (options.find((p) => p.key === key)) {\n      return key;\n    }\n    return options[0]?.key;\n  }, [key, currentIntegration]);\n\n  if (isLoading) {\n    return (\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-1 flex-col gap-[15px] transition-all items-center justify-center\">\n        <LoadingComponent />\n      </div>\n    );\n  }\n\n  if (!sortedIntegrations.length && !isLoading) {\n    return (\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all flex-1 justify-center items-center text-center\">\n        <div>\n          <img src=\"/peoplemarketplace.svg\" />\n        </div>\n        <div className=\"text-[48px]\">\n          {t('can_t_show_analytics_yet', \"Can't show analytics yet\")}\n          <br />\n          {t(\n            'you_have_to_add_social_media_channels',\n            'You have to add Social Media channels'\n          )}\n        </div>\n        <div className=\"text-[20px]\">\n          {t('supported', 'Supported:')}\n          {allowedIntegrations.map((p) => capitalize(p)).join(', ')}\n        </div>\n        <Button onClick={() => router.push('/launches')}>\n          {t(\n            'go_to_the_calendar_to_add_channels',\n            'Go to the calendar to add channels'\n          )}\n        </Button>\n      </div>\n    );\n  }\n  return (\n    <>\n      <div\n        className={clsx(\n          'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all',\n          collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]'\n        )}\n      >\n        <div className=\"flex gap-[12px] flex-col\">\n          <div className=\"flex items-center\">\n            <h2 className=\"group-[.sidebar]:hidden flex-1 text-[20px] font-[500]\">\n              {t('channels')}\n            </h2>\n            <div\n              onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')}\n              className=\"group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"7\"\n                height=\"13\"\n                viewBox=\"0 0 7 13\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M6 11.5L1 6.5L6 1.5\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n          </div>\n          {sortedIntegrations.map((integration, index) => (\n            <div\n              key={integration.id}\n              onClick={() => {\n                if (integration.refreshNeeded) {\n                  toaster.show(\n                    'Please refresh the integration from the calendar',\n                    'warning'\n                  );\n                  return;\n                }\n                setRefresh(true);\n                setTimeout(() => {\n                  setRefresh(false);\n                }, 10);\n                setCurrent(index);\n              }}\n              className={clsx(\n                'flex gap-[12px] items-center group/profile justify-center hover:bg-boxHover rounded-e-[8px]',\n                currentIntegration.id !== integration.id &&\n                  'opacity-20 hover:opacity-100 cursor-pointer'\n              )}\n            >\n              <div\n                className={clsx(\n                  'relative rounded-full flex justify-center items-center gap-[6px]',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {(integration.inBetweenSteps || integration.refreshNeeded) && (\n                  <div className=\"absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer\">\n                    <div className=\"bg-red-500 w-[15px] h-[15px] rounded-full start-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center\">\n                      !\n                    </div>\n                    <div className=\"bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]\" />\n                  </div>\n                )}\n                <div className=\"h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity\">\n                  <SVGLine />\n                </div>\n                <ImageWithFallback\n                  fallbackSrc={`/icons/platforms/${integration.identifier}.png`}\n                  src={integration.picture}\n                  className=\"rounded-[8px]\"\n                  alt={integration.identifier}\n                  width={36}\n                  height={36}\n                />\n                <Image\n                  src={`/icons/platforms/${integration.identifier}.png`}\n                  className=\"rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth\"\n                  alt={integration.identifier}\n                  width={18.41}\n                  height={18.41}\n                />\n              </div>\n              <div\n                className={clsx(\n                  'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {integration.name}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n        {!!options.length && (\n          <div className=\"flex-1 flex flex-col gap-[14px]\">\n            <div className=\"max-w-[200px]\">\n              <Select\n                label=\"\"\n                name=\"date\"\n                disableForm={true}\n                hideErrors={true}\n                onChange={(e) => setKey(+e.target.value)}\n              >\n                {options.map((option) => (\n                  <option key={option.key} value={option.key}>\n                    {option.value}\n                  </option>\n                ))}\n              </Select>\n            </div>\n            <div className=\"flex-1\">\n              {!!keys && !!currentIntegration && !refresh && (\n                <RenderAnalytics integration={currentIntegration} date={keys} />\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/platform-analytics/render.analytics.tsx",
    "content": "import { FC, useCallback, useMemo, useState } from 'react';\nimport { Integration } from '@prisma/client';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { ChartSocial } from '@gitroom/frontend/components/analytics/chart-social';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\ninterface AnalyticsDataItem {\n  label: string;\n  data: Array<{ total: number; date: string }>;\n  average?: boolean;\n  percentageChange?: number;\n}\n\nconst TrendIndicator: FC<{ value: number; average?: boolean }> = ({\n  value,\n  average,\n}) => {\n  if (value === 0) return null;\n\n  const isPositive = value > 0;\n  const displayValue = Math.abs(value).toFixed(1);\n\n  return (\n    <div\n      className={`flex items-center gap-[4px] text-[13px] font-medium ${\n        isPositive ? 'text-[#32d583]' : 'text-[#f97066]'\n      }`}\n    >\n      <svg\n        width=\"12\"\n        height=\"12\"\n        viewBox=\"0 0 12 12\"\n        fill=\"none\"\n        className={isPositive ? '' : 'rotate-180'}\n      >\n        <path\n          d=\"M6 2.5L10 7.5H2L6 2.5Z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n      <span>\n        {displayValue}\n        {average ? 'pp' : '%'}\n      </span>\n    </div>\n  );\n};\n\nconst AnalyticsCard: FC<{\n  item: AnalyticsDataItem;\n  total: string | number;\n  index: number;\n}> = ({ item, total, index }) => {\n  const colorVariants = ['purple', 'green', 'blue'] as const;\n  const color = colorVariants[index % colorVariants.length];\n\n  const hasMultipleDataPoints = item.data.length > 1;\n\n  return (\n    <div className=\"group relative\">\n      <div\n        className={`\n          flex flex-col h-full\n          bg-newTableHeader\n          border border-newTableBorder\n          rounded-[12px]\n          overflow-hidden\n          transition-all duration-200\n          hover:border-[#612bd3]/50\n        `}\n      >\n        {/* Header */}\n        <div className=\"flex items-center justify-between px-[16px] pt-[14px] pb-[8px]\">\n          <div className=\"flex items-center gap-[10px]\">\n            <div\n              className={`\n                w-[8px] h-[8px] rounded-full\n                ${color === 'purple' ? 'bg-[#612bd3]' : ''}\n                ${color === 'green' ? 'bg-[#32d583]' : ''}\n                ${color === 'blue' ? 'bg-[#1d9bf0]' : ''}\n              `}\n            />\n            <span className=\"text-[15px] font-medium text-newTableText\">\n              {item.label}\n            </span>\n          </div>\n          {item.percentageChange !== undefined && (\n            <TrendIndicator value={item.percentageChange} average={item.average} />\n          )}\n        </div>\n\n        {/* Content */}\n        {hasMultipleDataPoints ? (\n          <>\n            {/* Chart */}\n            <div className=\"flex-1 px-[12px] py-[8px]\">\n              <div className=\"h-[120px] relative\">\n                <ChartSocial data={item.data} color={color} key={`chart-${index}`} />\n              </div>\n            </div>\n\n            {/* Value */}\n            <div className=\"px-[16px] pb-[14px]\">\n              <div className=\"text-[36px] leading-[42px] font-semibold tracking-tight\">\n                {total}\n              </div>\n            </div>\n          </>\n        ) : (\n          /* Single value display */\n          <div className=\"flex-1 flex flex-col items-center justify-center py-[32px] px-[16px]\">\n            <div className=\"text-[48px] leading-[56px] font-semibold tracking-tight\">\n              {total}\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nconst EmptyState: FC<{ onRefresh: () => void }> = ({ onRefresh }) => {\n  const t = useT();\n\n  return (\n    <div className=\"col-span-full flex flex-col items-center justify-center py-[48px] px-[24px] bg-newTableHeader border border-newTableBorder rounded-[12px]\">\n      <div className=\"w-[48px] h-[48px] mb-[16px] rounded-full bg-[#612bd3]/10 flex items-center justify-center\">\n        <svg\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          className=\"text-[#612bd3]\"\n        >\n          <path d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n          <path d=\"M12 8v4l2 2\" />\n        </svg>\n      </div>\n      <p className=\"text-[15px] text-newTableText text-center mb-[12px]\">\n        {t(\n          'this_channel_needs_to_be_refreshed',\n          'This channel needs to be refreshed to display analytics'\n        )}\n      </p>\n      <button\n        onClick={onRefresh}\n        className=\"inline-flex items-center gap-[6px] px-[16px] py-[8px] text-[14px] font-medium text-white bg-[#612bd3] hover:bg-[#5023b8] rounded-[8px] transition-colors\"\n      >\n        <svg\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n        >\n          <path d=\"M23 4v6h-6M1 20v-6h6\" />\n          <path d=\"M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15\" />\n        </svg>\n        {t('refresh_channel', 'Refresh Channel')}\n      </button>\n    </div>\n  );\n};\n\nexport const RenderAnalytics: FC<{\n  integration: Integration;\n  date: number;\n}> = (props) => {\n  const { integration, date } = props;\n  const [loading, setLoading] = useState(true);\n  const fetch = useFetch();\n\n  const load = useCallback(async () => {\n    setLoading(true);\n    const load = (\n      await fetch(`/analytics/${integration.id}?date=${date}`)\n    ).json();\n    setLoading(false);\n    return load;\n  }, [integration, date]);\n\n  const { data } = useSWR(`/analytics-${integration?.id}-${date}`, load, {\n    refreshInterval: 0,\n    refreshWhenHidden: false,\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    refreshWhenOffline: false,\n    revalidateOnMount: true,\n  });\n\n  const refreshChannel = useCallback(\n    (\n        integrationData: Integration & {\n          identifier: string;\n        }\n      ) =>\n      async () => {\n        const { url } = await (\n          await fetch(\n            `/integrations/social/${integrationData.identifier}?refresh=${integrationData.internalId}`,\n            {\n              method: 'GET',\n            }\n          )\n        ).json();\n        window.location.href = url;\n      },\n    []\n  );\n\n  const t = useT();\n\n  const totals = useMemo(() => {\n    return data?.map((p: AnalyticsDataItem) => {\n      const value =\n        (p?.data.reduce((acc: number, curr: { total: number }) => acc + curr.total, 0) || 0) /\n        (p.average ? p.data.length : 1);\n      if (p.average) {\n        return value.toFixed(2) + '%';\n      }\n      return new Intl.NumberFormat().format(Math.round(value));\n    });\n  }, [data]);\n\n  if (loading) {\n    return (\n      <div className=\"flex items-center justify-center py-[48px]\">\n        <LoadingComponent />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-[16px]\">\n      {data?.length === 0 && (\n        <EmptyState onRefresh={refreshChannel(integration as any)} />\n      )}\n      {data?.map((item: AnalyticsDataItem, index: number) => (\n        <AnalyticsCard\n          key={`analytics-${index}`}\n          item={item}\n          total={totals[index]}\n          index={index}\n        />\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/plugs/plug.tsx",
    "content": "'use client';\n\nimport {\n  PlugSettings,\n  PlugsInterface,\n  usePlugs,\n} from '@gitroom/frontend/components/plugs/plugs.context';\nimport { Button } from '@gitroom/react/form/button';\nimport React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR, { mutate } from 'swr';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport {\n  FormProvider,\n  SubmitHandler,\n  useForm,\n  useFormContext,\n} from 'react-hook-form';\nimport { Input } from '@gitroom/react/form/input';\nimport { CopilotTextarea } from '@copilotkit/react-textarea';\nimport clsx from 'clsx';\nimport { string, object } from 'yup';\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport { Slider } from '@gitroom/react/form/slider';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';\nexport function convertBackRegex(s: string) {\n  const matches = s.match(/\\/(.*)\\/([a-z]*)/);\n  const pattern = matches?.[1] || '';\n  const flags = matches?.[2] || '';\n  return new RegExp(pattern, flags);\n}\nexport const TextArea: FC<{\n  name: string;\n  placeHolder: string;\n}> = (props) => {\n  const form = useFormContext();\n  const { onChange, onBlur, ...all } = form.register(props.name);\n  const value = form.watch(props.name);\n  return (\n    <>\n      <textarea className=\"hidden\" {...all}></textarea>\n      <CopilotTextarea\n        disableBranding={true}\n        placeholder={props.placeHolder}\n        value={value}\n        className={clsx(\n          '!min-h-40 !max-h-80 p-[24px] overflow-hidden bg-customColor2 outline-none rounded-[4px] border-fifth border'\n        )}\n        onChange={(e) => {\n          onChange({\n            target: {\n              name: props.name,\n              value: e.target.value,\n            },\n          });\n        }}\n        autosuggestionsConfig={{\n          textareaPurpose: `Assist me in writing social media posts.`,\n          chatApiConfigs: {},\n        }}\n      />\n      <div className=\"text-red-400 text-[12px]\">\n        {form?.formState?.errors?.[props.name]?.message as string}\n      </div>\n    </>\n  );\n};\nexport const PlugPop: FC<{\n  plug: PlugsInterface;\n  settings: PlugSettings;\n  data?: {\n    activated: boolean;\n    data: string;\n    id: string;\n    integrationId: string;\n    organizationId: string;\n    plugFunction: string;\n  };\n}> = (props) => {\n  const { plug, settings, data } = props;\n  const { closeAll } = useModals();\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const values = useMemo(() => {\n    if (!data?.data) {\n      return {};\n    }\n    return JSON.parse(data.data).reduce((acc: any, current: any) => {\n      return {\n        ...acc,\n        [current.name]: current.value,\n      };\n    }, {} as any);\n  }, []);\n  const yupSchema = useMemo(() => {\n    return object(\n      plug.fields.reduce((acc, field) => {\n        return {\n          ...acc,\n          [field.name]: field.validation\n            ? string().matches(convertBackRegex(field.validation), {\n                message: 'Invalid value',\n              })\n            : null,\n        };\n      }, {})\n    );\n  }, []);\n  const form = useForm({\n    resolver: yupResolver(yupSchema),\n    values,\n    mode: 'all',\n  });\n  const submit: SubmitHandler<any> = useCallback(async (data) => {\n    await fetch(`/integrations/${settings.providerId}/plugs`, {\n      method: 'POST',\n      body: JSON.stringify({\n        func: plug.methodName,\n        fields: Object.keys(data).map((key) => ({\n          name: key,\n          value: data[key],\n        })),\n      }),\n    });\n    toaster.show('Plug updated', 'success');\n    closeAll();\n  }, []);\n\n  const t = useT();\n\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(submit)}>\n        <div className=\"relative mx-auto\">\n          <div className=\"my-[20px]\">{plug.description}</div>\n          <div>\n            {plug.fields.map((field) => (\n              <div key={field.name}>\n                {field.type === 'richtext' ? (\n                  <TextArea name={field.name} placeHolder={field.placeholder} />\n                ) : (\n                  <Input\n                    name={field.name}\n                    label={field.description}\n                    className=\"w-full mt-[8px] p-[8px] border border-tableBorder rounded-md text-black\"\n                    placeholder={field.placeholder}\n                    type={field.type}\n                  />\n                )}\n              </div>\n            ))}\n          </div>\n          <div className=\"mt-[20px]\">\n            <Button type=\"submit\">{t('activate', 'Activate')}</Button>\n          </div>\n        </div>\n      </form>\n    </FormProvider>\n  );\n};\nexport const PlugItem: FC<{\n  plug: PlugsInterface;\n  addPlug: (data: any) => void;\n  data?: {\n    activated: boolean;\n    data: string;\n    id: string;\n    integrationId: string;\n    organizationId: string;\n    plugFunction: string;\n  };\n}> = (props) => {\n  const { plug, addPlug, data } = props;\n  const [activated, setActivated] = useState(!!data?.activated);\n  useEffect(() => {\n    setActivated(!!data?.activated);\n  }, [data?.activated]);\n  const fetch = useFetch();\n  const changeActivated = useCallback(\n    async (status: 'on' | 'off') => {\n      await fetch(`/integrations/plugs/${data?.id}/activate`, {\n        body: JSON.stringify({\n          status: status === 'on',\n        }),\n        method: 'PUT',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      });\n      setActivated(status === 'on');\n    },\n    [activated]\n  );\n  return (\n    <div\n      onClick={() => addPlug(data)}\n      key={plug.title}\n      className=\"w-full h-[300px] rounded-[8px] bg-newTableHeader hover:bg-newTableBorder\"\n    >\n      <div key={plug.title} className=\"p-[16px] h-full flex flex-col flex-1\">\n        <div className=\"flex\">\n          <div className=\"text-[20px] mb-[8px] flex-1\">{plug.title}</div>\n          {!!data && (\n            <div onClick={(e) => e.stopPropagation()}>\n              <Slider\n                value={activated ? 'on' : 'off'}\n                onChange={changeActivated}\n                fill={true}\n              />\n            </div>\n          )}\n        </div>\n        <div className=\"flex-1\">{plug.description}</div>\n        <Button>{!data ? 'Set Plug' : 'Edit Plug'}</Button>\n      </div>\n    </div>\n  );\n};\nexport const Plug = () => {\n  const plug = usePlugs();\n  const modals = useModals();\n  const fetch = useFetch();\n  const load = useCallback(async () => {\n    return (await fetch(`/integrations/${plug.providerId}/plugs`)).json();\n  }, [plug.providerId]);\n  const { data, isLoading, mutate } = useSWR(`plugs-${plug.providerId}`, load);\n  const addEditPlug = useCallback(\n    (p: PlugsInterface) =>\n      (data?: {\n        activated: boolean;\n        data: string;\n        id: string;\n        integrationId: string;\n        organizationId: string;\n        plugFunction: string;\n      }) => {\n        modals.openModal({\n          withCloseButton: false,\n          onClose() {\n            mutate();\n          },\n          size: '500px',\n          title: `Auto Plug: ${p.title}`,\n          children: (\n            <PlugPop\n              plug={p}\n              data={data}\n              settings={{\n                identifier: plug.identifier,\n                providerId: plug.providerId,\n                name: plug.name,\n              }}\n            />\n          ),\n        });\n      },\n    [data]\n  );\n  if (isLoading) {\n    return null;\n  }\n  return (\n    <div className=\"grid grid-cols-3 gap-[30px]\">\n      {plug.plugs.map((p) => (\n        <PlugItem\n          key={p.title + '-' + plug.providerId}\n          addPlug={addEditPlug(p)}\n          plug={p}\n          data={data?.find((a: any) => a.plugFunction === p.methodName)}\n        />\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/plugs/plugs.context.ts",
    "content": "'use client';\n\nimport { createContext, useContext } from 'react';\nexport interface PlugSettings {\n  providerId: string;\n  name: string;\n  identifier: string;\n}\nexport interface PlugInterface extends PlugSettings {\n  plugs: PlugsInterface[];\n}\nexport interface FieldsInterface {\n  name: string;\n  type: string;\n  validation: string;\n  placeholder: string;\n  description: string;\n}\nexport interface PlugsInterface {\n  title: string;\n  description: string;\n  runEveryMilliseconds: number;\n  methodName: string;\n  fields: FieldsInterface[];\n}\nexport const PlugsContext = createContext<PlugInterface>({\n  providerId: '',\n  name: '',\n  identifier: '',\n  plugs: [\n    {\n      title: '',\n      description: '',\n      runEveryMilliseconds: 0,\n      methodName: '',\n      fields: [\n        {\n          name: '',\n          type: '',\n          placeholder: '',\n          description: '',\n          validation: '',\n        },\n      ],\n    },\n  ],\n});\nexport const usePlugs = () => useContext(PlugsContext);\n"
  },
  {
    "path": "apps/frontend/src/components/plugs/plugs.tsx",
    "content": "'use client';\n\nimport useSWR from 'swr';\nimport { useCallback, useMemo, useState } from 'react';\nimport { capitalize, orderBy } from 'lodash';\nimport clsx from 'clsx';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\nimport Image from 'next/image';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Select } from '@gitroom/react/form/select';\nimport { Button } from '@gitroom/react/form/button';\nimport { useRouter } from 'next/navigation';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { PlugsContext } from '@gitroom/frontend/components/plugs/plugs.context';\nimport { Plug } from '@gitroom/frontend/components/plugs/plug';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport useCookie from 'react-use-cookie';\nimport { SVGLine } from '@gitroom/frontend/components/launches/launches.component';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\nexport const Plugs = () => {\n  const fetch = useFetch();\n  const router = useRouter();\n  const [current, setCurrent] = useState(0);\n  const [refresh, setRefresh] = useState(false);\n  const toaster = useToaster();\n  const load = useCallback(async () => {\n    return (await (await fetch('/integrations/list')).json()).integrations;\n  }, []);\n  const load2 = useCallback(async (path: string) => {\n    return await (await fetch(path)).json();\n  }, []);\n  const { data: plugList, isLoading: plugLoading } = useSWR(\n    '/integrations/plug/list',\n    load2,\n    {\n      fallbackData: [],\n      revalidateOnFocus: false,\n      revalidateOnReconnect: false,\n      revalidateIfStale: false,\n      revalidateOnMount: true,\n      refreshWhenHidden: false,\n      refreshWhenOffline: false,\n    }\n  );\n  const { data, isLoading } = useSWR('analytics-list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n\n  const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0');\n\n  const t = useT();\n\n  const sortedIntegrations = useMemo(() => {\n    return orderBy(\n      data.filter((integration: any) =>\n        plugList?.plugs?.some(\n          (f: any) => f.identifier === integration.identifier\n        )\n      ),\n      // data.filter((integration) => !integration.disabled),\n      ['type', 'disabled', 'identifier'],\n      ['desc', 'asc', 'asc']\n    );\n  }, [data, plugList]);\n  const currentIntegration = useMemo(() => {\n    return sortedIntegrations[current];\n  }, [current, sortedIntegrations]);\n  const currentIntegrationPlug = useMemo(() => {\n    const plug = plugList?.plugs?.find(\n      (f: any) => f?.identifier === currentIntegration?.identifier\n    );\n    if (!plug) {\n      return null;\n    }\n    return {\n      providerId: currentIntegration.id,\n      ...plug,\n    };\n  }, [currentIntegration, plugList]);\n\n  if (isLoading || plugLoading) {\n    return (\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-1 flex-col gap-[15px] transition-all items-center justify-center\">\n        <LoadingComponent />\n      </div>\n    );\n  }\n\n  if (!sortedIntegrations.length && !isLoading) {\n    return (\n      <div className=\"bg-newBgColorInner p-[20px] flex flex-1 flex-col gap-[15px] transition-all items-center justify-center\">\n        <div>\n          <img src=\"/peoplemarketplace.svg\" />\n        </div>\n        <div className=\"text-[48px]\">\n          {t(\n            'there_are_not_plugs_matching_your_channels',\n            'There are not plugs matching your channels'\n          )}\n          <br />\n          {t(\n            'you_have_to_add_x_or_linkedin_or_threads',\n            'You have to add: X or LinkedIn or Threads'\n          )}\n        </div>\n        <Button onClick={() => router.push('/launches')}>\n          {t(\n            'go_to_the_calendar_to_add_channels',\n            'Go to the calendar to add channels'\n          )}\n        </Button>\n      </div>\n    );\n  }\n  return (\n    <>\n      <div\n        className={clsx(\n          'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all',\n          collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]'\n        )}\n      >\n        <div className=\"flex gap-[12px] flex-col\">\n          <div className=\"flex items-center\">\n            <h2 className=\"group-[.sidebar]:hidden flex-1 text-[20px] font-[500]\">\n              {t('channels')}\n            </h2>\n            <div\n              onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')}\n              className=\"group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"7\"\n                height=\"13\"\n                viewBox=\"0 0 7 13\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M6 11.5L1 6.5L6 1.5\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n          </div>\n          {sortedIntegrations.map((integration, index) => (\n            <div\n              key={integration.id}\n              onClick={() => {\n                if (integration.refreshNeeded) {\n                  toaster.show(\n                    'Please refresh the integration from the calendar',\n                    'warning'\n                  );\n                  return;\n                }\n                setRefresh(true);\n                setTimeout(() => {\n                  setRefresh(false);\n                }, 10);\n                setCurrent(index);\n              }}\n              className={clsx(\n                'flex gap-[8px] items-center justify-center group/profile hover:bg-boxHover rounded-e-[8px]',\n                currentIntegration.id !== integration.id &&\n                  'opacity-20 hover:opacity-100 cursor-pointer'\n              )}\n            >\n              <div\n                className={clsx(\n                  'relative rounded-full flex justify-center items-center gap-[8px]',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {(integration.inBetweenSteps || integration.refreshNeeded) && (\n                  <div className=\"absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer\">\n                    <div className=\"bg-red-500 w-[15px] h-[15px] rounded-full start-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center\">\n                      !\n                    </div>\n                    <div className=\"bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]\" />\n                  </div>\n                )}\n                <div className=\"h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity\">\n                  <SVGLine />\n                </div>\n                <ImageWithFallback\n                  fallbackSrc={`/icons/platforms/${integration.identifier}.png`}\n                  src={integration.picture}\n                  className=\"rounded-[8px]\"\n                  alt={integration.identifier}\n                  width={36}\n                  height={36}\n                />\n                <Image\n                  src={`/icons/platforms/${integration.identifier}.png`}\n                  className=\"rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth\"\n                  alt={integration.identifier}\n                  width={18.41}\n                  height={18.41}\n                />\n              </div>\n              <div\n                className={clsx(\n                  'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden',\n                  integration.disabled && 'opacity-50'\n                )}\n              >\n                {integration.name}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n        <PlugsContext.Provider value={currentIntegrationPlug}>\n          <Plug />\n        </PlugsContext.Provider>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/post-url-selector/post.url.selector.tsx",
    "content": "'use client';\n\nimport { EventEmitter } from 'events';\nimport React, { FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport dayjs from 'dayjs';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport removeMd from 'remove-markdown';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nconst postUrlEmitter = new EventEmitter();\nexport const ShowPostSelector = () => {\n  const [showPostSelector, setShowPostSelector] = useState(false);\n  const [callback, setCallback] = useState<{\n    callback: (tag: string | undefined) => void;\n  } | null>({\n    callback: (tag: string | undefined) => {\n      return tag;\n    },\n  } as any);\n  const [date, setDate] = useState(newDayjs());\n  useEffect(() => {\n    postUrlEmitter.on(\n      'show',\n      (params: {\n        date: dayjs.Dayjs;\n        callback: (url: string | undefined) => void;\n      }) => {\n        setCallback(params);\n        setDate(params.date);\n        setShowPostSelector(true);\n      }\n    );\n    return () => {\n      setShowPostSelector(false);\n      setCallback(null);\n      setDate(newDayjs());\n      postUrlEmitter.removeAllListeners();\n    };\n  }, []);\n  const close = useCallback(() => {\n    setShowPostSelector(false);\n    setCallback(null);\n    setDate(newDayjs());\n  }, []);\n  if (!showPostSelector) {\n    return <></>;\n  }\n  return (\n    <PostSelector onClose={close} onSelect={callback?.callback!} date={date} />\n  );\n};\nexport const showPostSelector = (date: dayjs.Dayjs) => {\n  return new Promise<string>((resolve) => {\n    postUrlEmitter.emit('show', {\n      date,\n      callback: (tag: string) => {\n        resolve(tag);\n      },\n    });\n  });\n};\nexport const useShowPostSelector = (day: dayjs.Dayjs) => {\n  return useCallback(() => {\n    return showPostSelector(day);\n  }, [day]);\n};\nexport const PostSelector: FC<{\n  onClose: () => void;\n  onSelect: (tag: string | undefined) => void;\n  only?: 'article' | 'social';\n  noModal?: boolean;\n  date: dayjs.Dayjs;\n}> = (props) => {\n  const { onClose, onSelect, only, date, noModal } = props;\n  const fetch = useFetch();\n  const fetchOldPosts = useCallback(() => {\n    return fetch(\n      '/posts/old?date=' + date.utc().format('YYYY-MM-DDTHH:mm:00'),\n      {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      }\n    ).then((res) => res.json());\n  }, [date]);\n  const onCloseWithEmptyString = useCallback(() => {\n    onSelect('');\n    onClose();\n  }, []);\n  const [current, setCurrent] = useState<string | undefined>(undefined);\n  const select = useCallback(\n    (id: string) => () => {\n      setCurrent(current === id ? undefined : id);\n      onSelect(current === id ? undefined : `(post:${id})`);\n      onClose();\n    },\n    [current]\n  );\n  const { data: loadData } = useSWR('old-posts', fetchOldPosts);\n  const data = useMemo(() => {\n    if (!only) {\n      return loadData;\n    }\n    return loadData?.filter((p: any) => p.integration.type === only);\n  }, [loadData, only]);\n\n  const t = useT();\n\n  return (\n    <>\n      {!noModal ||\n        (data?.length > 0 && (\n          <div\n            className={\n              !noModal\n                ? 'text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade'\n                : ''\n            }\n          >\n            <div\n              className={\n                !noModal\n                  ? 'flex flex-col w-full max-w-[1200px] mx-auto h-full bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative'\n                  : ''\n              }\n            >\n              {!noModal && (\n                <div className=\"flex\">\n                  <div className=\"flex-1\">\n                    <TopTitle\n                      title={\n                        'Select Post Before ' +\n                        date.format('DD/MM/YYYY HH:mm:ss')\n                      }\n                    />\n                  </div>\n                  <button\n                    onClick={onCloseWithEmptyString}\n                    className=\"outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n                    type=\"button\"\n                  >\n                    <svg\n                      viewBox=\"0 0 15 15\"\n                      fill=\"none\"\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      width=\"16\"\n                      height=\"16\"\n                    >\n                      <path\n                        d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                        fill=\"currentColor\"\n                        fillRule=\"evenodd\"\n                        clipRule=\"evenodd\"\n                      ></path>\n                    </svg>\n                  </button>\n                </div>\n              )}\n              {!!data && data.length > 0 && (\n                <div className=\"mt-[10px]\">\n                  <div className=\"flex flex-row flex-wrap gap-[10px]\">\n                    {data.map((p: any) => (\n                      <div\n                        onClick={select(p.id)}\n                        className={clsx(\n                          'cursor-pointer overflow-hidden flex gap-[20px] flex-col w-[200px] h-[200px] text-ellipsis p-3 border border-tableBorder rounded-[8px] hover:bg-primary',\n                          current === p.id ? 'bg-primary' : 'bg-secondary'\n                        )}\n                        key={p.id}\n                      >\n                        <div className=\"flex gap-[10px] items-center\">\n                          <div className=\"relative\">\n                            <img\n                              src={p.integration.picture}\n                              className=\"w-[32px] h-[32px] rounded-full\"\n                            />\n                            <img\n                              className=\"w-[20px] h-[20px] rounded-full absolute z-10 -bottom-[5px] -end-[5px] border border-fifth\"\n                              src={\n                                `/icons/platforms/` +\n                                p?.integration?.providerIdentifier +\n                                '.png'\n                              }\n                            />\n                          </div>\n                          <div>{p.integration.name}</div>\n                        </div>\n                        <div className=\"flex-1\">{removeMd(p.content)}</div>\n                        <div>\n                          {t('status', 'Status:')}\n                          {p.state}\n                        </div>\n                      </div>\n                    ))}\n                  </div>\n                </div>\n              )}\n            </div>\n          </div>\n        ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/preview/comments.components.tsx",
    "content": "'use client';\n\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { Button } from '@gitroom/react/form/button';\nimport { FC, useCallback, useMemo, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { FieldValues, SubmitHandler, useForm } from 'react-hook-form';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const RenderComponents: FC<{\n  postId: string;\n}> = (props) => {\n  const { postId } = props;\n  const fetch = useFetch();\n  const comments = useCallback(async () => {\n    return (await fetch(`/public/posts/${postId}/comments`)).json();\n  }, [postId]);\n  const { data, mutate, isLoading } = useSWR('comments', comments);\n  const mapUsers = useMemo(() => {\n    return (data?.comments || []).reduce(\n      (all: any, current: any) => {\n        all.users[current.userId] = all.users[current.userId] || all.counter++;\n        return all;\n      },\n      {\n        users: {},\n        counter: 1,\n      }\n    ).users;\n  }, [data]);\n  const { handleSubmit, register, setValue } = useForm();\n  const submit: SubmitHandler<FieldValues> = useCallback(\n    async (e) => {\n      setValue('comment', '');\n      await fetch(`/posts/${postId}/comments`, {\n        method: 'POST',\n        body: JSON.stringify(e),\n      });\n      mutate();\n    },\n    [postId, mutate]\n  );\n\n  const t = useT();\n\n  if (isLoading) {\n    return <></>;\n  }\n  return (\n    <>\n      <div className=\"mb-6 flex space-x-3\">\n        <form className=\"flex-1 space-y-2\" onSubmit={handleSubmit(submit)}>\n          <textarea\n            {...register('comment', {\n              required: true,\n            })}\n            className=\"flex w-full px-3 py-2 h-[98px] text-sm ring-offset-background placeholder:text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 min-h-[80px] resize-none text-white bg-third border border-tableBorder placeholder-gray-500 focus:ring-0\"\n            placeholder=\"Add a comment...\"\n            defaultValue={''}\n          />\n          <div className=\"flex justify-end\">\n            <Button type=\"submit\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={24}\n                height={24}\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth={2}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"lucide lucide-send me-2 h-4 w-4\"\n              >\n                <path d=\"m22 2-7 20-4-9-9-4Z\" />\n                <path d=\"M22 2 11 13\" />\n              </svg>\n              {t('post', 'Post')}\n            </Button>\n          </div>\n        </form>\n      </div>\n      <div className=\"space-y-4\">\n        {!!data.comments.length && (\n          <h3 className=\"text-lg font-semibold\">{t('comments', 'Comments')}</h3>\n        )}\n        {data.comments.map((comment: any) => (\n          <div\n            key={comment.id}\n            className=\"flex space-x-3 border-t border-tableBorder py-3\"\n          >\n            <div className=\"flex-1 space-y-1\">\n              <div className=\"flex items-center space-x-2\">\n                <h3 className=\"text-sm font-semibold\">\n                  {t('user', 'User')}\n                  {mapUsers[comment.userId]}\n                </h3>\n              </div>\n              <p className=\"text-sm text-gray-300\">{comment.content}</p>\n            </div>\n          </div>\n        ))}\n      </div>\n    </>\n  );\n};\nexport const CommentsComponents: FC<{\n  postId: string;\n}> = (props) => {\n  const user = useUser();\n  const t = useT();\n\n  const { postId } = props;\n  const goToComments = useCallback(() => {\n    window.location.href = `/auth?returnUrl=${window.location.href}`;\n  }, []);\n  if (!user?.id) {\n    return (\n      <Button onClick={goToComments}>\n        {t(\n          'login_register_to_add_comments',\n          'Login / Register to add comments'\n        )}\n      </Button>\n    );\n  }\n  return <RenderComponents postId={postId} />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/preview/copy.client.tsx",
    "content": "'use client';\n\nimport { Button } from '@gitroom/react/form/button';\nimport copy from 'copy-to-clipboard';\nimport { useCallback } from 'react';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const CopyClient = () => {\n  const toast = useToaster();\n  const t = useT();\n  const copyToClipboard = useCallback(() => {\n    toast.show(\n      t('link_copied_to_clipboard', 'Link copied to clipboard'),\n      'success'\n    );\n    copy(window.location.href.split?.('?')?.shift()!);\n  }, []);\n  return (\n    <Button onClick={copyToClipboard}>\n      {t('share_with_a_client', 'Share with a client')}\n    </Button>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/preview/preview.wrapper.tsx",
    "content": "'use client';\n\nimport useSWR from 'swr';\nimport { ContextWrapper } from '@gitroom/frontend/components/layout/user.context';\nimport { ReactNode, useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { Toaster } from '@gitroom/react/toaster/toaster';\nimport { MantineWrapper } from '@gitroom/react/helpers/mantine.wrapper';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { CopilotKit } from '@copilotkit/react-core';\nexport const PreviewWrapper = ({ children }: { children: ReactNode }) => {\n  const fetch = useFetch();\n  const { backendUrl } = useVariables();\n  const load = useCallback(async (path: string) => {\n    return await (await fetch(path)).json();\n  }, []);\n  const { data: user } = useSWR('/user/self', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    refreshWhenOffline: false,\n    refreshWhenHidden: false,\n  });\n  return (\n    <ContextWrapper user={user}>\n      <CopilotKit\n        credentials=\"include\"\n        runtimeUrl={backendUrl + '/copilot/chat'}\n        showDevConsole={false}\n      >\n        <MantineWrapper>\n          <Toaster />\n          {children}\n        </MantineWrapper>\n      </CopilotKit>\n    </ContextWrapper>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/preview/render.preview.date.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\ndayjs.extend(utc);\n\nexport const RenderPreviewDate: FC<{ date: string }> = ({ date }) => {\n  console.log(date);\n  return <>{dayjs.utc(date).local().format('MMMM D, YYYY h:mm A')}</>;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/public-api/public.component.tsx",
    "content": "'use client';\n\nimport { useState, useCallback } from 'react';\nimport { useSWRConfig } from 'swr';\nimport { useUser } from '../layout/user.context';\nimport { Button } from '@gitroom/react/form/button';\nimport copy from 'copy-to-clipboard';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useDecisionModal } from '@gitroom/frontend/components/layout/new-modal';\nimport { DeveloperComponent } from '@gitroom/frontend/components/developer/developer.component';\nimport clsx from 'clsx';\n\nconst PublicApiContent = () => {\n  const user = useUser();\n  const { backendUrl, frontEndUrl, mcpUrl } = useVariables();\n  const toaster = useToaster();\n  const fetch = useFetch();\n  const decision = useDecisionModal();\n  const { mutate } = useSWRConfig();\n  const [reveal, setReveal] = useState(false);\n  const [reveal2, setReveal2] = useState(false);\n  const copyToClipboard = useCallback(() => {\n    toaster.show('API Key copied to clipboard', 'success');\n    copy(user?.publicApi!);\n  }, [user]);\n  const copyToClipboard2 = useCallback(() => {\n    toaster.show('MCP copied to clipboard', 'success');\n    copy(`${mcpUrl || backendUrl}/mcp/` + user?.publicApi);\n  }, [user]);\n\n  const rotateKey = useCallback(async () => {\n    const approved = await decision.open({\n      title: 'Rotate API Key?',\n      description:\n        'This will generate a new API key and invalidate the current one. Any integrations using the old key will stop working.',\n      approveLabel: 'Rotate',\n      cancelLabel: 'Cancel',\n    });\n    if (!approved) return;\n    await fetch('/user/api-key/rotate', { method: 'POST' });\n    await mutate('/user/self');\n    setReveal(false);\n    setReveal2(false);\n    toaster.show('API Key rotated successfully', 'success');\n  }, [decision, fetch, mutate, toaster]);\n\n  const t = useT();\n\n  if (!user || !user.publicApi) {\n    return null;\n  }\n  return (\n    <div className=\"flex flex-col gap-[20px]\">\n      <div className=\"flex flex-col\">\n        <h3 className=\"text-[20px]\">{t('public_api', 'Public API')}</h3>\n        <div className=\"text-customColor18 mt-[4px]\">\n          {t(\n            'use_postiz_api_to_integrate_with_your_tools',\n            'Use Postiz API to integrate with your tools.'\n          )}\n          <br />\n          <a\n            className=\"underline hover:font-bold hover:underline\"\n            href=\"https://docs.postiz.com/public-api\"\n            target=\"_blank\"\n          >\n            {t(\n              'read_how_to_use_it_over_the_documentation',\n              'Read how to use it over the documentation.'\n            )}\n          </a>\n          <a\n            className=\"underline hover:font-bold hover:underline\"\n            href=\"https://www.npmjs.com/package/n8n-nodes-postiz\"\n            target=\"_blank\"\n          >\n            <br />\n            {t('check_n8n', 'Check out our N8N custom node for Postiz.')}\n          </a>\n        </div>\n        <div className=\"flex flex-col\">\n          <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n            <div className=\"flex items-center\">\n              {reveal ? (\n                user.publicApi\n              ) : (\n                <>\n                  <div className=\"blur-sm\">{user.publicApi.slice(0, -5)}</div>\n                  <div>{user.publicApi.slice(-5)}</div>\n                </>\n              )}\n            </div>\n            <div>\n              {!reveal ? (\n                <Button onClick={() => setReveal(true)}>\n                  {t('reveal', 'Reveal')}\n                </Button>\n              ) : (\n                <Button onClick={copyToClipboard}>\n                  {t('copy_key', 'Copy Key')}\n                </Button>\n              )}\n            </div>\n          </div>\n          <div>\n            <Button onClick={rotateKey}>\n              {t('rotate_key', 'Rotate Key')}\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex flex-col\">\n        <h3 className=\"text-[20px]\">{t('mcp', 'MCP')}</h3>\n        <div className=\"text-customColor18 mt-[4px]\">\n          {t(\n            'connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster',\n            'Connect Postiz MCP server to your client (Http streaming) to schedule your posts faster.'\n          )}\n        </div>\n        <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n          <div className=\"flex items-center\">\n            {reveal2 ? (\n              `${mcpUrl || backendUrl}/mcp/` + user.publicApi\n            ) : (\n              <>\n                <div className=\"blur-sm\">\n                  {(`${mcpUrl || backendUrl}/mcp/` + user.publicApi).slice(0, -5)}\n                </div>\n                <div>{(`${mcpUrl || backendUrl}/mcp/` + user.publicApi).slice(-5)}</div>\n              </>\n            )}\n          </div>\n          <div>\n            {!reveal2 ? (\n              <Button onClick={() => setReveal2(true)}>\n                {t('reveal', 'Reveal')}\n              </Button>\n            ) : (\n              <Button onClick={copyToClipboard2}>\n                {t('copy_key', 'Copy Key')}\n              </Button>\n            )}\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex flex-col\">\n        <h3 className=\"text-[20px]\">Building your Postiz payload</h3>\n        <div className=\"text-customColor18 mt-[4px] whitespace-pre-line\">\n          Sending a POST request to <strong className=\"text-textColor\">/posts</strong> might feel a bit overwhelming as many\n          platforms have different requirements.{'\\n'}\n          We have created an easy way to build your Postiz payload to schedule\n          posts. {'\\n'}\n          You can use the Postiz wizard, and schedule a post with our UI, after\n          you added all your text and settings, the wizard will generate the\n          payload for you.{'\\n'}\n        </div>\n        <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n          <Button onClick={() => window.open(`${frontEndUrl}/modal/dark/all`, '_blank')}>\n            Open the payload wizard\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport const PublicComponent = () => {\n  const t = useT();\n  const [subTab, setSubTab] = useState<'api' | 'developer'>('api');\n\n  return (\n    <div className=\"flex flex-col gap-[20px]\">\n      <div className=\"flex gap-[4px] border-b border-fifth\">\n        <button\n          type=\"button\"\n          className={clsx(\n            'px-[16px] py-[8px] text-[14px] rounded-t-[4px] transition-colors',\n            subTab === 'api'\n              ? 'bg-sixth text-textColor border border-fifth border-b-0'\n              : 'text-customColor18 hover:text-textColor'\n          )}\n          onClick={() => setSubTab('api')}\n        >\n          {t('public_api', 'Public API')}\n        </button>\n        <button\n          type=\"button\"\n          className={clsx(\n            'px-[16px] py-[8px] text-[14px] rounded-t-[4px] transition-colors',\n            subTab === 'developer'\n              ? 'bg-sixth text-textColor border border-fifth border-b-0'\n              : 'text-customColor18 hover:text-textColor'\n          )}\n          onClick={() => setSubTab('developer')}\n        >\n          {t('apps', 'Apps')}\n        </button>\n      </div>\n      {subTab === 'api' && <PublicApiContent />}\n      {subTab === 'developer' && <DeveloperComponent />}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/sets/sets.tsx",
    "content": "'use client';\nimport 'reflect-metadata';\n\nimport React, { FC, Fragment, useCallback, useMemo, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { Button } from '@gitroom/react/form/button';\nimport { Input } from '@gitroom/react/form/input';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport clsx from 'clsx';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\n\nconst SaveSetModal: FC<{\n  postData: any;\n  initialValue?: string;\n  onSave: (name: string) => void;\n  onCancel: () => void;\n}> = ({ postData, onSave, onCancel, initialValue }) => {\n  const [name, setName] = useState(initialValue);\n  const t = useT();\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    if (name.trim()) {\n      onSave(name.trim());\n    }\n  };\n\n  return (\n    <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n      <div>\n        <Input\n          label=\"Set Name\"\n          translationKey=\"label_set_name\"\n          name=\"setName\"\n          value={name}\n          disableForm={true}\n          onChange={(e) => setName(e.target.value)}\n          placeholder=\"Enter a name for this set\"\n          autoFocus\n        />\n      </div>\n      <div className=\"flex gap-2 justify-end\">\n        <Button type=\"button\" secondary onClick={onCancel}>\n          {t('cancel', 'Cancel')}\n        </Button>\n        <Button type=\"submit\" disabled={!name.trim()}>\n          {t('save', 'Save')}\n        </Button>\n      </div>\n    </form>\n  );\n};\n\nexport const Sets: FC = () => {\n  const fetch = useFetch();\n  const user = useUser();\n  const modal = useModals();\n  const toaster = useToaster();\n\n  const load = useCallback(async (path: string) => {\n    return (await (await fetch(path)).json()).integrations;\n  }, []);\n\n  const { isLoading, data: integrations } = useSWR('/integrations/list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n\n  const list = useCallback(async () => {\n    return (await fetch('/sets')).json();\n  }, []);\n\n  const { data, mutate } = useSWR('sets', list, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n\n  const addSet = useCallback(\n    (params?: { id?: string; name?: string; content?: string }) => () => {\n      modal.openModal({\n        id: 'add-edit-modal',\n        closeOnClickOutside: false,\n        removeLayout: true,\n        closeOnEscape: false,\n        withCloseButton: false,\n        askClose: true,\n        fullScreen: true,\n        classNames: {\n          modal: 'w-[100%] max-w-[1400px] text-textColor',\n        },\n        children: (\n          <AddEditModal\n            allIntegrations={integrations.map((p: any) => ({\n              ...p,\n            }))}\n            {...(params?.id ? { set: JSON.parse(params.content) } : {})}\n            addEditSets={(data) => {\n              modal.openModal({\n                title: 'Save as Set',\n                children: (\n                  <SaveSetModal\n                    initialValue={params?.name || ''}\n                    postData={data}\n                    onSave={async (name: string) => {\n                      try {\n                        await fetch('/sets', {\n                          method: 'POST',\n                          body: JSON.stringify({\n                            ...(params?.id ? { id: params.id } : {}),\n                            name,\n                            content: JSON.stringify(data),\n                          }),\n                        });\n                        modal.closeAll();\n                        mutate();\n                        toaster.show('Set saved successfully', 'success');\n                      } catch (error) {\n                        toaster.show('Failed to save set', 'warning');\n                      }\n                    }}\n                    onCancel={() => modal.closeAll()}\n                  />\n                ),\n              });\n            }}\n            reopenModal={() => {}}\n            mutate={() => {}}\n            integrations={integrations}\n            date={newDayjs()}\n          />\n        ),\n        title: ``,\n      });\n    },\n    [integrations]\n  );\n\n  const deleteSet = useCallback(\n    (data: any) => async () => {\n      if (await deleteDialog(`Are you sure you want to delete ${data.name}?`)) {\n        await fetch(`/sets/${data.id}`, {\n          method: 'DELETE',\n        });\n        mutate();\n        toaster.show('Set deleted successfully', 'success');\n      }\n    },\n    []\n  );\n\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">Sets ({data?.length || 0})</h3>\n      <div className=\"text-customColor18 mt-[4px]\">\n        Manage your content sets for easy reuse across posts.\n      </div>\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n        <div className=\"flex flex-col w-full\">\n          {!!data?.length && (\n            <div className=\"grid grid-cols-[2fr,1fr,1fr] w-full gap-y-[10px]\">\n              <div>{t('name', 'Name')}</div>\n              <div>{t('edit', 'Edit')}</div>\n              <div>{t('delete', 'Delete')}</div>\n              {data?.map((p: any) => (\n                <Fragment key={p.id}>\n                  <div className=\"flex flex-col justify-center\">{p.name}</div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={addSet(p)}>{t('edit', 'Edit')}</Button>\n                    </div>\n                  </div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={deleteSet(p)}>\n                        {t('delete', 'Delete')}\n                      </Button>\n                    </div>\n                  </div>\n                </Fragment>\n              ))}\n            </div>\n          )}\n          <div>\n            <Button\n              onClick={addSet()}\n              className={clsx((data?.length || 0) > 0 && 'my-[16px]')}\n            >\n              Add a set\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/settings/email-notifications.component.tsx",
    "content": "'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Slider } from '@gitroom/react/form/slider';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\ninterface EmailNotifications {\n  sendSuccessEmails: boolean;\n  sendFailureEmails: boolean;\n  sendStreakEmails: boolean;\n}\n\nexport const useEmailNotifications = () => {\n  const fetch = useFetch();\n\n  const load = useCallback(async () => {\n    return (await fetch('/user/email-notifications')).json();\n  }, []);\n\n  return useSWR<EmailNotifications>('email-notifications', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n};\n\nconst EmailNotificationsComponent = () => {\n  const t = useT();\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const { data, isLoading } = useEmailNotifications();\n\n  const [localSettings, setLocalSettings] = useState<EmailNotifications>({\n    sendSuccessEmails: true,\n    sendFailureEmails: true,\n    sendStreakEmails: true,\n  });\n\n  // Keep a ref to always have the latest state\n  const settingsRef = useRef(localSettings);\n  settingsRef.current = localSettings;\n\n  // Sync local state with fetched data\n  useEffect(() => {\n    if (data) {\n      setLocalSettings(data);\n    }\n  }, [data]);\n\n  const updateSetting = useCallback(\n    async (key: keyof EmailNotifications, value: boolean) => {\n      // Use ref to get the latest state\n      const currentSettings = settingsRef.current;\n      const newData = {\n        ...currentSettings,\n        [key]: value,\n      };\n\n      // Update local state immediately\n      setLocalSettings(newData);\n\n      await fetch('/user/email-notifications', {\n        method: 'POST',\n        body: JSON.stringify(newData),\n      });\n\n      toaster.show(t('settings_updated', 'Settings updated'), 'success');\n    },\n    []\n  );\n\n  const handleSuccessEmailsChange = useCallback(\n    (value: 'on' | 'off') => {\n      updateSetting('sendSuccessEmails', value === 'on');\n    },\n    [updateSetting]\n  );\n\n  const handleFailureEmailsChange = useCallback(\n    (value: 'on' | 'off') => {\n      updateSetting('sendFailureEmails', value === 'on');\n    },\n    [updateSetting]\n  );\n\n  const handleStreakEmailsChange = useCallback(\n    (value: 'on' | 'off') => {\n      updateSetting('sendStreakEmails', value === 'on');\n    },\n    [updateSetting]\n  );\n\n  if (isLoading) {\n    return (\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n        <div className=\"animate-pulse\">\n          {t('loading', 'Loading...')}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]\">\n      <div className=\"mt-[4px]\">\n        {t('email_notifications', 'Email Notifications')}\n      </div>\n      <div className=\"flex items-center justify-between\">\n        <div className=\"flex flex-col\">\n          <div className=\"text-[14px]\">\n            {t('success_emails', 'Success Emails')}\n          </div>\n          <div className=\"text-[12px] text-customColor18\">\n            {t(\n              'success_emails_description',\n              'Receive email notifications when posts are published successfully'\n            )}\n          </div>\n        </div>\n        <Slider\n          value={localSettings.sendSuccessEmails ? 'on' : 'off'}\n          onChange={handleSuccessEmailsChange}\n          fill={true}\n        />\n      </div>\n      <div className=\"flex items-center justify-between\">\n        <div className=\"flex flex-col\">\n          <div className=\"text-[14px]\">\n            {t('failure_emails', 'Failure Emails')}\n          </div>\n          <div className=\"text-[12px] text-customColor18\">\n            {t(\n              'failure_emails_description',\n              'Receive email notifications when posts fail to publish'\n            )}\n          </div>\n        </div>\n        <Slider\n          value={localSettings.sendFailureEmails ? 'on' : 'off'}\n          onChange={handleFailureEmailsChange}\n          fill={true}\n        />\n      </div>\n      <div className=\"flex items-center justify-between\">\n        <div className=\"flex flex-col\">\n          <div className=\"text-[14px]\">\n            {t('streak_emails', 'Streak Reminder Emails')}\n          </div>\n          <div className=\"text-[12px] text-customColor18\">\n            {t(\n              'streak_emails_description',\n              'Receive email reminders when your posting streak is about to end'\n            )}\n          </div>\n        </div>\n        <Slider\n          value={localSettings.sendStreakEmails ? 'on' : 'off'}\n          onChange={handleStreakEmailsChange}\n          fill={true}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default EmailNotificationsComponent;\n\n"
  },
  {
    "path": "apps/frontend/src/components/settings/github.component.tsx",
    "content": "'use client';\n\nimport Image from 'next/image';\nimport { Button } from '@gitroom/react/form/button';\nimport { FC, useCallback, useEffect, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { Input } from '@gitroom/react/form/input';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nconst ConnectedComponent: FC<{\n  id: string;\n  login: string;\n  deleteRepository: () => void;\n}> = (props) => {\n  const { id, login, deleteRepository } = props;\n  const fetch = useFetch();\n  const disconnect = useCallback(async () => {\n    if (\n      !(await deleteDialog(\n        'Are you sure you want to disconnect this repository?'\n      ))\n    ) {\n      return;\n    }\n    deleteRepository();\n    await fetch(`/settings/repository/${id}`, {\n      method: 'DELETE',\n    });\n  }, []);\n\n  const t = useT();\n\n  return (\n    <div className=\"my-[16px] mt-[16px] h-[90px] bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n      <div className={`flex items-center gap-[8px]`}>\n        <div>\n          <Image src=\"/icons/github.svg\" alt=\"GitHub\" width={40} height={40} />\n        </div>\n        <div className=\"flex-1\">\n          <strong>{t('connected', 'Connected:')}</strong> {login}\n        </div>\n        <Button onClick={disconnect}>{t('disconnect', 'Disconnect')}</Button>\n      </div>\n    </div>\n  );\n};\nconst ConnectComponent: FC<{\n  setConnected: (name: string) => void;\n  id: string;\n  login: string;\n  organizations: Array<{\n    id: string;\n    login: string;\n  }>;\n  deleteRepository: () => void;\n}> = (props) => {\n  const { id, setConnected, deleteRepository } = props;\n  const [url, setUrl] = useState('');\n  const fetch = useFetch();\n  const toast = useToaster();\n  const cancelConnection = useCallback(async () => {\n    await (\n      await fetch(`/settings/repository/${id}`, {\n        method: 'DELETE',\n      })\n    ).json();\n    deleteRepository();\n  }, []);\n  const completeConnection = useCallback(async () => {\n    const [select, repo] = url\n      .match(/https:\\/\\/github\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)/)!\n      .slice(1);\n    const response = await fetch(`/settings/organizations/${id}`, {\n      method: 'POST',\n      body: JSON.stringify({\n        login: `${select}/${repo}`,\n      }),\n    });\n    if (response.status === 404) {\n      toast.show('Repository not found', 'warning');\n      return;\n    }\n    setConnected(`${select}/${repo}`);\n  }, [url]);\n\n  const t = useT();\n\n  return (\n    <div className=\"my-[16px] mt-[16px] h-[100px] bg-sixth border-fifth border rounded-[4px] px-[24px] flex\">\n      <div className={`flex items-center gap-[8px] flex-1`}>\n        <div>\n          <Image src=\"/icons/github.svg\" alt=\"GitHub\" width={40} height={40} />\n        </div>\n        <div className=\"flex-1\">\n          {t('connect_your_repository', 'Connect your repository')}\n        </div>\n        <Button\n          className=\"bg-transparent border-0 text-gray mt-[7px]\"\n          onClick={cancelConnection}\n        >\n          {t('cancel', 'Cancel')}\n        </Button>\n        <Input\n          value={url}\n          disableForm={true}\n          removeError={true}\n          onChange={(e) => setUrl(e.target.value)}\n          name=\"github\"\n          label=\"\"\n          placeholder=\"Full GitHub URL\"\n        />\n        <Button\n          className=\"h-[44px] mt-[7px]\"\n          disabled={\n            !url.match(\n              /https:\\/\\/github\\.com\\/([A-Za-z0-9_.-]+)\\/([A-Za-z0-9_.-]+)/\n            )\n          }\n          onClick={completeConnection}\n        >\n          {t('connect', 'Connect')}\n        </Button>\n      </div>\n    </div>\n  );\n};\nexport const GithubComponent: FC<{\n  organizations: Array<{\n    login: string;\n    id: string;\n  }>;\n  github: Array<{\n    id: string;\n    login: string;\n  }>;\n}> = (props) => {\n  if (typeof window !== 'undefined' && window.opener) {\n    window.close();\n  }\n  const { github, organizations } = props;\n  const [githubState, setGithubState] = useState(github);\n  useEffect(() => {\n    setGithubState(github);\n  }, [github]);\n  const fetch = useFetch();\n  const connect = useCallback(async () => {\n    const { url } = await (await fetch('/settings/github/url')).json();\n    window.open(url, 'Github Connect', 'width=700,height=700');\n  }, []);\n  const setConnected = useCallback(\n    (g: { id: string; login: string }) => (name: string) => {\n      setGithubState((gitlibs) => {\n        return gitlibs.map((git, index) => {\n          if (git.id === g.id) {\n            return {\n              id: g.id,\n              login: name,\n            };\n          }\n          return git;\n        });\n      });\n    },\n    [githubState]\n  );\n  const deleteConnect = useCallback(\n    (g: { id: string; login: string }) => () => {\n      setGithubState((gitlibs) => {\n        return gitlibs.filter((git, index) => {\n          return git.id !== g.id;\n        });\n      });\n    },\n    [githubState]\n  );\n\n  const t = useT();\n\n  return (\n    <>\n      {githubState.map((g) => (\n        <>\n          {!g.login ? (\n            <ConnectComponent\n              deleteRepository={deleteConnect(g)}\n              setConnected={setConnected(g)}\n              organizations={organizations}\n              {...g}\n            />\n          ) : (\n            <ConnectedComponent deleteRepository={deleteConnect(g)} {...g} />\n          )}\n        </>\n      ))}\n      {githubState.filter((f) => !f.login).length === 0 && (\n        <div className=\"my-[16px] mt-[16px] h-[90px] bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n          <div className={`flex items-center gap-[8px]`}>\n            <div>\n              <Image\n                src=\"/icons/github.svg\"\n                alt=\"GitHub\"\n                width={40}\n                height={40}\n              />\n            </div>\n            <div className=\"flex-1\">\n              {t('connect_your_repository', 'Connect your repository')}\n            </div>\n            <Button onClick={connect}>{t('connect', 'Connect')}</Button>\n          </div>\n        </div>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/settings/global.settings.tsx",
    "content": "'use client';\n\nimport React from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport dynamic from 'next/dynamic';\nimport EmailNotificationsComponent from '@gitroom/frontend/components/settings/email-notifications.component';\nimport ShortlinkPreferenceComponent from '@gitroom/frontend/components/settings/shortlink-preference.component';\n\nconst MetricComponent = dynamic(\n  () => import('@gitroom/frontend/components/settings/metric.component'),\n  {\n    ssr: false,\n  }\n);\n\nexport const GlobalSettings = () => {\n  const t = useT();\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">{t('global_settings', 'Global Settings')}</h3>\n      <MetricComponent />\n      <EmailNotificationsComponent />\n      <ShortlinkPreferenceComponent />\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/settings/metric.component.tsx",
    "content": "'use client';\n\nimport { Select } from '@gitroom/react/form/select';\nimport React, { useState } from 'react';\nimport { isUSCitizen } from '@gitroom/frontend/components/launches/helpers/isuscitizen.utils';\nimport timezones from 'timezones-list';\nconst dateMetrics = [\n  { label: 'AM:PM', value: 'US' },\n  { label: '24 hours', value: 'GLOBAL' },\n];\n\nimport dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone';\ndayjs.extend(timezone);\n\nconst MetricComponent = () => {\n  const [currentMetric, setCurrentMetric] = useState(isUSCitizen());\n  const [timezone, setTimezone] = useState(\n    localStorage.getItem('timezone') || dayjs.tz.guess()\n  );\n  const changeMetric = (event: React.ChangeEvent<HTMLSelectElement>) => {\n    const value = event.target.value;\n    setCurrentMetric(value === 'US');\n    localStorage.setItem('isUS', value);\n  };\n\n  const changeTimezone = (event: React.ChangeEvent<HTMLSelectElement>) => {\n    const value = event.target.value;\n    console.log(value);\n    setTimezone(value);\n    localStorage.setItem('timezone', value);\n    dayjs.tz.setDefault(value);\n  };\n  return (\n    <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]\">\n      <div className=\"mt-[4px]\">Date Metrics</div>\n      <Select name=\"metric\" disableForm={true} label=\"\" onChange={changeMetric}>\n        {dateMetrics.map((metric) => (\n          <option\n            key={metric.value}\n            value={metric.value}\n            selected={currentMetric === (metric.value === 'US')}\n          >\n            {metric.label}\n          </option>\n        ))}\n      </Select>\n\n      {/*<div className=\"mt-[4px]\">Current Timezone</div>*/}\n      {/*<Select*/}\n      {/*  name=\"timezone\"*/}\n      {/*  disableForm={true}*/}\n      {/*  label=\"\"*/}\n      {/*  onChange={changeTimezone}*/}\n      {/*>*/}\n      {/*  {timezones.map((metric) => (*/}\n      {/*    <option*/}\n      {/*      key={metric.name}*/}\n      {/*      value={metric.tzCode}*/}\n      {/*      selected={metric.tzCode === timezone}*/}\n      {/*    >*/}\n      {/*      {metric.label}*/}\n      {/*    </option>*/}\n      {/*  ))}*/}\n      {/*</Select>*/}\n    </div>\n  );\n};\n\nexport default MetricComponent;\n"
  },
  {
    "path": "apps/frontend/src/components/settings/shortlink-preference.component.tsx",
    "content": "'use client';\n\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Select } from '@gitroom/react/form/select';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\ntype ShortLinkPreference = 'ASK' | 'YES' | 'NO';\n\ninterface ShortlinkPreferenceResponse {\n  shortlink: ShortLinkPreference;\n}\n\nexport const useShortlinkPreference = () => {\n  const fetch = useFetch();\n\n  const load = useCallback(async () => {\n    return (await fetch('/settings/shortlink')).json();\n  }, []);\n\n  return useSWR<ShortlinkPreferenceResponse>('shortlink-preference', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n};\n\nconst ShortlinkPreferenceComponent = () => {\n  const t = useT();\n  const fetch = useFetch();\n  const toaster = useToaster();\n  const { data, isLoading, mutate } = useShortlinkPreference();\n\n  const [localValue, setLocalValue] = useState<ShortLinkPreference>('ASK');\n\n  // Sync local state with fetched data\n  useEffect(() => {\n    if (data?.shortlink) {\n      setLocalValue(data.shortlink);\n    }\n  }, [data]);\n\n  const handleChange = useCallback(\n    async (event: React.ChangeEvent<HTMLSelectElement>) => {\n      const newValue = event.target.value as ShortLinkPreference;\n\n      // Update local state immediately\n      setLocalValue(newValue);\n\n      await fetch('/settings/shortlink', {\n        method: 'POST',\n        body: JSON.stringify({ shortlink: newValue }),\n      });\n\n      mutate({ shortlink: newValue });\n      toaster.show(t('settings_updated', 'Settings updated'), 'success');\n    },\n    [fetch, mutate, toaster, t]\n  );\n\n  if (isLoading) {\n    return (\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px]\">\n        <div className=\"animate-pulse\">{t('loading', 'Loading...')}</div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]\">\n      <div className=\"mt-[4px]\">\n        {t('shortlink_settings', 'Shortlink Settings')}\n      </div>\n      <div className=\"flex items-center justify-between gap-[24px]\">\n        <div className=\"flex flex-col flex-1\">\n          <div className=\"text-[14px]\">\n            {t('shortlink_preference', 'Shortlink Preference')}\n          </div>\n          <div className=\"text-[12px] text-customColor18\">\n            {t(\n              'shortlink_preference_description',\n              'Control how URLs in your posts are handled. Shortlinks provide click statistics.'\n            )}\n          </div>\n        </div>\n        <div className=\"w-[200px]\">\n          <Select\n            name=\"shortlink\"\n            label=\"\"\n            disableForm={true}\n            hideErrors={true}\n            value={localValue}\n            onChange={handleChange}\n          >\n            <option value=\"ASK\">\n              {t('shortlink_ask', 'Ask every time')}\n            </option>\n            <option value=\"YES\">\n              {t('shortlink_yes', 'Always shortlink')}\n            </option>\n            <option value=\"NO\">\n              {t('shortlink_no', 'Never shortlink')}\n            </option>\n          </Select>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default ShortlinkPreferenceComponent;\n\n"
  },
  {
    "path": "apps/frontend/src/components/settings/signatures.component.tsx",
    "content": "import React, { FC, Fragment, useCallback } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { Button } from '@gitroom/react/form/button';\nimport clsx from 'clsx';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport { array, boolean, object, string } from 'yup';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport { CopilotTextarea } from '@copilotkit/react-textarea';\nimport { Select } from '@gitroom/react/form/select';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nexport const SignaturesComponent: FC<{\n  appendSignature?: (value: string) => void;\n}> = (props) => {\n  const { appendSignature } = props;\n  const fetch = useFetch();\n  const modal = useModals();\n  const toaster = useToaster();\n  const load = useCallback(async () => {\n    return (await fetch('/signatures')).json();\n  }, []);\n  const { data, mutate } = useSWR('signatures', load);\n  const addSignature = useCallback(\n    (data?: any) => () => {\n      modal.openModal({\n        title: data ? 'Edit Signature' : 'Add Signature',\n        withCloseButton: true,\n        children: <AddOrRemoveSignature data={data} reload={mutate} />,\n      });\n    },\n    [mutate]\n  );\n\n  const deleteSignature = useCallback(\n    (data: any) => async () => {\n      if (\n        await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete',\n            `Are you sure you want to delete?`,\n            { name: data.content.slice(0, 15) + '...' }\n          )\n        )\n      ) {\n        await fetch(`/signatures/${data.id}`, {\n          method: 'DELETE',\n        });\n        mutate();\n        toaster.show('Signature deleted successfully', 'success');\n      }\n    },\n    []\n  );\n\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">{t('signatures', 'Signatures')}</h3>\n      <div className=\"text-customColor18 mt-[4px]\">\n        {t(\n          'you_can_add_signatures_to_your_account_to_be_used_in_your_posts',\n          'You can add signatures to your account to be used in your posts.'\n        )}\n      </div>\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n        <div className=\"flex flex-col w-full\">\n          {!!data?.length && (\n            <div\n              className={`grid ${\n                !!appendSignature\n                  ? 'grid-cols-[1fr,1fr,1fr,1fr,1fr]'\n                  : 'grid-cols-[1fr,1fr,1fr,1fr]'\n              } w-full gap-y-[10px]`}\n            >\n              <div>{t('content', 'Content')}</div>\n              <div className=\"text-center\">{t('auto_add', 'Auto Add?')}</div>\n              {!!appendSignature && (\n                <div className=\"text-center\">{t('actions', 'Actions')}</div>\n              )}\n              <div className=\"text-center\">{t('edit', 'Edit')}</div>\n              <div className=\"text-center\">{t('delete', 'Delete')}</div>\n              {data?.map((p: any) => (\n                <Fragment key={p.id}>\n                  <div className=\"relative flex-1 me-[20px] overflow-x-hidden\">\n                    <div className=\"absolute start-0 line-clamp-1 top-[50%] -translate-y-[50%] text-ellipsis\">\n                      {p.content.slice(0, 15) + '...'}\n                    </div>\n                  </div>\n                  <div className=\"flex flex-col justify-center relative me-[20px]\">\n                    <div className=\"text-center w-full absolute start-0 line-clamp-1 top-[50%] -translate-y-[50%]\">\n                      {p.autoAdd ? 'Yes' : 'No'}\n                    </div>\n                  </div>\n                  {!!appendSignature && (\n                    <div className=\"flex justify-center\">\n                      <Button onClick={() => appendSignature(p.content)}>\n                        {t('use_signature', 'Use Signature')}\n                      </Button>\n                    </div>\n                  )}\n                  <div className=\"flex justify-center\">\n                    <div>\n                      <Button onClick={addSignature(p)}>\n                        {t('edit', 'Edit')}\n                      </Button>\n                    </div>\n                  </div>\n                  <div className=\"flex justify-center\">\n                    <div>\n                      <Button onClick={deleteSignature(p)}>\n                        {t('delete', 'Delete')}\n                      </Button>\n                    </div>\n                  </div>\n                </Fragment>\n              ))}\n            </div>\n          )}\n          <div>\n            <Button\n              onClick={addSignature()}\n              className={clsx((data?.length || 0) > 0 && 'my-[16px]')}\n            >\n              {t('add_a_signature', 'Add a signature')}\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\nconst details = object().shape({\n  content: string().required(),\n  autoAdd: boolean().required(),\n});\nconst AddOrRemoveSignature: FC<{\n  data?: any;\n  reload: () => void;\n}> = (props) => {\n  const { data, reload } = props;\n  const toast = useToaster();\n  const fetch = useFetch();\n  const form = useForm({\n    resolver: yupResolver(details),\n    values: {\n      content: data?.content || '',\n      autoAdd: data?.autoAdd || false,\n    },\n  });\n  const text = form.watch('content');\n  const autoAdd = form.watch('autoAdd');\n  const modal = useModals();\n  const callBack = useCallback(\n    async (values: any) => {\n      await fetch(data?.id ? `/signatures/${data.id}` : '/signatures', {\n        method: data?.id ? 'PUT' : 'POST',\n        body: JSON.stringify(values),\n      });\n      toast.show(\n        data?.id\n          ? 'Signature updated successfully'\n          : 'Signature added successfully',\n        'success'\n      );\n      modal.closeCurrent();\n      reload();\n    },\n    [data, modal]\n  );\n\n  const t = useT();\n\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(callBack)}>\n        <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px] pt-0\">\n          <button\n            className=\"outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa\"\n            type=\"button\"\n            onClick={() => modal.closeCurrent()}\n          >\n            <svg\n              viewBox=\"0 0 15 15\"\n              fill=\"none\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n            >\n              <path\n                d=\"M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z\"\n                fill=\"currentColor\"\n                fillRule=\"evenodd\"\n                clipRule=\"evenodd\"\n              ></path>\n            </svg>\n          </button>\n\n          <div className=\"relative bg-customColor2\">\n            <CopilotTextarea\n              disableBranding={true}\n              className={clsx(\n                '!min-h-40 !max-h-80 p-2 overflow-x-hidden scrollbar scrollbar-thumb-[#612AD5] bg-bigStrip outline-none'\n              )}\n              value={text}\n              onChange={(e) => {\n                form.setValue('content', e.target.value);\n              }}\n              placeholder=\"Write your signature...\"\n              autosuggestionsConfig={{\n                textareaPurpose: `Assist me in writing social media signature`,\n                chatApiConfigs: {},\n              }}\n            />\n          </div>\n\n          <Select\n            label=\"Auto add signature?\"\n            translationKey=\"label_auto_add_signature\"\n            {...form.register('autoAdd', {\n              setValueAs: (value) => value === 'true',\n            })}\n          >\n            <option value=\"false\" selected={!autoAdd}>\n              {t('no', 'No')}\n            </option>\n            <option value=\"true\" selected={autoAdd}>\n              {t('yes', 'Yes')}\n            </option>\n          </Select>\n\n          <Button type=\"submit\">{t('save', 'Save')}</Button>\n        </div>\n      </form>\n    </FormProvider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/settings/teams.component.tsx",
    "content": "'use client';\n\nimport { Button } from '@gitroom/react/form/button';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport React, { useCallback, useMemo } from 'react';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { capitalize } from 'lodash';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Input } from '@gitroom/react/form/input';\nimport { useForm, FormProvider, useWatch } from 'react-hook-form';\nimport { Select } from '@gitroom/react/form/select';\nimport { Checkbox } from '@gitroom/react/form/checkbox';\nimport { classValidatorResolver } from '@hookform/resolvers/class-validator';\nimport { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport copy from 'copy-to-clipboard';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nconst roles = [\n  {\n    name: 'User',\n    value: 'USER',\n  },\n  {\n    name: 'Admin',\n    value: 'ADMIN',\n  },\n];\nexport const AddMember = () => {\n  const modals = useModals();\n  const fetch = useFetch();\n  const toast = useToaster();\n  const resolver = useMemo(() => {\n    return classValidatorResolver(AddTeamMemberDto);\n  }, []);\n  const form = useForm({\n    values: {\n      email: '',\n      role: '',\n      sendEmail: true,\n    },\n    resolver,\n    mode: 'onChange',\n  });\n  const sendEmail = useWatch({\n    control: form.control,\n    name: 'sendEmail',\n  });\n  const submit = useCallback(\n    async (values: { email: string; role: string; sendEmail: boolean }) => {\n      const { url } = await (\n        await fetch('/settings/team', {\n          method: 'POST',\n          body: JSON.stringify(values),\n        })\n      ).json();\n      if (values.sendEmail) {\n        modals.closeAll();\n        toast.show(t('invitation_link_sent', 'Invitation link sent'));\n        return;\n      }\n      copy(url);\n      modals.closeAll();\n      toast.show(t('link_copied_to_clipboard', 'Link copied to clipboard'));\n    },\n    []\n  );\n\n  const t = useT();\n\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(submit)}>\n        <div className=\"relative flex gap-[10px] flex-col flex-1 p-[16px] pt-0\">\n          {sendEmail && (\n            <Input\n              label=\"Email\"\n              placeholder={t('enter_email', 'Enter email')}\n              name=\"email\"\n            />\n          )}\n          <Select label=\"Role\" name=\"role\">\n            <option value=\"\">{t('select_role', 'Select Role')}</option>\n            {roles.map((role) => (\n              <option key={role.value} value={role.value}>\n                {role.name}\n              </option>\n            ))}\n          </Select>\n          <div className=\"flex gap-[5px]\">\n            <div>\n              <Checkbox name=\"sendEmail\" />\n            </div>\n            <div>\n              {t('send_invitation_via_email', 'Send invitation via email?')}\n            </div>\n          </div>\n          <Button type=\"submit\" className=\"mt-[18px]\">\n            {sendEmail ? t('send_invitation_link', 'Send Invitation Link') : t('copy_link', 'Copy Link')}\n          </Button>\n        </div>\n      </form>\n    </FormProvider>\n  );\n};\nexport const TeamsComponent = () => {\n  const fetch = useFetch();\n  const user = useUser();\n  const modals = useModals();\n  const t = useT();\n  const myLevel = user?.role === 'USER' ? 0 : user?.role === 'ADMIN' ? 1 : 2;\n  const getLevel = useCallback(\n    (role: 'USER' | 'ADMIN' | 'SUPERADMIN') =>\n      role === 'USER' ? 0 : role === 'ADMIN' ? 1 : 2,\n    []\n  );\n  const loadTeam = useCallback(async () => {\n    return (await (await fetch('/settings/team')).json()).users as Array<{\n      id: string;\n      role: 'SUPERADMIN' | 'ADMIN' | 'USER';\n      user: {\n        email: string;\n        id: string;\n      };\n    }>;\n  }, []);\n  const addMember = useCallback(() => {\n    modals.openModal({\n      classNames: {\n        modal: 'bg-transparent text-textColor',\n      },\n      title: t('top_title_add_member', 'Add Member'),\n      withCloseButton: true,\n      children: <AddMember />,\n    });\n  }, [t]);\n  const { data, mutate } = useSWR('/api/teams', loadTeam, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n  });\n  const remove = useCallback(\n    (toRemove: {\n        user: {\n          id: string;\n        };\n      }) =>\n      async () => {\n        if (\n          !(await deleteDialog(\n            t('are_you_sure_remove_team_member', 'Are you sure you want to remove this team member?')\n          ))\n        ) {\n          return;\n        }\n        await fetch(`/settings/team/${toRemove.user.id}`, {\n          method: 'DELETE',\n        });\n        await mutate();\n      },\n    [t]\n  );\n\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">{t('team_members', 'Team Members')}</h3>\n      <div className=\"text-customColor18 mt-[4px]\">\n        {t(\n          'invite_your_assistant_or_team_member_to_manage_your_account',\n          'Invite your assistant or team member to manage your account'\n        )}\n      </div>\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]\">\n        <div className=\"flex flex-col gap-[16px]\">\n          {(data || []).map((p) => (\n            <div key={p.user.id} className=\"flex items-center\">\n              <div className=\"flex-1\">\n                {capitalize(p.user.email.split('@')[0]).split('.')[0]}\n              </div>\n              <div className=\"flex-1\">\n                {p.role === 'USER'\n                  ? t('user', 'User')\n                  : p.role === 'ADMIN'\n                  ? t('admin', 'Admin')\n                  : t('super_admin', 'Super Admin')}\n              </div>\n              {+myLevel > +getLevel(p.role) ? (\n                <div className=\"flex-1 flex justify-end\">\n                  <Button\n                    className={`!bg-customColor3 !h-[24px] border border-customColor21 rounded-[4px] text-[12px]`}\n                    onClick={remove(p)}\n                    secondary={true}\n                  >\n                    <div className=\"flex justify-center items-center gap-[4px]\">\n                      <div>\n                        <svg\n                          xmlns=\"http://www.w3.org/2000/svg\"\n                          width=\"14\"\n                          height=\"15\"\n                          viewBox=\"0 0 14 15\"\n                          fill=\"none\"\n                        >\n                          <path\n                            d=\"M11.8125 3.125H9.625V2.6875C9.625 2.3394 9.48672 2.00556 9.24058 1.75942C8.99444 1.51328 8.6606 1.375 8.3125 1.375H5.6875C5.3394 1.375 5.00556 1.51328 4.75942 1.75942C4.51328 2.00556 4.375 2.3394 4.375 2.6875V3.125H2.1875C2.07147 3.125 1.96019 3.17109 1.87814 3.25314C1.79609 3.33519 1.75 3.44647 1.75 3.5625C1.75 3.67853 1.79609 3.78981 1.87814 3.87186C1.96019 3.95391 2.07147 4 2.1875 4H2.625V11.875C2.625 12.1071 2.71719 12.3296 2.88128 12.4937C3.04538 12.6578 3.26794 12.75 3.5 12.75H10.5C10.7321 12.75 10.9546 12.6578 11.1187 12.4937C11.2828 12.3296 11.375 12.1071 11.375 11.875V4H11.8125C11.9285 4 12.0398 3.95391 12.1219 3.87186C12.2039 3.78981 12.25 3.67853 12.25 3.5625C12.25 3.44647 12.2039 3.33519 12.1219 3.25314C12.0398 3.17109 11.9285 3.125 11.8125 3.125ZM5.25 2.6875C5.25 2.57147 5.29609 2.46019 5.37814 2.37814C5.46019 2.29609 5.57147 2.25 5.6875 2.25H8.3125C8.42853 2.25 8.53981 2.29609 8.62186 2.37814C8.70391 2.46019 8.75 2.57147 8.75 2.6875V3.125H5.25V2.6875ZM10.5 11.875H3.5V4H10.5V11.875ZM6.125 6.1875V9.6875C6.125 9.80353 6.07891 9.91481 5.99686 9.99686C5.91481 10.0789 5.80353 10.125 5.6875 10.125C5.57147 10.125 5.46019 10.0789 5.37814 9.99686C5.29609 9.91481 5.25 9.80353 5.25 9.6875V6.1875C5.25 6.07147 5.29609 5.96019 5.37814 5.87814C5.46019 5.79609 5.57147 5.75 5.6875 5.75C5.80353 5.75 5.91481 5.79609 5.99686 5.87814C6.07891 5.96019 6.125 6.07147 6.125 6.1875ZM8.75 6.1875V9.6875C8.75 9.80353 8.70391 9.91481 8.62186 9.99686C8.53981 10.0789 8.42853 10.125 8.3125 10.125C8.19647 10.125 8.08519 10.0789 8.00314 9.99686C7.92109 9.91481 7.875 9.80353 7.875 9.6875V6.1875C7.875 6.07147 7.92109 5.96019 8.00314 5.87814C8.08519 5.79609 8.19647 5.75 8.3125 5.75C8.42853 5.75 8.53981 5.79609 8.62186 5.87814C8.70391 5.96019 8.75 6.07147 8.75 6.1875Z\"\n                            fill=\"currentColor\"\n                          />\n                        </svg>\n                      </div>\n                      <div>{t('remove', 'Remove')}</div>\n                    </div>\n                  </Button>\n                </div>\n              ) : (\n                <div className=\"flex-1\" />\n              )}\n            </div>\n          ))}\n        </div>\n        <div>\n          <Button onClick={addMember}>\n            {t('add_another_member', 'Add another member')}\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/signature.tsx",
    "content": "import { FC, useCallback } from 'react';\nimport { SignaturesComponent } from '@gitroom/frontend/components/settings/signatures.component';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nexport const SignatureBox: FC<{\n  editor: any;\n}> = ({ editor }) => {\n  const modals = useModals();\n  const appendValue = (val: string) => {\n    editor?.commands?.insertContent('\\n\\n' + val);\n    editor?.commands?.focus();\n  };\n\n  const addSignature = useCallback(() => {\n    modals.openModal({\n      title: 'Add Signature',\n      withCloseButton: true,\n      children: (close) => (\n        <SignatureModal appendSignature={appendValue} close={close} />\n      ),\n    });\n  }, [appendValue]);\n\n  return (\n    <>\n      <div\n        onClick={addSignature}\n        data-tooltip-id=\"tooltip\"\n        data-tooltip-content=\"Add Signature\"\n        className=\"select-none cursor-pointer rounded-[6px] w-[30px] h-[30px] bg-newColColor flex justify-center items-center\"\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          fill=\"none\"\n        >\n          <g clipPath=\"url(#clip0_2352_53073)\">\n            <path\n              d=\"M1.61528 13.4634C4.98807 11.1708 6.12853 2.72516 2.42576 2.54001C-0.271332 2.40515 1.52719 6.99029 4.04454 11.4405C4.43518 12.1311 5.08312 12.0221 5.35096 11.8873C6.23825 11.4405 6.49355 9.29898 6.95238 8.8935C7.41122 8.48802 7.98909 8.45521 8.49293 9.16322C9.12361 10.0494 9.65463 9.91856 10.0456 9.70264C10.6103 9.39078 11.3197 8.22463 12.1949 9.16322C12.7765 9.78692 12.5068 10.4612 14.9173 10.1915\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.2\"\n              strokeLinecap=\"round\"\n            />\n            <path\n              d=\"M7.16602 12.917H13.8327\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.2\"\n              strokeLinecap=\"round\"\n            />\n          </g>\n          <defs>\n            <clipPath id=\"clip0_2352_53073\">\n              <rect width=\"16\" height=\"16\" fill=\"white\" />\n            </clipPath>\n          </defs>\n        </svg>\n      </div>\n    </>\n  );\n};\nexport const SignatureModal: FC<{\n  close: () => void;\n  appendSignature: (sign: string) => void;\n}> = (props) => {\n  const { appendSignature } = props;\n  return <SignaturesComponent appendSignature={appendSignature} />;\n};\n"
  },
  {
    "path": "apps/frontend/src/components/standalone-modal/standalone.modal.tsx",
    "content": "'use client';\n\nimport 'reflect-metadata';\nimport { FC, useCallback } from 'react';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport dayjs from 'dayjs';\nimport { useParams } from 'next/navigation';\nimport { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';\nimport { newDayjs } from '@gitroom/frontend/components/layout/set.timezone';\nexport const StandaloneModal: FC = () => {\n  const fetch = useFetch();\n  const params = useParams<{ platform: string }>();\n\n  const load = useCallback(async (path: string) => {\n    return (await (await fetch(path)).json()).integrations;\n  }, []);\n\n  const loadDate = useCallback(async () => {\n    if (params.platform === 'all') {\n      return newDayjs().utc().format('YYYY-MM-DDTHH:mm:ss');\n    }\n    return (await (await fetch('/posts/find-slot')).json()).date;\n  }, []);\n\n  const {\n    isLoading,\n    data: integrations,\n    mutate,\n  } = useSWR('/integrations/list', load, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n    fallbackData: [],\n  });\n  const { isLoading: isLoading2, data } = useSWR('/posts/find-slot', loadDate, {\n    fallbackData: [],\n  });\n  if (isLoading || isLoading2) {\n    return null;\n  }\n  return (\n    <AddEditModal\n      dummy={params.platform === 'all'}\n      customClose={() => {\n        window.parent.postMessage(\n          {\n            action: 'closeIframe',\n          },\n          '*'\n        );\n      }}\n      mutate={() => {}}\n      integrations={integrations}\n      reopenModal={() => {}}\n      allIntegrations={integrations}\n      date={dayjs.utc(data).local()}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/providers/heygen.provider.tsx",
    "content": "import { thirdPartyWrapper } from '@gitroom/frontend/components/third-parties/third-party.wrapper';\nimport {\n  useThirdPartyFunction,\n  useThirdPartyFunctionSWR,\n  useThirdPartySubmit,\n} from '@gitroom/frontend/components/third-parties/third-party.function';\nimport { useThirdParty } from '@gitroom/frontend/components/third-parties/third-party.media';\nimport { useForm, FormProvider, SubmitHandler } from 'react-hook-form';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { Button } from '@gitroom/react/form/button';\nimport { FC, useCallback, useState } from 'react';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport clsx from 'clsx';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { object, string } from 'zod';\nimport { Select } from '@gitroom/react/form/select';\nimport { LoadingComponent } from '@gitroom/frontend/components/layout/loading';\n\nconst aspectRatio = [\n  { key: 'portrait', value: 'Portrait' },\n  { key: 'story', value: 'Story' },\n];\n\nconst generateCaptions = [\n  { key: 'yes', value: 'Yes' },\n  { key: 'no', value: 'No' },\n];\n\nconst SelectAvatarComponent: FC<{\n  avatarList: any[];\n  onChange: (id: string) => void;\n}> = (props) => {\n  const [current, setCurrent] = useState<any>({});\n  const { avatarList, onChange } = props;\n\n  return (\n    <div className=\"grid grid-cols-4 gap-[10px] justify-items-center justify-center\">\n      {avatarList?.map((p) => (\n        <div\n          onClick={() => {\n            setCurrent(p.avatar_id === current?.avatar_id ? undefined : p);\n            onChange(p.avatar_id === current?.avatar_id ? {} : p.avatar_id);\n          }}\n          key={p.avatar_id}\n          className={clsx(\n            'w-full h-full p-[20px] min-h-[100px] text-[14px] hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer',\n            current?.avatar_id === p.avatar_id\n              ? 'bg-input border border-red-500'\n              : 'bg-third'\n          )}\n        >\n          <div>\n            <img\n              src={p.preview_image_url}\n              className=\"w-full h-full object-cover\"\n            />\n          </div>\n          <div>{p.avatar_name}</div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst SelectVoiceComponent: FC<{\n  voiceList: any[];\n  onChange: (id: string) => void;\n}> = (props) => {\n  const [current, setCurrent] = useState<any>({});\n  const { voiceList, onChange } = props;\n\n  return (\n    <div className=\"grid grid-cols-6 gap-[10px] justify-items-center justify-center\">\n      {voiceList?.map((p) => (\n        <div\n          onClick={() => {\n            setCurrent(p.voice_id === current?.voice_id ? undefined : p);\n            onChange(p.voice_id === current?.voice_id ? {} : p.voice_id);\n          }}\n          key={p.avatar_id}\n          className={clsx(\n            'w-full h-full p-[20px] min-h-[100px] text-[14px] hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer',\n            current?.voice_id === p.voice_id\n              ? 'bg-input border border-red-500'\n              : 'bg-third'\n          )}\n        >\n          <div className=\"text-[14px] text-balance whitespace-pre-line\">\n            {p.name}\n          </div>\n          <div className=\"text-[12px]\">{p.language}</div>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nconst HeygenProviderComponent = () => {\n  const thirdParty = useThirdParty();\n  const load = useThirdPartyFunction('EVERYTIME');\n  const { data } = useThirdPartyFunctionSWR('LOAD_ONCE', 'avatars');\n  const { data: voices } = useThirdPartyFunctionSWR('LOAD_ONCE', 'voices');\n  const send = useThirdPartySubmit();\n  const [hideVoiceGenerator, setHideVoiceGenerator] = useState(false);\n  const [voiceLoading, setVoiceLoading] = useState(false);\n\n  const form = useForm({\n    values: {\n      voice: '',\n      avatar: '',\n      aspect_ratio: '',\n      captions: '',\n      selectedVoice: '',\n      type: '',\n    },\n    mode: 'all',\n    resolver: zodResolver(\n      object({\n        voice: string().min(20, 'Voice must be at least 20 characters long'),\n        avatar: string().min(1, 'Avatar is required'),\n        selectedVoice: string().min(1, 'Voice is required'),\n        aspect_ratio: string().min(1, 'Aspect ratio is required'),\n        captions: string().min(1, 'Captions is required'),\n      })\n    ),\n  });\n\n  const generateVoice = useCallback(async () => {\n    if (\n      !(await deleteDialog('Are you sure? it will delete the current text'))\n    ) {\n      return;\n    }\n\n    setVoiceLoading(true);\n\n    form.setValue(\n      'voice',\n      (\n        await load('generateVoice', {\n          text: thirdParty.data.map((p) => p.content).join('\\n'),\n        })\n      ).voice\n    );\n\n    setVoiceLoading(false);\n    setHideVoiceGenerator(true);\n  }, [thirdParty]);\n\n  const submit: SubmitHandler<{ voice: string; avatar: string }> = useCallback(\n    async (params) => {\n      thirdParty.onChange(await send(params));\n      thirdParty.close();\n    },\n    []\n  );\n\n  return (\n    <div>\n      {form.formState.isSubmitting && (\n        <div className=\"fixed left-0 top-0 w-full leading-[50px] pt-[200px] h-screen bg-black/90 z-50 flex flex-col justify-center items-center text-center text-3xl\">\n          Grab a coffee and relax, this may take a while...\n          <br />\n          You can also track the progress directly in HeyGen Dashboard.\n          <br />\n          DO NOT CLOSE THIS WINDOW!\n          <br />\n          <LoadingComponent width={200} height={200} />\n        </div>\n      )}\n\n      <FormProvider {...form}>\n        <form\n          onSubmit={form.handleSubmit(submit)}\n          className=\"w-full flex flex-col\"\n        >\n          <Select label=\"Aspect Ratio\" {...form.register('aspect_ratio')}>\n            <option value=\"\">--SELECT--</option>\n            {aspectRatio.map((p) => (\n              <option key={p.key} value={p.key}>\n                {p.value}\n              </option>\n            ))}\n          </Select>\n\n          <Select label=\"Generate Captions\" {...form.register('captions')}>\n            <option value=\"\">--SELECT--</option>\n            {generateCaptions.map((p) => (\n              <option key={p.key} value={p.key}>\n                {p.value}\n              </option>\n            ))}\n          </Select>\n\n          <div className=\"text-lg mb-3\">Voice to generate</div>\n          {!hideVoiceGenerator && (\n            <Button onClick={generateVoice} loading={voiceLoading}>\n              Generate Voice From My Post Text\n            </Button>\n          )}\n          <Textarea label=\"\" {...form.register('voice')} />\n          {!!data?.length && (\n            <>\n              <div className=\"text-lg my-3\">Select Avatar</div>\n              <SelectAvatarComponent\n                avatarList={data.map((p: any) => ({\n                  avatar_id: p.avatar_id || p.id,\n                  avatar_name: p.avatar_name || p.name,\n                  preview_image_url: p.preview_image_url || p.image_url,\n                }))}\n                onChange={(id: string) => {\n                  form.setValue('avatar', id);\n                  form.setValue(\n                    'type',\n                    data?.find((p: any) => p.id === id || p.avatar_id === id)?.id\n                      ? 'talking_photo'\n                      : 'avatar'\n                  );\n                }}\n              />\n              <div className=\"text-red-400 text-[12px] mb-3\">\n                {form?.formState?.errors?.avatar?.message || ''}\n              </div>\n            </>\n          )}\n\n          {!!voices?.length && (\n            <>\n              <div className=\"text-lg my-3\">Select Voice</div>\n              <SelectVoiceComponent\n                voiceList={voices}\n                onChange={(id: string) => form.setValue('selectedVoice', id)}\n              />\n              <div className=\"text-red-400 text-[12px] mb-3\">\n                {form?.formState?.errors?.selectedVoice?.message || ''}\n              </div>\n            </>\n          )}\n\n          <Button type=\"submit\">Generate Video</Button>\n        </form>\n      </FormProvider>\n    </div>\n  );\n};\n\nexport default thirdPartyWrapper('heygen', HeygenProviderComponent);\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/slider.component.tsx",
    "content": "import { FC, ReactNode, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n} from '@gitroom/frontend/components/ui/icons';\n\nexport const SliderComponent: FC<{\n  className: string;\n  list: ReactNode[];\n}> = ({ className, list }) => {\n  const [show, setShow] = useState(0);\n\n  const goToPrevious = useCallback(() => {\n    setShow((prev) => (prev > 0 ? prev - 1 : prev));\n  }, []);\n\n  const goToNext = useCallback(() => {\n    setShow((prev) => (prev < list.length - 1 ? prev + 1 : prev));\n  }, [list.length]);\n\n  const canGoPrevious = show > 0;\n  const canGoNext = show < list.length - 1;\n\n  return (\n    <div className={clsx(className, 'relative')}>\n      {list[show]}\n\n      {/* Left Arrow */}\n      {canGoPrevious && (\n        <button\n          onClick={goToPrevious}\n          className=\"absolute top-[50%] start-[10px] -translate-y-[50%] flex items-center justify-center w-8 h-8 rounded-full bg-black/60 hover:bg-black/80 text-white transition-colors backdrop-blur-sm cursor-pointer\"\n          aria-label=\"Previous slide\"\n        >\n          <ChevronLeftIcon size={18} />\n        </button>\n      )}\n\n      {/* Right Arrow */}\n      {canGoNext && (\n        <button\n          onClick={goToNext}\n          className=\"absolute top-[50%] end-[10px] -translate-y-[50%] flex items-center justify-center w-8 h-8 rounded-full bg-black/60 hover:bg-black/80 text-white transition-colors backdrop-blur-sm cursor-pointer\"\n          aria-label=\"Next slide\"\n        >\n          <ChevronRightIcon size={18} />\n        </button>\n      )}\n\n      {/* Pagination Dots */}\n      {list.length > 1 && (\n        <div className=\"absolute bottom-[10px] left-[50%] -translate-x-[50%] flex gap-2\">\n          {list.map((_, index) => (\n            <button\n              key={index}\n              onClick={() => setShow(index)}\n              className={clsx(\n                'w-2 h-2 rounded-full transition-colors cursor-pointer',\n                index === show\n                  ? 'bg-white'\n                  : 'bg-transparent border border-white'\n              )}\n              aria-label={`Go to slide ${index + 1}`}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/third-party.component.tsx",
    "content": "'use client';\n\nimport clsx from 'clsx';\nimport ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { ThirdPartyListComponent } from '@gitroom/frontend/components/third-parties/third-party.list.component';\nimport React, { FC, useCallback, useState } from 'react';\nimport useSWR from 'swr';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport useCookie from 'react-use-cookie';\nimport { SVGLine } from '@gitroom/frontend/components/launches/launches.component';\n\nexport const ThirdPartyMenuComponent: FC<{\n  reload: () => void;\n  tParty: { id: string };\n}> = (props) => {\n  const { tParty, reload } = props;\n  const fetch = useFetch();\n  const [show, setShow] = useState(false);\n  const t = useT();\n  const toaster = useToaster();\n\n  const changeShow = () => {\n    setShow((prev) => !prev);\n  };\n\n  const deleteChannel = (id: string) => async () => {\n    setShow(false);\n    if (\n      !(await deleteDialog('Are you sure you want to delete this integration?'))\n    ) {\n      return;\n    }\n\n    const res = await fetch(`/third-party/${id}`, {\n      method: 'DELETE',\n    });\n\n    if (res.ok) {\n      toaster.show('Integration deleted successfully', 'success');\n      reload();\n    } else {\n      const error = await res.json();\n      console.error('Error deleting integration:', error);\n    }\n  };\n\n  return (\n    <div className=\"cursor-pointer relative select-none\" onClick={changeShow}>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n      >\n        <path\n          d=\"M13.125 12C13.125 12.2225 13.059 12.44 12.9354 12.625C12.8118 12.81 12.6361 12.9542 12.4305 13.0394C12.225 13.1245 11.9988 13.1468 11.7805 13.1034C11.5623 13.06 11.3618 12.9528 11.2045 12.7955C11.0472 12.6382 10.94 12.4377 10.8966 12.2195C10.8532 12.0012 10.8755 11.775 10.9606 11.5695C11.0458 11.3639 11.19 11.1882 11.375 11.0646C11.56 10.941 11.7775 10.875 12 10.875C12.2984 10.875 12.5845 10.9935 12.7955 11.2045C13.0065 11.4155 13.125 11.7016 13.125 12ZM12 6.75C12.2225 6.75 12.44 6.68402 12.625 6.5604C12.81 6.43679 12.9542 6.26109 13.0394 6.05552C13.1245 5.84995 13.1468 5.62375 13.1034 5.40552C13.06 5.1873 12.9528 4.98684 12.7955 4.82951C12.6382 4.67217 12.4377 4.56503 12.2195 4.52162C12.0012 4.47821 11.775 4.50049 11.5695 4.58564C11.3639 4.67078 11.1882 4.81498 11.0646 4.99998C10.941 5.18499 10.875 5.4025 10.875 5.625C10.875 5.92337 10.9935 6.20952 11.2045 6.4205C11.4155 6.63147 11.7016 6.75 12 6.75ZM12 17.25C11.7775 17.25 11.56 17.316 11.375 17.4396C11.19 17.5632 11.0458 17.7389 10.9606 17.9445C10.8755 18.15 10.8532 18.3762 10.8966 18.5945C10.94 18.8127 11.0472 19.0132 11.2045 19.1705C11.3618 19.3278 11.5623 19.435 11.7805 19.4784C11.9988 19.5218 12.225 19.4995 12.4305 19.4144C12.6361 19.3292 12.8118 19.185 12.9354 19C13.059 18.815 13.125 18.5975 13.125 18.375C13.125 18.0766 13.0065 17.7905 12.7955 17.5795C12.5845 17.3685 12.2984 17.25 12 17.25Z\"\n          fill=\"#506490\"\n        />\n      </svg>\n      {show && (\n        <div\n          onClick={(e) => e.stopPropagation()}\n          className={`absolute top-[100%] start-0 p-[8px] px-[20px] bg-fifth flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder text-nowrap`}\n        >\n          <div\n            className=\"flex gap-[12px] items-center\"\n            onClick={deleteChannel(tParty.id)}\n          >\n            <div>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 16 16\"\n                fill=\"currentColor\"\n              >\n                <path\n                  d=\"M13.5 3H11V2.5C11 2.10218 10.842 1.72064 10.5607 1.43934C10.2794 1.15804 9.89782 1 9.5 1H6.5C6.10218 1 5.72064 1.15804 5.43934 1.43934C5.15804 1.72064 5 2.10218 5 2.5V3H2.5C2.36739 3 2.24021 3.05268 2.14645 3.14645C2.05268 3.24021 2 3.36739 2 3.5C2 3.63261 2.05268 3.75979 2.14645 3.85355C2.24021 3.94732 2.36739 4 2.5 4H3V13C3 13.2652 3.10536 13.5196 3.29289 13.7071C3.48043 13.8946 3.73478 14 4 14H12C12.2652 14 12.5196 13.8946 12.7071 13.7071C12.8946 13.5196 13 13.2652 13 13V4H13.5C13.6326 4 13.7598 3.94732 13.8536 3.85355C13.9473 3.75979 14 3.63261 14 3.5C14 3.36739 13.9473 3.24021 13.8536 3.14645C13.7598 3.05268 13.6326 3 13.5 3ZM6 2.5C6 2.36739 6.05268 2.24021 6.14645 2.14645C6.24021 2.05268 6.36739 2 6.5 2H9.5C9.63261 2 9.75979 2.05268 9.85355 2.14645C9.94732 2.24021 10 2.36739 10 2.5V3H6V2.5ZM12 13H4V4H12V13ZM7 6.5V10.5C7 10.6326 6.94732 10.7598 6.85355 10.8536C6.75979 10.9473 6.63261 11 6.5 11C6.36739 11 6.24021 10.9473 6.14645 10.8536C6.05268 10.7598 6 10.6326 6 10.5V6.5C6 6.36739 6.05268 6.24021 6.14645 6.14645C6.24021 6.05268 6.36739 6 6.5 6C6.63261 6 6.75979 6.05268 6.85355 6.14645C6.94732 6.24021 7 6.36739 7 6.5ZM10 6.5V10.5C10 10.6326 9.94732 10.7598 9.85355 10.8536C9.75979 10.9473 9.63261 11 9.5 11C9.36739 11 9.24021 10.9473 9.14645 10.8536C9.05268 10.7598 9 10.6326 9 10.5V6.5C9 6.36739 9.05268 6.24021 9.14645 6.14645C9.24021 6.05268 9.36739 6 9.5 6C9.63261 6 9.75979 6.05268 9.85355 6.14645C9.94732 6.24021 10 6.36739 10 6.5Z\"\n                  fill=\"#F97066\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[12px]\">\n              {t('delete_integration', 'Delete Integration')}\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport const ThirdPartyComponent = () => {\n  const t = useT();\n  const fetch = useFetch();\n\n  const integrations = useCallback(async () => {\n    return (await fetch('/third-party')).json();\n  }, []);\n\n  const { data, isLoading, mutate } = useSWR('third-party', integrations, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n  const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0');\n\n  return (\n    <>\n      <div\n        className={clsx(\n          'bg-newBgColorInner p-[20px] flex flex-col gap-[15px] transition-all',\n          collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]'\n        )}\n      >\n        <div className=\"flex gap-[12px] flex-col\">\n          <div className=\"flex items-center\">\n            <h2 className=\"group-[.sidebar]:hidden flex-1 text-[20px] font-[500]\">\n              {t('integrations')}\n            </h2>\n            <div\n              onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')}\n              className=\"group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"7\"\n                height=\"13\"\n                viewBox=\"0 0 7 13\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M6 11.5L1 6.5L6 1.5\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            </div>\n          </div>\n          <div className=\"flex flex-col gap-[10px]\">\n            <div className=\"flex-1 flex flex-col gap-[14px]\">\n              <div\n                className={clsx(\n                  'gap-[16px] flex flex-col relative justify-center group/profile hover:bg-boxHover rounded-e-[8px]'\n                )}\n              >\n                {!isLoading && !data?.length ? (\n                  <div>No Integrations Yet</div>\n                ) : (\n                  data?.map((p: any) => (\n                    <div\n                      key={p.id}\n                      className={clsx('flex gap-[8px] items-center')}\n                    >\n                      <div className=\"h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity\">\n                        <SVGLine />\n                      </div>\n                      <div\n                        className={clsx(\n                          'relative rounded-full flex justify-center items-center bg-fifth'\n                        )}\n                        data-tooltip-id=\"tooltip\"\n                        data-tooltip-content={p.title}\n                      >\n                        <ImageWithFallback\n                          fallbackSrc={`/icons/third-party/${p.identifier}.png`}\n                          src={`/icons/third-party/${p.identifier}.png`}\n                          className=\"rounded-full\"\n                          alt={p.title}\n                          width={32}\n                          height={32}\n                        />\n                      </div>\n                      <div\n                        // @ts-ignore\n                        role=\"Handle\"\n                        className={clsx(\n                          'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden'\n                        )}\n                        data-tooltip-id=\"tooltip\"\n                        data-tooltip-content={p.title}\n                      >\n                        {p.name}\n                      </div>\n                      <ThirdPartyMenuComponent reload={mutate} tParty={p} />\n                    </div>\n                  ))\n                )}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div className=\"bg-newBgColorInner flex-1 flex-col flex p-[20px] gap-[12px]\">\n        <ThirdPartyListComponent reload={mutate} />\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/third-party.function.tsx",
    "content": "import { useThirdParty } from '@gitroom/frontend/components/third-parties/third-party.media';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport useSWR from 'swr';\n\nexport const useThirdPartySubmit = () => {\n  const thirdParty = useThirdParty();\n  const fetch = useFetch();\n\n  return useCallback(async (data?: any) => {\n    if (!thirdParty.id) {\n      return;\n    }\n\n    const response = await fetch(`/third-party/${thirdParty.id}/submit`, {\n      body: JSON.stringify(data),\n      method: 'POST',\n    });\n\n    return response.json();\n  }, []);\n};\n\nexport const useThirdPartyFunction = (type: 'EVERYTIME' | 'ONCE') => {\n  const thirdParty = useThirdParty();\n  const data = useRef<any>(undefined);\n  const fetch = useFetch();\n\n  return useCallback(\n    async (functionName: string, sendData?: any) => {\n      if (data.current && type === 'ONCE') {\n        return data.current;\n      }\n\n      data.current = await (\n        await fetch(`/third-party/function/${thirdParty.id}/${functionName}`, {\n          ...(data ? { body: JSON.stringify(sendData) } : {}),\n          method: 'POST',\n        })\n      ).json();\n\n      return data.current;\n    },\n    [thirdParty, data]\n  );\n};\n\nexport const useThirdPartyFunctionSWR = (\n  type: 'SWR' | 'LOAD_ONCE',\n  functionName: string,\n  data?: any\n) => {\n  const thirdParty = useThirdParty();\n  const fetch = useFetch();\n\n  const callBack = useCallback(\n    async (functionName: string, data?: any) => {\n      return (\n        await fetch(`/third-party/function/${thirdParty.id}/${functionName}`, {\n          ...(data ? { body: JSON.stringify(data) } : {}),\n          method: 'POST',\n        })\n      ).json();\n    },\n    [thirdParty]\n  );\n\n  return useSWR<any>(\n    `function-${thirdParty.id}-${functionName}`,\n    () => {\n      // @ts-ignore\n      return callBack(functionName, { ...data });\n    },\n    {\n      ...(type === 'LOAD_ONCE'\n        ? {\n            revalidateOnMount: true,\n            revalidateOnFocus: false,\n            revalidateOnReconnect: false,\n            refreshInterval: 0,\n            refreshWhenHidden: false,\n            refreshWhenOffline: false,\n            revalidateIfStale: false,\n          }\n        : {}),\n    }\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/third-party.list.component.tsx",
    "content": "'use client';\n\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport React, { FC, useCallback, useState } from 'react';\nimport { Button } from '@gitroom/react/form/button';\nimport { useRouter } from 'next/navigation';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { FieldValues, FormProvider, useForm } from 'react-hook-form';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { Input } from '@gitroom/react/form/input';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport { ModalWrapperComponent } from '@gitroom/frontend/components/new-launch/modal.wrapper.component';\n\nexport const ApiModal: FC<{\n  identifier: string;\n  title: string;\n  update: () => void;\n}> = (props) => {\n  const { title, identifier, update } = props;\n  const fetch = useFetch();\n  const router = useRouter();\n  const modal = useModals();\n  const toaster = useToaster();\n  const [loading, setLoading] = useState(false);\n  const closePopup = useCallback(() => {\n    modal.closeAll();\n  }, []);\n\n  const methods = useForm({\n    mode: 'onChange',\n  });\n\n  const close = useCallback(() => {\n    if (closePopup) {\n      return closePopup();\n    }\n    modal.closeAll();\n  }, []);\n\n  const submit = useCallback(\n    async (data: FieldValues) => {\n      setLoading(true);\n      const add = await fetch(`/third-party/${identifier}`, {\n        method: 'POST',\n        body: JSON.stringify({\n          api: data.api,\n        }),\n      });\n\n      if (add.ok) {\n        toaster.show('Integration added successfully', 'success');\n        if (closePopup) {\n          closePopup();\n        } else {\n          modal.closeAll();\n        }\n        router.refresh();\n        if (update) update();\n        return;\n      }\n\n      const { message } = await add.json();\n\n      methods.setError('api', {\n        message,\n      });\n\n      setLoading(false);\n    },\n    [props]\n  );\n\n  const t = useT();\n\n  return (\n    <div className=\"relative\">\n      <FormProvider {...methods}>\n        <form\n          className=\"gap-[8px] flex flex-col\"\n          onSubmit={methods.handleSubmit(submit)}\n        >\n          <div className=\"pt-[10px]\">\n            <Input label=\"API Key\" name=\"api\" />\n          </div>\n          <div>\n            <Button loading={loading} type=\"submit\">\n              {t('add_integration', 'Add Integration')}\n            </Button>\n          </div>\n        </form>\n      </FormProvider>\n    </div>\n  );\n};\n\nexport const ThirdPartyListComponent: FC<{ reload: () => void }> = (props) => {\n  const fetch = useFetch();\n  const modals = useModals();\n  const { reload } = props;\n\n  const integrationsList = useCallback(async () => {\n    return (await fetch('/third-party/list')).json();\n  }, []);\n\n  const { data } = useSWR('third-party-list', integrationsList, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n\n  const addApiKey = useCallback(\n    (title: string, identifier: string) => () => {\n      modals.openModal({\n        title: `Add API key for ${title}`,\n        withCloseButton: false,\n        children: (\n          <ApiModal identifier={identifier} title={title} update={reload} />\n        ),\n      });\n    },\n    []\n  );\n\n  return (\n    <div className=\"grid grid-cols-4 gap-[10px] justify-items-center justify-center\">\n      {data?.map((p: any) => (\n        <div\n          onClick={addApiKey(p.title, p.identifier)}\n          key={p.identifier}\n          className=\"w-full h-full p-[20px] min-h-[100px] text-[14px] bg-newTableHeader hover:bg-newTableBorder rounded-[8px] transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer\"\n        >\n          <div>\n            <img\n              className=\"w-[32px] h-[32px]\"\n              src={`/icons/third-party/${p.identifier}.png`}\n            />\n          </div>\n          <div className=\"whitespace-pre-wrap text-left text-lg\">{p.title}</div>\n          <div className=\"whitespace-pre-wrap text-left\">{p.description}</div>\n          <div className=\"w-full flex\">\n            <Button className=\"w-full\">Add</Button>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/third-party.media.tsx",
    "content": "'use client';\n\nimport { Button } from '@gitroom/react/form/button';\nimport clsx from 'clsx';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport React, {\n  createContext,\n  FC,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';\nimport './providers/heygen.provider';\nimport { thirdPartyList } from '@gitroom/frontend/components/third-parties/third-party.wrapper';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\n\nconst ThirdPartyContext = createContext({\n  id: '',\n  name: '',\n  title: '',\n  identifier: '',\n  description: '',\n  close: () => {},\n  onChange: (data: any) => {},\n  fields: [],\n  data: [\n    {\n      content: '',\n      id: '',\n      image: [\n        {\n          id: '',\n          path: '',\n        },\n      ],\n    },\n  ],\n});\nexport const useThirdParty = () => React.useContext(ThirdPartyContext);\nconst EmptyComponent: FC = () => null;\n\nexport const ThirdPartyPopup: FC<{\n  closeModal: () => void;\n  thirdParties: any[];\n  onChange: (data: any) => void;\n  allData: {\n    content: string;\n    id?: string;\n    image?: Array<{\n      id: string;\n      path: string;\n    }>;\n  }[];\n}> = (props) => {\n  const { closeModal, thirdParties, allData, onChange } = props;\n  const [thirdParty, setThirdParty] = useState<any>(null);\n  const refNew = useRef(null);\n\n  const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton);\n  useEffect(() => {\n    setActivateExitButton(false);\n    return () => {\n      setActivateExitButton(true);\n    };\n  }, []);\n\n  const Component = useMemo(() => {\n    if (!thirdParty) {\n      return EmptyComponent;\n    }\n\n    return (\n      thirdPartyList.find((p) => p.identifier === thirdParty.identifier)\n        ?.Component || EmptyComponent\n    );\n  }, [thirdParty]);\n\n  const close = useCallback(() => {\n    setThirdParty(null);\n    closeModal();\n  }, [setThirdParty, closeModal]);\n\n  useEffect(() => {\n    refNew?.current?.scrollIntoView({\n      behavior: 'smooth',\n    });\n  }, []);\n\n  return (\n    <div className={clsx('flex flex-wrap flex-col gap-[10px] pt-[20px]')}>\n      {!thirdParty && (\n        <div className=\"grid grid-cols-4 gap-[10px] justify-items-center justify-center\">\n          {thirdParties.map((p: any) => (\n            <div\n              onClick={() => {\n                setThirdParty(p);\n              }}\n              key={p.identifier}\n              className=\"w-full h-full p-[20px] min-h-[100px] text-[14px] bg-third hover:bg-input transition-all text-textColor relative flex flex-col gap-[15px] cursor-pointer\"\n            >\n              <div>\n                <img\n                  className=\"w-[32px] h-[32px]\"\n                  src={`/icons/third-party/${p.identifier}.png`}\n                />\n              </div>\n              <div className=\"whitespace-pre-wrap text-left text-lg\">\n                {p.title}: {p.name}\n              </div>\n              <div className=\"whitespace-pre-wrap text-left\">\n                {p.description}\n              </div>\n              <div className=\"w-full flex\">\n                <Button className=\"w-full\">Use</Button>\n              </div>\n            </div>\n          ))}\n        </div>\n      )}\n      {thirdParty && (\n        <>\n          <div>\n            <div\n              className=\"cursor-pointer float-left\"\n              onClick={() => setThirdParty(null)}\n            >\n              {'<'} Back\n            </div>\n          </div>\n          <ThirdPartyContext.Provider\n            value={{ ...thirdParty, data: allData, close, onChange }}\n          >\n            <Component />\n          </ThirdPartyContext.Provider>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport const ThirdPartyMedia: FC<{\n  onChange: (data: any) => void;\n  allData: {\n    content: string;\n    id?: string;\n    image?: Array<{\n      id: string;\n      path: string;\n    }>;\n  }[];\n}> = (props) => {\n  const { allData, onChange } = props;\n  const t = useT();\n  const fetch = useFetch();\n  const modals = useModals();\n\n  const thirdParties = useCallback(async () => {\n    return (await (await fetch('/third-party')).json()).filter(\n      (f: any) => f.position === 'media'\n    );\n  }, []);\n\n  const { data, isLoading, mutate } = useSWR('third-party', thirdParties, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n\n  if (isLoading || !data.length) {\n    return null;\n  }\n\n  return (\n    <>\n      <div className=\"relative group\">\n        <div\n          className={clsx(\n            'cursor-pointer h-[30px] rounded-[6px] justify-center items-center flex bg-newColColor px-[8px]'\n          )}\n          onClick={() => {\n            modals.openModal({\n              title: t('integrations', 'Integrations'),\n              size: '80%',\n              children: (close) => (\n                <ThirdPartyPopup\n                  thirdParties={data}\n                  closeModal={close}\n                  allData={allData}\n                  onChange={onChange}\n                />\n              ),\n            });\n          }}\n        >\n          <div className={clsx('flex gap-[5px] items-center')}>\n            <div>\n              <svg\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 32 32\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M29.7081 8.29257C29.6152 8.19959 29.5049 8.12583 29.3835 8.07551C29.2621 8.02518 29.132 7.99928 29.0006 7.99928C28.8691 7.99928 28.739 8.02518 28.6176 8.07551C28.4962 8.12583 28.3859 8.19959 28.2931 8.29257L24.0006 12.5863L19.4143 8.00007L23.7081 3.70757C23.8957 3.51993 24.0011 3.26543 24.0011 3.00007C24.0011 2.7347 23.8957 2.48021 23.7081 2.29257C23.5204 2.10493 23.2659 1.99951 23.0006 1.99951C22.7352 1.99951 22.4807 2.10493 22.2931 2.29257L18.0006 6.58632L14.7081 3.29257C14.5204 3.10493 14.2659 2.99951 14.0006 2.99951C13.7352 2.99951 13.4807 3.10493 13.2931 3.29257C13.1054 3.48021 13 3.7347 13 4.00007C13 4.26543 13.1054 4.51993 13.2931 4.70757L14.0868 5.50007L7.46181 12.1251C6.99749 12.5894 6.62917 13.1406 6.37788 13.7472C6.12659 14.3539 5.99725 15.0041 5.99725 15.6607C5.99725 16.3173 6.12659 16.9675 6.37788 17.5742C6.62917 18.1808 6.99749 18.732 7.46181 19.1963L9.42556 21.1601L3.29306 27.2926C3.20015 27.3855 3.12645 27.4958 3.07616 27.6172C3.02588 27.7386 3 27.8687 3 28.0001C3 28.1315 3.02588 28.2616 3.07616 28.383C3.12645 28.5044 3.20015 28.6147 3.29306 28.7076C3.4807 28.8952 3.73519 29.0006 4.00056 29.0006C4.13195 29.0006 4.26206 28.9747 4.38345 28.9245C4.50485 28.8742 4.61515 28.8005 4.70806 28.7076L10.8443 22.5713L12.8081 24.5351C13.2724 24.9994 13.8236 25.3677 14.4302 25.619C15.0368 25.8703 15.687 25.9996 16.3437 25.9996C17.0003 25.9996 17.6505 25.8703 18.2572 25.619C18.8638 25.3677 19.415 24.9994 19.8793 24.5351L26.5043 17.9101L27.2968 18.7038C27.3897 18.7967 27.5 18.8704 27.6214 18.9207C27.7428 18.971 27.8729 18.9969 28.0043 18.9969C28.1357 18.9969 28.2658 18.971 28.3872 18.9207C28.5086 18.8704 28.6189 18.7967 28.7118 18.7038C28.8047 18.6109 28.8784 18.5006 28.9287 18.3792C28.979 18.2578 29.0049 18.1277 29.0049 17.9963C29.0049 17.8649 28.979 17.7348 28.9287 17.6134C28.8784 17.492 28.8047 17.3817 28.7118 17.2888L25.4143 14.0001L29.7081 9.70757C29.801 9.6147 29.8748 9.50441 29.9251 9.38301C29.9754 9.26161 30.0013 9.13148 30.0013 9.00007C30.0013 8.86865 29.9754 8.73853 29.9251 8.61713C29.8748 8.49573 29.801 8.38544 29.7081 8.29257ZM18.4656 23.1251C18.187 23.4038 17.8562 23.6249 17.4921 23.7758C17.128 23.9267 16.7378 24.0043 16.3437 24.0043C15.9496 24.0043 15.5593 23.9267 15.1953 23.7758C14.8312 23.6249 14.5004 23.4038 14.2218 23.1251L8.87556 17.7788C8.59681 17.5002 8.3757 17.1694 8.22483 16.8054C8.07397 16.4413 7.99632 16.051 7.99632 15.6569C7.99632 15.2628 8.07397 14.8726 8.22483 14.5085C8.3757 14.1445 8.59681 13.8137 8.87556 13.5351L15.5006 6.91007L25.0868 16.5001L18.4656 23.1251Z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            </div>\n            <div className=\"text-[10px] font-[600] iconBreak:hidden block\">\n              {t('integrations', 'Integrations')}\n            </div>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/third-parties/third-party.wrapper.tsx",
    "content": "import { FC } from 'react';\n\nexport const thirdPartyList: {identifier: string, Component: FC}[] = [];\n\nexport const thirdPartyWrapper = (identifier: string, Component: any): null => {\n  if (thirdPartyList.map(p => p.identifier).includes(identifier)) {\n    return null;\n  }\n\n  thirdPartyList.push({\n    identifier,\n    Component\n  });\n\n  return null;\n}"
  },
  {
    "path": "apps/frontend/src/components/ui/check.icon.component.tsx",
    "content": "export const CheckIconComponent = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"22\"\n      height=\"23\"\n      viewBox=\"0 0 22 23\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M13.2742 1.76687C5.72114 -3.89752 -3.90021 12.5553 2.93125 18.9592C4.68315 20.6017 6.79417 20.7975 9.0813 21.1959C10.475 21.4379 12.6902 21.8312 14.113 21.4755C25.5332 18.6198 20.3442 1.20854 10.339 1.20854\"\n        stroke=\"#00FF00\"\n        strokeWidth=\"1.2\"\n        strokeMiterlimit=\"10\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      <path\n        d=\"M6.00586 11.2722C7.2516 12.5171 8.10319 14.104 9.22067 15.4652C9.75676 16.1175 10.0671 15.3361 10.1988 15.0462C11.4308 12.3359 14.0548 11.1902 16.0692 9.17578\"\n        stroke=\"#00FF00\"\n        strokeWidth=\"1.2\"\n        strokeMiterlimit=\"10\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/ui/icons/index.tsx",
    "content": "import React, { FC, SVGProps, useEffect } from 'react';\nimport clsx from 'clsx';\nimport useCookie from 'react-use-cookie';\nimport { modeEmitter } from '@gitroom/frontend/components/layout/mode.component';\n\nexport type IconProps = SVGProps<SVGSVGElement> & {\n  size?: number;\n};\n\n// Settings/Gear Icon\nexport const SettingsIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M7.82888 16.1419L8.31591 17.2373C8.4607 17.5634 8.69698 17.8404 8.9961 18.0348C9.29522 18.2293 9.64434 18.3327 10.0011 18.3327C10.3579 18.3327 10.707 18.2293 11.0061 18.0348C11.3052 17.8404 11.5415 17.5634 11.6863 17.2373L12.1733 16.1419C12.3467 15.7533 12.6383 15.4292 13.0067 15.216C13.3773 15.0022 13.8061 14.9111 14.2317 14.9558L15.4233 15.0827C15.778 15.1202 16.136 15.054 16.4539 14.8921C16.7717 14.7302 17.0358 14.4796 17.2141 14.1706C17.3925 13.8619 17.4776 13.5079 17.4588 13.1518C17.4401 12.7956 17.3184 12.4525 17.1085 12.1642L16.403 11.1947C16.1517 10.847 16.0175 10.4284 16.0196 9.99935C16.0195 9.57151 16.155 9.15464 16.4067 8.80861L17.1122 7.83916C17.3221 7.55081 17.4438 7.20774 17.4625 6.85158C17.4813 6.49541 17.3962 6.14147 17.2178 5.83268C17.0395 5.52371 16.7754 5.27309 16.4576 5.1112C16.1397 4.94932 15.7817 4.88312 15.427 4.92065L14.2354 5.0475C13.8098 5.09219 13.381 5.00112 13.0104 4.78731C12.6413 4.57289 12.3496 4.24715 12.177 3.85676L11.6863 2.76139C11.5415 2.43532 11.3052 2.15828 11.0061 1.96385C10.707 1.76942 10.3579 1.66596 10.0011 1.66602C9.64434 1.66596 9.29522 1.76942 8.9961 1.96385C8.69698 2.15828 8.4607 2.43532 8.31591 2.76139L7.82888 3.85676C7.65632 4.24715 7.3646 4.57289 6.99554 4.78731C6.62489 5.00112 6.1961 5.09219 5.77054 5.0475L4.57517 4.92065C4.22045 4.88312 3.86246 4.94932 3.5446 5.1112C3.22675 5.27309 2.96269 5.52371 2.78443 5.83268C2.60595 6.14147 2.52092 6.49541 2.53965 6.85158C2.55839 7.20774 2.68009 7.55081 2.88999 7.83916L3.59554 8.80861C3.84716 9.15464 3.98266 9.57151 3.98258 9.99935C3.98266 10.4272 3.84716 10.8441 3.59554 11.1901L2.88999 12.1595C2.68009 12.4479 2.55839 12.791 2.53965 13.1471C2.52092 13.5033 2.60595 13.8572 2.78443 14.166C2.96286 14.4748 3.22696 14.7253 3.54476 14.8872C3.86257 15.049 4.22047 15.1153 4.57517 15.0781L5.76684 14.9512C6.1924 14.9065 6.62119 14.9976 6.99184 15.2114C7.36228 15.4252 7.65535 15.751 7.82888 16.1419Z\"\n      stroke=\"white\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      style={{ fill: 'rgb(255, 255, 255)' }}\n    />\n    <path\n      d=\"M9.99961 12.4993C11.3803 12.4993 12.4996 11.3801 12.4996 9.99935C12.4996 8.61864 11.3803 7.49935 9.99961 7.49935C8.6189 7.49935 7.49961 8.61864 7.49961 9.99935C7.49961 11.3801 8.6189 12.4993 9.99961 12.4993Z\"\n      stroke=\"#612BD3\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      style={{ fill: '#612BD3' }}\n    />\n  </svg>\n);\n\n// Chevron Down Icon (rotatable)\nexport const ChevronDownIcon: FC<IconProps & { rotated?: boolean }> = ({\n  size = 20,\n  className,\n  rotated,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={clsx(rotated && 'rotate-180', 'transition-transform', className)}\n    {...props}\n  >\n    <path\n      d=\"M5 7.5L10 12.5L15 7.5\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Chevron Up Icon\nexport const ChevronUpIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M15 12.5L10 7.5L5 12.5\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Close/X Icon\nexport const CloseIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M16 4L4 16M4 4L16 16\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Small Close Icon (10x11 variant)\nexport const CloseIconSmall: FC<IconProps> = ({\n  size = 10,\n  className,\n  ...props\n}) => (\n  <svg\n    width={size}\n    height={size + 1}\n    viewBox=\"0 0 10 11\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M9.85403 9.64628C9.90048 9.69274 9.93733 9.74789 9.96247 9.80859C9.98762 9.86928 10.0006 9.93434 10.0006 10C10.0006 10.0657 9.98762 10.1308 9.96247 10.1915C9.93733 10.2522 9.90048 10.3073 9.85403 10.3538C9.80757 10.4002 9.75242 10.4371 9.69173 10.4622C9.63103 10.4874 9.56598 10.5003 9.50028 10.5003C9.43458 10.5003 9.36953 10.4874 9.30883 10.4622C9.24813 10.4371 9.19298 10.4002 9.14653 10.3538L5.00028 6.20691L0.854028 10.3538C0.760208 10.4476 0.63296 10.5003 0.500278 10.5003C0.367596 10.5003 0.240348 10.4476 0.146528 10.3538C0.0527077 10.26 2.61548e-09 10.1327 0 10C-2.61548e-09 9.86735 0.0527077 9.7401 0.146528 9.64628L4.2934 5.50003L0.146528 1.35378C0.0527077 1.25996 0 1.13272 0 1.00003C0 0.867352 0.0527077 0.740104 0.146528 0.646284C0.240348 0.552464 0.367596 0.499756 0.500278 0.499756C0.63296 0.499756 0.760208 0.552464 0.854028 0.646284L5.00028 4.79316L9.14653 0.646284C9.24035 0.552464 9.3676 0.499756 9.50028 0.499756C9.63296 0.499756 9.76021 0.552464 9.85403 0.646284C9.94785 0.740104 10.0006 0.867352 10.0006 1.00003C10.0006 1.13272 9.94785 1.25996 9.85403 1.35378L5.70715 5.50003L9.85403 9.64628Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Trash/Delete Icon\nexport const TrashIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M7.5 2.5H12.5M2.5 5H17.5M15.8333 5L15.2489 13.7661C15.1612 15.0813 15.1174 15.7389 14.8333 16.2375C14.5833 16.6765 14.206 17.0294 13.7514 17.2497C13.235 17.5 12.5759 17.5 11.2578 17.5H8.74221C7.42409 17.5 6.76503 17.5 6.24861 17.2497C5.79396 17.0294 5.41674 16.6765 5.16665 16.2375C4.88259 15.7389 4.83875 15.0813 4.75107 13.7661L4.16667 5M8.33333 8.75V12.9167M11.6667 8.75V12.9167\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport const DelayIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    width={size}\n    height={size}\n    viewBox=\"0 0 32 32\"\n    fill=\"red\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9964 12.5533 27.6256 9.24882 25.1884 6.81163C22.7512 4.37445 19.4467 3.00364 16 3ZM16 27C13.8244 27 11.6977 26.3549 9.88873 25.1462C8.07979 23.9375 6.66989 22.2195 5.83733 20.2095C5.00477 18.1995 4.78693 15.9878 5.21137 13.854C5.63581 11.7202 6.68345 9.7602 8.22183 8.22183C9.76021 6.68345 11.7202 5.6358 13.854 5.21136C15.9878 4.78692 18.1995 5.00476 20.2095 5.83733C22.2195 6.66989 23.9375 8.07979 25.1462 9.88873C26.3549 11.6977 27 13.8244 27 16C26.9967 18.9164 25.8367 21.7123 23.7745 23.7745C21.7123 25.8367 18.9164 26.9967 16 27ZM24 16C24 16.2652 23.8946 16.5196 23.7071 16.7071C23.5196 16.8946 23.2652 17 23 17H16C15.7348 17 15.4804 16.8946 15.2929 16.7071C15.1054 16.5196 15 16.2652 15 16V9C15 8.73478 15.1054 8.48043 15.2929 8.29289C15.4804 8.10536 15.7348 8 16 8C16.2652 8 16.5196 8.10536 16.7071 8.29289C16.8946 8.48043 17 8.73478 17 9V15H23C23.2652 15 23.5196 15.1054 23.7071 15.2929C23.8946 15.4804 24 15.7348 24 16Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Dropdown Arrow (filled triangle)\nexport const DropdownArrowIcon: FC<IconProps & { rotated?: boolean }> = ({\n  size = 20,\n  className,\n  rotated,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={clsx(rotated && 'rotate-180', 'transition-transform', className)}\n    {...props}\n  >\n    <path\n      d=\"M7.4563 8L12.5437 8C12.9494 8 13.1526 8.56798 12.8657 8.90016L10.322 11.8456C10.1442 12.0515 9.85583 12.0515 9.67799 11.8456L7.13429 8.90016C6.84741 8.56798 7.05059 8 7.4563 8Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Small Dropdown Arrow (6x4)\nexport const DropdownArrowSmallIcon: FC<IconProps & { rotated?: boolean }> = ({\n  className,\n  rotated,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"6\"\n    height=\"4\"\n    viewBox=\"0 0 6 4\"\n    fill=\"none\"\n    className={clsx(rotated && 'rotate-180', 'transition-transform', className)}\n    {...props}\n  >\n    <path\n      d=\"M0.456301 9.69291e-07L5.5437 7.97823e-08C5.94941 8.84616e-09 6.15259 0.567978 5.86571 0.90016L3.32201 3.84556C3.14417 4.05148 2.85583 4.05148 2.67799 3.84556L0.134293 0.900162C-0.152585 0.56798 0.0505934 1.04023e-06 0.456301 9.69291e-07Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Calendar Icon\nexport const CalendarIcon: FC<IconProps> = ({ className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"17\"\n    height=\"19\"\n    viewBox=\"0 0 17 19\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M15.75 7.41667H0.75M11.5833 0.75V4.08333M4.91667 0.75V4.08333M4.75 17.4167H11.75C13.1501 17.4167 13.8502 17.4167 14.385 17.1442C14.8554 16.9045 15.2378 16.522 15.4775 16.0516C15.75 15.5169 15.75 14.8168 15.75 13.4167V6.41667C15.75 5.01654 15.75 4.31647 15.4775 3.78169C15.2378 3.31129 14.8554 2.92883 14.385 2.68915C13.8502 2.41667 13.1501 2.41667 11.75 2.41667H4.75C3.34987 2.41667 2.6498 2.41667 2.11502 2.68915C1.64462 2.92883 1.26217 3.31129 1.02248 3.78169C0.75 4.31647 0.75 5.01654 0.75 6.41667V13.4167C0.75 14.8168 0.75 15.5169 1.02248 16.0516C1.26217 16.522 1.64462 16.9045 2.11502 17.1442C2.6498 17.4167 3.34987 17.4167 4.75 17.4167Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Repeat/Cycle Icon\nexport const RepeatIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <g clipPath=\"url(#clip0_repeat)\">\n      <path\n        d=\"M14.1667 1.66602L17.5 4.99935M17.5 4.99935L14.1667 8.33268M17.5 4.99935H6.5C5.09987 4.99935 4.3998 4.99935 3.86502 5.27183C3.39462 5.51152 3.01217 5.89397 2.77248 6.36437C2.5 6.89915 2.5 7.59922 2.5 8.99935V9.16602M2.5 14.9993H13.5C14.9001 14.9993 15.6002 14.9993 16.135 14.7269C16.6054 14.4872 16.9878 14.1047 17.2275 13.6343C17.5 13.0995 17.5 12.3995 17.5 10.9993V10.8327M2.5 14.9993L5.83333 18.3327M2.5 14.9993L5.83333 11.666\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_repeat\">\n        <rect width=\"20\" height=\"20\" fill=\"currentColor\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\n// Tag Icon\nexport const TagIcon: FC<IconProps> = ({ className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"17\"\n    height=\"19\"\n    viewBox=\"0 0 17 19\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M15.75 8.25L9.42157 1.92157C8.98919 1.48919 8.773 1.273 8.52071 1.1184C8.29703 0.981328 8.05317 0.880317 7.79808 0.819075C7.51036 0.75 7.20462 0.75 6.59314 0.75L3.25 0.75M0.75 6.33333L0.75 7.97876C0.75 8.38641 0.75 8.59024 0.79605 8.78205C0.836878 8.95211 0.904218 9.11469 0.9956 9.26381C1.09867 9.432 1.2428 9.57613 1.53105 9.86438L8.03105 16.3644C8.69108 17.0244 9.02109 17.3544 9.40164 17.4781C9.73638 17.5868 10.097 17.5868 10.4317 17.4781C10.8122 17.3544 11.1423 17.0244 11.8023 16.3644L13.8644 14.3023C14.5244 13.6423 14.8544 13.3122 14.9781 12.9317C15.0868 12.597 15.0868 12.2364 14.9781 11.9016C14.8544 11.5211 14.5244 11.1911 13.8644 10.531L7.78105 4.44772C7.4928 4.15946 7.34867 4.01534 7.18048 3.91227C7.03135 3.82089 6.86878 3.75354 6.69872 3.71272C6.50691 3.66667 6.30308 3.66667 5.89543 3.66667H3.41667C2.48325 3.66667 2.01654 3.66667 1.66002 3.84832C1.34641 4.00811 1.09145 4.26308 0.931656 4.57668C0.75 4.9332 0.75 5.39991 0.75 6.33333Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Plus Icon\nexport const PlusIcon: FC<IconProps> = ({ size = 16, className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M8.00065 3.33301V12.6663M3.33398 7.99967H12.6673\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Checkmark Icon\nexport const CheckmarkIcon: FC<IconProps> = ({ className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"11\"\n    height=\"8\"\n    viewBox=\"0 0 11 8\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M10.7071 0.292893C11.0976 0.683417 11.0976 1.31658 10.7071 1.70711L4.70711 7.70711C4.31658 8.09763 3.68342 8.09763 3.29289 7.70711L0.292893 4.70711C-0.0976311 4.31658 -0.0976311 3.68342 0.292893 3.29289C0.683417 2.90237 1.31658 2.90237 1.70711 3.29289L4 5.58579L9.29289 0.292893C9.68342 -0.0976311 10.3166 -0.0976311 10.7071 0.292893Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Global/Planet Icon\nexport const GlobalIcon: FC<IconProps> = ({\n  size = 20,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M2.56267 6.23601L6.13604 8.78837C6.32197 8.92118 6.41494 8.98759 6.51225 9.00289C6.59786 9.01635 6.68554 9.00278 6.76309 8.96407C6.85121 8.92008 6.91976 8.82868 7.05686 8.64588L7.81194 7.63909C7.85071 7.5874 7.8701 7.56155 7.89288 7.53925C7.91311 7.51945 7.93531 7.50177 7.95913 7.48647C7.98595 7.46924 8.01547 7.45612 8.07452 7.42988L11.2983 5.99707C11.432 5.93767 11.4988 5.90798 11.5492 5.8616C11.5938 5.82057 11.6288 5.77033 11.652 5.71436C11.6782 5.65108 11.6831 5.57812 11.6928 5.4322L11.9288 1.8915M11.2493 11.2503L13.4294 12.1846C13.6823 12.293 13.8088 12.3472 13.8757 12.4372C13.9345 12.5162 13.9634 12.6135 13.9573 12.7117C13.9504 12.8237 13.8741 12.9382 13.7214 13.1672L12.6973 14.7035C12.6249 14.812 12.5887 14.8663 12.5409 14.9056C12.4986 14.9403 12.4498 14.9664 12.3974 14.9824C12.3382 15.0003 12.273 15.0003 12.1426 15.0003H10.4799C10.3071 15.0003 10.2207 15.0003 10.1472 14.9714C10.0822 14.9459 10.0248 14.9045 9.98003 14.851C9.92936 14.7904 9.90204 14.7084 9.8474 14.5445L9.25334 12.7623C9.22111 12.6656 9.205 12.6173 9.20076 12.5681C9.19699 12.5246 9.20011 12.4807 9.21 12.4381C9.22114 12.3901 9.24393 12.3445 9.28951 12.2533L9.74077 11.3508C9.83246 11.1674 9.8783 11.0758 9.94891 11.0188C10.0111 10.9687 10.0865 10.9375 10.166 10.9289C10.2561 10.9193 10.3534 10.9517 10.5479 11.0165L11.2493 11.2503ZM18.3327 10.0003C18.3327 14.6027 14.6017 18.3337 9.99935 18.3337C5.39698 18.3337 1.66602 14.6027 1.66602 10.0003C1.66602 5.39795 5.39698 1.66699 9.99935 1.66699C14.6017 1.66699 18.3327 5.39795 18.3327 10.0003Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// User Icon\nexport const UserIcon: FC<IconProps> = ({ size = 20, className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M16.6673 17.5C16.6673 16.337 16.6673 15.7555 16.5238 15.2824C16.2006 14.217 15.3669 13.3834 14.3016 13.0602C13.8284 12.9167 13.247 12.9167 12.084 12.9167H7.91732C6.75435 12.9167 6.17286 12.9167 5.6997 13.0602C4.63436 13.3834 3.80068 14.217 3.47752 15.2824C3.33398 15.7555 3.33398 16.337 3.33398 17.5M13.7507 6.25C13.7507 8.32107 12.0717 10 10.0007 10C7.92958 10 6.25065 8.32107 6.25065 6.25C6.25065 4.17893 7.92958 2.5 10.0007 2.5C12.0717 2.5 13.7507 4.17893 13.7507 6.25Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Expand Icon\nexport const ExpandIcon: FC<IconProps> = ({\n  size = 24,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size + 1}\n    viewBox=\"0 0 24 25\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M20.25 5V9.5C20.25 9.69891 20.171 9.88968 20.0303 10.0303C19.8897 10.171 19.6989 10.25 19.5 10.25C19.3011 10.25 19.1103 10.171 18.9697 10.0303C18.829 9.88968 18.75 9.69891 18.75 9.5V6.81031L14.0306 11.5306C13.8899 11.6714 13.699 11.7504 13.5 11.7504C13.301 11.7504 13.1101 11.6714 12.9694 11.5306C12.8286 11.3899 12.7496 11.199 12.7496 11C12.7496 10.801 12.8286 10.6101 12.9694 10.4694L17.6897 5.75H15C14.8011 5.75 14.6103 5.67098 14.4697 5.53033C14.329 5.38968 14.25 5.19891 14.25 5C14.25 4.80109 14.329 4.61032 14.4697 4.46967C14.6103 4.32902 14.8011 4.25 15 4.25H19.5C19.6989 4.25 19.8897 4.32902 20.0303 4.46967C20.171 4.61032 20.25 4.80109 20.25 5ZM9.96937 13.4694L5.25 18.1897V15.5C5.25 15.3011 5.17098 15.1103 5.03033 14.9697C4.88968 14.829 4.69891 14.75 4.5 14.75C4.30109 14.75 4.11032 14.829 3.96967 14.9697C3.82902 15.1103 3.75 15.3011 3.75 15.5V20C3.75 20.1989 3.82902 20.3897 3.96967 20.5303C4.11032 20.671 4.30109 20.75 4.5 20.75H9C9.19891 20.75 9.38968 20.671 9.53033 20.5303C9.67098 20.3897 9.75 20.1989 9.75 20C9.75 19.8011 9.67098 19.6103 9.53033 19.4697C9.38968 19.329 9.19891 19.25 9 19.25H6.31031L11.0306 14.5306C11.1714 14.3899 11.2504 14.199 11.2504 14C11.2504 13.801 11.1714 13.6101 11.0306 13.4694C10.8899 13.3286 10.699 13.2496 10.5 13.2496C10.301 13.2496 10.1101 13.3286 9.96937 13.4694Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Collapse Icon\nexport const CollapseIcon: FC<IconProps> = ({\n  size = 24,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size + 1}\n    viewBox=\"0 0 24 25\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M13.5004 10.2499V6.49993C13.5004 6.30102 13.5794 6.11025 13.7201 5.9696C13.8607 5.82895 14.0515 5.74993 14.2504 5.74993C14.4493 5.74993 14.6401 5.82895 14.7807 5.9696C14.9214 6.11025 15.0004 6.30102 15.0004 6.49993V8.43962L18.9698 4.4693C19.1105 4.32857 19.3014 4.24951 19.5004 4.24951C19.6994 4.24951 19.8903 4.32857 20.031 4.4693C20.1718 4.61003 20.2508 4.80091 20.2508 4.99993C20.2508 5.19895 20.1718 5.38982 20.031 5.53055L16.0607 9.49993H18.0004C18.1993 9.49993 18.3901 9.57895 18.5307 9.7196C18.6714 9.86025 18.7504 10.051 18.7504 10.2499C18.7504 10.4488 18.6714 10.6396 18.5307 10.7803C18.3901 10.9209 18.1993 10.9999 18.0004 10.9999H14.2504C14.0515 10.9999 13.8607 10.9209 13.7201 10.7803C13.5794 10.6396 13.5004 10.4488 13.5004 10.2499ZM9.75042 13.9999H6.00042C5.8015 13.9999 5.61074 14.0789 5.47009 14.2196C5.32943 14.3603 5.25042 14.551 5.25042 14.7499C5.25042 14.9488 5.32943 15.1396 5.47009 15.2803C5.61074 15.4209 5.8015 15.4999 6.00042 15.4999H7.9401L3.96979 19.4693C3.82906 19.61 3.75 19.8009 3.75 19.9999C3.75 20.199 3.82906 20.3898 3.96979 20.5306C4.11052 20.6713 4.30139 20.7503 4.50042 20.7503C4.69944 20.7503 4.89031 20.6713 5.03104 20.5306L9.00042 16.5602V18.4999C9.00042 18.6988 9.07943 18.8896 9.22009 19.0303C9.36074 19.1709 9.5515 19.2499 9.75042 19.2499C9.94933 19.2499 10.1401 19.1709 10.2807 19.0303C10.4214 18.8896 10.5004 18.6988 10.5004 18.4999V14.7499C10.5004 14.551 10.4214 14.3603 10.2807 14.2196C10.1401 14.0789 9.94933 13.9999 9.75042 13.9999ZM16.0607 15.4999H18.0004C18.1993 15.4999 18.3901 15.4209 18.5307 15.2803C18.6714 15.1396 18.7504 14.9488 18.7504 14.7499C18.7504 14.551 18.6714 14.3603 18.5307 14.2196C18.3901 14.0789 18.1993 13.9999 18.0004 13.9999H14.2504C14.0515 13.9999 13.8607 14.0789 13.7201 14.2196C13.5794 14.3603 13.5004 14.551 13.5004 14.7499V18.4999C13.5004 18.6988 13.5794 18.8896 13.7201 19.0303C13.8607 19.1709 14.0515 19.2499 14.2504 19.2499C14.4493 19.2499 14.6401 19.1709 14.7807 19.0303C14.9214 18.8896 15.0004 18.6988 15.0004 18.4999V16.5602L18.9698 20.5306C19.0395 20.6002 19.1222 20.6555 19.2132 20.6932C19.3043 20.7309 19.4019 20.7503 19.5004 20.7503C19.599 20.7503 19.6965 20.7309 19.7876 20.6932C19.8786 20.6555 19.9614 20.6002 20.031 20.5306C20.1007 20.4609 20.156 20.3781 20.1937 20.2871C20.2314 20.1961 20.2508 20.0985 20.2508 19.9999C20.2508 19.9014 20.2314 19.8038 20.1937 19.7128C20.156 19.6217 20.1007 19.539 20.031 19.4693L16.0607 15.4999ZM9.75042 5.74993C9.5515 5.74993 9.36074 5.82895 9.22009 5.9696C9.07943 6.11025 9.00042 6.30102 9.00042 6.49993V8.43962L5.03104 4.4693C4.89031 4.32857 4.69944 4.24951 4.50042 4.24951C4.30139 4.24951 4.11052 4.32857 3.96979 4.4693C3.82906 4.61003 3.75 4.80091 3.75 4.99993C3.75 5.19895 3.82906 5.38982 3.96979 5.53055L7.9401 9.49993H6.00042C5.8015 9.49993 5.61074 9.57895 5.47009 9.7196C5.32943 9.86025 5.25042 10.051 5.25042 10.2499C5.25042 10.4488 5.32943 10.6396 5.47009 10.7803C5.61074 10.9209 5.8015 10.9999 6.00042 10.9999H9.75042C9.94933 10.9999 10.1401 10.9209 10.2807 10.7803C10.4214 10.6396 10.5004 10.4488 10.5004 10.2499V6.49993C10.5004 6.30102 10.4214 6.11025 10.2807 5.9696C10.1401 5.82895 9.94933 5.74993 9.75042 5.74993Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Lock Icon\nexport const LockIcon: FC<IconProps> = ({ size = 32, className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 32 32\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M22.6673 13.3333V10.6667C22.6673 6.98477 19.6825 4 16.0007 4C12.3188 4 9.33398 6.98477 9.33398 10.6667V13.3333M16.0007 19.3333V22M11.734 28H20.2673C22.5075 28 23.6276 28 24.4833 27.564C25.2359 27.1805 25.8479 26.5686 26.2313 25.816C26.6673 24.9603 26.6673 23.8402 26.6673 21.6V19.7333C26.6673 17.4931 26.6673 16.373 26.2313 15.5174C25.8479 14.7647 25.2359 14.1528 24.4833 13.7693C23.6276 13.3333 22.5075 13.3333 20.2673 13.3333H11.734C9.49377 13.3333 8.37367 13.3333 7.51802 13.7693C6.76537 14.1528 6.15345 14.7647 5.76996 15.5174C5.33398 16.373 5.33398 17.4931 5.33398 19.7333V21.6C5.33398 23.8402 5.33398 24.9603 5.76996 25.816C6.15345 26.5686 6.76537 27.1805 7.51802 27.564C8.37367 28 9.49377 28 11.734 28Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Connection Line Icon (for thread/comment indication)\nexport const ConnectionLineIcon: FC<IconProps> = ({ className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"18\"\n    height=\"87\"\n    viewBox=\"0 0 18 87\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M0.75 0.75V79.75C0.75 83.0637 3.43629 85.75 6.75 85.75H16.75\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n    />\n  </svg>\n);\n\n// Reset/Back to Global Icon\nexport const ResetIcon: FC<IconProps> = ({\n  size = 16,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M1.33398 6.66667C1.33398 6.66667 2.67064 4.84548 3.75654 3.75883C4.84244 2.67218 6.34305 2 8.00065 2C11.3144 2 14.0007 4.68629 14.0007 8C14.0007 11.3137 11.3144 14 8.00065 14C5.26526 14 2.95739 12.1695 2.23516 9.66667M1.33398 6.66667V2.66667M1.33398 6.66667H5.33398\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Emoji Icon\nexport const EmojiIcon: FC<IconProps> = ({\n  size = 16,\n  className,\n  ...props\n}) => (\n  <svg\n    width={size}\n    height={size}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M7.97917 14.6663C11.6611 14.6663 14.6458 11.6816 14.6458 7.99967C14.6458 4.31778 11.6611 1.33301 7.97917 1.33301C4.29727 1.33301 1.3125 4.31778 1.3125 7.99967C1.3125 11.6816 4.29727 14.6663 7.97917 14.6663Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M4.80664 10C5.50664 11.0067 6.67997 11.6667 7.99997 11.6667C9.31997 11.6667 10.4866 11.0067 11.1933 10\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M4.66602 6.16699C5.33268 6.83366 6.41935 6.83366 7.09268 6.16699\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.2\"\n      strokeMiterlimit=\"10\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M8.90625 6.16699C9.57292 6.83366 10.6596 6.83366 11.3329 6.16699\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.2\"\n      strokeMiterlimit=\"10\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\n// Chevron Left Icon\nexport const ChevronLeftIcon: FC<IconProps> = ({\n  size = 24,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth={2}\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className={className}\n    {...props}\n  >\n    <path d=\"m15 18-6-6 6-6\" />\n  </svg>\n);\n\n// Chevron Right Icon\nexport const ChevronRightIcon: FC<IconProps> = ({\n  size = 24,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth={2}\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className={className}\n    {...props}\n  >\n    <path d=\"m9 18 6-6-6-6\" />\n  </svg>\n);\n\n// Delete Circle Icon (for media delete)\nexport const DeleteCircleIcon: FC<IconProps> = ({\n  size = 18,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 18 18\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <ellipse cx=\"9.96484\" cy=\"9.10742\" rx=\"6\" ry=\"5.5\" fill=\"white\" />\n    <path\n      d=\"M9 1.5C4.8675 1.5 1.5 4.8675 1.5 9C1.5 13.1325 4.8675 16.5 9 16.5C13.1325 16.5 16.5 13.1325 16.5 9C16.5 4.8675 13.1325 1.5 9 1.5ZM11.52 10.725C11.7375 10.9425 11.7375 11.3025 11.52 11.52C11.4075 11.6325 11.265 11.685 11.1225 11.685C10.98 11.685 10.8375 11.6325 10.725 11.52L9 9.795L7.275 11.52C7.1625 11.6325 7.02 11.685 6.8775 11.685C6.735 11.685 6.5925 11.6325 6.48 11.52C6.2625 11.3025 6.2625 10.9425 6.48 10.725L8.205 9L6.48 7.275C6.2625 7.0575 6.2625 6.6975 6.48 6.48C6.6975 6.2625 7.0575 6.2625 7.275 6.48L9 8.205L10.725 6.48C10.9425 6.2625 11.3025 6.2625 11.52 6.48C11.7375 6.6975 11.7375 7.0575 11.52 7.275L9.795 9L11.52 10.725Z\"\n      fill=\"#FF3535\"\n    />\n  </svg>\n);\n\n// Close Circle Icon (smaller, for clearing media)\nexport const CloseCircleIcon: FC<IconProps> = ({\n  size = 15,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 15 15\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M7.5 0C3.3675 0 0 3.3675 0 7.5C0 11.6325 3.3675 15 7.5 15C11.6325 15 15 11.6325 15 7.5C15 3.3675 11.6325 0 7.5 0ZM10.02 9.225C10.2375 9.4425 10.2375 9.8025 10.02 10.02C9.9075 10.1325 9.765 10.185 9.6225 10.185C9.48 10.185 9.3375 10.1325 9.225 10.02L7.5 8.295L5.775 10.02C5.6625 10.1325 5.52 10.185 5.3775 10.185C5.235 10.185 5.0925 10.1325 4.98 10.02C4.7625 9.8025 4.7625 9.4425 4.98 9.225L6.705 7.5L4.98 5.775C4.7625 5.5575 4.7625 5.1975 4.98 4.98C5.1975 4.7625 5.5575 4.7625 5.775 4.98L7.5 6.705L9.225 4.98C9.4425 4.7625 9.8025 4.7625 10.02 4.98C10.2375 5.1975 10.2375 5.5575 10.02 5.775L8.295 7.5L10.02 9.225Z\"\n      fill=\"#FF3535\"\n    />\n  </svg>\n);\n\n// Drag Handle Icon\nexport const DragHandleIcon: FC<IconProps> = ({\n  size = 15,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 15 15\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <ellipse cx=\"8.23242\" cy=\"7.5\" rx=\"6\" ry=\"5.5\" fill=\"white\" />\n    <path\n      d=\"M7.5 0C11.6421 0 15 3.35786 15 7.5C14.9998 11.642 11.642 15 7.5 15C3.35799 15 0.000197912 11.642 0 7.5C0 3.35786 3.35786 0 7.5 0ZM5.55566 8.38867C4.97286 8.38867 4.50026 8.86159 4.5 9.44434C4.5 10.0273 4.9727 10.5 5.55566 10.5C6.13858 10.4999 6.61133 10.0273 6.61133 9.44434C6.61107 8.86162 6.13842 8.38873 5.55566 8.38867ZM9.44434 8.38867C8.86158 8.38873 8.38893 8.86162 8.38867 9.44434C8.38867 10.0273 8.86142 10.4999 9.44434 10.5C10.0273 10.5 10.5 10.0273 10.5 9.44434C10.4997 8.86159 10.0271 8.38867 9.44434 8.38867ZM5.55566 9.38867C5.58614 9.38873 5.61107 9.41391 5.61133 9.44434C5.61133 9.47498 5.5863 9.49994 5.55566 9.5C5.52498 9.5 5.5 9.47502 5.5 9.44434C5.50026 9.41387 5.52514 9.38867 5.55566 9.38867ZM9.44434 9.38867C9.47486 9.38867 9.49974 9.41387 9.5 9.44434C9.5 9.47502 9.47502 9.5 9.44434 9.5C9.4137 9.49994 9.38867 9.47498 9.38867 9.44434C9.38893 9.41391 9.41386 9.38873 9.44434 9.38867ZM5.55566 4.5C4.97282 4.5 4.5002 4.97287 4.5 5.55566C4.50006 6.13858 4.97273 6.61133 5.55566 6.61133C6.13855 6.61127 6.61127 6.13855 6.61133 5.55566C6.61113 4.9729 6.13846 4.50006 5.55566 4.5ZM9.44434 4.5C8.86154 4.50006 8.38887 4.9729 8.38867 5.55566C8.38873 6.13855 8.86145 6.61127 9.44434 6.61133C10.0273 6.61133 10.4999 6.13858 10.5 5.55566C10.4998 4.97287 10.0272 4.5 9.44434 4.5ZM5.55566 5.5C5.58617 5.50006 5.61113 5.52519 5.61133 5.55566C5.61127 5.58626 5.58626 5.61127 5.55566 5.61133C5.52502 5.61133 5.50006 5.5863 5.5 5.55566C5.5002 5.52515 5.5251 5.5 5.55566 5.5ZM9.44434 5.5C9.4749 5.5 9.4998 5.52515 9.5 5.55566C9.49994 5.5863 9.47498 5.61133 9.44434 5.61133C9.41374 5.61127 9.38873 5.58626 9.38867 5.55566C9.38887 5.52519 9.41383 5.50006 9.44434 5.5Z\"\n      fill=\"#618DFF\"\n    />\n  </svg>\n);\n\n// Media Settings Icon (gear for media)\nexport const MediaSettingsIcon: FC<IconProps> = ({\n  size = 40,\n  className,\n  ...props\n}) => (\n  <svg\n    width={size}\n    height={size}\n    viewBox=\"0 0 40 40\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M19.9987 15.5C19.1087 15.5 18.2387 15.7639 17.4986 16.2584C16.7586 16.7528 16.1818 17.4556 15.8413 18.2779C15.5007 19.1002 15.4115 20.005 15.5852 20.8779C15.7588 21.7508 16.1874 22.5526 16.8167 23.182C17.4461 23.8113 18.2479 24.2399 19.1208 24.4135C19.9937 24.5871 20.8985 24.498 21.7208 24.1574C22.5431 23.8168 23.2459 23.2401 23.7403 22.5C24.2348 21.76 24.4987 20.89 24.4987 20C24.4975 18.8069 24.023 17.663 23.1793 16.8194C22.3357 15.9757 21.1918 15.5012 19.9987 15.5ZM19.9987 23C19.4054 23 18.8254 22.824 18.332 22.4944C17.8387 22.1647 17.4541 21.6962 17.2271 21.148C17 20.5999 16.9406 19.9967 17.0564 19.4147C17.1721 18.8328 17.4578 18.2982 17.8774 17.8787C18.297 17.4591 18.8315 17.1734 19.4134 17.0576C19.9954 16.9419 20.5986 17.0013 21.1468 17.2283C21.6949 17.4554 22.1635 17.8399 22.4931 18.3333C22.8228 18.8266 22.9987 19.4066 22.9987 20C22.9987 20.7956 22.6826 21.5587 22.12 22.1213C21.5574 22.6839 20.7944 23 19.9987 23ZM30.3056 18.0509C30.2847 17.9453 30.2413 17.8454 30.1784 17.7581C30.1155 17.6707 30.0345 17.5979 29.9409 17.5447L27.1443 15.9509L27.1331 12.799C27.1327 12.6905 27.1089 12.5833 27.063 12.4849C27.0172 12.3865 26.9506 12.2992 26.8678 12.229C25.8533 11.3709 24.6851 10.7134 23.4253 10.2912C23.3261 10.2577 23.2209 10.2452 23.1166 10.2547C23.0123 10.2643 22.9111 10.2955 22.8197 10.3465L19.9987 11.9234L17.175 10.3437C17.0834 10.2924 16.9821 10.2609 16.8776 10.2513C16.7732 10.2416 16.6678 10.2539 16.5684 10.2875C15.3095 10.7127 14.1426 11.3728 13.1297 12.2328C13.0469 12.3028 12.9804 12.39 12.9346 12.4882C12.8888 12.5865 12.8648 12.6935 12.8643 12.8019L12.8503 15.9565L10.0537 17.5503C9.96015 17.6036 9.87916 17.6763 9.81623 17.7637C9.7533 17.8511 9.70992 17.9509 9.68903 18.0565C9.43309 19.3427 9.43309 20.6667 9.68903 21.9528C9.70992 22.0584 9.7533 22.1583 9.81623 22.2456C9.87916 22.333 9.96015 22.4058 10.0537 22.459L12.8503 24.0528L12.8615 27.2047C12.8619 27.3132 12.8858 27.4204 12.9316 27.5188C12.9774 27.6172 13.044 27.7045 13.1268 27.7747C14.1413 28.6328 15.3095 29.2904 16.5693 29.7125C16.6686 29.7461 16.7737 29.7585 16.878 29.749C16.9823 29.7394 17.0835 29.7082 17.175 29.6572L19.9987 28.0765L22.8225 29.6562C22.9342 29.7185 23.0602 29.7508 23.1881 29.75C23.27 29.75 23.3514 29.7367 23.429 29.7106C24.6878 29.286 25.8547 28.6265 26.8678 27.7672C26.9505 27.6971 27.017 27.61 27.0628 27.5117C27.1087 27.4135 27.1326 27.3065 27.1331 27.1981L27.1472 24.0434L29.9437 22.4497C30.0373 22.3964 30.1183 22.3236 30.1812 22.2363C30.2441 22.1489 30.2875 22.049 30.3084 21.9434C30.5629 20.6583 30.562 19.3357 30.3056 18.0509ZM28.8993 21.3237L26.2209 22.8472C26.1035 22.9139 26.0064 23.0111 25.9397 23.1284C25.8853 23.2222 25.8281 23.3215 25.77 23.4153C25.6956 23.5335 25.6559 23.6703 25.6556 23.81L25.6415 26.8334C24.9216 27.3988 24.1195 27.8509 23.2631 28.174L20.5612 26.6684C20.449 26.6064 20.3228 26.5741 20.1947 26.5747H20.1768C20.0634 26.5747 19.949 26.5747 19.8356 26.5747C19.7014 26.5713 19.5688 26.6037 19.4512 26.6684L16.7475 28.1778C15.8892 27.8571 15.0849 27.4072 14.3625 26.8437L14.3522 23.825C14.3517 23.685 14.3121 23.548 14.2378 23.4294C14.1797 23.3356 14.1225 23.2419 14.069 23.1425C14.0028 23.0233 13.9056 22.9242 13.7878 22.8556L11.1065 21.3284C10.9678 20.4507 10.9678 19.5567 11.1065 18.679L13.7803 17.1528C13.8976 17.0861 13.9948 16.9889 14.0615 16.8715C14.1159 16.7778 14.1731 16.6784 14.2312 16.5847C14.3056 16.4664 14.3453 16.3297 14.3456 16.19L14.3597 13.1665C15.0796 12.6012 15.8816 12.1491 16.7381 11.8259L19.4362 13.3315C19.5536 13.3966 19.6864 13.429 19.8206 13.4253C19.934 13.4253 20.0484 13.4253 20.1618 13.4253C20.296 13.4286 20.4287 13.3963 20.5462 13.3315L23.25 11.8222C24.1082 12.1429 24.9125 12.5927 25.635 13.1562L25.6453 16.175C25.6457 16.3149 25.6854 16.452 25.7597 16.5706C25.8178 16.6644 25.875 16.7581 25.9284 16.8575C25.9947 16.9767 26.0918 17.0758 26.2097 17.1444L28.8909 18.6715C29.0315 19.5499 29.0331 20.4449 28.8956 21.3237H28.8993Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\n// Insert Media Icon\nexport const InsertMediaIcon: FC<IconProps> = ({\n  size = 16,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <g clipPath=\"url(#clip0_insertmedia)\">\n      <path\n        d=\"M8.33333 1.99967H5.2C4.0799 1.99967 3.51984 1.99967 3.09202 2.21766C2.71569 2.40941 2.40973 2.71537 2.21799 3.09169C2 3.51952 2 4.07957 2 5.19967V10.7997C2 11.9198 2 12.4798 2.21799 12.9077C2.40973 13.284 2.71569 13.5899 3.09202 13.7817C3.51984 13.9997 4.07989 13.9997 5.2 13.9997H11.3333C11.9533 13.9997 12.2633 13.9997 12.5176 13.9315C13.2078 13.7466 13.7469 13.2075 13.9319 12.5173C14 12.263 14 11.953 14 11.333M12.6667 5.33301V1.33301M10.6667 3.33301H14.6667M7 5.66634C7 6.40272 6.40305 6.99967 5.66667 6.99967C4.93029 6.99967 4.33333 6.40272 4.33333 5.66634C4.33333 4.92996 4.93029 4.33301 5.66667 4.33301C6.40305 4.33301 7 4.92996 7 5.66634ZM9.99336 7.94511L4.3541 13.0717C4.03691 13.3601 3.87831 13.5042 3.86429 13.6291C3.85213 13.7374 3.89364 13.8448 3.97546 13.9167C4.06985 13.9997 4.28419 13.9997 4.71286 13.9997H10.9707C11.9301 13.9997 12.4098 13.9997 12.7866 13.8385C13.2596 13.6361 13.6365 13.2593 13.8388 12.7863C14 12.4095 14 11.9298 14 10.9703C14 10.6475 14 10.4861 13.9647 10.3358C13.9204 10.1469 13.8353 9.96991 13.7155 9.81727C13.6202 9.69581 13.4941 9.59497 13.242 9.39331L11.3772 7.90145C11.1249 7.69961 10.9988 7.5987 10.8599 7.56308C10.7374 7.53169 10.6086 7.53575 10.4884 7.5748C10.352 7.6191 10.2324 7.72777 9.99336 7.94511Z\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_insertmedia\">\n        <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\n// Design Media Icon (pencil/edit)\nexport const DesignMediaIcon: FC<IconProps> = ({\n  size = 16,\n  className,\n  ...props\n}) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width={size}\n    height={size}\n    viewBox=\"0 0 16 16\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <g clipPath=\"url(#clip0_designmedia)\">\n      <path\n        d=\"M7.79167 1.99984H5.2C4.07989 1.99984 3.51984 1.99984 3.09202 2.21782C2.71569 2.40957 2.40973 2.71553 2.21799 3.09186C2 3.51968 2 4.07973 2 5.19984V10.7998C2 11.9199 2 12.48 2.21799 12.9078C2.40973 13.2841 2.71569 13.5901 3.09202 13.7818C3.51984 13.9998 4.07989 13.9998 5.2 13.9998H11.3333C11.9533 13.9998 12.2633 13.9998 12.5176 13.9317C13.2078 13.7468 13.7469 13.2077 13.9319 12.5175C14 12.2631 14 11.9532 14 11.3332M7 5.6665C7 6.40288 6.40305 6.99984 5.66667 6.99984C4.93029 6.99984 4.33333 6.40288 4.33333 5.6665C4.33333 4.93012 4.93029 4.33317 5.66667 4.33317C6.40305 4.33317 7 4.93012 7 5.6665ZM9.99336 7.94527L4.3541 13.0719C4.03691 13.3602 3.87831 13.5044 3.86429 13.6293C3.85213 13.7376 3.89364 13.8449 3.97546 13.9169C4.06985 13.9998 4.28419 13.9998 4.71286 13.9998H10.9707C11.9301 13.9998 12.4098 13.9998 12.7866 13.8387C13.2596 13.6363 13.6365 13.2595 13.8388 12.7864C14 12.4097 14 11.9299 14 10.9705C14 10.6477 14 10.4863 13.9647 10.3359C13.9204 10.147 13.8353 9.97007 13.7155 9.81743C13.6202 9.69597 13.4941 9.59514 13.242 9.39348L11.3772 7.90161C11.1249 7.69978 10.9988 7.59886 10.8599 7.56324C10.7374 7.53185 10.6086 7.53592 10.4884 7.57496C10.352 7.61926 10.2324 7.72794 9.99336 7.94527ZM15.0951 6.49981L13.0275 5.90908C12.9285 5.88079 12.879 5.86664 12.8328 5.84544C12.7918 5.82662 12.7528 5.80368 12.7164 5.77698C12.6755 5.74692 12.6391 5.71051 12.5663 5.6377L10.2617 3.33317C9.80143 2.87292 9.80144 2.1267 10.2617 1.66646C10.7219 1.20623 11.4681 1.20623 11.9284 1.66647L14.2329 3.97103C14.3058 4.04384 14.3422 4.08025 14.3722 4.12121C14.3989 4.15757 14.4219 4.19655 14.4407 4.23755C14.4619 4.28373 14.476 4.33323 14.5043 4.43224L15.0951 6.49981Z\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_designmedia\">\n        <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\n// Vertical Divider Icon\nexport const VerticalDividerIcon: FC<IconProps> = ({ className, ...props }) => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"2\"\n    height=\"17\"\n    viewBox=\"0 0 2 17\"\n    fill=\"none\"\n    className={className}\n    {...props}\n  >\n    <path\n      d=\"M0.75 0.75V16\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n    />\n  </svg>\n);\n\nexport const NoMediaIcon: FC = () => {\n  const [mode, setMode] = useCookie('mode', 'dark');\n\n  useEffect(() => {\n    modeEmitter.on('mode', (value) => {\n      setMode(value);\n    });\n\n    return () => {\n      modeEmitter.removeAllListeners();\n    };\n  }, []);\n\n  return (\n    <>\n      {mode === 'light' ? (\n        <svg\n          width=\"192\"\n          height=\"151\"\n          viewBox=\"0 0 192 151\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M109.75 59.0141C104.489 59.0141 113.46 -5.73557 91.0289 1.57563C69.7021 8.5269 99.5229 59.0141 94.5119 59.0141C89.5009 59.0141 54.4775 56.107 52.1458 71.9377C49.5418 89.6178 95.4225 79.7216 96.7894 81.9895C98.1563 84.2573 78.775 111.109 91.0289 119.324C103.724 127.835 119.934 96.3491 122.711 96.3491C125.489 96.3491 139.845 147.93 151.514 133.684C160.997 122.106 138.391 96.3491 142.873 96.3491C147.355 96.3491 180.793 98.9658 186.076 81.9895C192.534 61.2424 134.828 76.0575 131.352 71.9377C127.876 67.818 159.167 34.7484 142.873 25.987C126.785 17.3361 115.012 59.0141 109.75 59.0141Z\"\n            stroke=\"#DACBFB\"\n            stroke-opacity=\"0.4\"\n            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n          />\n          <rect\n            x=\"22.6328\"\n            y=\"62.543\"\n            width=\"49.2079\"\n            height=\"49.2079\"\n            rx=\"12.6792\"\n            transform=\"rotate(-16.275 22.6328 62.543)\"\n            fill=\"#E7DDFE\"\n          />\n          <path\n            d=\"M66.8612 81.5418L60.5419 73.8544C59.3886 72.4461 58.1025 71.8318 56.9211 72.1115C55.7516 72.3877 54.8703 73.5173 54.4666 75.3019L53.3809 80.0591C53.1531 81.0631 52.6197 81.7787 51.8933 82.0558C51.1583 82.3485 50.2868 82.1731 49.4472 81.5718L49.0852 81.3128C47.9215 80.4935 46.715 80.2857 45.6666 80.7089C44.6181 81.1321 43.9113 82.1457 43.653 83.5362L42.7853 88.2819C42.4791 89.9989 43.0589 91.7178 44.3481 92.8781C45.6373 94.0384 47.4099 94.4456 49.0778 93.9588L64.3891 89.4902C65.997 89.0209 67.2623 87.7792 67.758 86.1761C68.2777 84.566 67.9279 82.8321 66.8612 81.5418Z\"\n            fill=\"white\"\n          />\n          <path\n            d=\"M45.8451 76.6857C48.085 76.032 49.3709 73.6862 48.7172 71.4462C48.0634 69.2063 45.7176 67.9204 43.4777 68.5741C41.2377 69.2279 39.9518 71.5737 40.6056 73.8136C41.2593 76.0536 43.6051 77.3395 45.8451 76.6857Z\"\n            fill=\"white\"\n          />\n          <rect\n            x=\"64.8105\"\n            y=\"70.6133\"\n            width=\"66.3578\"\n            height=\"66.3578\"\n            rx=\"18.1132\"\n            fill=\"#DACBFB\"\n          />\n          <path\n            d=\"M80.1222 117.087L80.0843 117.125C79.5723 116.006 79.2499 114.735 79.1172 113.332C79.2499 114.716 79.6102 115.968 80.1222 117.087Z\"\n            fill=\"white\"\n          />\n          <path\n            d=\"M92.2983 100.718C94.7909 100.718 96.8115 98.6972 96.8115 96.2046C96.8115 93.712 94.7909 91.6914 92.2983 91.6914C89.8058 91.6914 87.7852 93.712 87.7852 96.2046C87.7852 98.6972 89.8058 100.718 92.2983 100.718Z\"\n            fill=\"white\"\n          />\n          <path\n            d=\"M105.932 84.8281H90.0409C83.1384 84.8281 79.0234 88.9431 79.0234 95.8456V111.737C79.0234 113.804 79.3837 115.605 80.0854 117.122C81.7162 120.725 85.2054 122.754 90.0409 122.754H105.932C112.834 122.754 116.949 118.639 116.949 111.737V107.394V95.8456C116.949 88.9431 112.834 84.8281 105.932 84.8281ZM113.858 104.739C112.379 103.469 109.99 103.469 108.511 104.739L100.622 111.509C99.1431 112.78 96.7538 112.78 95.2747 111.509L94.63 110.978C93.2836 109.802 91.1408 109.689 89.6237 110.713L82.5316 115.472C82.1144 114.41 81.8679 113.178 81.8679 111.737V95.8456C81.8679 90.4981 84.6934 87.6726 90.0409 87.6726H105.932C111.279 87.6726 114.105 90.4981 114.105 95.8456V104.948L113.858 104.739Z\"\n            fill=\"white\"\n          />\n        </svg>\n      ) : (\n        <svg\n          width=\"192\"\n          height=\"151\"\n          viewBox=\"0 0 192 151\"\n          fill=\"none\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path\n            d=\"M109.75 59.0141C104.489 59.0141 113.46 -5.73557 91.0289 1.57563C69.7021 8.5269 99.5229 59.0141 94.5119 59.0141C89.5009 59.0141 54.4775 56.107 52.1458 71.9377C49.5418 89.6178 95.4225 79.7216 96.7894 81.9895C98.1563 84.2573 78.775 111.109 91.0289 119.324C103.724 127.835 119.934 96.3491 122.711 96.3491C125.489 96.3491 139.845 147.93 151.514 133.684C160.997 122.106 138.391 96.3491 142.873 96.3491C147.355 96.3491 180.793 98.9658 186.076 81.9895C192.534 61.2424 134.828 76.0575 131.352 71.9377C127.876 67.818 159.167 34.7484 142.873 25.987C126.785 17.3361 115.012 59.0141 109.75 59.0141Z\"\n            stroke=\"white\"\n            strokeOpacity=\"0.08\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n          />\n          <rect\n            x=\"22.6328\"\n            y=\"62.541\"\n            width=\"49.2079\"\n            height=\"49.2079\"\n            rx=\"12.6792\"\n            transform=\"rotate(-16.275 22.6328 62.541)\"\n            fill=\"#232222\"\n          />\n          <path\n            d=\"M66.8573 81.5379L60.538 73.8505C59.3847 72.4421 58.0986 71.8279 56.9172 72.1076C55.7477 72.3838 54.8664 73.5134 54.4627 75.298L53.377 80.0552C53.1492 81.0592 52.6158 81.7748 51.8894 82.0519C51.1544 82.3446 50.2829 82.1692 49.4433 81.5678L49.0813 81.3089C47.9176 80.4896 46.7111 80.2818 45.6626 80.705C44.6142 81.1282 43.9074 82.1418 43.6491 83.5323L42.7814 88.278C42.4752 89.995 43.055 91.7139 44.3442 92.8742C45.6334 94.0345 47.406 94.4417 49.0739 93.9549L64.3851 89.4863C65.9931 89.017 67.2584 87.7753 67.7541 86.1722C68.2738 84.5621 67.924 82.8282 66.8573 81.5379Z\"\n            fill=\"white\"\n            fillOpacity=\"0.4\"\n          />\n          <path\n            d=\"M45.8412 76.6818C48.0811 76.0281 49.367 73.6823 48.7133 71.4423C48.0595 69.2024 45.7137 67.9165 43.4738 68.5702C41.2338 69.2239 39.9479 71.5697 40.6017 73.8097C41.2554 76.0497 43.6012 77.3355 45.8412 76.6818Z\"\n            fill=\"white\"\n            fillOpacity=\"0.4\"\n          />\n          <rect\n            x=\"64.8125\"\n            y=\"70.6133\"\n            width=\"66.3578\"\n            height=\"66.3578\"\n            rx=\"18.1132\"\n            fill=\"#2C2B2B\"\n          />\n          <path\n            d=\"M80.1261 117.087L80.0882 117.125C79.5762 116.006 79.2538 114.735 79.1211 113.332C79.2538 114.716 79.6141 115.968 80.1261 117.087Z\"\n            fill=\"white\"\n            fillOpacity=\"0.4\"\n          />\n          <path\n            d=\"M92.3022 100.72C94.7948 100.72 96.8154 98.6991 96.8154 96.2065C96.8154 93.714 94.7948 91.6934 92.3022 91.6934C89.8097 91.6934 87.7891 93.714 87.7891 96.2065C87.7891 98.6991 89.8097 100.72 92.3022 100.72Z\"\n            fill=\"white\"\n            fillOpacity=\"0.4\"\n          />\n          <path\n            d=\"M105.936 84.8301H90.0448C83.1423 84.8301 79.0273 88.945 79.0273 95.8476V111.739C79.0273 113.805 79.3876 115.607 80.0893 117.124C81.7201 120.727 85.2093 122.756 90.0448 122.756H105.936C112.838 122.756 116.953 118.641 116.953 111.739V107.396V95.8476C116.953 88.945 112.838 84.8301 105.936 84.8301ZM113.862 104.741C112.383 103.471 109.994 103.471 108.515 104.741L100.626 111.511C99.147 112.781 96.7577 112.781 95.2786 111.511L94.6339 110.98C93.2875 109.804 91.1447 109.691 89.6276 110.715L82.5355 115.474C82.1183 114.412 81.8718 113.18 81.8718 111.739V95.8476C81.8718 90.5 84.6973 87.6745 90.0448 87.6745H105.936C111.283 87.6745 114.109 90.5 114.109 95.8476V104.95L113.862 104.741Z\"\n            fill=\"white\"\n            fillOpacity=\"0.4\"\n          />\n        </svg>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/ui/is.scroll.hook.tsx",
    "content": "import { useState, useEffect } from 'react';\n\nexport function useHasScroll(\n  ref: React.RefObject<HTMLElement>,\n) {\n  const [hasScroll, setHasScroll] = useState(false);\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n\n    const check = () => setHasScroll(el.scrollHeight > el.clientHeight);\n    check();\n\n    const observer = new MutationObserver(check);\n    observer.observe(el, { childList: true, subtree: true });\n\n    return () => observer.disconnect();\n  }, [ref]);\n\n  return hasScroll;\n}\n"
  },
  {
    "path": "apps/frontend/src/components/ui/logo-text.component.tsx",
    "content": "import React from 'react';\n\nexport const LogoTextComponent = () => {\n  return (\n    <svg\n      width=\"101\"\n      height=\"33\"\n      viewBox=\"0 0 101 33\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <rect width=\"20\" height=\"22\" x={8} y={3} fill=\"black\" />\n      <path\n        d=\"M41.7953 5.76801V8.00208C42.1329 7.64463 42.5598 7.34675 43.0761 7.10845C43.5925 6.85029 44.218 6.72121 44.9528 6.72121C45.6279 6.72121 46.2634 6.85029 46.8592 7.10845C47.4748 7.36661 48.0109 7.78364 48.4677 8.35953C48.9443 8.91556 49.3216 9.66025 49.5996 10.5936C49.8776 11.5269 50.0166 12.6589 50.0166 13.9894C50.0166 14.9426 49.9273 15.8958 49.7486 16.849C49.5897 17.8022 49.3017 18.6561 48.8847 19.4107C48.4677 20.1653 47.9017 20.781 47.1868 21.2576C46.4918 21.7143 45.618 21.9427 44.5655 21.9427C43.8109 21.9427 43.2151 21.8434 42.7783 21.6448C42.3414 21.4264 42.0137 21.1781 41.7953 20.9001V28.1385L37.5059 29.2108V5.76801H41.7953ZM43.1357 19.3512C43.652 19.3512 44.0889 19.1824 44.4464 18.8448C44.8038 18.4873 45.0818 18.0306 45.2804 17.4745C45.4989 16.9185 45.6478 16.3029 45.7272 15.6277C45.8265 14.9327 45.8762 14.2475 45.8762 13.5724C45.8762 12.4801 45.7769 11.6163 45.5783 10.9808C45.3996 10.3454 45.1811 9.8787 44.923 9.58082C44.6648 9.26309 44.4067 9.0645 44.1485 8.98507C43.9102 8.90563 43.7215 8.86592 43.5825 8.86592C43.2251 8.86592 42.8776 8.995 42.54 9.25316C42.2024 9.49146 41.9541 9.85884 41.7953 10.3553V18.666C41.8946 18.8249 42.0534 18.9838 42.2719 19.1426C42.4903 19.2816 42.7783 19.3512 43.1357 19.3512Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M69.9378 5.94673C70.4343 7.85314 70.8711 9.41202 71.2484 10.6234C71.6258 11.8149 71.9435 12.8177 72.2016 13.6319C72.4797 14.4461 72.6882 15.161 72.8272 15.7766C72.9861 16.3923 73.0655 17.0774 73.0655 17.832C73.4031 17.6135 73.7307 17.3852 74.0485 17.1469C74.3662 16.8887 74.6343 16.6504 74.8527 16.432H76.1038C75.5676 17.2462 75.0017 17.9213 74.4059 18.4575C73.8102 18.9738 73.2343 19.4306 72.6783 19.8278C72.3208 20.6022 71.7747 21.1483 71.0399 21.4661C70.3052 21.7838 69.5406 21.9427 68.7463 21.9427C67.8527 21.9427 67.0881 21.8235 66.4526 21.5852C65.8172 21.3271 65.2909 20.9895 64.8739 20.5724C64.4767 20.1356 64.1789 19.6391 63.9803 19.0831C63.7817 18.527 63.6824 17.9412 63.6824 17.3256C63.6824 16.6504 63.8413 16.1242 64.159 15.7469C64.4966 15.3497 64.8838 15.1511 65.3207 15.1511C66.5321 15.1511 67.1378 15.6376 67.1378 16.6107C67.1378 16.9284 67.0285 17.1965 66.8101 17.415C66.5917 17.6334 66.3136 17.7426 65.976 17.7426C65.8172 17.7426 65.6484 17.7228 65.4697 17.683C65.3108 17.6235 65.1817 17.5142 65.0824 17.3554C65.2413 18.0504 65.4994 18.5965 65.8569 18.9937C66.2342 19.3909 66.7108 19.5895 67.2867 19.5895C67.7832 19.5895 68.1505 19.4703 68.3888 19.232C68.6271 18.9738 68.7463 18.537 68.7463 17.9213C68.7463 17.266 68.6867 16.6802 68.5676 16.1639C68.4683 15.6277 68.3094 15.0419 68.091 14.4064C67.8725 13.7709 67.6044 13.0163 67.2867 12.1426C66.969 11.2489 66.6115 10.0971 66.2143 8.68719C65.2214 10.812 63.9108 12.1723 62.2824 12.7681C62.2824 12.927 62.2824 13.0858 62.2824 13.2447C62.3022 13.3837 62.3122 13.5326 62.3122 13.6915C62.3122 14.8433 62.2129 15.9256 62.0143 16.9384C61.8157 17.9313 61.4781 18.805 61.0015 19.5597C60.5448 20.2944 59.949 20.8802 59.2143 21.3171C58.4795 21.7342 57.5759 21.9427 56.5036 21.9427C55.7688 21.9427 55.0639 21.8335 54.3887 21.615C53.7333 21.3767 53.1475 20.9994 52.6312 20.4831C52.1149 19.9469 51.6979 19.2519 51.3801 18.3979C51.0823 17.5242 50.9333 16.4618 50.9333 15.2107C50.9333 14.3568 51.0227 13.4433 51.2014 12.4702C51.3801 11.4773 51.7078 10.5539 52.1844 9.69997C52.661 8.84606 53.3064 8.14109 54.1206 7.58505C54.9546 7.00916 56.0171 6.72121 57.3079 6.72121C58.6384 6.72121 59.7008 7.07866 60.4951 7.79356C61.3093 8.50847 61.8554 9.66025 62.1334 11.2489C62.8285 11.0901 63.454 10.6135 64.0101 9.81912C64.586 9.00493 65.1321 7.91271 65.6484 6.54249L69.9378 5.94673ZM57.3376 19.1426C57.7547 19.1426 58.1121 19.0036 58.41 18.7256C58.7277 18.4277 58.9859 18.0306 59.1845 17.5341C59.3831 17.0178 59.5221 16.4121 59.6015 15.7171C59.7008 15.022 59.7504 14.2674 59.7504 13.4532V13.066C58.9561 12.8872 58.5589 12.3014 58.5589 11.3085C58.5589 10.673 58.8171 10.256 59.3334 10.0574C59.115 9.42195 58.8469 9.00493 58.5291 8.80634C58.2313 8.60776 57.9731 8.50847 57.7547 8.50847C57.3178 8.50847 56.9405 8.71698 56.6227 9.13401C56.3249 9.53117 56.0766 10.0475 55.8781 10.683C55.6795 11.2986 55.5305 11.9837 55.4312 12.7383C55.3518 13.4929 55.3121 14.2178 55.3121 14.9128C55.3121 15.8064 55.3717 16.5313 55.4908 17.0873C55.6298 17.6433 55.7986 18.0703 55.9972 18.3682C56.1958 18.666 56.4142 18.8745 56.6525 18.9937C56.8908 19.093 57.1192 19.1426 57.3376 19.1426Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M78.797 2.16371V6.87015H80.6141V8.06165H78.797V16.9979C78.797 17.832 78.9063 18.388 79.1247 18.666C79.363 18.9242 79.7701 19.0533 80.346 19.0533C80.9219 19.0533 81.3985 18.805 81.7758 18.3086C82.173 17.8121 82.4013 17.1866 82.4609 16.432H83.712C83.5531 17.6433 83.2751 18.6164 82.8779 19.3512C82.4808 20.0661 82.034 20.6221 81.5375 21.0193C81.041 21.3966 80.5347 21.6448 80.0183 21.7639C79.502 21.8831 79.0453 21.9427 78.6481 21.9427C77.119 21.9427 76.0467 21.5256 75.431 20.6916C74.8154 19.8377 74.5076 18.7157 74.5076 17.3256V8.06165H73.5544V6.87015H74.5076V2.75946L78.797 2.16371Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M82.1214 2.9084C82.1214 2.25307 82.3498 1.69704 82.8065 1.24029C83.2632 0.763692 83.8193 0.525391 84.4746 0.525391C85.1299 0.525391 85.686 0.763692 86.1427 1.24029C86.6193 1.69704 86.8576 2.25307 86.8576 2.9084C86.8576 3.56373 86.6193 4.11976 86.1427 4.5765C85.686 5.03325 85.1299 5.26162 84.4746 5.26162C83.8193 5.26162 83.2632 5.03325 82.8065 4.5765C82.3498 4.11976 82.1214 3.56373 82.1214 2.9084ZM86.7385 6.87015V16.9979C86.7385 17.832 86.8477 18.388 87.0661 18.666C87.3044 18.9242 87.7115 19.0533 88.2874 19.0533C88.5456 19.0533 88.7342 19.0334 88.8534 18.9937C88.9924 18.954 89.1115 18.9143 89.2108 18.8745C89.2307 18.9738 89.2406 19.0731 89.2406 19.1724C89.2406 19.2717 89.2406 19.371 89.2406 19.4703C89.2406 19.9668 89.1513 20.3739 88.9725 20.6916C88.8137 21.0093 88.5952 21.2675 88.3172 21.4661C88.059 21.6448 87.7711 21.7639 87.4534 21.8235C87.1555 21.903 86.8675 21.9427 86.5895 21.9427C85.0604 21.9427 83.9881 21.5256 83.3725 20.6916C82.7569 19.8377 82.449 18.7157 82.449 17.3256V6.87015H86.7385ZM98.1471 19.0533C97.9088 19.0334 97.7301 18.954 97.6109 18.815C97.4918 18.6561 97.4322 18.4774 97.4322 18.2788C97.4322 18.0206 97.5414 17.7724 97.7599 17.5341C97.9783 17.2759 98.3358 17.1469 98.8322 17.1469C99.3883 17.1469 99.8053 17.3355 100.083 17.7128C100.361 18.0703 100.5 18.4972 100.5 18.9937C100.5 19.3114 100.441 19.6391 100.322 19.9767C100.202 20.2944 100.014 20.5923 99.7556 20.8703C99.4975 21.1285 99.1797 21.3469 98.8024 21.5256C98.4251 21.6845 97.9882 21.7639 97.4918 21.7639H89.2704L95.1088 8.47868H92.9045C92.4676 8.47868 92.1002 8.50847 91.8023 8.56804C91.5243 8.60776 91.3853 8.71698 91.3853 8.89571C91.3853 8.97514 91.4052 9.01486 91.4449 9.01486C91.5045 9.01486 91.564 9.03471 91.6236 9.07443C91.7031 9.11415 91.7626 9.19358 91.8023 9.31273C91.8619 9.43188 91.8917 9.6404 91.8917 9.93827C91.8917 10.3752 91.7527 10.6929 91.4747 10.8915C91.2165 11.0901 90.9187 11.1894 90.5811 11.1894C90.1839 11.1894 89.7966 11.0603 89.4193 10.8021C89.0619 10.5241 88.8832 10.1071 88.8832 9.55103C88.8832 9.25316 88.9427 8.95528 89.0619 8.6574C89.181 8.33967 89.3598 8.05172 89.5981 7.79356C89.8364 7.51555 90.1342 7.2971 90.4917 7.13824C90.8491 6.95951 91.2662 6.87015 91.7428 6.87015H99.9344L94.2449 19.4703C94.3641 19.4703 94.5329 19.4802 94.7513 19.5001C94.9698 19.5199 95.1981 19.5398 95.4364 19.5597C95.6946 19.5795 95.9428 19.5994 96.1811 19.6192C96.4393 19.6391 96.6677 19.649 96.8662 19.649C97.2237 19.649 97.5216 19.6093 97.7599 19.5299C98.018 19.4504 98.1471 19.2916 98.1471 19.0533Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M5.02707 4.73535C5.04984 5.1031 5.08195 5.51796 5.11859 5.9915L6.44189 23.0906C6.5898 25.0018 6.66375 25.9574 7.09218 26.6586C7.46903 27.2753 8.03148 27.757 8.69889 28.0345C9.45764 28.3499 10.4132 28.2759 12.3244 28.128L25.4208 27.1145C26.6598 27.0186 27.4972 26.9538 28.124 26.8034C27.9138 27.2969 27.5895 27.7366 27.1746 28.0846C26.545 28.6126 25.6111 28.828 23.7433 29.2589L10.9438 32.2113C9.07597 32.6422 8.14206 32.8576 7.34479 32.6586C6.6435 32.4837 6.01561 32.0911 5.55111 31.5373C5.02305 30.9078 4.80762 29.9739 4.37678 28.106L0.521985 11.3946C0.0911391 9.52677 -0.124284 8.59286 0.0746573 7.79559C0.249651 7.0943 0.642167 6.46641 1.19595 6.00191C1.82552 5.47385 2.75943 5.25842 4.62725 4.82758L5.02707 4.73535Z\"\n        fill=\"#612BD3\"\n      />\n      <path\n        d=\"M18.5599 14.4471C18.3196 14.7119 18.0123 14.8588 17.6378 14.8878C17.3786 14.9078 17.1659 14.8735 16.9998 14.785C16.8324 14.682 16.7083 14.5757 16.6274 14.4661L16.1615 8.43966C16.2489 8.07075 16.4083 7.79043 16.6397 7.5987C16.87 7.39257 17.1148 7.27949 17.374 7.25945C17.4748 7.25165 17.6138 7.26988 17.7911 7.31412C17.9827 7.35725 18.1811 7.48677 18.3861 7.7027C18.59 7.90423 18.7746 8.23039 18.9398 8.68117C19.1194 9.13084 19.2399 9.75168 19.3011 10.5437C19.3389 11.0333 19.3413 11.5329 19.3083 12.0424C19.2886 12.5365 19.2151 12.9913 19.0879 13.4067C18.975 13.821 18.799 14.1678 18.5599 14.4471Z\"\n        fill=\"white\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M6.10722 5.22211C6.00627 3.91762 5.95579 3.26537 6.1711 2.74748C6.36049 2.29192 6.68924 1.90802 7.11024 1.65079C7.58884 1.35836 8.24108 1.30788 9.54557 1.20693L24.0205 0.0867131C25.325 -0.0142411 25.9772 -0.0647182 26.4951 0.150593C26.9507 0.339986 27.3346 0.668737 27.5918 1.08973C27.8843 1.56833 27.9347 2.22058 28.0357 3.52507L29.4655 22.0008C29.5665 23.3052 29.617 23.9575 29.4016 24.4754C29.2123 24.9309 28.8835 25.3148 28.4625 25.5721C27.9839 25.8645 27.3317 25.915 26.0272 26.0159L11.5522 27.1362C10.2477 27.2371 9.59548 27.2876 9.0776 27.0723C8.62205 26.8829 8.23814 26.5541 7.98091 26.1331C7.68848 25.6545 7.63801 25.0023 7.53705 23.6978L6.10722 5.22211ZM16.0296 6.73324L15.9043 5.11323L12.7939 5.35371L14.1082 22.3531L17.1585 21.335L16.7527 16.0861C16.9267 16.2755 17.1782 16.4371 17.5072 16.571C17.8352 16.6905 18.2727 16.7291 18.8199 16.6868C19.5832 16.6278 20.204 16.4132 20.6824 16.0431C21.174 15.6574 21.5499 15.1792 21.81 14.6087C22.0701 14.0381 22.231 13.4027 22.2928 12.7026C22.369 12.0014 22.3803 11.3052 22.3269 10.614C22.2523 9.64915 22.088 8.83614 21.8341 8.17492C21.5802 7.5137 21.2648 6.99485 20.8881 6.61837C20.5246 6.22637 20.1124 5.95403 19.6515 5.80134C19.205 5.64754 18.737 5.58956 18.2474 5.62742C17.7146 5.66861 17.2682 5.79728 16.9083 6.01343C16.5472 6.21518 16.2543 6.45511 16.0296 6.73324Z\"\n        fill=\"white\"\n      />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/ui/translated-label.tsx",
    "content": "import { ReactNode } from 'react';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\ninterface TranslatedLabelProps {\n  label: string;\n  translationKey?: string;\n  translationParams?: Record<string, string | number>;\n  children?: ReactNode;\n}\n\n/**\n * TranslatedLabel is a wrapper component that translates labels in form components\n *\n * @param label - The original label text (fallback if no translation found)\n * @param translationKey - Optional custom translation key, defaults to normalized label text\n * @param translationParams - Optional parameters for translation interpolation\n * @param children - Optional children components\n */\nexport function TranslatedLabel({\n  label,\n  translationKey,\n  translationParams = {},\n  children,\n}: TranslatedLabelProps) {\n  const t = useT();\n\n  // If no explicit key is provided, create one from the label\n  const key =\n    translationKey ||\n    `label_${label.toLowerCase().replace(/\\s+/g, '_').replace(/[^\\w]/g, '')}`;\n\n  const translatedLabel = t(key, label, translationParams);\n\n  return (\n    <>\n      {translatedLabel}\n      {children}\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/frontend/src/components/videos/providers/image-text-slides.provider.tsx",
    "content": "import { videoWrapper } from '@gitroom/frontend/components/videos/video.wrapper';\nimport { FC, useCallback, useRef, useState, useEffect } from 'react';\nimport { useVideoFunction } from '@gitroom/frontend/components/videos/video.render.component';\nimport useSWR from 'swr';\nimport { useFormContext } from 'react-hook-form';\nimport { Button } from '@gitroom/react/form/button';\nimport clsx from 'clsx';\nimport { useVideo } from '@gitroom/frontend/components/videos/video.context.wrapper';\n\nexport interface Voices {\n  voices: Voice[];\n}\n\nexport interface Voice {\n  id: string;\n  name: string;\n  preview_url: string;\n}\n\nconst VoiceSelector: FC = () => {\n  const { register, watch, setValue } = useFormContext();\n  const videoFunction = useVideoFunction();\n  const [currentlyPlaying, setCurrentlyPlaying] = useState<string | null>(null);\n  const [loadingVoice, setLoadingVoice] = useState<string | null>(null);\n  const audioRef = useRef<HTMLAudioElement | null>(null);\n  const { value } = useVideo();\n\n  register('prompt', {\n    value,\n  });\n\n  const loadVideos = useCallback(() => {\n    return videoFunction('loadVoices', {});\n  }, []);\n\n  const selectedVoice = watch('voice');\n  const { isLoading, data } = useSWR<Voices>('load-voices', loadVideos);\n\n  // Auto-select first voice when data loads\n  useEffect(() => {\n    if (data?.voices?.length && !selectedVoice) {\n      setValue('voice', data.voices[0].id);\n    }\n  }, [data, selectedVoice, setValue]);\n\n  const playVoice = useCallback(\n    async (voiceId: string, previewUrl: string) => {\n      try {\n        setLoadingVoice(voiceId);\n\n        // Stop current audio if playing\n        if (audioRef.current) {\n          audioRef.current.pause();\n          audioRef.current = null;\n        }\n\n        // If clicking the same voice that's playing, stop it\n        if (currentlyPlaying === voiceId) {\n          setCurrentlyPlaying(null);\n          setLoadingVoice(null);\n          return;\n        }\n\n        // Create new audio element\n        const audio = new Audio(previewUrl);\n        audioRef.current = audio;\n\n        audio.addEventListener('loadeddata', () => {\n          setLoadingVoice(null);\n          setCurrentlyPlaying(voiceId);\n        });\n\n        audio.addEventListener('ended', () => {\n          setCurrentlyPlaying(null);\n          audioRef.current = null;\n        });\n\n        audio.addEventListener('error', () => {\n          setLoadingVoice(null);\n          setCurrentlyPlaying(null);\n          audioRef.current = null;\n        });\n\n        await audio.play();\n      } catch (error) {\n        console.error('Error playing voice:', error);\n        setLoadingVoice(null);\n        setCurrentlyPlaying(null);\n      }\n    },\n    [currentlyPlaying]\n  );\n\n  const selectVoice = useCallback(\n    (voiceId: string) => {\n      setValue('voice', voiceId);\n    },\n    [setValue]\n  );\n\n  if (isLoading || !data?.voices?.length) {\n    return (\n      <div className=\"flex items-center justify-center py-4\">\n        <div className=\"text-sm text-gray-500\">Loading voices...</div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"text-sm font-medium text-textColor mb-4\">\n        Select a Voice\n      </div>\n      <div className=\"space-y-2\">\n        {data.voices.map((voice) => (\n          <div\n            key={voice.id}\n            className={clsx(\n              'flex items-center justify-between p-3 rounded-lg border transition-colors cursor-pointer',\n              selectedVoice === voice.id\n                ? 'border-primary bg-primary/10'\n                : 'border-tableBorder bg-sixth hover:bg-seventh'\n            )}\n            onClick={() => selectVoice(voice.id)}\n          >\n            <div className=\"flex items-center space-x-3\">\n              <input\n                {...register('voice')}\n                type=\"radio\"\n                value={voice.id}\n                className=\"w-4 h-4 text-primary border-gray-300 focus:ring-primary\"\n                checked={selectedVoice === voice.id}\n                onChange={() => selectVoice(voice.id)}\n              />\n              <div>\n                <div className=\"text-sm font-medium text-textColor\">\n                  {voice.name}\n                </div>\n              </div>\n            </div>\n\n            <Button\n              type=\"button\"\n              className={clsx(\n                'px-3 py-1 text-xs',\n                loadingVoice === voice.id && 'opacity-50 cursor-not-allowed',\n                currentlyPlaying === voice.id && 'bg-red-500 hover:bg-red-600'\n              )}\n              onClick={(e) => {\n                e.stopPropagation();\n                playVoice(voice.id, voice.preview_url);\n              }}\n              disabled={loadingVoice === voice.id}\n            >\n              {loadingVoice === voice.id\n                ? '...'\n                : currentlyPlaying === voice.id\n                ? '⏹ Stop'\n                : '▶ Play'}\n            </Button>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nconst ImageSlidesComponent = () => {\n  return <VoiceSelector />;\n};\n\nvideoWrapper('image-text-slides', ImageSlidesComponent);\n"
  },
  {
    "path": "apps/frontend/src/components/videos/providers/veo3.provider.tsx",
    "content": "import { videoWrapper } from '@gitroom/frontend/components/videos/video.wrapper';\nimport { FC, useState } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { useVideo } from '@gitroom/frontend/components/videos/video.context.wrapper';\nimport { Textarea } from '@gitroom/react/form/textarea';\nimport { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';\n\nexport interface Voice {\n  id: string;\n  name: string;\n  preview_url: string;\n}\n\nconst VEO3Settings: FC = () => {\n  const { register, watch, setValue, formState } = useFormContext();\n  const { value } = useVideo();\n\n  const media = register('media', {\n    value: [],\n  });\n\n  const mediaValue = watch('media');\n\n  return (\n    <div>\n      <Textarea\n        label=\"Prompt\"\n        name=\"prompt\"\n        {...register('prompt', {\n          required: true,\n          minLength: 5,\n          value,\n        })}\n        error={formState?.errors?.prompt?.message}\n      />\n      <div className=\"mb-[6px]\">Images (max 3)</div>\n      <MultiMediaComponent\n        allData={[]}\n        dummy={true}\n        text=\"Images\"\n        description=\"Images\"\n        name=\"images\"\n        label=\"Media\"\n        value={mediaValue}\n        onChange={(val) =>\n          setValue(\n            'images',\n            val.target.value\n              .filter((f) => f.path.indexOf('mp4') === -1)\n              .slice(0, 3)\n          )\n        }\n        error={formState?.errors?.media?.message}\n      />\n    </div>\n  );\n};\n\nconst VeoComponent = () => {\n  return <VEO3Settings />;\n};\n\nvideoWrapper('veo3', VeoComponent);\n"
  },
  {
    "path": "apps/frontend/src/components/videos/video.context.wrapper.tsx",
    "content": "import { createContext, useContext } from 'react';\n\nexport const VideoContextWrapper = createContext({value: ''});\nexport const useVideo = () => useContext(VideoContextWrapper);"
  },
  {
    "path": "apps/frontend/src/components/videos/video.render.component.tsx",
    "content": "import { createContext, FC, useCallback, useContext, useEffect } from 'react';\nimport './providers/image-text-slides.provider';\nimport './providers/veo3.provider';\nimport { videosList } from '@gitroom/frontend/components/videos/video.wrapper';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';\n\nconst VideoFunctionWrapper = createContext({\n  identifier: '',\n});\n\nexport const useVideoFunction = () => {\n  const { identifier } = useContext(VideoFunctionWrapper);\n  const fetch = useFetch();\n\n  return useCallback(\n    async (funcName: string, params: any) => {\n      return (\n        await fetch(`/media/video/function`, {\n          method: 'POST',\n          body: JSON.stringify({ identifier, functionName: funcName, params }),\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        })\n      ).json();\n    },\n    [identifier]\n  );\n};\n\nexport const VideoWrapper: FC<{ identifier: string }> = (props) => {\n  const setActivateExitButton = useLaunchStore((e) => e.setActivateExitButton);\n  useEffect(() => {\n    setActivateExitButton(false);\n    return () => {\n      setActivateExitButton(true);\n    };\n  }, []);\n\n  const { identifier } = props;\n  const Component = videosList.find(\n    (v) => v.identifier === identifier\n  )?.Component;\n  if (!Component) {\n    return null;\n  }\n  return (\n    <VideoFunctionWrapper.Provider value={{ identifier }}>\n      <Component />\n    </VideoFunctionWrapper.Provider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/components/videos/video.wrapper.tsx",
    "content": "import { FC } from 'react';\n\nexport const videosList: {identifier: string, Component: FC}[] = [];\n\nexport const videoWrapper = (identifier: string, Component: any): null => {\n  if (videosList.map(p => p.identifier).includes(identifier)) {\n    return null;\n  }\n\n  videosList.push({\n    identifier,\n    Component\n  });\n\n  return null;\n}"
  },
  {
    "path": "apps/frontend/src/components/webhooks/webhooks.tsx",
    "content": "'use client';\n\nimport React, { FC, Fragment, useCallback, useState } from 'react';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport useSWR from 'swr';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { Button } from '@gitroom/react/form/button';\nimport { useModals } from '@gitroom/frontend/components/layout/new-modal';\nimport { Input } from '@gitroom/react/form/input';\nimport { FormProvider, useForm } from 'react-hook-form';\nimport { array, object, string } from 'yup';\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport { Select } from '@gitroom/react/form/select';\nimport { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';\nimport { useToaster } from '@gitroom/react/toaster/toaster';\nimport clsx from 'clsx';\nimport { deleteDialog } from '@gitroom/react/helpers/delete.dialog';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\n\nexport const Webhooks: FC = () => {\n  const fetch = useFetch();\n  const user = useUser();\n  const modal = useModals();\n  const toaster = useToaster();\n  const t = useT();\n  const list = useCallback(async () => {\n    return (await fetch('/webhooks')).json();\n  }, []);\n  const { data, mutate } = useSWR('webhooks', list);\n  const addWebhook = useCallback(\n    (data?: any) => () => {\n      modal.openModal({\n        title: data ? t('update_webhook', 'Update webhook') : t('add_webhook', 'Add webhook'),\n        withCloseButton: true,\n        children: <AddOrEditWebhook data={data} reload={mutate} />,\n      });\n    },\n    [t]\n  );\n  const deleteHook = useCallback(\n    (data: any) => async () => {\n      if (\n        await deleteDialog(\n          t(\n            'are_you_sure_you_want_to_delete',\n            `Are you sure you want to delete ${data.name}?`,\n            { name: data.name }\n          )\n        )\n      ) {\n        await fetch(`/webhooks/${data.id}`, {\n          method: 'DELETE',\n        });\n        mutate();\n        toaster.show(t('webhook_deleted_successfully', 'Webhook deleted successfully'), 'success');\n      }\n    },\n    []\n  );\n\n  return (\n    <div className=\"flex flex-col\">\n      <h3 className=\"text-[20px]\">\n        {t('webhooks', 'Webhooks')} ({data?.length || 0}/{user?.tier?.webhooks})\n      </h3>\n      <div className=\"text-customColor18 mt-[4px]\">\n        {t(\n          'webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request',\n          'Webhooks are a way to get notified when something happens in Postiz via\\n        an HTTP request.'\n        )}\n      </div>\n      <div className=\"my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]\">\n        <div className=\"flex flex-col w-full\">\n          {!!data?.length && (\n            <div className=\"grid grid-cols-[1fr,1fr,1fr,1fr] w-full gap-y-[10px]\">\n              <div>{t('name', 'Name')}</div>\n              <div>{t('url', 'URL')}</div>\n              <div>{t('edit', 'Edit')}</div>\n              <div>{t('delete', 'Delete')}</div>\n              {data?.map((p: any) => (\n                <Fragment key={p.id}>\n                  <div className=\"flex flex-col justify-center\">{p.name}</div>\n                  <div className=\"flex flex-col justify-center\">{p.url}</div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={addWebhook(p)}>\n                        {t('edit', 'Edit')}\n                      </Button>\n                    </div>\n                  </div>\n                  <div className=\"flex flex-col justify-center\">\n                    <div>\n                      <Button onClick={deleteHook(p)}>\n                        {t('delete', 'Delete')}\n                      </Button>\n                    </div>\n                  </div>\n                </Fragment>\n              ))}\n            </div>\n          )}\n          <div>\n            <Button\n              onClick={addWebhook()}\n              className={clsx((data?.length || 0) > 0 && 'my-[16px]')}\n            >\n              {t('add_a_webhook', 'Add a webhook')}\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\nconst details = object().shape({\n  name: string().required(),\n  url: string().url().required(),\n  integrations: array(),\n});\nconst getWebhookOptions = (t: (key: string, fallback: string) => string) => [\n  {\n    label: t('all_integrations', 'All integrations'),\n    value: 'all',\n  },\n  {\n    label: t('specific_integrations', 'Specific integrations'),\n    value: 'specific',\n  },\n];\nexport const AddOrEditWebhook: FC<{\n  data?: any;\n  reload: () => void;\n}> = (props) => {\n  const { data, reload } = props;\n  const fetch = useFetch();\n  const t = useT();\n  const options = getWebhookOptions(t);\n  const [allIntegrations, setAllIntegrations] = useState(\n    (data?.integrations?.length || 0) > 0 ? options[1] : options[0]\n  );\n  const modal = useModals();\n  const toast = useToaster();\n  const form = useForm({\n    resolver: yupResolver(details),\n    values: {\n      name: data?.name || '',\n      url: data?.url || '',\n      integrations: data?.integrations?.map((p: any) => p.integration) || [],\n    },\n  });\n  const integrations = form.watch('integrations');\n  const integration = useCallback(async () => {\n    return (await fetch('/integrations/list')).json();\n  }, []);\n  const changeIntegration = useCallback(\n    (e: React.ChangeEvent<HTMLSelectElement>) => {\n      const findValue = options.find(\n        (option) => option.value === e.target.value\n      )!;\n      setAllIntegrations(findValue);\n      if (findValue.value === 'all') {\n        form.setValue('integrations', []);\n      }\n    },\n    []\n  );\n  const { data: dataList, isLoading } = useSWR('integrations', integration, {\n    revalidateOnFocus: false,\n    revalidateOnReconnect: false,\n    revalidateIfStale: false,\n    revalidateOnMount: true,\n    refreshWhenHidden: false,\n    refreshWhenOffline: false,\n  });\n  const callBack = useCallback(\n    async (values: any) => {\n      await fetch('/webhooks', {\n        method: data?.id ? 'PUT' : 'POST',\n        body: JSON.stringify({\n          ...(data?.id\n            ? {\n                id: data.id,\n              }\n            : {}),\n          ...values,\n        }),\n      });\n      toast.show(\n        data?.id\n          ? t('webhook_updated_successfully', 'Webhook updated successfully')\n          : t('webhook_added_successfully', 'Webhook added successfully'),\n        'success'\n      );\n      modal.closeAll();\n      reload();\n    },\n    [data, integrations]\n  );\n  const sendTest = useCallback(async () => {\n    const url = form.getValues('url');\n    toast.show(t('webhook_sent', 'Webhook send'), 'success');\n    try {\n      await fetch(`/webhooks/send?url=${encodeURIComponent(url)}`, {\n        method: 'POST',\n        headers: {\n          contentType: 'application/json',\n        },\n        body: JSON.stringify([\n          {\n            id: 'cm6tcts4f0005qcwit25cis26',\n            content: 'This is the first post to instagram',\n            publishDate: '2025-02-06T13:09:00.000Z',\n            releaseURL: 'https://facebook.com/release/release',\n            state: 'PUBLISHED',\n            integration: {\n              id: 'cm6s4uyou0001i2r47pxix6z1',\n              name: 'test',\n              providerIdentifier: 'instagram',\n              picture: 'https://uploads.gitroom.com/F6LSCD8wrrQ.jpeg',\n              type: 'social',\n            },\n          },\n          {\n            id: 'cm6tcts4f0005qcwit25cis26',\n            content: 'This is the second post to facebook',\n            publishDate: '2025-02-06T13:09:00.000Z',\n            releaseURL: 'https://facebook.com/release2/release2',\n            state: 'PUBLISHED',\n            integration: {\n              id: 'cm6s4uyou0001i2r47pxix6z1',\n              name: 'test2',\n              providerIdentifier: 'facebook',\n              picture: 'https://uploads.gitroom.com/F6LSCD8wrrQ.jpeg',\n              type: 'social',\n            },\n          },\n        ]),\n      });\n    } catch (e: any) {\n      /** empty **/\n    }\n  }, []);\n\n  return (\n    <FormProvider {...form}>\n      <form onSubmit={form.handleSubmit(callBack)}>\n        <div className=\"relative flex gap-[20px] flex-col flex-1 rounded-[4px] pt-0\">\n          <div>\n            <Input\n              label=\"Name\"\n              translationKey=\"label_name\"\n              {...form.register('name')}\n            />\n            <Input\n              label=\"URL\"\n              translationKey=\"label_url\"\n              {...form.register('url')}\n            />\n            <Select\n              value={allIntegrations.value}\n              name=\"integrations\"\n              label=\"Integrations\"\n              translationKey=\"label_integrations\"\n              disableForm={true}\n              onChange={changeIntegration}\n            >\n              {options.map((option) => (\n                <option key={option.value} value={option.value}>\n                  {option.label}\n                </option>\n              ))}\n            </Select>\n            {allIntegrations.value === 'specific' && dataList && !isLoading && (\n              <PickPlatforms\n                integrations={dataList.integrations}\n                selectedIntegrations={integrations as any[]}\n                onChange={(e) => form.setValue('integrations', e)}\n                singleSelect={false}\n                toolTip={true}\n                isMain={true}\n              />\n            )}\n            <div className=\"flex gap-[10px]\">\n              <Button\n                type=\"submit\"\n                className=\"mt-[24px]\"\n                disabled={\n                  !form.formState.isValid ||\n                  (allIntegrations.value === 'specific' &&\n                    !integrations?.length)\n                }\n              >\n                {t('save', 'Save')}\n              </Button>\n              <Button\n                type=\"button\"\n                secondary={true}\n                className=\"mt-[24px]\"\n                onClick={sendTest}\n                disabled={\n                  !form.formState.isValid ||\n                  (allIntegrations.value === 'specific' &&\n                    !integrations?.length)\n                }\n              >\n                {t('send_test', 'Send Test')}\n              </Button>\n            </div>\n          </div>\n        </div>\n      </form>\n    </FormProvider>\n  );\n};\n"
  },
  {
    "path": "apps/frontend/src/instrumentation.ts",
    "content": "export async function register() {\n  if (!process.env.NEXT_PUBLIC_SENTRY_DSN) {\n    return;\n  }\n  if (process.env.NEXT_RUNTIME === 'nodejs') {\n    await import('./sentry.server.config');\n  }\n  if (process.env.NEXT_RUNTIME === 'edge') {\n    await import('./sentry.edge.config');\n  }\n}\n"
  },
  {
    "path": "apps/frontend/src/middleware.ts",
    "content": "import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';\nimport { internalFetch } from '@gitroom/helpers/utils/internal.fetch';\nimport acceptLanguage from 'accept-language';\nimport {\n  cookieName,\n  fallbackLng,\n  headerName,\n  languages,\n} from '@gitroom/react/translation/i18n.config';\nacceptLanguage.languages(languages);\n\n// This function can be marked `async` if using `await` inside\nexport async function middleware(request: NextRequest) {\n  const nextUrl = request.nextUrl;\n  const authCookie =\n    request.cookies.get('auth') ||\n    request.headers.get('auth') ||\n    nextUrl.searchParams.get('loggedAuth');\n  const lng = request.cookies.has(cookieName)\n    ? acceptLanguage.get(request.cookies.get(cookieName).value)\n    : acceptLanguage.get(\n        request.headers.get('Accept-Language') ||\n          request.headers.get('accept-language')\n      );\n\n  const topResponse = NextResponse.next();\n\n  if (lng) {\n    topResponse.headers.set(cookieName, lng);\n  }\n\n  if (nextUrl.pathname.startsWith('/modal/') && !authCookie) {\n    return NextResponse.redirect(new URL(`/auth/login-required`, nextUrl.href));\n  }\n\n  if (\n    nextUrl.pathname.startsWith('/uploads/') ||\n    nextUrl.pathname.startsWith('/p/') ||\n    nextUrl.pathname.startsWith('/icons/')\n  ) {\n    return topResponse;\n  }\n\n  if (\n    nextUrl.pathname.startsWith('/integrations/social/') &&\n    nextUrl.href.indexOf('state=login') === -1\n  ) {\n    return topResponse;\n  }\n\n  // If the URL is logout, delete the cookie and redirect to login\n  if (nextUrl.href.indexOf('/auth/logout') > -1) {\n    const response = NextResponse.redirect(\n      new URL('/auth/login', nextUrl.href)\n    );\n    response.cookies.set('auth', '', {\n      path: '/',\n      ...(!process.env.NOT_SECURED\n        ? {\n            secure: true,\n            httpOnly: true,\n            sameSite: false,\n          }\n        : {}),\n      maxAge: -1,\n      domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n    });\n    return response;\n  }\n\n  const org = nextUrl.searchParams.get('org');\n  const url = new URL(nextUrl).search;\n  if (!nextUrl.pathname.startsWith('/auth') && !authCookie) {\n    const providers = ['google', 'settings'];\n    const findIndex = providers.find((p) => nextUrl.href.indexOf(p) > -1);\n    const additional = !findIndex\n      ? ''\n      : (url.indexOf('?') > -1 ? '&' : '?') +\n        `provider=${(findIndex === 'settings'\n          ? process.env.POSTIZ_GENERIC_OAUTH\n            ? 'generic'\n            : 'github'\n          : findIndex\n        ).toUpperCase()}`;\n    return NextResponse.redirect(\n      new URL(`/auth${url}${additional}`, nextUrl.href)\n    );\n  }\n\n  // If the url is /auth and the cookie exists, redirect to /\n  if (nextUrl.pathname.startsWith('/auth') && authCookie) {\n    return NextResponse.redirect(new URL(`/${url}`, nextUrl.href));\n  }\n  if (nextUrl.pathname.startsWith('/auth') && !authCookie) {\n    if (org) {\n      const redirect = NextResponse.redirect(new URL(`/`, nextUrl.href));\n      redirect.cookies.set('org', org, {\n        ...(!process.env.NOT_SECURED\n          ? {\n              path: '/',\n              secure: true,\n              httpOnly: true,\n              sameSite: false,\n              domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n            }\n          : {}),\n        expires: new Date(Date.now() + 15 * 60 * 1000),\n      });\n      return redirect;\n    }\n    return topResponse;\n  }\n  try {\n    if (org) {\n      const { id } = await (\n        await internalFetch('/user/join-org', {\n          body: JSON.stringify({\n            org,\n          }),\n          method: 'POST',\n        })\n      ).json();\n      const redirect = NextResponse.redirect(\n        new URL(`/?added=true`, nextUrl.href)\n      );\n      if (id) {\n        redirect.cookies.set('showorg', id, {\n          ...(!process.env.NOT_SECURED\n            ? {\n                path: '/',\n                secure: true,\n                httpOnly: true,\n                sameSite: false,\n                domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!),\n              }\n            : {}),\n          expires: new Date(Date.now() + 15 * 60 * 1000),\n        });\n      }\n      return redirect;\n    }\n    if (nextUrl.pathname === '/') {\n      return NextResponse.redirect(\n        new URL(\n          !!process.env.IS_GENERAL ? '/launches' : `/analytics`,\n          nextUrl.href\n        )\n      );\n    }\n\n    return topResponse;\n  } catch (err) {\n    console.log('err', err);\n    return NextResponse.redirect(new URL('/auth/logout', nextUrl.href));\n  }\n}\n\n// See \"Matching Paths\" below to learn more\nexport const config = {\n  matcher: '/((?!api/|_next/|_static/|_vercel|[\\\\w-]+\\\\.\\\\w+).*)',\n};\n"
  },
  {
    "path": "apps/frontend/src/sentry.edge.config.ts",
    "content": "import { initializeSentryServer } from '@gitroom/react/sentry/initialize.sentry.server';\n\ninitializeSentryServer(process.env.NODE_ENV, process.env.NEXT_PUBLIC_SENTRY_DSN);"
  },
  {
    "path": "apps/frontend/src/sentry.server.config.ts",
    "content": "import { initializeSentryServer } from '@gitroom/react/sentry/initialize.sentry.server';\n\ninitializeSentryServer(process.env.NODE_ENV!, process.env.NEXT_PUBLIC_SENTRY_DSN!);\n"
  },
  {
    "path": "apps/frontend/tailwind.config.js",
    "content": "const { join } = require('path');\nmodule.exports = {\n  darkMode: 'class',\n  content: ['./src/**/*.{ts,tsx,html}', '../../libraries/**/*.{ts,tsx,html}'],\n  theme: {\n    extend: {\n      colors: {\n        primary: 'var(--color-primary)',\n        secondary: 'var(--color-secondary)',\n        textColor: 'var(--new-btn-text)',\n        third: 'var(--color-third)',\n        forth: 'var(--color-forth)',\n        fifth: 'var(--color-fifth)',\n        sixth: 'var(--color-sixth)',\n        seventh: 'var(--color-seventh)',\n        gray: 'var(--color-gray)',\n        input: 'var(--color-input)',\n        inputText: 'var(--color-input-text)',\n        tableBorder: 'var(--color-table-border)',\n        customColor1: 'var(--color-custom1)',\n        customColor2: 'var(--color-custom2)',\n        customColor3: 'var(--color-custom3)',\n        customColor4: 'var(--color-custom4)',\n        customColor5: 'var(--color-custom5)',\n        customColor6: 'var(--color-custom6)',\n        customColor7: 'var(--color-custom7)',\n        customColor8: 'var(--color-custom8)',\n        customColor9: 'var(--color-custom9)',\n        customColor10: 'var(--color-custom10)',\n        customColor11: 'var(--color-custom11)',\n        customColor12: 'var(--color-custom12)',\n        customColor13: 'var(--color-custom13)',\n        customColor14: 'var(--color-custom14)',\n        customColor15: 'var(--color-custom15)',\n        customColor16: 'var(--color-custom16)',\n        customColor17: 'var(--color-custom17)',\n        customColor18: 'var(--color-custom18)',\n        customColor19: 'var(--color-custom19)',\n        customColor20: 'var(--color-custom20)',\n        customColor21: 'var(--color-custom21)',\n        customColor22: 'var(--color-custom22)',\n        customColor23: 'var(--color-custom23)',\n        customColor24: 'var(--color-custom24)',\n        customColor25: 'var(--color-custom25)',\n        customColor26: 'var(--color-custom26)',\n        customColor27: 'var(--color-custom27)',\n        customColor28: 'var(--color-custom28)',\n        customColor29: 'var(--color-custom29)',\n        customColor30: 'var(--color-custom30)',\n        customColor31: 'var(--color-custom31)',\n        customColor32: 'var(--color-custom32)',\n        customColor33: 'var(--color-custom33)',\n        customColor34: 'var(--color-custom34)',\n        customColor35: 'var(--color-custom35)',\n        customColor36: 'var(--color-custom36)',\n        customColor37: 'var(--color-custom37)',\n        customColor38: 'var(--color-custom38)',\n        customColor39: 'var(--color-custom39)',\n        customColor40: 'var(--color-custom40)',\n        customColor41: 'var(--color-custom41)',\n        customColor42: 'var(--color-custom42)',\n        customColor43: 'var(--color-custom43)',\n        customColor44: 'var(--color-custom44)',\n        customColor45: 'var(--color-custom45)',\n        customColor46: 'var(--color-custom46)',\n        customColor47: 'var(--color-custom47)',\n        customColor48: 'var(--color-custom48)',\n        customColor49: 'var(--color-custom49)',\n        customColor50: 'var(--color-custom50)',\n        customColor51: 'var(--color-custom51)',\n        customColor52: 'var(--color-custom52)',\n        customColor53: 'var(--color-custom53)',\n        customColor54: 'var(--color-custom54)',\n        customColor55: 'var(--color-custom55)',\n        modalCustom: 'var(--color-modalCustom)',\n\n        newBgColor: 'var(--new-bgColor)',\n        newBackdrop: 'var(--new-back-drop)',\n        newSep: 'var(--new-sep)',\n        newBorder: 'var(--new-border)',\n        newBgColorInner: 'var(--new-bgColorInner)',\n        newBgLineColor: 'var(--new-bgLineColor)',\n        textItemFocused: 'var(--new-textItemFocused)',\n        textItemBlur: 'var(--new-textItemBlur)',\n        boxFocused: 'var(--new-boxFocused)',\n        newTextColor: 'rgb(var(--new-textColor) / <alpha-value>)',\n        blockSeparator: 'var(--new-blockSeparator)',\n        btnSimple: 'var(--new-btn-simple)',\n        btnText: 'var(--new-btn-text)',\n        btnPrimary: 'var(--new-btn-primary)',\n        ai: 'var(--new-ai-btn)',\n        boxHover: 'var(--new-box-hover)',\n        newTableBorder: 'var(--new-table-border)',\n        newTableHeader: 'var(--new-table-header)',\n        newTableText: 'var(--new-table-text)',\n        newTableTextFocused: 'var(--new-table-text-focused)',\n        newColColor: 'var(--new-col-color)',\n        newSettings: 'var(--new-settings)',\n        menuDots: 'var(--new-menu-dots)',\n        menuDotsHover: 'var(--new-menu-hover)',\n        bigStrip: 'var(--new-big-strips)',\n        popup: 'var(--popup-color)',\n        bgLinkedin: 'var(--linkedin-bg)',\n        bgFacebook: 'var(--facebook-bg)',\n        bgInstagram: 'var(--instagram-bg)',\n        bgTiktokItem: 'var(--tiktok-item-bg)',\n        bgTiktokItemIcon: 'var(--tiktok-item-icon-bg)',\n        bgYoutube: 'var(--youtube-bg)',\n        bgCommentFacebook: 'var(--facebook-bg-comment)',\n        textLinkedin: 'var(--linkedin-text)',\n        borderPreview: 'var(--border-preview)',\n        borderLinkedin: 'var(--linkedin-border)',\n        youtubeButton: 'var(--youtube-button)',\n        youtubeBgAction: 'var(--youtube-action-color)',\n        youtubeSvg: 'var(--youtube-svg-border)',\n      },\n      gridTemplateColumns: {\n        13: 'repeat(13, minmax(0, 1fr));',\n      },\n      backgroundImage: {\n        loginBox: 'url(/auth/login-box.png)',\n        loginBg: 'url(/auth/bg-login.png)',\n      },\n      fontFamily: {\n        sans: ['Helvetica Neue'],\n      },\n      animation: {\n        fade: 'fadeOut 0.5s ease-in-out',\n        normalFadeIn: 'normalFadeIn 0.5s ease-in-out',\n        fadeIn: 'normalFadeIn 0.2s ease-in-out forwards',\n        normalFadeOut: 'normalFadeOut 0.5s linear 5s forwards',\n        overflow: 'overFlow 0.5s ease-in-out forwards',\n        overflowReverse: 'overFlowReverse 0.5s ease-in-out forwards',\n        fadeDown: 'fadeDown 4s ease-in-out forwards',\n        normalFadeDown: 'normalFadeDown 0.5s ease-in-out forwards',\n        newMessages: 'newMessages 1s ease-in-out 4s forwards',\n        marqueeUp: 'marquee-up 100s linear infinite',\n        marqueeDown: 'marquee-down 100s linear infinite',\n      },\n      boxShadow: {\n        yellow: '0 0 60px 20px #6b6237',\n        yellowToast: '0px 0px 50px rgba(252, 186, 3, 0.3)',\n        greenToast: '0px 0px 50px rgba(60, 124, 90, 0.3)',\n        menu: 'var(--menu-shadow)',\n        previewShadow: 'var(--preview-box-shadow)',\n      },\n      dropShadow: {\n        glow: [\n          '0 0 6px rgba(250,204,21,0.6)',\n          '0 0 12px rgba(250,204,21,0.5)',\n          '0 0 24px rgba(250,204,21,0.4)',\n        ],\n      },\n      // that is actual animation\n      keyframes: (theme) => ({\n        fadeOut: {\n          '0%': {\n            opacity: 0,\n            transform: 'translateY(30px)',\n          },\n          '100%': {\n            opacity: 1,\n            transform: 'translateY(0)',\n          },\n        },\n        normalFadeOut: {\n          '0%': {\n            opacity: 1,\n          },\n          '100%': {\n            opacity: 0,\n          },\n        },\n        normalFadeIn: {\n          '0%': {\n            opacity: 0,\n          },\n          '100%': {\n            opacity: 1,\n          },\n        },\n        overFlow: {\n          '0%': {\n            overflow: 'hidden',\n          },\n          '99%': {\n            overflow: 'hidden',\n          },\n          '100%': {\n            overflow: 'visible',\n          },\n        },\n        overFlowReverse: {\n          '0%': {\n            overflow: 'visible',\n          },\n          '99%': {\n            overflow: 'visible',\n          },\n          '100%': {\n            overflow: 'hidden',\n          },\n        },\n        fadeDown: {\n          '0%': {\n            opacity: 0,\n            marginTop: -30,\n          },\n          '10%': {\n            opacity: 1,\n            marginTop: 0,\n          },\n          '85%': {\n            opacity: 1,\n            marginTop: 0,\n          },\n          '90%': {\n            opacity: 1,\n            marginTop: 10,\n          },\n          '100%': {\n            opacity: 0,\n            marginTop: -30,\n          },\n        },\n        normalFadeDown: {\n          '0%': {\n            opacity: 0,\n            transform: 'translateY(-30px)',\n          },\n          '100%': {\n            opacity: 1,\n            transform: 'translateY(0)',\n          },\n        },\n        newMessages: {\n          '0%': {\n            backgroundColor: 'var(--color-seventh)',\n            fontWeight: 'bold',\n          },\n          '99%': {\n            backgroundColor: 'var(--color-third)',\n            fontWeight: 'bold',\n          },\n          '100%': {\n            backgroundColor: 'var(--color-third)',\n            fontWeight: 'normal',\n          },\n        },\n      }),\n      screens: {\n        mobile: {\n          raw: '(max-width: 1025px)',\n        },\n        tablet: {\n          raw: '(max-width: 1300px)',\n        },\n        iconBreak: {\n          raw: '(max-width: 1560px)',\n        },\n        maxMedia: {\n          raw: '(max-width: 1400px)',\n        },\n        minCustom: {\n          raw: '(min-height: 800px)',\n        },\n        custom: {\n          raw: '(max-height: 800px)',\n        },\n        xs: {\n          max: '401px',\n        },\n      },\n    },\n  },\n  plugins: [\n    require('tailwind-scrollbar'),\n    require('tailwindcss-rtl'),\n    function ({ addVariant }) {\n      addVariant('child', '& > *');\n      addVariant('child-hover', '& > *:hover');\n    },\n  ],\n};\n"
  },
  {
    "path": "apps/frontend/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\",\n    \"allowJs\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \"**/*.js\",\n    \"**/*.jsx\",\n    \"../../apps/frontend/.next/types/**/*.ts\",\n    \"../../dist/apps/frontend/.next/types/**/*.ts\",\n    \"next-env.d.ts\",\n    \".next/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"jest.config.ts\",\n    \"src/**/*.spec.ts\",\n    \"src/**/*.test.ts\"\n  ]\n}\n"
  },
  {
    "path": "apps/orchestrator/.gitignore",
    "content": "dist/\nnode_modules/\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n"
  },
  {
    "path": "apps/orchestrator/.swcrc",
    "content": "{\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"tsx\": false,\n      \"decorators\": true,\n      \"dynamicImport\": true\n    },\n    \"target\": \"es2020\",\n    \"baseUrl\": \"/Users/nevodavid/Projects/gitroom\",\n    \"paths\": {\n      \"@gitroom/backend/*\": [\"apps/backend/src/*\"],\n      \"@gitroom/cron/*\": [\"apps/cron/src/*\"],\n      \"@gitroom/frontend/*\": [\"apps/frontend/src/*\"],\n      \"@gitroom/helpers/*\": [\"libraries/helpers/src/*\"],\n      \"@gitroom/nestjs-libraries/*\": [\"libraries/nestjs-libraries/src/*\"],\n      \"@gitroom/react/*\": [\"libraries/react-shared-libraries/src/*\"],\n      \"@gitroom/plugins/*\": [\"libraries/plugins/src/*\"],\n      \"@gitroom/workers/*\": [\"apps/workers/src/*\"],\n      \"@gitroom/extension/*\": [\"apps/extension/src/*\"]\n    },\n    \"keepClassNames\": true,\n    \"transform\": {\n      \"legacyDecorator\": true,\n      \"decoratorMetadata\": true\n    },\n    \"loose\": true\n  },\n  \"module\": {\n    \"type\": \"commonjs\",\n    \"strict\": false,\n    \"strictMode\": true,\n    \"lazy\": false,\n    \"noInterop\": false\n  },\n  \"sourceMaps\": true,\n  \"minify\": false\n} "
  },
  {
    "path": "apps/orchestrator/nest-cli.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"monorepo\": false,\n  \"sourceRoot\": \"src\",\n  \"entryFile\": \"../../dist/orchestrator/apps/orchestrator/src/main\",\n  \"language\": \"ts\",\n  \"generateOptions\": {\n    \"spec\": false\n  },\n  \"compilerOptions\": {\n    \"manualRestart\": true,\n    \"tsConfigPath\": \"./tsconfig.build.json\",\n    \"webpack\": false,\n    \"deleteOutDir\": true,\n    \"assets\": [],\n    \"watchAssets\": false,\n    \"plugins\": []\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/package.json",
    "content": "{\n  \"name\": \"postiz-orchestrator\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"scripts\": {\n    \"dev\": \"dotenv -e ../../.env -- nest start --watch --entryFile=./apps/orchestrator/src/main\",\n    \"build\": \"cross-env NODE_ENV=production nest build\",\n    \"start\": \"dotenv -e ../../.env -- node --experimental-require-module ./dist/apps/orchestrator/src/main.js\",\n    \"pm2\": \"pm2 start pnpm --name orchestrator -- start\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\"\n}\n"
  },
  {
    "path": "apps/orchestrator/src/activities/autopost.activity.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Activity, ActivityMethod } from 'nestjs-temporal-core';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport {\n  NotificationService,\n  NotificationType,\n} from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { Integration, Post, State } from '@prisma/client';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';\nimport { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';\n\n@Injectable()\n@Activity()\nexport class AutopostActivity {\n  constructor(private _autoPostService: AutopostService) {}\n\n  @ActivityMethod()\n  async autoPost(id: string) {\n    return this._autoPostService.startAutopost(id)\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/activities/email.activity.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Activity, ActivityMethod } from 'nestjs-temporal-core';\nimport { EmailService } from '@gitroom/nestjs-libraries/services/email.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\n\n@Injectable()\n@Activity()\nexport class EmailActivity {\n  constructor(\n    private _emailService: EmailService,\n    private _organizationService: OrganizationService\n  ) {}\n\n  @ActivityMethod()\n  async sendEmail(to: string, subject: string, html: string, replyTo?: string) {\n    return this._emailService.sendEmailSync(to, subject, html, replyTo);\n  }\n\n  @ActivityMethod()\n  async sendEmailAsync(to: string, subject: string, html: string, sendTo: 'top' | 'bottom', replyTo?: string) {\n    return await this._emailService.sendEmail(to, subject, html, sendTo, replyTo);\n  }\n\n  @ActivityMethod()\n  async getUserOrgs(id: string) {\n    return this._organizationService.getTeam(id);\n  }\n\n  @ActivityMethod()\n  async setStreak(organizationId: string, type: 'start' | 'end') {\n    return this._organizationService.setStreak(organizationId, type);\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/activities/integrations.activity.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Activity, ActivityMethod } from 'nestjs-temporal-core';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { Integration } from '@prisma/client';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\n\n@Injectable()\n@Activity()\nexport class IntegrationsActivity {\n  constructor(\n    private _integrationService: IntegrationService,\n    private _refreshIntegrationService: RefreshIntegrationService\n  ) {}\n\n  @ActivityMethod()\n  async getIntegrationsById(id: string, orgId: string) {\n    return this._integrationService.getIntegrationById(orgId, id);\n  }\n\n  async refreshToken(integration: Integration) {\n    return this._refreshIntegrationService.refresh(integration);\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/activities/post.activity.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport {\n  Activity,\n  ActivityMethod,\n  TemporalService,\n} from 'nestjs-temporal-core';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport {\n  NotificationService,\n  NotificationType,\n} from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { Integration, Post, State } from '@prisma/client';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';\nimport { TypedSearchAttributes } from '@temporalio/common';\nimport {\n  organizationId,\n  postId as postIdSearchParam,\n} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\n\n@Injectable()\n@Activity()\nexport class PostActivity {\n  constructor(\n    private _postService: PostsService,\n    private _notificationService: NotificationService,\n    private _integrationManager: IntegrationManager,\n    private _integrationService: IntegrationService,\n    private _refreshIntegrationService: RefreshIntegrationService,\n    private _webhookService: WebhooksService,\n    private _temporalService: TemporalService,\n    private _subscriptionService: SubscriptionService\n  ) {}\n\n  @ActivityMethod()\n  async getIntegrationById(orgId: string, id: string) {\n    return this._integrationService.getIntegrationById(orgId, id);\n  }\n\n  @ActivityMethod()\n  async searchForMissingThreeHoursPosts() {\n    const list = await this._postService.searchForMissingThreeHoursPosts();\n    for (const post of list) {\n      await this._temporalService.client\n        .getRawClient()\n        .workflow.signalWithStart('postWorkflowV101', {\n          workflowId: `post_${post.id}`,\n          taskQueue: 'main',\n          signal: 'poke',\n          workflowIdConflictPolicy: 'USE_EXISTING',\n          signalArgs: [],\n          args: [\n            {\n              taskQueue: post.integration.providerIdentifier\n                .split('-')[0]\n                .toLowerCase(),\n              postId: post.id,\n              organizationId: post.organizationId,\n            },\n          ],\n          typedSearchAttributes: new TypedSearchAttributes([\n            {\n              key: postIdSearchParam,\n              value: post.id,\n            },\n            {\n              key: organizationId,\n              value: post.organizationId,\n            },\n          ]),\n        });\n    }\n  }\n\n  @ActivityMethod()\n  async updatePost(id: string, postId: string, releaseURL: string) {\n    return this._postService.updatePost(id, postId, releaseURL);\n  }\n\n  @ActivityMethod()\n  async getPostsList(orgId: string, postId: string) {\n    if (process.env.STRIPE_SECRET_KEY) {\n      const subscription = await this._subscriptionService.getSubscription(orgId);\n      if (!subscription) {\n        return [];\n      }\n    }\n\n    const getPosts = await this._postService.getPostsRecursively(\n      postId,\n      true,\n      orgId\n    );\n    if (!getPosts || getPosts.length === 0 || getPosts[0].parentPostId) {\n      return [];\n    }\n\n    return getPosts;\n  }\n\n  @ActivityMethod()\n  async isCommentable(integration: Integration) {\n    const getIntegration = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n\n    return !!getIntegration.comment;\n  }\n\n  @ActivityMethod()\n  async postComment(\n    postId: string,\n    lastPostId: string | undefined,\n    integration: Integration,\n    posts: Post[]\n  ) {\n    const getIntegration = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n\n    const newPosts = await this._postService.updateTags(\n      integration.organizationId,\n      posts\n    );\n\n    return getIntegration.comment(\n      integration.internalId,\n      postId,\n      lastPostId,\n      integration.token,\n      await Promise.all(\n        (newPosts || []).map(async (p) => ({\n          id: p.id,\n          message: stripHtmlValidation(\n            getIntegration.editor,\n            p.content,\n            true,\n            false,\n            !/<\\/?[a-z][\\s\\S]*>/i.test(p.content),\n            getIntegration.mentionFormat\n          ),\n          settings: JSON.parse(p.settings || '{}'),\n          media: await this._postService.updateMedia(\n            p.id,\n            JSON.parse(p.image || '[]'),\n            getIntegration?.convertToJPEG || false\n          ),\n        }))\n      ),\n      integration\n    );\n  }\n\n  @ActivityMethod()\n  async postSocial(integration: Integration, posts: Post[]) {\n    const getIntegration = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n\n    const newPosts = await this._postService.updateTags(\n      integration.organizationId,\n      posts\n    );\n\n    const postNow = await getIntegration.post(\n      integration.internalId,\n      integration.token,\n      await Promise.all(\n        (newPosts || []).map(async (p) => ({\n          id: p.id,\n          message: stripHtmlValidation(\n            getIntegration.editor,\n            p.content,\n            true,\n            false,\n            !/<\\/?[a-z][\\s\\S]*>/i.test(p.content),\n            getIntegration.mentionFormat\n          ),\n          settings: JSON.parse(p.settings || '{}'),\n          media: await this._postService.updateMedia(\n            p.id,\n            JSON.parse(p.image || '[]'),\n            getIntegration?.convertToJPEG || false\n          ),\n        }))\n      ),\n      integration\n    );\n\n    await this._temporalService.client\n      .getRawClient()\n      .workflow.start('streakWorkflow', {\n        args: [{ organizationId: integration.organizationId }],\n        workflowId: `streak_${integration.organizationId}`,\n        taskQueue: 'main',\n        workflowIdConflictPolicy: 'TERMINATE_EXISTING',\n        typedSearchAttributes: new TypedSearchAttributes([\n          {\n            key: organizationId,\n            value: integration.organizationId,\n          },\n        ]),\n      });\n\n    return postNow;\n  }\n\n  @ActivityMethod()\n  async inAppNotification(\n    orgId: string,\n    subject: string,\n    message: string,\n    sendEmail = false,\n    digest = false,\n    type: NotificationType = 'success'\n  ) {\n    return this._notificationService.inAppNotification(\n      orgId,\n      subject,\n      message,\n      sendEmail,\n      digest,\n      type\n    );\n  }\n\n  @ActivityMethod()\n  async globalPlugs(integration: Integration) {\n    return this._postService.checkPlugs(\n      integration.organizationId,\n      integration.providerIdentifier,\n      integration.id\n    );\n  }\n\n  @ActivityMethod()\n  async changeState(id: string, state: State, err?: any, body?: any) {\n    return this._postService.changeState(id, state, err, body);\n  }\n\n  @ActivityMethod()\n  async internalPlugs(integration: Integration, settings: any) {\n    return this._postService.checkInternalPlug(\n      integration,\n      integration.organizationId,\n      integration.id,\n      settings\n    );\n  }\n\n  @ActivityMethod()\n  async sendWebhooks(postId: string, orgId: string, integrationId: string) {\n    const webhooks = (await this._webhookService.getWebhooks(orgId)).filter(\n      (f) => {\n        return (\n          f.integrations.length === 0 ||\n          f.integrations.some((i) => i.integration.id === integrationId)\n        );\n      }\n    );\n\n    const post = await this._postService.getPostByForWebhookId(postId);\n    return Promise.all(\n      webhooks.map(async (webhook) => {\n        try {\n          await fetch(webhook.url, {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify(post),\n          });\n        } catch (e) {\n          /**empty**/\n        }\n      })\n    );\n  }\n  @ActivityMethod()\n  async processPlug(data: {\n    plugId: string;\n    postId: string;\n    delay: number;\n    totalRuns: number;\n    currentRun: number;\n  }) {\n    return this._integrationService.processPlugs(data);\n  }\n\n  @ActivityMethod()\n  async processInternalPlug(data: {\n    post: string;\n    originalIntegration: string;\n    integration: string;\n    plugName: string;\n    orgId: string;\n    delay: number;\n    information: any;\n  }) {\n    return this._integrationService.processInternalPlug(data);\n  }\n\n  @ActivityMethod()\n  async refreshToken(\n    integration: Integration\n  ): Promise<false | AuthTokenDetails> {\n    const getIntegration = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n\n    try {\n      const refresh = await this._refreshIntegrationService.refresh(\n        integration\n      );\n      if (!refresh) {\n        return false;\n      }\n\n      if (getIntegration.refreshWait) {\n        await timer(10000);\n      }\n\n      return refresh;\n    } catch (err) {\n      await this._refreshIntegrationService.setBetweenSteps(integration);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/app.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { PostActivity } from '@gitroom/orchestrator/activities/post.activity';\nimport { getTemporalModule } from '@gitroom/nestjs-libraries/temporal/temporal.module';\nimport { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module';\nimport { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';\nimport { EmailActivity } from '@gitroom/orchestrator/activities/email.activity';\nimport { IntegrationsActivity } from '@gitroom/orchestrator/activities/integrations.activity';\n\nconst activities = [\n  PostActivity,\n  AutopostService,\n  EmailActivity,\n  IntegrationsActivity,\n];\n@Module({\n  imports: [\n    DatabaseModule,\n    getTemporalModule(true, require.resolve('./workflows'), activities),\n  ],\n  controllers: [],\n  providers: [...activities],\n  get exports() {\n    return [...this.providers, ...this.imports];\n  },\n})\nexport class AppModule {}\n"
  },
  {
    "path": "apps/orchestrator/src/main.ts",
    "content": "import 'source-map-support/register';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\ndayjs.extend(utc);\n\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from '@gitroom/orchestrator/app.module';\nimport * as dns from 'node:dns';\ndns.setDefaultResultOrder('ipv4first');\n\nasync function bootstrap() {\n  // some comment again\n  const app = await NestFactory.createApplicationContext(AppModule);\n  app.enableShutdownHooks();\n}\n\nbootstrap();\n"
  },
  {
    "path": "apps/orchestrator/src/signals/email.signal.ts",
    "content": "import { defineSignal } from '@temporalio/workflow';\n\nexport type Email = {\n  message: string;\n  title?: string;\n  type: 'success' | 'fail' | 'info';\n};\n\nexport const emailSignal = defineSignal<[Email[]]>('email');\n"
  },
  {
    "path": "apps/orchestrator/src/signals/send.email.signal.ts",
    "content": "import { defineSignal } from '@temporalio/workflow';\n\nexport type SendEmail = {\n  to: string;\n  subject: string;\n  html: string;\n  replyTo?: string;\n  addTo: 'top' | 'bottom';\n};\nexport const sendEmailSignal = defineSignal<[SendEmail]>('sendEmail');\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/autopost.workflow.ts",
    "content": "import { proxyActivities, sleep } from '@temporalio/workflow';\nimport { AutopostActivity } from '@gitroom/orchestrator/activities/autopost.activity';\n\nconst { autoPost } = proxyActivities<AutopostActivity>({\n  startToCloseTimeout: '10 minute',\n  taskQueue: 'main',\n  retry: {\n    maximumAttempts: 3,\n    backoffCoefficient: 1,\n    initialInterval: '2 minutes',\n  },\n});\n\nexport async function autoPostWorkflow({\n  id,\n  immediately,\n}: {\n  id: string;\n  immediately: boolean;\n}) {\n  while (true) {\n    try {\n      if (immediately) {\n        await autoPost(id);\n      }\n    } catch (err) {}\n    immediately = true;\n    await sleep(3600000);\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/digest.email.workflow.ts",
    "content": "import {\n  condition,\n  continueAsNew,\n  proxyActivities,\n  setHandler,\n  sleep,\n} from '@temporalio/workflow';\nimport { Email, emailSignal } from '@gitroom/orchestrator/signals/email.signal';\nimport { EmailActivity } from '@gitroom/orchestrator/activities/email.activity';\n\nconst { getUserOrgs, sendEmailAsync } = proxyActivities<EmailActivity>({\n  startToCloseTimeout: '10 minute',\n  taskQueue: 'main',\n  cancellationType: 'ABANDON',\n  retry: {\n    maximumAttempts: 3,\n    backoffCoefficient: 1,\n    initialInterval: '2 minutes',\n  },\n});\n\nexport async function digestEmailWorkflow({\n  organizationId,\n  queue = [],\n}: {\n  organizationId: string;\n  queue?: Email[];\n}) {\n  setHandler(emailSignal, (data) => {\n    queue.push(...data);\n  });\n\n  while (true) {\n    await condition(() => queue.length > 0);\n    await sleep(3600000);\n\n    // Take a snapshot batch and immediately clear queue.\n    const batch = queue.splice(0, queue.length);\n    queue = [];\n\n    const org = await getUserOrgs(organizationId);\n\n    for (const user of org.users) {\n      const allowFailure = user.user.sendFailureEmails ? 'fail' : null;\n      const allowSuccess = user.user.sendSuccessEmails ? 'success' : null;\n\n      const toSend = batch.filter(\n        (email) =>\n          email.type === allowFailure ||\n          email.type === allowSuccess ||\n          email.type === 'info'\n      );\n\n      if (toSend.length === 0) continue;\n\n      await sendEmailAsync(\n        user.user.email,\n        toSend.length === 1\n          ? toSend[0].title\n          : `[Postiz] Your latest notifications`,\n        toSend.map((p) => p.message).join('<br/>'),\n        'bottom'\n      );\n    }\n\n    return await continueAsNew({\n      organizationId,\n      queue,\n    });\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/index.ts",
    "content": "export * from './post-workflows/post.workflow.v1.0.1';\nexport * from './autopost.workflow';\nexport * from './digest.email.workflow';\nexport * from './missing.post.workflow';\nexport * from './send.email.workflow';\nexport * from './refresh.token.workflow';\nexport * from './streak.workflow';\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/missing.post.workflow.ts",
    "content": "import { proxyActivities, sleep } from '@temporalio/workflow';\nimport { PostActivity } from '@gitroom/orchestrator/activities/post.activity';\n\nconst { searchForMissingThreeHoursPosts } = proxyActivities<PostActivity>({\n  startToCloseTimeout: '10 minute',\n  retry: {\n    maximumAttempts: 3,\n    backoffCoefficient: 1,\n    initialInterval: '2 minutes',\n  },\n});\n\nexport async function missingPostWorkflow() {\n  await searchForMissingThreeHoursPosts();\n  while (true) {\n    await sleep('1 hour');\n    await searchForMissingThreeHoursPosts();\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/post-workflows/post.workflow.v1.0.1.ts",
    "content": "import { PostActivity } from '@gitroom/orchestrator/activities/post.activity';\nimport {\n  ActivityFailure,\n  ApplicationFailure,\n  startChild,\n  proxyActivities,\n  sleep,\n  defineSignal,\n  setHandler,\n} from '@temporalio/workflow';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { capitalize, sortBy } from 'lodash';\nimport { PostResponse } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { TypedSearchAttributes } from '@temporalio/common';\nimport { postId as postIdSearchParam } from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';\n\nconst proxyTaskQueue = (taskQueue: string) => {\n  return proxyActivities<PostActivity>({\n    startToCloseTimeout: '10 minute',\n    taskQueue,\n    retry: {\n      maximumAttempts: 3,\n      backoffCoefficient: 1,\n      initialInterval: '2 minutes',\n    },\n  });\n};\n\nconst {\n  getPostsList,\n  inAppNotification,\n  changeState,\n  updatePost,\n  sendWebhooks,\n  isCommentable,\n} = proxyActivities<PostActivity>({\n  startToCloseTimeout: '10 minute',\n  retry: {\n    maximumAttempts: 3,\n    backoffCoefficient: 1,\n    initialInterval: '2 minutes',\n  },\n});\n\nconst poke = defineSignal('poke');\n\nconst iterate = Array.from({ length: 5 });\n\nexport async function postWorkflowV101({\n  taskQueue,\n  postId,\n  organizationId,\n  postNow = false,\n}: {\n  taskQueue: string;\n  postId: string;\n  organizationId: string;\n  postNow?: boolean;\n}) {\n  // Dynamic task queue, for concurrency\n  const {\n    postSocial,\n    postComment,\n    getIntegrationById,\n    refreshToken,\n    internalPlugs,\n    globalPlugs,\n    processInternalPlug,\n    processPlug,\n  } = proxyTaskQueue(taskQueue);\n\n  let poked = false;\n  setHandler(poke, () => {\n    poked = true;\n  });\n\n  const startTime = new Date();\n  // get all the posts and comments to post\n  const postsListBefore = await getPostsList(organizationId, postId);\n  const [post] = postsListBefore;\n\n  // in case doesn't exists for some reason, fail it\n  if (!post || (!postNow && post.state !== 'QUEUE')) {\n    return;\n  }\n\n  // if it's a repeatable post, we should ignore this.\n  if (!postNow) {\n    await sleep(\n      dayjs(post.publishDate).isBefore(dayjs())\n        ? 0\n        : dayjs(post.publishDate).diff(dayjs(), 'millisecond')\n    );\n  }\n\n  // if refresh is needed from last time, let's inform the user\n  if (post.integration?.refreshNeeded) {\n    await inAppNotification(\n      post.organizationId,\n      `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name}`,\n      `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name} because you need to reconnect it. Please enable it and try again.`,\n      true,\n      false,\n      'info'\n    );\n    return;\n  }\n\n  // if it's disabled, inform the user\n  if (post.integration?.disabled) {\n    await inAppNotification(\n      post.organizationId,\n      `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name}`,\n      `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name} because it's disabled. Please enable it and try again.`,\n      true,\n      false,\n      'info'\n    );\n    return;\n  }\n\n  // Do we need to post comment for this social?\n  const toComment: boolean =\n    postsListBefore.length === 1\n      ? false\n      : await isCommentable(post.integration);\n\n  const postsList = toComment ? postsListBefore : [postsListBefore[0]];\n\n  // list of all the saved results\n  const postsResults: PostResponse[] = [];\n\n  // iterate over the posts\n  for (let i = 0; i < postsList.length; i++) {\n    const before = postsResults.length;\n    // this is a small trick to repeat an action in case of token refresh\n    for (const _ of iterate) {\n      try {\n        // first post the main post\n        if (i === 0) {\n          postsResults.push(\n            ...(await postSocial(post.integration as Integration, [\n              postsList[i],\n            ]))\n          );\n\n          // then post the comments if any\n        } else {\n          if (postsList[i].delay) {\n            await sleep(60000 * Math.max(0, Number(postsList[i].delay ?? 0)));\n          }\n\n          postsResults.push(\n            ...(await postComment(\n              postsResults[0].postId,\n              postsResults.length === 1\n                ? undefined\n                : postsResults[i - 1].postId,\n              post.integration,\n              [postsList[i]]\n            ))\n          );\n        }\n\n        // mark post as successful\n        await updatePost(\n          postsList[i].id,\n          postsResults[i].postId,\n          postsResults[i].releaseURL\n        );\n\n        if (i === 0) {\n          // send notification on a sucessful post\n          await inAppNotification(\n            post.integration.organizationId,\n            `Your post has been published on ${capitalize(\n              post.integration.providerIdentifier\n            )}`,\n            `Your post has been published on ${capitalize(\n              post.integration.providerIdentifier\n            )} at ${postsResults[0].releaseURL}`,\n            true,\n            true\n          );\n        }\n\n        // break the current while to move to the next post\n        break;\n      } catch (err) {\n        // if token refresh is needed, do it and repeat\n        if (\n          err instanceof ActivityFailure &&\n          err.cause instanceof ApplicationFailure &&\n          err.cause.type === 'refresh_token'\n        ) {\n          const refresh = await refreshToken(post.integration);\n          if (!refresh || !refresh.accessToken) {\n            await changeState(postsList[0].id, 'ERROR', err, postsList);\n            return false;\n          }\n\n          post.integration.token = refresh.accessToken;\n          continue;\n        }\n\n        // for other errors, change state and inform the user if needed\n        await changeState(postsList[0].id, 'ERROR', err, postsList);\n\n        // specific case for bad body errors\n        if (\n          err instanceof ActivityFailure &&\n          err.cause instanceof ApplicationFailure &&\n          err.cause.type === 'bad_body'\n        ) {\n          await inAppNotification(\n            post.organizationId,\n            `Error posting${i === 0 ? ' ' : ' comments '}on ${\n              post.integration?.providerIdentifier\n            } for ${post?.integration?.name}`,\n            `An error occurred while posting${i === 0 ? ' ' : ' comments '}on ${\n              post.integration?.providerIdentifier\n            }${err?.cause?.message ? `: ${err?.cause?.message}` : ``}`,\n            true,\n            false,\n            'fail'\n          );\n          return false;\n        }\n      }\n    }\n\n    if (postsResults.length === before) {\n      // all retries exhausted without success\n      return false;\n    }\n  }\n\n  // send webhooks for the post\n  await sendWebhooks(\n    postsResults[0].postId,\n    post.organizationId,\n    post.integration.id\n  );\n\n  // load internal plugs like repost by other users\n  const internalPlugsList = await internalPlugs(\n    post.integration,\n    JSON.parse(post.settings)\n  );\n\n  // load global plugs, like repost a post if it gets to a certain number of likes\n  const globalPlugsList = (await globalPlugs(post.integration)).reduce(\n    (all, current) => {\n      for (let i = 1; i <= current.totalRuns; i++) {\n        all.push({\n          ...current,\n          delay: current.delay * i,\n        });\n      }\n\n      return all;\n    },\n    []\n  );\n\n  // Check if the post is repeatable\n  const repeatPost = !post.intervalInDays\n    ? []\n    : [\n        {\n          type: 'repeat-post',\n          delay:\n            post.intervalInDays * 24 * 60 * 60 * 1000 -\n            (new Date().getTime() - startTime.getTime()),\n        },\n      ];\n\n  // Sort all the actions by delay, so we can process them in order\n  const list = sortBy(\n    [...internalPlugsList, ...globalPlugsList, ...repeatPost],\n    'delay'\n  );\n\n  // process all the plugs in order, we are using while because in some cases we need to remove items from the list\n  while (list.length > 0) {\n    // get the next to process\n    const todo = list.shift();\n\n    // wait for the delay\n    await sleep(Math.max(0, Number(todo.delay ?? 0)));\n\n    // process internal plug\n    if (todo.type === 'internal-plug') {\n      for (const _ of iterate) {\n        try {\n          await processInternalPlug({ ...todo, post: postsResults[0].postId });\n        } catch (err) {\n          if (\n            err instanceof ActivityFailure &&\n            err.cause instanceof ApplicationFailure &&\n            err.cause.type === 'refresh_token'\n          ) {\n            const refresh = await refreshToken(\n              await getIntegrationById(organizationId, todo.integration)\n            );\n            if (!refresh || !refresh.accessToken) {\n              break;\n            }\n\n            continue;\n          }\n\n          if (\n            err instanceof ActivityFailure &&\n            err.cause instanceof ApplicationFailure &&\n            err.cause.type === 'bad_body'\n          ) {\n            break;\n          }\n\n          continue;\n        }\n        break;\n      }\n    }\n\n    // process global plug\n    if (todo.type === 'global') {\n      for (const _ of iterate) {\n        try {\n          const process = await processPlug({\n            ...todo,\n            postId: postsResults[0].postId,\n          });\n          if (process) {\n            const toDelete = list\n              .reduce((all, current, index) => {\n                if (current.plugId === todo.plugId) {\n                  all.push(index);\n                }\n\n                return all;\n              }, [])\n              .reverse();\n\n            for (const index of toDelete) {\n              list.splice(index, 1);\n            }\n          }\n        } catch (err) {\n          if (\n            err instanceof ActivityFailure &&\n            err.cause instanceof ApplicationFailure &&\n            err.cause.type === 'refresh_token'\n          ) {\n            const refresh = await refreshToken(post.integration);\n            if (!refresh || !refresh.accessToken) {\n              break;\n            }\n\n            continue;\n          }\n\n          if (\n            err instanceof ActivityFailure &&\n            err.cause instanceof ApplicationFailure &&\n            err.cause.type === 'bad_body'\n          ) {\n            break;\n          }\n\n          continue;\n        }\n\n        break;\n      }\n    }\n\n    // process repeat post in a new workflow, this is important so the other plugs can keep running\n    if (todo.type === 'repeat-post') {\n      await startChild(postWorkflowV101, {\n        parentClosePolicy: 'ABANDON',\n        args: [\n          {\n            taskQueue,\n            postId,\n            organizationId,\n            postNow: true,\n          },\n        ],\n        workflowId: `post_${post.id}_${makeId(10)}`,\n        typedSearchAttributes: new TypedSearchAttributes([\n          {\n            key: postIdSearchParam,\n            value: postId,\n          },\n        ]),\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/refresh.token.workflow.ts",
    "content": "import { proxyActivities, sleep } from '@temporalio/workflow';\nimport { IntegrationsActivity } from '@gitroom/orchestrator/activities/integrations.activity';\n\nconst { getIntegrationsById, refreshToken } =\n  proxyActivities<IntegrationsActivity>({\n    startToCloseTimeout: '10 minute',\n    retry: {\n      maximumAttempts: 3,\n      backoffCoefficient: 1,\n      initialInterval: '2 minutes',\n    },\n  });\n\nexport async function refreshTokenWorkflow({\n  organizationId,\n  integrationId,\n}: {\n  integrationId: string;\n  organizationId: string;\n}) {\n  while (true) {\n    let integration = await getIntegrationsById(integrationId, organizationId);\n    if (\n      !integration ||\n      integration.deletedAt ||\n      integration.inBetweenSteps ||\n      integration.refreshNeeded\n    ) {\n      return false;\n    }\n\n    const today = new Date();\n    const endDate = new Date(integration.tokenExpiration);\n\n    const minMax = Math.max(0, endDate.getTime() - today.getTime());\n    if (!minMax) {\n      return false;\n    }\n\n    await sleep(minMax as number);\n\n    // while we were sleeping, the integration might have been deleted\n    integration = await getIntegrationsById(integrationId, organizationId);\n    if (\n      !integration ||\n      integration.deletedAt ||\n      integration.inBetweenSteps ||\n      integration.refreshNeeded\n    ) {\n      return false;\n    }\n\n    await refreshToken(integration);\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/send.email.workflow.ts",
    "content": "import {\n  proxyActivities,\n  setHandler,\n  condition,\n  sleep,\n  continueAsNew,\n} from '@temporalio/workflow';\nimport { EmailActivity } from '@gitroom/orchestrator/activities/email.activity';\nimport {\n  SendEmail,\n  sendEmailSignal,\n} from '@gitroom/orchestrator/signals/send.email.signal';\n\nconst { sendEmail } = proxyActivities<EmailActivity>({\n  startToCloseTimeout: '10 minute',\n  taskQueue: 'main',\n  cancellationType: 'ABANDON',\n});\n\nconst RATE_LIMIT_MS = 700;\n\nexport async function sendEmailWorkflow({\n  queue = [],\n}: {\n  queue: SendEmail[];\n}) {\n  let processedThisRun = 0;\n  // Handle incoming email signals\n  setHandler(sendEmailSignal, (addEmail: SendEmail) => {\n    if (addEmail.to && addEmail.subject) {\n      if (addEmail.addTo === 'top') {\n        queue.unshift(addEmail);\n      } else {\n        queue.push(addEmail);\n      }\n    }\n  });\n\n  // Process emails with rate limiting\n  while (true) {\n    // Wait until there's an email in the queue or timeout after 1 hour of inactivity\n    await condition(() => queue.length > 0);\n\n    try {\n      const email = queue.shift()!;\n      if (!email) {\n        continue;\n      }\n      await sendEmail(email.to, email.subject, email.html, email.replyTo);\n      processedThisRun++;\n    } catch (err) {\n      console.log(err);\n    }\n\n    await sleep(RATE_LIMIT_MS);\n\n    if (processedThisRun >= 30) {\n      return await continueAsNew({ queue });\n    }\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/src/workflows/streak.workflow.ts",
    "content": "import { proxyActivities, sleep } from '@temporalio/workflow';\nimport { EmailActivity } from '@gitroom/orchestrator/activities/email.activity';\n\nconst { sendEmailAsync, getUserOrgs, setStreak } = proxyActivities<EmailActivity>({\n  startToCloseTimeout: '10 minute',\n  taskQueue: 'main',\n  cancellationType: 'ABANDON',\n});\n\nexport async function streakWorkflow({\n  organizationId,\n}: {\n  organizationId: string;\n}) {\n  await setStreak(organizationId, 'start');\n  await sleep(79200000);\n  const userOrgs = await getUserOrgs(organizationId);\n  for (const user of userOrgs.users) {\n    if (!user.user.sendStreakEmails) {\n      continue;\n    }\n    await sendEmailAsync(\n      user.user.email,\n      'Streak Reminder',\n      '<p>You are about to lose your streak in two hours! schedule a post now to keep it!</p>',\n      'bottom'\n    );\n  }\n  await sleep(7200000);\n  await setStreak(organizationId, 'end');\n}\n"
  },
  {
    "path": "apps/orchestrator/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"],\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"resolveJsonModule\": true,\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"ES2021\",\n    \"sourceMap\": true,\n    \"incremental\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"strictBindCallApply\": false,\n    \"forceConsistentCasingInFileNames\": false,\n    \"noFallthroughCasesInSwitch\": false,\n    \"outDir\": \"./dist\"\n  }\n}\n"
  },
  {
    "path": "apps/orchestrator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true\n  }\n}\n"
  },
  {
    "path": "apps/sdk/.babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-typescript\"],\n  \"plugins\": [\n    [\"@babel/plugin-syntax-decorators\", { \"legacy\": true }]\n  ]\n}"
  },
  {
    "path": "apps/sdk/.npmignore",
    "content": "src\ntsconfig.json"
  },
  {
    "path": "apps/sdk/README.md",
    "content": "# Postiz NodeJS SDK\n\nThis is the NodeJS SDK for [Postiz](https://postiz.com).\n\nYou can start by installing the package:\n\n```bash\nnpm install @postiz/node\n```\n\n## Usage\n```typescript\nimport Postiz from '@postiz/node';\nconst postiz = new Postiz('your api key', 'your self-hosted instance (optional)');\n```\n\nThe available methods are:\n- `post(posts: CreatePostDto)` - Schedule a post to Postiz\n- `postList(filters: GetPostsDto)` - Get a list of posts\n- `upload(file: Buffer, extension: string)` - Upload a file to Postiz\n- `integrations()` - Get a list of connected channels\n- `deletePost(id: string)` - Delete a post by ID\n\nAlternatively you can use the SDK with curl, check the [Postiz API documentation](https://docs.postiz.com/public-api) for more information."
  },
  {
    "path": "apps/sdk/package.json",
    "content": "{\n  \"name\": \"@postiz/node\",\n  \"version\": \"1.0.8\",\n  \"description\": \"The ultimate social media scheduling tool\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"publish\": \"tsup && pnpm publish --access public\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"keywords\": [\n    \"social media\",\n    \"scheduling tool\",\n    \"social media scheduling tool\"\n  ],\n  \"author\": \"Nevo David\",\n  \"license\": \"AGPL-3.0\",\n  \"dependencies\": {\n    \"node-fetch\": \"^3.3.2\"\n  }\n}\n"
  },
  {
    "path": "apps/sdk/src/index.ts",
    "content": "import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';\nimport { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';\nimport fetch, { FormData } from 'node-fetch';\n\nfunction toQueryString(obj: Record<string, any>): string {\n  const params = new URLSearchParams();\n  Object.entries(obj).forEach(([key, value]) => {\n    if (value !== undefined && value !== null) {\n      params.append(key, String(value));\n    }\n  });\n  return params.toString();\n}\n\nexport default class Postiz {\n  constructor(\n    private _apiKey: string,\n    private _path = 'https://api.postiz.com'\n  ) {}\n\n  async post(posts: CreatePostDto) {\n    return (\n      await fetch(`${this._path}/public/v1/posts`, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: this._apiKey,\n        },\n        body: JSON.stringify(posts),\n      })\n    ).json();\n  }\n\n  async postList(filters: GetPostsDto) {\n    return (\n      await fetch(`${this._path}/public/v1/posts?${toQueryString(filters)}`, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: this._apiKey,\n        },\n      })\n    ).json();\n  }\n\n  async upload(file: Buffer, extension: string) {\n    const formData = new FormData();\n    const type =\n      extension === 'png'\n        ? 'image/png'\n        : extension === 'jpg'\n        ? 'image/jpeg'\n        : extension === 'gif'\n        ? 'image/gif'\n        : extension === 'jpeg'\n        ? 'image/jpeg'\n        : 'image/jpeg';\n\n    const blob = new Blob([file], { type });\n    formData.append('file', blob, extension);\n\n    return (\n      await fetch(`${this._path}/public/v1/upload`, {\n        method: 'POST',\n        // @ts-ignore\n        body: formData,\n        headers: {\n          Authorization: this._apiKey,\n        },\n      })\n    ).json();\n  }\n\n  async integrations() {\n    return (\n      await fetch(`${this._path}/public/v1/integrations`, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: this._apiKey,\n        },\n      })\n    ).json();\n  }\n\n  deletePost(id: string) {\n    return fetch(`${this._path}/public/v1/posts/${id}`, {\n      method: 'DELETE',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: this._apiKey,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "apps/sdk/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"esModuleInterop\": true,\n    \"rootDir\": \"../../\",\n    \"incremental\": false\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "apps/sdk/tsup.config.ts",
    "content": "import { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs'],\n  dts: true,\n  minify: true,\n  clean: true,\n  outDir: 'dist',\n});"
  },
  {
    "path": "docker-compose.dev.yaml",
    "content": "# Do **not** use this yml for production. It is not up-to-date.\n# Use https://docs.postiz.com/installation/docker-compose\n# This is only for the dev enviroment\n\nservices:\n  postiz-postgres:\n    # ref: https://hub.docker.com/_/postgres\n    image: postgres:17-alpine # 17.0\n    container_name: postiz-postgres\n    restart: always\n    environment:\n      POSTGRES_PASSWORD: postiz-local-pwd\n      POSTGRES_USER: postiz-local\n      POSTGRES_DB: postiz-db-local\n      TEMPORAL_ADDRESS: \"temporal:7233\"\n    volumes:\n      - postgres-volume:/var/lib/postgresql/data\n    ports:\n      - 5432:5432\n    networks:\n      - postiz-network\n  postiz-redis:\n    # ref: https://hub.docker.com/_/redis\n    image: redis:7-alpine # 7.4.0\n    container_name: postiz-redis\n    restart: always\n    ports:\n      - 6379:6379\n    networks:\n      - postiz-network\n  postiz-pg-admin:\n    # ref: https://hub.docker.com/r/dpage/pgadmin4/tags\n    image: dpage/pgadmin4:latest\n    container_name: postiz-pg-admin\n    restart: always\n    ports:\n      - 8081:80\n    environment:\n      PGADMIN_DEFAULT_EMAIL: admin@admin.com\n      PGADMIN_DEFAULT_PASSWORD: admin\n    networks:\n      - postiz-network\n  postiz-redisinsight:\n    # ref: https://hub.docker.com/r/redis/redisinsight\n    image: redis/redisinsight:latest\n    container_name: postiz-redisinsight\n    links:\n      - postiz-redis\n    ports:\n      - '5540:5540'\n    volumes:\n      - redisinsight:/data\n    networks:\n      - postiz-network\n    restart: always\n\n  temporal-elasticsearch:\n    container_name: temporal-elasticsearch\n    image: elasticsearch:7.17.27\n    environment:\n      - cluster.routing.allocation.disk.threshold_enabled=true\n      - cluster.routing.allocation.disk.watermark.low=512mb\n      - cluster.routing.allocation.disk.watermark.high=256mb\n      - cluster.routing.allocation.disk.watermark.flood_stage=128mb\n      - discovery.type=single-node\n      - ES_JAVA_OPTS=-Xms256m -Xmx256m\n      - xpack.security.enabled=false\n    networks:\n      - temporal-network\n    expose:\n      - 9200\n    volumes:\n      - /var/lib/elasticsearch/data\n\n  temporal-postgresql:\n    container_name: temporal-postgresql\n    image: postgres:16\n    environment:\n      POSTGRES_PASSWORD: temporal\n      POSTGRES_USER: temporal\n    networks:\n      - temporal-network\n    expose:\n      - 5432\n    volumes:\n      - /var/lib/postgresql/data\n\n  temporal:\n    container_name: temporal\n    ports:\n      - \"7233:7233\"\n    image: temporalio/auto-setup:1.28.1\n    depends_on:\n      - temporal-postgresql\n      - temporal-elasticsearch\n    environment:\n      - DB=postgres12\n      - DB_PORT=5432\n      - POSTGRES_USER=temporal\n      - POSTGRES_PWD=temporal\n      - POSTGRES_SEEDS=temporal-postgresql\n      - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml\n      - ENABLE_ES=true\n      - ES_SEEDS=temporal-elasticsearch\n      - ES_VERSION=v7\n      - TEMPORAL_NAMESPACE=default\n    networks:\n      - temporal-network\n    volumes:\n      - ./dynamicconfig:/etc/temporal/config/dynamicconfig\n    labels:\n      kompose.volume.type: configMap\n\n  temporal-admin-tools:\n    container_name: temporal-admin-tools\n    image: temporalio/admin-tools:1.28.1-tctl-1.18.4-cli-1.4.1\n    environment:\n      - TEMPORAL_ADDRESS=temporal:7233\n      - TEMPORAL_CLI_ADDRESS=temporal:7233\n    networks:\n      - temporal-network\n    stdin_open: true\n    depends_on:\n      - temporal\n    tty: true\n\n  temporal-ui:\n    container_name: temporal-ui\n    image: temporalio/ui:2.34.0\n    environment:\n      - TEMPORAL_ADDRESS=temporal:7233\n      - TEMPORAL_CORS_ORIGINS=http://127.0.0.1:3000\n    networks:\n      - temporal-network\n    ports:\n      - \"8080:8080\"\n\nvolumes:\n  redisinsight:\n  postgres-volume:\n    external: false\n\nnetworks:\n  postiz-network:\n    external: false\n  temporal-network:\n    driver: bridge\n    name: temporal-network\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "services:\n  postiz:\n    image: ghcr.io/gitroomhq/postiz-app:latest\n    container_name: postiz\n    restart: always\n    environment:\n      # === Required Settings\n      MAIN_URL: 'http://localhost:4007'\n      FRONTEND_URL: 'http://localhost:4007'\n      NEXT_PUBLIC_BACKEND_URL: 'http://localhost:4007/api'\n      JWT_SECRET: 'random string that is unique to every install - just type random characters here!'\n      DATABASE_URL: 'postgresql://postiz-user:postiz-password@postiz-postgres:5432/postiz-db-local'\n      REDIS_URL: 'redis://postiz-redis:6379'\n      BACKEND_INTERNAL_URL: 'http://localhost:3000'\n      TEMPORAL_ADDRESS: \"temporal:7233\"\n      IS_GENERAL: 'true'\n      DISABLE_REGISTRATION: 'false'\n\n      # === Storage Settings\n      STORAGE_PROVIDER: 'local'\n      UPLOAD_DIRECTORY: '/uploads'\n      NEXT_PUBLIC_UPLOAD_DIRECTORY: '/uploads'\n\n      # === Cloudflare (R2) Settings\n      # STORAGE_PROVIDER: 'cloudflare'\n      # CLOUDFLARE_ACCOUNT_ID: 'your-account-id'\n      # CLOUDFLARE_ACCESS_KEY: 'your-access-key'\n      # CLOUDFLARE_SECRET_ACCESS_KEY: 'your-secret-access-key'\n      # CLOUDFLARE_BUCKETNAME: 'your-bucket-name'\n      # CLOUDFLARE_BUCKET_URL: 'https://your-bucket-url.r2.cloudflarestorage.com/'\n      # CLOUDFLARE_REGION: 'auto'\n\n      # === Social Media API Settings\n      X_API_KEY: ''\n      X_API_SECRET: ''\n      LINKEDIN_CLIENT_ID: ''\n      LINKEDIN_CLIENT_SECRET: ''\n      REDDIT_CLIENT_ID: ''\n      REDDIT_CLIENT_SECRET: ''\n      GITHUB_CLIENT_ID: ''\n      GITHUB_CLIENT_SECRET: ''\n      BEEHIIVE_API_KEY: ''\n      BEEHIIVE_PUBLICATION_ID: ''\n      THREADS_APP_ID: ''\n      THREADS_APP_SECRET: ''\n      FACEBOOK_APP_ID: ''\n      FACEBOOK_APP_SECRET: ''\n      YOUTUBE_CLIENT_ID: ''\n      YOUTUBE_CLIENT_SECRET: ''\n      TIKTOK_CLIENT_ID: ''\n      TIKTOK_CLIENT_SECRET: ''\n      PINTEREST_CLIENT_ID: ''\n      PINTEREST_CLIENT_SECRET: ''\n      DRIBBBLE_CLIENT_ID: ''\n      DRIBBBLE_CLIENT_SECRET: ''\n      DISCORD_CLIENT_ID: ''\n      DISCORD_CLIENT_SECRET: ''\n      DISCORD_BOT_TOKEN_ID: ''\n      SLACK_ID: ''\n      SLACK_SECRET: ''\n      SLACK_SIGNING_SECRET: ''\n      MASTODON_URL: 'https://mastodon.social'\n      MASTODON_CLIENT_ID: ''\n      MASTODON_CLIENT_SECRET: ''\n\n      # === OAuth & Authentik Settings\n      # NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: 'Authentik'\n      # NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png'\n      # POSTIZ_GENERIC_OAUTH: 'false'\n      # POSTIZ_OAUTH_URL: 'https://auth.example.com'\n      # POSTIZ_OAUTH_AUTH_URL: 'https://auth.example.com/application/o/authorize'\n      # POSTIZ_OAUTH_TOKEN_URL: 'https://auth.example.com/application/o/token'\n      # POSTIZ_OAUTH_USERINFO_URL: 'https://authentik.example.com/application/o/userinfo'\n      # POSTIZ_OAUTH_CLIENT_ID: ''\n      # POSTIZ_OAUTH_CLIENT_SECRET: ''\n      # POSTIZ_OAUTH_SCOPE: \"openid profile email\"  # Optional: uncomment to override default scope\n\n      # === Sentry\n\n      # NEXT_PUBLIC_SENTRY_DSN: 'http://spotlight:8969/stream'\n      # SENTRY_SPOTLIGHT: '1'\n\n      # === Misc Settings\n      OPENAI_API_KEY: ''\n      NEXT_PUBLIC_DISCORD_SUPPORT: ''\n      NEXT_PUBLIC_POLOTNO: ''\n      API_LIMIT: 30\n\n      # === Payment / Stripe Settings\n      FEE_AMOUNT: 0.05\n      STRIPE_PUBLISHABLE_KEY: ''\n      STRIPE_SECRET_KEY: ''\n      STRIPE_SIGNING_KEY: ''\n      STRIPE_SIGNING_KEY_CONNECT: ''\n\n      # === Developer Settings\n      NX_ADD_PLUGINS: false\n\n      # === Short Link Service Settings (Optional - leave blank if unused)\n      # DUB_TOKEN: \"\"\n      # DUB_API_ENDPOINT: \"https://api.dub.co\"\n      # DUB_SHORT_LINK_DOMAIN: \"dub.sh\"\n      # SHORT_IO_SECRET_KEY: \"\"\n      # KUTT_API_KEY: \"\"\n      # KUTT_API_ENDPOINT: \"https://kutt.it/api/v2\"\n      # KUTT_SHORT_LINK_DOMAIN: \"kutt.it\"\n      # LINK_DRIP_API_KEY: \"\"\n      # LINK_DRIP_API_ENDPOINT: \"https://api.linkdrip.com/v1/\"\n      # LINK_DRIP_SHORT_LINK_DOMAIN: \"dripl.ink\"\n\n    volumes:\n      - postiz-config:/config/\n      - postiz-uploads:/uploads/\n    ports:\n      - \"4007:5000\"\n    networks:\n      - postiz-network\n      - temporal-network\n    depends_on:\n      postiz-postgres:\n        condition: service_healthy\n      postiz-redis:\n        condition: service_healthy\n\n  postiz-postgres:\n    image: postgres:17-alpine\n    container_name: postiz-postgres\n    restart: always\n    environment:\n      POSTGRES_PASSWORD: postiz-password\n      POSTGRES_USER: postiz-user\n      POSTGRES_DB: postiz-db-local\n    volumes:\n      - postgres-volume:/var/lib/postgresql/data\n    networks:\n      - postiz-network\n    healthcheck:\n      test: pg_isready -U postiz-user -d postiz-db-local\n      interval: 10s\n      timeout: 3s\n      retries: 3\n  postiz-redis:\n    image: redis:7.2\n    container_name: postiz-redis\n    restart: always\n    healthcheck:\n      test: redis-cli ping\n      interval: 10s\n      timeout: 3s\n      retries: 3\n    volumes:\n      - postiz-redis-data:/data\n    networks:\n      - postiz-network\n\n  # For Application Monitoring / Debugging\n  spotlight:\n    pull_policy: always\n    container_name: spotlight\n    ports:\n      - 8969:8969/tcp\n    image: ghcr.io/getsentry/spotlight:latest\n    networks:\n      - postiz-network\n\n  # -----------------------\n  # Temporal Stack\n  # -----------------------\n  temporal-elasticsearch:\n    container_name: temporal-elasticsearch\n    image: elasticsearch:7.17.27\n    environment:\n      - cluster.routing.allocation.disk.threshold_enabled=true\n      - cluster.routing.allocation.disk.watermark.low=512mb\n      - cluster.routing.allocation.disk.watermark.high=256mb\n      - cluster.routing.allocation.disk.watermark.flood_stage=128mb\n      - discovery.type=single-node\n      - ES_JAVA_OPTS=-Xms256m -Xmx256m\n      - xpack.security.enabled=false\n    networks:\n      - temporal-network\n    expose:\n      - 9200\n    volumes:\n      - /var/lib/elasticsearch/data\n\n  temporal-postgresql:\n    container_name: temporal-postgresql\n    image: postgres:16\n    environment:\n      POSTGRES_PASSWORD: temporal\n      POSTGRES_USER: temporal\n    networks:\n      - temporal-network\n    expose:\n      - 5432\n    volumes:\n      - /var/lib/postgresql/data\n\n  temporal:\n    container_name: temporal\n    ports:\n      - '7233:7233'\n    image: temporalio/auto-setup:1.28.1\n    depends_on:\n      - temporal-postgresql\n      - temporal-elasticsearch\n    environment:\n      - DB=postgres12\n      - DB_PORT=5432\n      - POSTGRES_USER=temporal\n      - POSTGRES_PWD=temporal\n      - POSTGRES_SEEDS=temporal-postgresql\n      - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml\n      - ENABLE_ES=true\n      - ES_SEEDS=temporal-elasticsearch\n      - ES_VERSION=v7\n      - TEMPORAL_NAMESPACE=default\n    networks:\n      - temporal-network\n    volumes:\n      - ./dynamicconfig:/etc/temporal/config/dynamicconfig\n    labels:\n      kompose.volume.type: configMap\n\n  temporal-admin-tools:\n    container_name: temporal-admin-tools\n    image: temporalio/admin-tools:1.28.1-tctl-1.18.4-cli-1.4.1\n    environment:\n      - TEMPORAL_ADDRESS=temporal:7233\n      - TEMPORAL_CLI_ADDRESS=temporal:7233\n    networks:\n      - temporal-network\n    stdin_open: true\n    depends_on:\n      - temporal\n    tty: true\n\n  temporal-ui:\n    container_name: temporal-ui\n    image: temporalio/ui:2.34.0\n    environment:\n      - TEMPORAL_ADDRESS=temporal:7233\n      - TEMPORAL_CORS_ORIGINS=http://127.0.0.1:3000\n    networks:\n      - temporal-network\n    ports:\n      - '8080:8080'\n\nvolumes:\n  postgres-volume:\n    external: false\n\n  postiz-redis-data:\n    external: false\n\n  postiz-config:\n    external: false\n\n  postiz-uploads:\n    external: false\n\nnetworks:\n  postiz-network:\n    external: false\n  temporal-network:\n    driver: bridge\n    name: temporal-network\n"
  },
  {
    "path": "dynamicconfig/development-cass.yaml",
    "content": "system.forceSearchAttributesCacheRefreshOnRead:\n  - value: true # Dev setup only. Please don't turn this on in production.\n    constraints: {}\n"
  },
  {
    "path": "dynamicconfig/development-sql.yaml",
    "content": "limit.maxIDLength:\n  - value: 255\n    constraints: {}\nsystem.forceSearchAttributesCacheRefreshOnRead:\n  - value: true # Dev setup only. Please don't turn this on in production.\n    constraints: {}"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { FlatCompat } from '@eslint/eslintrc';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst compat = new FlatCompat({\n  baseDirectory: __dirname,\n});\n\nconst eslintConfig = [\n  ...compat.config({\n    extends: ['next/core-web-vitals', 'next/typescript'],\n    rules: {\n      'react/no-unescaped-entities': 'off',\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-unused-vars': 'off',\n      'react/display-name': 'off',\n      '@typescript-eslint/ban-ts-comment': 'off',\n      '@typescript-eslint/no-empty-object-type': 'off',\n      '@typescript-eslint/prefer-as-const': 'off',\n      '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',\n    },\n  }),\n];\n\nexport default eslintConfig;\n"
  },
  {
    "path": "i18n.json",
    "content": "{\n  \"version\": 1.8,\n  \"provider\": {\n    \"id\": \"openai\",\n    \"model\": \"gpt-4.1\",\n    \"prompt\": \"Translate accurately from {source} to {target}, maintaining the original meaning.\",\n    \"baseUrl\": \"https://api.openai.com/v1\"\n  },\n  \"locale\": {\n    \"source\": \"en\",\n    \"targets\": [\n      \"he\",\n      \"ru\",\n      \"zh\",\n      \"fr\",\n      \"bn\",\n      \"es\",\n      \"pt\",\n      \"de\",\n      \"it\",\n      \"ja\",\n      \"ko\",\n      \"ar\",\n      \"tr\",\n      \"vi\"\n    ]\n  },\n  \"buckets\": {\n    \"json\": {\n      \"include\": [\"libraries/react-shared-libraries/src/translation/locales/[locale]/translation.json\"]\n    }\n  },\n  \"$schema\": \"https://lingo.dev/schema/i18n.json\"\n}\n"
  },
  {
    "path": "jest.config.ts",
    "content": "import { getJestProjects } from '@nx/jest';\n\nexport default {\n  projects: getJestProjects(),\n};\n"
  },
  {
    "path": "jest.preset.js",
    "content": "const nxPreset = require('@nx/jest/preset').default;\n\nmodule.exports = { ...nxPreset };\n"
  },
  {
    "path": "libraries/helpers/src/auth/auth.service.ts",
    "content": "import { sign, verify } from 'jsonwebtoken';\nimport { hashSync, compareSync } from 'bcrypt';\nimport crypto from 'crypto';\n// @ts-ignore\nimport EVP_BytesToKey from 'evp_bytestokey';\nconst algorithm = 'aes-256-cbc';\nconst { keyLength, ivLength } = crypto.getCipherInfo(algorithm);\n\nfunction deriveLegacyKeyIv(secret: string) {\n  const { keyLength, ivLength } = crypto.getCipherInfo(algorithm); // 32, 16\n  const pass = Buffer.isBuffer(secret) ? secret : Buffer.from(secret ?? '', 'utf8');\n\n  // evp_bytestokey: key length in **bits**, IV length in **bytes**\n  const { key, iv } = EVP_BytesToKey(pass, null, keyLength * 8, ivLength, 'md5');\n\n  if (key.length !== keyLength || iv.length !== ivLength) {\n    throw new Error(`Derived wrong sizes (key=${key.length}, iv=${iv.length})`);\n  }\n  return { key, iv };\n}\n\nexport function decrypt_legacy_using_IV(hexCiphertext: string) {\n  const { key, iv } = deriveLegacyKeyIv(process.env.JWT_SECRET);\n  const decipher = crypto.createDecipheriv(algorithm, key, iv);\n  const out = Buffer.concat([decipher.update(hexCiphertext, 'hex'), decipher.final()]);\n  return out.toString('utf8');\n}\n\nexport function encrypt_legacy_using_IV(utf8Plaintext: string) {\n  const { key, iv } = deriveLegacyKeyIv(process.env.JWT_SECRET);\n  const cipher = crypto.createCipheriv(algorithm, key, iv);\n  const out = Buffer.concat([cipher.update(utf8Plaintext, 'utf8'), cipher.final()]);\n  return out.toString('hex');\n}\nexport class AuthService {\n  static hashPassword(password: string) {\n    return hashSync(password, 10);\n  }\n  static comparePassword(password: string, hash: string) {\n    return compareSync(password, hash);\n  }\n  static signJWT(value: object) {\n    return sign(value, process.env.JWT_SECRET!);\n  }\n  static verifyJWT(token: string) {\n    return verify(token, process.env.JWT_SECRET!);\n  }\n\n  static fixedEncryption(value: string) {\n    return encrypt_legacy_using_IV(value);\n  }\n\n  static fixedDecryption(hash: string) {\n    return decrypt_legacy_using_IV(hash);\n  }\n}\n"
  },
  {
    "path": "libraries/helpers/src/configuration/configuration.checker.ts",
    "content": "import { readFileSync, existsSync } from 'fs';\nimport * as dotenv from 'dotenv';\nimport { resolve } from 'path';\n\nexport class ConfigurationChecker {\n  cfg: dotenv.DotenvParseOutput;\n  issues: string[] = [];\n\n  readEnvFromFile() {\n    const envFile = resolve(__dirname, '../../../.env');\n\n    if (!existsSync(envFile)) {\n      console.error('Env file not found!: ', envFile);\n      return;\n    }\n\n    const handle = readFileSync(envFile, 'utf-8');\n\n    this.cfg = dotenv.parse(handle);\n  }\n\n  readEnvFromProcess() {\n    this.cfg = process.env;\n  }\n\n  check() {\n    this.checkDatabaseServers();\n    this.checkNonEmpty('JWT_SECRET');\n    this.checkIsValidUrl('MAIN_URL');\n    this.checkIsValidUrl('FRONTEND_URL');\n    this.checkIsValidUrl('NEXT_PUBLIC_BACKEND_URL');\n    this.checkIsValidUrl('BACKEND_INTERNAL_URL');\n    this.checkNonEmpty('STORAGE_PROVIDER', 'Needed to setup storage.');\n  }\n\n  checkNonEmpty(key: string, description?: string): boolean {\n    const v = this.get(key);\n\n    if (!description) {\n      description = '';\n    }\n\n    if (!v) {\n      this.issues.push(key + ' not set. ' + description);\n      return false;\n    }\n\n    if (v.length === 0) {\n      this.issues.push(key + ' is empty.' + description);\n      return false;\n    }\n\n    return true;\n  }\n\n  get(key: string): string | undefined {\n    return this.cfg[key as keyof typeof this.cfg];\n  }\n\n  checkDatabaseServers() {\n    this.checkRedis();\n    this.checkIsValidUrl('DATABASE_URL');\n  }\n\n  checkRedis() {\n    if (!this.cfg.REDIS_URL) {\n      this.issues.push('REDIS_URL not set');\n    }\n\n    try {\n      const redisUrl = new URL(this.cfg.REDIS_URL);\n\n      if (redisUrl.protocol !== 'redis:') {\n        this.issues.push('REDIS_URL must start with redis://');\n      }\n    } catch (error) {\n      this.issues.push('REDIS_URL is not a valid URL');\n    }\n  }\n\n  checkIsValidUrl(key: string) {\n    if (!this.checkNonEmpty(key)) {\n      return;\n    }\n\n    const urlString = this.get(key);\n\n    try {\n      new URL(urlString);\n    } catch (error) {\n      this.issues.push(key + ' is not a valid URL');\n    }\n\n    if (urlString.endsWith('/')) {\n      this.issues.push(key + ' should not end with /');\n    }\n  }\n\n  hasIssues() {\n    return this.issues.length > 0;\n  }\n\n  getIssues() {\n    return this.issues;\n  }\n\n  getIssuesCount() {\n    return this.issues.length;\n  }\n}\n"
  },
  {
    "path": "libraries/helpers/src/decorators/plug.decorator.ts",
    "content": "import 'reflect-metadata';\n\nexport function Plug(params: {\n  identifier: string;\n  title: string;\n  description: string;\n  runEveryMilliseconds: number;\n  totalRuns: number;\n  disabled?: boolean;\n  fields: {\n    name: string;\n    description: string;\n    type: string;\n    placeholder: string;\n    validation?: RegExp;\n  }[];\n}) {\n  return function (\n    target: Object,\n    propertyKey: string | symbol,\n    descriptor: any\n  ) {\n    // Retrieve existing metadata or initialize an empty array\n    const existingMetadata = Reflect.getMetadata('custom:plug', target) || [];\n\n    // Add the metadata information for this method\n    existingMetadata.push({ methodName: propertyKey, ...params });\n\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata('custom:plug', existingMetadata, target);\n  };\n}\n"
  },
  {
    "path": "libraries/helpers/src/decorators/post.plug.ts",
    "content": "import 'reflect-metadata';\n\nexport function PostPlug(params: {\n  identifier: string;\n  title: string;\n  disabled?: boolean;\n  description: string;\n  pickIntegration: string[];\n  fields: {\n    name: string;\n    description: string;\n    type: string;\n    placeholder: string;\n    validation?: RegExp;\n  }[];\n}) {\n  return function (\n    target: Object,\n    propertyKey: string | symbol,\n    descriptor: any\n  ) {\n    // Retrieve existing metadata or initialize an empty array\n    const existingMetadata =\n      Reflect.getMetadata('custom:internal_plug', target) || [];\n\n    // Add the metadata information for this method\n    existingMetadata.push({ methodName: propertyKey, ...params });\n\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata('custom:internal_plug', existingMetadata, target);\n  };\n}\n"
  },
  {
    "path": "libraries/helpers/src/subdomain/all.two.level.subdomain.ts",
    "content": "export const allTwoLevelSubdomain = [\n  '.com.de',\n  '.net.ac',\n  '.ddns.net',\n  '.tplinkdns.com',\n  '.synology.me',\n  '.gov.ac',\n  '.org.ac',\n  '.mil.ac',\n  '.co.ae',\n  '.net.ae',\n  '.gov.ae',\n  '.ac.ae',\n  '.sch.ae',\n  '.org.ae',\n  '.mil.ae',\n  '.pro.ae',\n  '.name.ae',\n  '.com.af',\n  '.edu.af',\n  '.gov.af',\n  '.net.af',\n  '.org.af',\n  '.com.al',\n  '.edu.al',\n  '.gov.al',\n  '.mil.al',\n  '.net.al',\n  '.org.al',\n  '.ed.ao',\n  '.gv.ao',\n  '.og.ao',\n  '.co.ao',\n  '.pb.ao',\n  '.it.ao',\n  '.com.ar',\n  '.edu.ar',\n  '.gob.ar',\n  '.gov.ar',\n  '.gov.ar',\n  '.int.ar',\n  '.mil.ar',\n  '.net.ar',\n  '.org.ar',\n  '.tur.ar',\n  '.gv.at',\n  '.ac.at',\n  '.co.at',\n  '.or.at',\n  '.com.au',\n  '.net.au',\n  '.org.au',\n  '.edu.au',\n  '.gov.au',\n  '.csiro.au',\n  '.asn.au',\n  '.id.au',\n  '.org.ba',\n  '.net.ba',\n  '.edu.ba',\n  '.gov.ba',\n  '.mil.ba',\n  '.unsa.ba',\n  '.untz.ba',\n  '.unmo.ba',\n  '.unbi.ba',\n  '.unze.ba',\n  '.co.ba',\n  '.com.ba',\n  '.rs.ba',\n  '.co.bb',\n  '.com.bb',\n  '.net.bb',\n  '.org.bb',\n  '.gov.bb',\n  '.edu.bb',\n  '.info.bb',\n  '.store.bb',\n  '.tv.bb',\n  '.biz.bb',\n  '.com.bh',\n  '.info.bh',\n  '.cc.bh',\n  '.edu.bh',\n  '.biz.bh',\n  '.net.bh',\n  '.org.bh',\n  '.gov.bh',\n  '.com.bn',\n  '.edu.bn',\n  '.gov.bn',\n  '.net.bn',\n  '.org.bn',\n  '.com.bo',\n  '.net.bo',\n  '.org.bo',\n  '.tv.bo',\n  '.mil.bo',\n  '.int.bo',\n  '.gob.bo',\n  '.gov.bo',\n  '.edu.bo',\n  '.adm.br',\n  '.adv.br',\n  '.agr.br',\n  '.am.br',\n  '.arq.br',\n  '.art.br',\n  '.ato.br',\n  '.b.br',\n  '.bio.br',\n  '.blog.br',\n  '.bmd.br',\n  '.cim.br',\n  '.cng.br',\n  '.cnt.br',\n  '.com.br',\n  '.coop.br',\n  '.ecn.br',\n  '.edu.br',\n  '.eng.br',\n  '.esp.br',\n  '.etc.br',\n  '.eti.br',\n  '.far.br',\n  '.flog.br',\n  '.fm.br',\n  '.fnd.br',\n  '.fot.br',\n  '.fst.br',\n  '.g12.br',\n  '.ggf.br',\n  '.gov.br',\n  '.imb.br',\n  '.ind.br',\n  '.inf.br',\n  '.jor.br',\n  '.jus.br',\n  '.lel.br',\n  '.mat.br',\n  '.med.br',\n  '.mil.br',\n  '.mus.br',\n  '.net.br',\n  '.nom.br',\n  '.not.br',\n  '.ntr.br',\n  '.odo.br',\n  '.org.br',\n  '.ppg.br',\n  '.pro.br',\n  '.psc.br',\n  '.psi.br',\n  '.qsl.br',\n  '.rec.br',\n  '.slg.br',\n  '.srv.br',\n  '.tmp.br',\n  '.trd.br',\n  '.tur.br',\n  '.tv.br',\n  '.vet.br',\n  '.vlog.br',\n  '.wiki.br',\n  '.zlg.br',\n  '.com.bs',\n  '.net.bs',\n  '.org.bs',\n  '.edu.bs',\n  '.gov.bs',\n  'com.bz',\n  'edu.bz',\n  'gov.bz',\n  'net.bz',\n  'org.bz',\n  '.ab.ca',\n  '.bc.ca',\n  '.mb.ca',\n  '.nb.ca',\n  '.nf.ca',\n  '.nl.ca',\n  '.ns.ca',\n  '.nt.ca',\n  '.nu.ca',\n  '.on.ca',\n  '.pe.ca',\n  '.qc.ca',\n  '.sk.ca',\n  '.yk.ca',\n  '.co.ck',\n  '.org.ck',\n  '.edu.ck',\n  '.gov.ck',\n  '.net.ck',\n  '.gen.ck',\n  '.biz.ck',\n  '.info.ck',\n  '.ac.cn',\n  '.com.cn',\n  '.edu.cn',\n  '.gov.cn',\n  '.mil.cn',\n  '.net.cn',\n  '.org.cn',\n  '.ah.cn',\n  '.bj.cn',\n  '.cq.cn',\n  '.fj.cn',\n  '.gd.cn',\n  '.gs.cn',\n  '.gz.cn',\n  '.gx.cn',\n  '.ha.cn',\n  '.hb.cn',\n  '.he.cn',\n  '.hi.cn',\n  '.hl.cn',\n  '.hn.cn',\n  '.jl.cn',\n  '.js.cn',\n  '.jx.cn',\n  '.ln.cn',\n  '.nm.cn',\n  '.nx.cn',\n  '.qh.cn',\n  '.sc.cn',\n  '.sd.cn',\n  '.sh.cn',\n  '.sn.cn',\n  '.sx.cn',\n  '.tj.cn',\n  '.tw.cn',\n  '.xj.cn',\n  '.xz.cn',\n  '.yn.cn',\n  '.zj.cn',\n  '.com.co',\n  '.org.co',\n  '.edu.co',\n  '.gov.co',\n  '.net.co',\n  '.mil.co',\n  '.nom.co',\n  '.ac.cr',\n  '.co.cr',\n  '.ed.cr',\n  '.fi.cr',\n  '.go.cr',\n  '.or.cr',\n  '.sa.cr',\n  '.cr',\n  '.ac.cy',\n  '.net.cy',\n  '.gov.cy',\n  '.org.cy',\n  '.pro.cy',\n  '.name.cy',\n  '.ekloges.cy',\n  '.tm.cy',\n  '.ltd.cy',\n  '.biz.cy',\n  '.press.cy',\n  '.parliament.cy',\n  '.com.cy',\n  '.edu.do',\n  '.gob.do',\n  '.gov.do',\n  '.com.do',\n  '.sld.do',\n  '.org.do',\n  '.net.do',\n  '.web.do',\n  '.mil.do',\n  '.art.do',\n  '.com.dz',\n  '.org.dz',\n  '.net.dz',\n  '.gov.dz',\n  '.edu.dz',\n  '.asso.dz',\n  '.pol.dz',\n  '.art.dz',\n  '.com.ec',\n  '.info.ec',\n  '.net.ec',\n  '.fin.ec',\n  '.med.ec',\n  '.pro.ec',\n  '.org.ec',\n  '.edu.ec',\n  '.gov.ec',\n  '.mil.ec',\n  '.com.eg',\n  '.edu.eg',\n  '.eun.eg',\n  '.gov.eg',\n  '.mil.eg',\n  '.name.eg',\n  '.net.eg',\n  '.org.eg',\n  '.sci.eg',\n  '.com.er',\n  '.edu.er',\n  '.gov.er',\n  '.mil.er',\n  '.net.er',\n  '.org.er',\n  '.ind.er',\n  '.rochest.er',\n  '.w.er',\n  '.com.es',\n  '.nom.es',\n  '.org.es',\n  '.gob.es',\n  '.edu.es',\n  '.com.et',\n  '.gov.et',\n  '.org.et',\n  '.edu.et',\n  '.net.et',\n  '.biz.et',\n  '.name.et',\n  '.info.et',\n  '.ac.fj',\n  '.biz.fj',\n  '.com.fj',\n  '.info.fj',\n  '.mil.fj',\n  '.name.fj',\n  '.net.fj',\n  '.org.fj',\n  '.pro.fj',\n  '.co.fk',\n  '.org.fk',\n  '.gov.fk',\n  '.ac.fk',\n  '.nom.fk',\n  '.net.fk',\n  '.fr',\n  '.tm.fr',\n  '.asso.fr',\n  '.nom.fr',\n  '.prd.fr',\n  '.presse.fr',\n  '.com.fr',\n  '.gouv.fr',\n  '.co.gg',\n  '.net.gg',\n  '.org.gg',\n  '.com.gh',\n  '.edu.gh',\n  '.gov.gh',\n  '.org.gh',\n  '.mil.gh',\n  '.com.gn',\n  '.ac.gn',\n  '.gov.gn',\n  '.org.gn',\n  '.net.gn',\n  '.com.gr',\n  '.edu.gr',\n  '.net.gr',\n  '.org.gr',\n  '.gov.gr',\n  '.mil.gr',\n  '.com.gt',\n  '.edu.gt',\n  '.net.gt',\n  '.gob.gt',\n  '.org.gt',\n  '.mil.gt',\n  '.ind.gt',\n  '.com.gu',\n  '.net.gu',\n  '.gov.gu',\n  '.org.gu',\n  '.edu.gu',\n  '.com.hk',\n  '.edu.hk',\n  '.gov.hk',\n  '.idv.hk',\n  '.net.hk',\n  '.org.hk',\n  '.ac.id',\n  '.co.id',\n  '.net.id',\n  '.or.id',\n  '.web.id',\n  '.sch.id',\n  '.mil.id',\n  '.go.id',\n  '.war.net.id',\n  '.ac.il',\n  '.co.il',\n  '.org.il',\n  '.net.il',\n  '.k12.il',\n  '.gov.il',\n  '.muni.il',\n  '.idf.il',\n  '.in',\n  '.4fd.in',\n  '.co.in',\n  '.firm.in',\n  '.net.in',\n  '.org.in',\n  '.gen.in',\n  '.ind.in',\n  '.ac.in',\n  '.edu.in',\n  '.res.in',\n  '.ernet.in',\n  '.gov.in',\n  '.mil.in',\n  '.nic.in',\n  '.nic.in',\n  '.iq',\n  '.gov.iq',\n  '.edu.iq',\n  '.com.iq',\n  '.mil.iq',\n  '.org.iq',\n  '.net.iq',\n  '.ir',\n  '.ac.ir',\n  '.co.ir',\n  '.gov.ir',\n  '.id.ir',\n  '.net.ir',\n  '.org.ir',\n  '.sch.ir',\n  '.dnssec.ir',\n  '.gov.it',\n  '.edu.it',\n  '.co.je',\n  '.net.je',\n  '.org.je',\n  '.com.jo',\n  '.net.jo',\n  '.gov.jo',\n  '.edu.jo',\n  '.org.jo',\n  '.mil.jo',\n  '.name.jo',\n  '.sch.jo',\n  '.ac.jp',\n  '.ad.jp',\n  '.co.jp',\n  '.ed.jp',\n  '.go.jp',\n  '.gr.jp',\n  '.lg.jp',\n  '.ne.jp',\n  '.or.jp',\n  '.co.ke',\n  '.or.ke',\n  '.ne.ke',\n  '.go.ke',\n  '.ac.ke',\n  '.sc.ke',\n  '.me.ke',\n  '.mobi.ke',\n  '.info.ke',\n  '.per.kh',\n  '.com.kh',\n  '.edu.kh',\n  '.gov.kh',\n  '.mil.kh',\n  '.net.kh',\n  '.org.kh',\n  '.com.ki',\n  '.biz.ki',\n  '.de.ki',\n  '.net.ki',\n  '.info.ki',\n  '.org.ki',\n  '.gov.ki',\n  '.edu.ki',\n  '.mob.ki',\n  '.tel.ki',\n  '.km',\n  '.com.km',\n  '.coop.km',\n  '.asso.km',\n  '.nom.km',\n  '.presse.km',\n  '.tm.km',\n  '.medecin.km',\n  '.notaires.km',\n  '.pharmaciens.km',\n  '.veterinaire.km',\n  '.edu.km',\n  '.gouv.km',\n  '.mil.km',\n  '.net.kn',\n  '.org.kn',\n  '.edu.kn',\n  '.gov.kn',\n  '.kr',\n  '.co.kr',\n  '.ne.kr',\n  '.or.kr',\n  '.re.kr',\n  '.pe.kr',\n  '.go.kr',\n  '.mil.kr',\n  '.ac.kr',\n  '.hs.kr',\n  '.ms.kr',\n  '.es.kr',\n  '.sc.kr',\n  '.kg.kr',\n  '.seoul.kr',\n  '.busan.kr',\n  '.daegu.kr',\n  '.incheon.kr',\n  '.gwangju.kr',\n  '.daejeon.kr',\n  '.ulsan.kr',\n  '.gyeonggi.kr',\n  '.gangwon.kr',\n  '.chungbuk.kr',\n  '.chungnam.kr',\n  '.jeonbuk.kr',\n  '.jeonnam.kr',\n  '.gyeongbuk.kr',\n  '.gyeongnam.kr',\n  '.jeju.kr',\n  '.edu.kw',\n  '.com.kw',\n  '.net.kw',\n  '.org.kw',\n  '.gov.kw',\n  '.com.ky',\n  '.org.ky',\n  '.net.ky',\n  '.edu.ky',\n  '.gov.ky',\n  '.com.kz',\n  '.edu.kz',\n  '.gov.kz',\n  '.mil.kz',\n  '.net.kz',\n  '.org.kz',\n  '.com.lb',\n  '.edu.lb',\n  '.gov.lb',\n  '.net.lb',\n  '.org.lb',\n  '.gov.lk',\n  '.sch.lk',\n  '.net.lk',\n  '.int.lk',\n  '.com.lk',\n  '.org.lk',\n  '.edu.lk',\n  '.ngo.lk',\n  '.soc.lk',\n  '.web.lk',\n  '.ltd.lk',\n  '.assn.lk',\n  '.grp.lk',\n  '.hotel.lk',\n  '.com.lr',\n  '.edu.lr',\n  '.gov.lr',\n  '.org.lr',\n  '.net.lr',\n  '.com.lv',\n  '.edu.lv',\n  '.gov.lv',\n  '.org.lv',\n  '.mil.lv',\n  '.id.lv',\n  '.net.lv',\n  '.asn.lv',\n  '.conf.lv',\n  '.com.ly',\n  '.net.ly',\n  '.gov.ly',\n  '.plc.ly',\n  '.edu.ly',\n  '.sch.ly',\n  '.med.ly',\n  '.org.ly',\n  '.id.ly',\n  '.ma',\n  '.net.ma',\n  '.ac.ma',\n  '.org.ma',\n  '.gov.ma',\n  '.press.ma',\n  '.co.ma',\n  '.tm.mc',\n  '.asso.mc',\n  '.co.me',\n  '.net.me',\n  '.org.me',\n  '.edu.me',\n  '.ac.me',\n  '.gov.me',\n  '.its.me',\n  '.priv.me',\n  '.org.mg',\n  '.nom.mg',\n  '.gov.mg',\n  '.prd.mg',\n  '.tm.mg',\n  '.edu.mg',\n  '.mil.mg',\n  '.com.mg',\n  '.com.mk',\n  '.org.mk',\n  '.net.mk',\n  '.edu.mk',\n  '.gov.mk',\n  '.inf.mk',\n  '.name.mk',\n  '.pro.mk',\n  '.com.ml',\n  '.net.ml',\n  '.org.ml',\n  '.edu.ml',\n  '.gov.ml',\n  '.presse.ml',\n  '.gov.mn',\n  '.edu.mn',\n  '.org.mn',\n  '.com.mo',\n  '.edu.mo',\n  '.gov.mo',\n  '.net.mo',\n  '.org.mo',\n  '.com.mt',\n  '.org.mt',\n  '.net.mt',\n  '.edu.mt',\n  '.gov.mt',\n  '.aero.mv',\n  '.biz.mv',\n  '.com.mv',\n  '.coop.mv',\n  '.edu.mv',\n  '.gov.mv',\n  '.info.mv',\n  '.int.mv',\n  '.mil.mv',\n  '.museum.mv',\n  '.name.mv',\n  '.net.mv',\n  '.org.mv',\n  '.pro.mv',\n  '.ac.mw',\n  '.co.mw',\n  '.com.mw',\n  '.coop.mw',\n  '.edu.mw',\n  '.gov.mw',\n  '.int.mw',\n  '.museum.mw',\n  '.net.mw',\n  '.org.mw',\n  '.com.mx',\n  '.net.mx',\n  '.org.mx',\n  '.edu.mx',\n  '.gob.mx',\n  '.com.my',\n  '.net.my',\n  '.org.my',\n  '.gov.my',\n  '.edu.my',\n  '.sch.my',\n  '.mil.my',\n  '.name.my',\n  '.com.nf',\n  '.net.nf',\n  '.arts.nf',\n  '.store.nf',\n  '.web.nf',\n  '.firm.nf',\n  '.info.nf',\n  '.other.nf',\n  '.per.nf',\n  '.rec.nf',\n  '.com.ng',\n  '.org.ng',\n  '.gov.ng',\n  '.edu.ng',\n  '.net.ng',\n  '.sch.ng',\n  '.name.ng',\n  '.mobi.ng',\n  '.biz.ng',\n  '.mil.ng',\n  '.gob.ni',\n  '.co.ni',\n  '.com.ni',\n  '.ac.ni',\n  '.edu.ni',\n  '.org.ni',\n  '.nom.ni',\n  '.net.ni',\n  '.mil.ni',\n  '.com.np',\n  '.edu.np',\n  '.gov.np',\n  '.org.np',\n  '.mil.np',\n  '.net.np',\n  '.edu.nr',\n  '.gov.nr',\n  '.biz.nr',\n  '.info.nr',\n  '.net.nr',\n  '.org.nr',\n  '.com.nr',\n  '.com.om',\n  '.co.om',\n  '.edu.om',\n  '.ac.om',\n  '.sch.om',\n  '.gov.om',\n  '.net.om',\n  '.org.om',\n  '.mil.om',\n  '.museum.om',\n  '.biz.om',\n  '.pro.om',\n  '.med.om',\n  '.edu.pe',\n  '.gob.pe',\n  '.nom.pe',\n  '.mil.pe',\n  '.sld.pe',\n  '.org.pe',\n  '.com.pe',\n  '.net.pe',\n  '.com.ph',\n  '.net.ph',\n  '.org.ph',\n  '.mil.ph',\n  '.ngo.ph',\n  '.i.ph',\n  '.gov.ph',\n  '.edu.ph',\n  '.com.pk',\n  '.net.pk',\n  '.edu.pk',\n  '.org.pk',\n  '.fam.pk',\n  '.biz.pk',\n  '.web.pk',\n  '.gov.pk',\n  '.gob.pk',\n  '.gok.pk',\n  '.gon.pk',\n  '.gop.pk',\n  '.gos.pk',\n  '.pwr.pl',\n  '.com.pl',\n  '.biz.pl',\n  '.net.pl',\n  '.art.pl',\n  '.edu.pl',\n  '.org.pl',\n  '.ngo.pl',\n  '.gov.pl',\n  '.info.pl',\n  '.mil.pl',\n  '.waw.pl',\n  '.warszawa.pl',\n  '.wroc.pl',\n  '.wroclaw.pl',\n  '.krakow.pl',\n  '.katowice.pl',\n  '.poznan.pl',\n  '.lodz.pl',\n  '.gda.pl',\n  '.gdansk.pl',\n  '.slupsk.pl',\n  '.radom.pl',\n  '.szczecin.pl',\n  '.lublin.pl',\n  '.bialystok.pl',\n  '.olsztyn.pl',\n  '.torun.pl',\n  '.gorzow.pl',\n  '.zgora.pl',\n  '.biz.pr',\n  '.com.pr',\n  '.edu.pr',\n  '.gov.pr',\n  '.info.pr',\n  '.isla.pr',\n  '.name.pr',\n  '.net.pr',\n  '.org.pr',\n  '.pro.pr',\n  '.est.pr',\n  '.prof.pr',\n  '.ac.pr',\n  '.com.ps',\n  '.net.ps',\n  '.org.ps',\n  '.edu.ps',\n  '.gov.ps',\n  '.plo.ps',\n  '.sec.ps',\n  '.co.pw',\n  '.ne.pw',\n  '.or.pw',\n  '.ed.pw',\n  '.go.pw',\n  '.belau.pw',\n  '.arts.ro',\n  '.com.ro',\n  '.firm.ro',\n  '.info.ro',\n  '.nom.ro',\n  '.nt.ro',\n  '.org.ro',\n  '.rec.ro',\n  '.store.ro',\n  '.tm.ro',\n  '.www.ro',\n  '.co.rs',\n  '.org.rs',\n  '.edu.rs',\n  '.ac.rs',\n  '.gov.rs',\n  '.in.rs',\n  '.com.sb',\n  '.net.sb',\n  '.edu.sb',\n  '.org.sb',\n  '.gov.sb',\n  '.com.sc',\n  '.net.sc',\n  '.edu.sc',\n  '.gov.sc',\n  '.org.sc',\n  '.co.sh',\n  '.com.sh',\n  '.org.sh',\n  '.gov.sh',\n  '.edu.sh',\n  '.net.sh',\n  '.nom.sh',\n  '.com.sl',\n  '.net.sl',\n  '.org.sl',\n  '.edu.sl',\n  '.gov.sl',\n  '.gov.st',\n  '.saotome.st',\n  '.principe.st',\n  '.consulado.st',\n  '.embaixada.st',\n  '.org.st',\n  '.edu.st',\n  '.net.st',\n  '.com.st',\n  '.store.st',\n  '.mil.st',\n  '.co.st',\n  '.edu.sv',\n  '.gob.sv',\n  '.com.sv',\n  '.org.sv',\n  '.red.sv',\n  '.co.sz',\n  '.ac.sz',\n  '.org.sz',\n  '.com.tr',\n  '.gen.tr',\n  '.org.tr',\n  '.biz.tr',\n  '.info.tr',\n  '.av.tr',\n  '.dr.tr',\n  '.pol.tr',\n  '.bel.tr',\n  '.tsk.tr',\n  '.bbs.tr',\n  '.k12.tr',\n  '.edu.tr',\n  '.name.tr',\n  '.net.tr',\n  '.gov.tr',\n  '.web.tr',\n  '.tel.tr',\n  '.tv.tr',\n  '.co.tt',\n  '.com.tt',\n  '.org.tt',\n  '.net.tt',\n  '.biz.tt',\n  '.info.tt',\n  '.pro.tt',\n  '.int.tt',\n  '.coop.tt',\n  '.jobs.tt',\n  '.mobi.tt',\n  '.travel.tt',\n  '.museum.tt',\n  '.aero.tt',\n  '.cat.tt',\n  '.tel.tt',\n  '.name.tt',\n  '.mil.tt',\n  '.edu.tt',\n  '.gov.tt',\n  '.edu.tw',\n  '.gov.tw',\n  '.mil.tw',\n  '.com.tw',\n  '.net.tw',\n  '.org.tw',\n  '.idv.tw',\n  '.game.tw',\n  '.ebiz.tw',\n  '.club.tw',\n  '.com.mu',\n  '.gov.mu',\n  '.net.mu',\n  '.org.mu',\n  '.ac.mu',\n  '.co.mu',\n  '.or.mu',\n  '.ac.mz',\n  '.co.mz',\n  '.edu.mz',\n  '.org.mz',\n  '.gov.mz',\n  '.com.na',\n  '.co.na',\n  '.ac.nz',\n  '.co.nz',\n  '.cri.nz',\n  '.geek.nz',\n  '.gen.nz',\n  '.govt.nz',\n  '.health.nz',\n  '.iwi.nz',\n  '.maori.nz',\n  '.mil.nz',\n  '.net.nz',\n  '.org.nz',\n  '.parliament.nz',\n  '.school.nz',\n  '.abo.pa',\n  '.ac.pa',\n  '.com.pa',\n  '.edu.pa',\n  '.gob.pa',\n  '.ing.pa',\n  '.med.pa',\n  '.net.pa',\n  '.nom.pa',\n  '.org.pa',\n  '.sld.pa',\n  '.com.pt',\n  '.edu.pt',\n  '.gov.pt',\n  '.int.pt',\n  '.net.pt',\n  '.nome.pt',\n  '.org.pt',\n  '.publ.pt',\n  '.com.py',\n  '.edu.py',\n  '.gov.py',\n  '.mil.py',\n  '.net.py',\n  '.org.py',\n  '.com.qa',\n  '.edu.qa',\n  '.gov.qa',\n  '.mil.qa',\n  '.net.qa',\n  '.org.qa',\n  '.asso.re',\n  '.com.re',\n  '.nom.re',\n  '.ac.ru',\n  '.adygeya.ru',\n  '.altai.ru',\n  '.amur.ru',\n  '.arkhangelsk.ru',\n  '.astrakhan.ru',\n  '.bashkiria.ru',\n  '.belgorod.ru',\n  '.bir.ru',\n  '.bryansk.ru',\n  '.buryatia.ru',\n  '.cbg.ru',\n  '.chel.ru',\n  '.chelyabinsk.ru',\n  '.chita.ru',\n  '.chita.ru',\n  '.chukotka.ru',\n  '.chuvashia.ru',\n  '.com.ru',\n  '.dagestan.ru',\n  '.e-burg.ru',\n  '.edu.ru',\n  '.gov.ru',\n  '.grozny.ru',\n  '.int.ru',\n  '.irkutsk.ru',\n  '.ivanovo.ru',\n  '.izhevsk.ru',\n  '.jar.ru',\n  '.joshkar-ola.ru',\n  '.kalmykia.ru',\n  '.kaluga.ru',\n  '.kamchatka.ru',\n  '.karelia.ru',\n  '.kazan.ru',\n  '.kchr.ru',\n  '.kemerovo.ru',\n  '.khabarovsk.ru',\n  '.khakassia.ru',\n  '.khv.ru',\n  '.kirov.ru',\n  '.koenig.ru',\n  '.komi.ru',\n  '.kostroma.ru',\n  '.kranoyarsk.ru',\n  '.kuban.ru',\n  '.kurgan.ru',\n  '.kursk.ru',\n  '.lipetsk.ru',\n  '.magadan.ru',\n  '.mari.ru',\n  '.mari-el.ru',\n  '.marine.ru',\n  '.mil.ru',\n  '.mordovia.ru',\n  '.mosreg.ru',\n  '.msk.ru',\n  '.murmansk.ru',\n  '.nalchik.ru',\n  '.net.ru',\n  '.nnov.ru',\n  '.nov.ru',\n  '.novosibirsk.ru',\n  '.nsk.ru',\n  '.omsk.ru',\n  '.orenburg.ru',\n  '.org.ru',\n  '.oryol.ru',\n  '.penza.ru',\n  '.perm.ru',\n  '.pp.ru',\n  '.pskov.ru',\n  '.ptz.ru',\n  '.rnd.ru',\n  '.ryazan.ru',\n  '.sakhalin.ru',\n  '.samara.ru',\n  '.saratov.ru',\n  '.simbirsk.ru',\n  '.smolensk.ru',\n  '.spb.ru',\n  '.stavropol.ru',\n  '.stv.ru',\n  '.surgut.ru',\n  '.tambov.ru',\n  '.tatarstan.ru',\n  '.tom.ru',\n  '.tomsk.ru',\n  '.tsaritsyn.ru',\n  '.tsk.ru',\n  '.tula.ru',\n  '.tuva.ru',\n  '.tver.ru',\n  '.tyumen.ru',\n  '.udm.ru',\n  '.udmurtia.ru',\n  '.ulan-ude.ru',\n  '.vladikavkaz.ru',\n  '.vladimir.ru',\n  '.vladivostok.ru',\n  '.volgograd.ru',\n  '.vologda.ru',\n  '.voronezh.ru',\n  '.vrn.ru',\n  '.vyatka.ru',\n  '.yakutia.ru',\n  '.yamal.ru',\n  '.yekaterinburg.ru',\n  '.yuzhno-sakhalinsk.ru',\n  '.ac.rw',\n  '.co.rw',\n  '.com.rw',\n  '.edu.rw',\n  '.gouv.rw',\n  '.gov.rw',\n  '.int.rw',\n  '.mil.rw',\n  '.net.rw',\n  '.com.sa',\n  '.edu.sa',\n  '.gov.sa',\n  '.med.sa',\n  '.net.sa',\n  '.org.sa',\n  '.pub.sa',\n  '.sch.sa',\n  '.com.sd',\n  '.edu.sd',\n  '.gov.sd',\n  '.info.sd',\n  '.med.sd',\n  '.net.sd',\n  '.org.sd',\n  '.tv.sd',\n  '.a.se',\n  '.ac.se',\n  '.b.se',\n  '.bd.se',\n  '.c.se',\n  '.d.se',\n  '.e.se',\n  '.f.se',\n  '.g.se',\n  '.h.se',\n  '.i.se',\n  '.k.se',\n  '.l.se',\n  '.m.se',\n  '.n.se',\n  '.o.se',\n  '.org.se',\n  '.p.se',\n  '.parti.se',\n  '.pp.se',\n  '.press.se',\n  '.r.se',\n  '.s.se',\n  '.t.se',\n  '.tm.se',\n  '.u.se',\n  '.w.se',\n  '.x.se',\n  '.y.se',\n  '.z.se',\n  '.com.sg',\n  '.edu.sg',\n  '.gov.sg',\n  '.idn.sg',\n  '.net.sg',\n  '.org.sg',\n  '.per.sg',\n  '.art.sn',\n  '.com.sn',\n  '.edu.sn',\n  '.gouv.sn',\n  '.org.sn',\n  '.perso.sn',\n  '.univ.sn',\n  '.com.sy',\n  '.edu.sy',\n  '.gov.sy',\n  '.mil.sy',\n  '.net.sy',\n  '.news.sy',\n  '.org.sy',\n  '.ac.th',\n  '.co.th',\n  '.go.th',\n  '.in.th',\n  '.mi.th',\n  '.net.th',\n  '.or.th',\n  '.ac.tj',\n  '.biz.tj',\n  '.co.tj',\n  '.com.tj',\n  '.edu.tj',\n  '.go.tj',\n  '.gov.tj',\n  '.info.tj',\n  '.int.tj',\n  '.mil.tj',\n  '.name.tj',\n  '.net.tj',\n  '.nic.tj',\n  '.org.tj',\n  '.test.tj',\n  '.web.tj',\n  '.agrinet.tn',\n  '.com.tn',\n  '.defense.tn',\n  '.edunet.tn',\n  '.ens.tn',\n  '.fin.tn',\n  '.gov.tn',\n  '.ind.tn',\n  '.info.tn',\n  '.intl.tn',\n  '.mincom.tn',\n  '.nat.tn',\n  '.net.tn',\n  '.org.tn',\n  '.perso.tn',\n  '.rnrt.tn',\n  '.rns.tn',\n  '.rnu.tn',\n  '.tourism.tn',\n  '.ac.tz',\n  '.co.tz',\n  '.go.tz',\n  '.ne.tz',\n  '.or.tz',\n  '.biz.ua',\n  '.cherkassy.ua',\n  '.chernigov.ua',\n  '.chernovtsy.ua',\n  '.ck.ua',\n  '.cn.ua',\n  '.co.ua',\n  '.com.ua',\n  '.crimea.ua',\n  '.cv.ua',\n  '.dn.ua',\n  '.dnepropetrovsk.ua',\n  '.donetsk.ua',\n  '.dp.ua',\n  '.edu.ua',\n  '.gov.ua',\n  '.if.ua',\n  '.in.ua',\n  '.ivano-frankivsk.ua',\n  '.kh.ua',\n  '.kharkov.ua',\n  '.kherson.ua',\n  '.khmelnitskiy.ua',\n  '.kiev.ua',\n  '.kirovograd.ua',\n  '.km.ua',\n  '.kr.ua',\n  '.ks.ua',\n  '.kv.ua',\n  '.lg.ua',\n  '.lugansk.ua',\n  '.lutsk.ua',\n  '.lviv.ua',\n  '.me.ua',\n  '.mk.ua',\n  '.net.ua',\n  '.nikolaev.ua',\n  '.od.ua',\n  '.odessa.ua',\n  '.org.ua',\n  '.pl.ua',\n  '.poltava.ua',\n  '.pp.ua',\n  '.rovno.ua',\n  '.rv.ua',\n  '.sebastopol.ua',\n  '.sumy.ua',\n  '.te.ua',\n  '.ternopil.ua',\n  '.uzhgorod.ua',\n  '.vinnica.ua',\n  '.vn.ua',\n  '.zaporizhzhe.ua',\n  '.zhitomir.ua',\n  '.zp.ua',\n  '.zt.ua',\n  '.ac.ug',\n  '.co.ug',\n  '.go.ug',\n  '.ne.ug',\n  '.or.ug',\n  '.org.ug',\n  '.sc.ug',\n  '.ac.uk',\n  '.bl.uk',\n  '.british-library.uk',\n  '.co.uk',\n  '.cym.uk',\n  '.gov.uk',\n  '.govt.uk',\n  '.icnet.uk',\n  '.jet.uk',\n  '.lea.uk',\n  '.ltd.uk',\n  '.me.uk',\n  '.mil.uk',\n  '.mod.uk',\n  '.mod.uk',\n  '.national-library-scotland.uk',\n  '.nel.uk',\n  '.net.uk',\n  '.nhs.uk',\n  '.nhs.uk',\n  '.nic.uk',\n  '.nls.uk',\n  '.org.uk',\n  '.orgn.uk',\n  '.parliament.uk',\n  '.parliament.uk',\n  '.plc.uk',\n  '.police.uk',\n  '.sch.uk',\n  '.scot.uk',\n  '.soc.uk',\n  '.4fd.us',\n  '.dni.us',\n  '.fed.us',\n  '.isa.us',\n  '.kids.us',\n  '.nsn.us',\n  '.com.uy',\n  '.edu.uy',\n  '.gub.uy',\n  '.mil.uy',\n  '.net.uy',\n  '.org.uy',\n  '.co.ve',\n  '.com.ve',\n  '.edu.ve',\n  '.gob.ve',\n  '.info.ve',\n  '.mil.ve',\n  '.net.ve',\n  '.org.ve',\n  '.web.ve',\n  '.co.vi',\n  '.com.vi',\n  '.k12.vi',\n  '.net.vi',\n  '.org.vi',\n  '.ac.vn',\n  '.biz.vn',\n  '.com.vn',\n  '.edu.vn',\n  '.gov.vn',\n  '.health.vn',\n  '.info.vn',\n  '.int.vn',\n  '.name.vn',\n  '.net.vn',\n  '.org.vn',\n  '.pro.vn',\n  '.co.ye',\n  '.com.ye',\n  '.gov.ye',\n  '.ltd.ye',\n  '.me.ye',\n  '.net.ye',\n  '.org.ye',\n  '.plc.ye',\n  '.ac.yu',\n  '.co.yu',\n  '.edu.yu',\n  '.gov.yu',\n  '.org.yu',\n  '.ac.za',\n  '.agric.za',\n  '.alt.za',\n  '.bourse.za',\n  '.city.za',\n  '.co.za',\n  '.cybernet.za',\n  '.db.za',\n  '.ecape.school.za',\n  '.edu.za',\n  '.fs.school.za',\n  '.gov.za',\n  '.gp.school.za',\n  '.grondar.za',\n  '.iaccess.za',\n  '.imt.za',\n  '.inca.za',\n  '.kzn.school.za',\n  '.landesign.za',\n  '.law.za',\n  '.lp.school.za',\n  '.mil.za',\n  '.mpm.school.za',\n  '.ncape.school.za',\n  '.net.za',\n  '.ngo.za',\n  '.nis.za',\n  '.nom.za',\n  '.nw.school.za',\n  '.olivetti.za',\n  '.org.za',\n  '.pix.za',\n  '.school.za',\n  '.tm.za',\n  '.wcape.school.za',\n  '.web.za',\n  '.ac.zm',\n  '.co.zm',\n  '.com.zm',\n  '.edu.zm',\n  '.gov.zm',\n  '.net.zm',\n  '.org.zm',\n  '.sch.zm',\n];\n"
  },
  {
    "path": "libraries/helpers/src/subdomain/subdomain.management.ts",
    "content": "import { parse } from 'tldts';\n\nexport function getCookieUrlFromDomain(domain: string) {\n  const url = parse(domain);\n  return url.domain! ? '.' + url.domain! : url.hostname!;\n}\n"
  },
  {
    "path": "libraries/helpers/src/swagger/load.swagger.ts",
    "content": "import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';\nimport { INestApplication } from '@nestjs/common';\n\nexport const loadSwagger = (app: INestApplication) => {\n  const config = new DocumentBuilder()\n    .setTitle('Postiz Swagger file')\n    .setDescription('API description')\n    .setVersion('1.0')\n    .build();\n\n  const document = SwaggerModule.createDocument(app, config);\n  SwaggerModule.setup('docs', app, document);\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/count.length.ts",
    "content": "// @ts-ignore\nimport twitter from 'twitter-text';\n\nexport const textSlicer = (\n  integrationType: string,\n  end: number,\n  text: string\n): { start: number; end: number } => {\n  if (integrationType !== 'x') {\n    return {\n      start: 0,\n      end,\n    };\n  }\n\n  const { validRangeEnd, valid } = twitter.parseTweet(text, {\n    version: 3,\n    maxWeightedTweetLength: end,\n    scale: 100,\n    defaultWeight: 200,\n    emojiParsingEnabled: true,\n    transformedURLLength: 23,\n    ranges: [\n      { start: 0, end: 4351, weight: 100 },\n      { start: 8192, end: 8205, weight: 100 },\n      { start: 8208, end: 8223, weight: 100 },\n      { start: 8242, end: 8247, weight: 100 },\n    ],\n  });\n\n  return {\n    start: 0,\n    end: valid ? end : validRangeEnd,\n  };\n};\n\nexport const weightedLength = (text: string): number => {\n  return twitter.parseTweet(text).weightedLength;\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/custom.fetch.func.ts",
    "content": "export interface Params {\n  baseUrl: string;\n  beforeRequest?: (url: string, options: RequestInit) => Promise<RequestInit>;\n  afterRequest?: (\n    url: string,\n    options: RequestInit,\n    response: Response\n  ) => Promise<boolean>;\n}\nexport const customFetch = (\n  params: Params,\n  auth?: string,\n  showorg?: string,\n  secured: boolean = true\n) => {\n  return async function newFetch(url: string, options: RequestInit = {}) {\n    const loggedAuth =\n      typeof window === 'undefined'\n        ? undefined\n        : new URL(window.location.href).searchParams.get('loggedAuth');\n    const newRequestObject = await params?.beforeRequest?.(url, options);\n    const authNonSecuredCookie =\n      typeof document === 'undefined'\n        ? null\n        : document.cookie\n            .split(';')\n            .find((p) => p.includes('auth='))\n            ?.split('=')[1];\n\n    const authNonSecuredOrg =\n      typeof document === 'undefined'\n        ? null\n        : document.cookie\n            .split(';')\n            .find((p) => p.includes('showorg='))\n            ?.split('=')[1];\n\n    const authNonSecuredImpersonate =\n      typeof document === 'undefined'\n        ? null\n        : document.cookie\n            .split(';')\n            .find((p) => p.includes('impersonate='))\n            ?.split('=')[1];\n\n    const fetchRequest = await fetch(params.baseUrl + url, {\n      ...(secured ? { credentials: 'include' } : {}),\n      ...(newRequestObject || options),\n      headers: {\n        ...(showorg\n          ? { showorg }\n          : authNonSecuredOrg\n          ? { showorg: authNonSecuredOrg }\n          : {}),\n        ...(options.body instanceof FormData\n          ? {}\n          : { 'Content-Type': 'application/json' }),\n        Accept: 'application/json',\n        ...(loggedAuth ? { auth: loggedAuth } : {}),\n        ...options?.headers,\n        ...(auth\n          ? { auth }\n          : authNonSecuredCookie\n          ? { auth: authNonSecuredCookie }\n          : {}),\n        ...(authNonSecuredImpersonate\n          ? { impersonate: authNonSecuredImpersonate }\n          : {}),\n      },\n      // @ts-ignore\n      ...(!options.next && options.cache !== 'force-cache'\n        ? { cache: options.cache || 'no-store' }\n        : {}),\n    });\n\n    if (\n      !params?.afterRequest ||\n      (await params?.afterRequest?.(url, options, fetchRequest))\n    ) {\n      return fetchRequest;\n    }\n\n    // @ts-ignore\n    return new Promise((res) => {}) as Response;\n  };\n};\n\nexport const fetchBackend = customFetch({\n  get baseUrl() {\n    return process.env.BACKEND_URL!;\n  },\n});\n"
  },
  {
    "path": "libraries/helpers/src/utils/custom.fetch.tsx",
    "content": "'use client';\n\nimport {\n  createContext,\n  FC,\n  ReactNode,\n  useContext,\n  useRef,\n  useState,\n} from 'react';\nimport { customFetch, Params } from './custom.fetch.func';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\n\nconst FetchProvider = createContext(\n  customFetch(\n    // @ts-ignore\n    {\n      baseUrl: '',\n      beforeRequest: () => {},\n      afterRequest: () => {\n        return true;\n      },\n    } as Params\n  )\n);\n\nexport const FetchWrapperComponent: FC<Params & { children: ReactNode }> = (\n  props\n) => {\n  const { children, ...params } = props;\n  const { isSecured } = useVariables();\n  // @ts-ignore\n  const fetchData = useRef(\n    customFetch(params, undefined, undefined, isSecured)\n  );\n  return (\n    // @ts-ignore\n    <FetchProvider.Provider value={fetchData.current}>\n      {children}\n    </FetchProvider.Provider>\n  );\n};\n\nexport const useFetch = () => {\n  return useContext(FetchProvider);\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/internal.fetch.ts",
    "content": "import { cookies } from 'next/headers';\nimport { customFetch } from '@gitroom/helpers/utils/custom.fetch.func';\n\nexport const internalFetch = (url: string, options: RequestInit = {}) =>\n  customFetch(\n    { baseUrl: process.env.BACKEND_INTERNAL_URL! },\n    cookies()?.get('auth')?.value!,\n    cookies()?.get('showorg')?.value!\n  )(url, options);\n"
  },
  {
    "path": "libraries/helpers/src/utils/is.dev.ts",
    "content": "export const isDev = () => {\n  return process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/is.general.server.side.ts",
    "content": "export const isGeneralServerSide = () => {\n  return !!process.env.IS_GENERAL;\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/linkedin.company.prevent.remove.ts",
    "content": "export const linkedinCompanyPreventRemove = (text: string) => {\n  const regex = /@\\[(.*?)]\\(urn:li:organization:(\\d+)\\)/g;\n\n  return text.replace(regex, `[bold]@$1[/bold]`);\n};\n\nexport const afterLinkedinCompanyPreventRemove = (text: string) => {\n  const regex = /\\[bold]@([^[]+)\\[\\/bold]/g;\n  return text.replace(regex, '<strong>@$1</strong>');\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/posts.list.minify.ts",
    "content": "// Key mappings for minifying post list/calendar responses to reduce payload size.\n// Both backend (minify) and frontend (expand) import from here.\n\nconst POST_LIST_KEYS: Record<string, string> = {\n  posts: 'p',\n  total: 't',\n  page: 'pg',\n  limit: 'l',\n  hasMore: 'hm',\n};\n\nconst POST_CALENDAR_KEYS: Record<string, string> = {\n  posts: 'p',\n};\n\nconst POST_ITEM_KEYS: Record<string, string> = {\n  id: 'i',\n  content: 'c',\n  publishDate: 'd',\n  releaseURL: 'u',\n  releaseId: 'ri',\n  state: 's',\n  group: 'g',\n  tags: 'tg',\n  integration: 'n',\n  intervalInDays: 'iv',\n  actualDate: 'ad',\n};\n\nconst INTEGRATION_KEYS: Record<string, string> = {\n  id: 'i',\n  providerIdentifier: 'pi',\n  name: 'n',\n  picture: 'p',\n};\n\nconst TAG_KEYS: Record<string, string> = {\n  tag: 't',\n};\n\nconst TAG_INNER_KEYS: Record<string, string> = {\n  id: 'i',\n  name: 'n',\n  color: 'c',\n  orgId: 'o',\n  createdAt: 'ca',\n  updatedAt: 'ua',\n  deletedAt: 'da',\n};\n\nfunction mapKeys(obj: Record<string, any>, keyMap: Record<string, string>) {\n  const result: Record<string, any> = {};\n  for (const [key, value] of Object.entries(obj)) {\n    result[keyMap[key] || key] = value;\n  }\n  return result;\n}\n\nfunction reverseMap(keyMap: Record<string, string>) {\n  const reversed: Record<string, string> = {};\n  for (const [key, value] of Object.entries(keyMap)) {\n    reversed[value] = key;\n  }\n  return reversed;\n}\n\nfunction minifyPostItem(post: any) {\n  return mapKeys(\n    {\n      ...post,\n      integration: post.integration\n        ? mapKeys(post.integration, INTEGRATION_KEYS)\n        : post.integration,\n      tags: post.tags?.map((tagWrapper: any) =>\n        mapKeys(\n          {\n            ...tagWrapper,\n            tag: tagWrapper.tag\n              ? mapKeys(tagWrapper.tag, TAG_INNER_KEYS)\n              : tagWrapper.tag,\n          },\n          TAG_KEYS\n        )\n      ),\n    },\n    POST_ITEM_KEYS\n  );\n}\n\nfunction expandPostItem(post: any) {\n  const postReversed = reverseMap(POST_ITEM_KEYS);\n  const integrationReversed = reverseMap(INTEGRATION_KEYS);\n  const tagReversed = reverseMap(TAG_KEYS);\n  const tagInnerReversed = reverseMap(TAG_INNER_KEYS);\n\n  const expandedPost = mapKeys(post, postReversed);\n  if (expandedPost.integration) {\n    expandedPost.integration = mapKeys(\n      expandedPost.integration,\n      integrationReversed\n    );\n  }\n  if (expandedPost.tags) {\n    expandedPost.tags = expandedPost.tags.map((tagWrapper: any) => {\n      const expandedWrapper = mapKeys(tagWrapper, tagReversed);\n      if (expandedWrapper.tag) {\n        expandedWrapper.tag = mapKeys(expandedWrapper.tag, tagInnerReversed);\n      }\n      return expandedWrapper;\n    });\n  }\n  return expandedPost;\n}\n\n// --- getPostsList (paginated list view) ---\n\nexport function minifyPostsList(data: {\n  posts: any[];\n  total: number;\n  page: number;\n  limit: number;\n  hasMore: boolean;\n}) {\n  return mapKeys(\n    {\n      ...data,\n      posts: data.posts.map(minifyPostItem),\n    },\n    POST_LIST_KEYS\n  );\n}\n\nexport function expandPostsList(data: any) {\n  const topReversed = reverseMap(POST_LIST_KEYS);\n  const expanded = mapKeys(data, topReversed);\n  expanded.posts = (expanded.posts || []).map(expandPostItem);\n  return expanded;\n}\n\n// --- getPosts (calendar view) ---\n\nexport function minifyPosts(data: { posts: any[] }) {\n  return mapKeys(\n    {\n      ...data,\n      posts: data.posts.map(minifyPostItem),\n    },\n    POST_CALENDAR_KEYS\n  );\n}\n\nexport function expandPosts(data: any) {\n  const topReversed = reverseMap(POST_CALENDAR_KEYS);\n  const expanded = mapKeys(data, topReversed);\n  expanded.posts = (expanded.posts || []).map(expandPostItem);\n  return expanded;\n}\n"
  },
  {
    "path": "libraries/helpers/src/utils/read.or.fetch.ts",
    "content": "import { readFileSync } from 'fs';\nimport axios from 'axios';\n\nexport const readOrFetch = async (path: string) => {\n  if (path.indexOf('http') === 0) {\n    return (\n      await axios({\n        url: path,\n        method: 'GET',\n        responseType: 'arraybuffer',\n      })\n    ).data;\n  }\n\n  return readFileSync(path);\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/remove.markdown.ts",
    "content": "import removeMd from 'remove-markdown';\nimport { makeId } from '../../../nestjs-libraries/src/services/make.is';\n\nexport const removeMarkdown = (params: { text: string; except?: RegExp[] }) => {\n  let modifiedText = params.text;\n  const except = params.except || [];\n  const placeholders: { [key: string]: string } = {};\n\n  // Step 2: Replace exceptions with placeholders\n  except.forEach((regexp, index) => {\n    modifiedText = modifiedText.replace(regexp, (match) => {\n      const placeholder = `[[EXCEPT_PLACEHOLDER_${makeId(5)}]]`;\n      placeholders[placeholder] = match;\n      return placeholder;\n    });\n  });\n\n  // Step 3: Remove markdown from modified text\n  // Assuming removeMd is the function that removes markdown\n  const cleanedText = removeMd(modifiedText);\n\n  // Step 4: Replace placeholders with original text\n  const finalText = Object.keys(placeholders).reduce((text, placeholder) => {\n    return text.replace(placeholder, placeholders[placeholder]);\n  }, cleanedText);\n\n  return finalText;\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/strip.html.validation.ts",
    "content": "import striptags from 'striptags';\nimport { parseFragment, serialize } from 'parse5';\n\nconst bold = {\n  a: '𝗮',\n  b: '𝗯',\n  c: '𝗰',\n  d: '𝗱',\n  e: '𝗲',\n  f: '𝗳',\n  g: '𝗴',\n  h: '𝗵',\n  i: '𝗶',\n  j: '𝗷',\n  k: '𝗸',\n  l: '𝗹',\n  m: '𝗺',\n  n: '𝗻',\n  o: '𝗼',\n  p: '𝗽',\n  q: '𝗾',\n  r: '𝗿',\n  s: '𝘀',\n  t: '𝘁',\n  u: '𝘂',\n  v: '𝘃',\n  w: '𝘄',\n  x: '𝘅',\n  y: '𝘆',\n  z: '𝘇',\n  A: '𝗔',\n  B: '𝗕',\n  C: '𝗖',\n  D: '𝗗',\n  E: '𝗘',\n  F: '𝗙',\n  G: '𝗚',\n  H: '𝗛',\n  I: '𝗜',\n  J: '𝗝',\n  K: '𝗞',\n  L: '𝗟',\n  M: '𝗠',\n  N: '𝗡',\n  O: '𝗢',\n  P: '𝗣',\n  Q: '𝗤',\n  R: '𝗥',\n  S: '𝗦',\n  T: '𝗧',\n  U: '𝗨',\n  V: '𝗩',\n  W: '𝗪',\n  X: '𝗫',\n  Y: '𝗬',\n  Z: '𝗭',\n  '1': '𝟭',\n  '2': '𝟮',\n  '3': '𝟯',\n  '4': '𝟰',\n  '5': '𝟱',\n  '6': '𝟲',\n  '7': '𝟳',\n  '8': '𝟴',\n  '9': '𝟵',\n  '0': '𝟬',\n};\n\nconst underlineMap = {\n  a: 'a̲',\n  b: 'b̲',\n  c: 'c̲',\n  d: 'd̲',\n  e: 'e̲',\n  f: 'f̲',\n  g: 'g̲',\n  h: 'h̲',\n  i: 'i̲',\n  j: 'j̲',\n  k: 'k̲',\n  l: 'l̲',\n  m: 'm̲',\n  n: 'n̲',\n  o: 'o̲',\n  p: 'p̲',\n  q: 'q̲',\n  r: 'r̲',\n  s: 's̲',\n  t: 't̲',\n  u: 'u̲',\n  v: 'v̲',\n  w: 'w̲',\n  x: 'x̲',\n  y: 'y̲',\n  z: 'z̲',\n  A: 'A̲',\n  B: 'B̲',\n  C: 'C̲',\n  D: 'D̲',\n  E: 'E̲',\n  F: 'F̲',\n  G: 'G̲',\n  H: 'H̲',\n  I: 'I̲',\n  J: 'J̲',\n  K: 'K̲',\n  L: 'L̲',\n  M: 'M̲',\n  N: 'N̲',\n  O: 'O̲',\n  P: 'P̲',\n  Q: 'Q̲',\n  R: 'R̲',\n  S: 'S̲',\n  T: 'T̲',\n  U: 'U̲',\n  V: 'V̲',\n  W: 'W̲',\n  X: 'X̲',\n  Y: 'Y̲',\n  Z: 'Z̲',\n  '1': '1̲',\n  '2': '2̲',\n  '3': '3̲',\n  '4': '4̲',\n  '5': '5̲',\n  '6': '6̲',\n  '7': '7̲',\n  '8': '8̲',\n  '9': '9̲',\n  '0': '0̲',\n};\n\nexport const stripHtmlValidation = (\n  type: 'none' | 'normal' | 'markdown' | 'html',\n  val: string,\n  replaceBold = false,\n  none = false,\n  plain = false,\n  convertMentionFunction?: (idOrHandle: string, name: string) => string\n): string => {\n  if (plain) {\n    return val;\n  }\n\n  const value = serialize(parseFragment(val));\n\n  if (type === 'none') {\n    return striptags(value)\n      .replace(/&gt;/gi, '>')\n      .replace(/&lt;/gi, '<')\n      .replace(/&amp;/gi, '&')\n      .replace(/&nbsp;/gi, ' ')\n      .replace(/&quot;/gi, '\"')\n      .replace(/&#39;/gi, \"'\");\n  }\n\n  if (type === 'html') {\n    return striptags(convertMention(value, convertMentionFunction), [\n      'ul',\n      'li',\n      'h1',\n      'h2',\n      'h3',\n      'p',\n      'strong',\n      'u',\n      'a',\n    ])\n      .replace(/&gt;/gi, '>')\n      .replace(/&lt;/gi, '<')\n      .replace(/&amp;/gi, '&')\n      .replace(/&nbsp;/gi, ' ')\n      .replace(/&quot;/gi, '\"')\n      .replace(/&#39;/gi, \"'\");\n  }\n\n  if (type === 'markdown') {\n    return striptags(\n      convertMention(\n        value\n          .replace(/<h1>([.\\s\\S]*?)<\\/h1>/g, (match, p1) => {\n            return `<h1># ${p1}</h1>\\n`;\n          })\n          .replace(/&amp;/gi, '&')\n          .replace(/&nbsp;/gi, ' ')\n          .replace(/&quot;/gi, '\"')\n          .replace(/&#39;/gi, \"'\")\n          .replace(/<h2>([.\\s\\S]*?)<\\/h2>/g, (match, p1) => {\n            return `<h2>## ${p1}</h2>\\n`;\n          })\n          .replace(/<h3>([.\\s\\S]*?)<\\/h3>/g, (match, p1) => {\n            return `<h3>### ${p1}</h3>\\n`;\n          })\n          .replace(/<u>([.\\s\\S]*?)<\\/u>/g, (match, p1) => {\n            return `<u>__${p1}__</u>`;\n          })\n          .replace(/<strong>([.\\s\\S]*?)<\\/strong>/g, (match, p1) => {\n            return `<strong>**${p1}**</strong>`;\n          })\n          .replace(/<li.*?>([.\\s\\S]*?)<\\/li.*?>/gm, (match, p1) => {\n            return `<li>- ${p1.replace(/\\n/gm, '')}</li>`;\n          })\n          .replace(/<p>([.\\s\\S]*?)<\\/p>/g, (match, p1) => {\n            return `<p>${p1}</p>\\n`;\n          })\n          .replace(\n            /<a.*?href=\"([.\\s\\S]*?)\".*?>([.\\s\\S]*?)<\\/a>/g,\n            (match, p1, p2) => {\n              return `<a href=\"${p1}\">[${p2}](${p1})</a>`;\n            }\n          ),\n        convertMentionFunction\n      )\n    )\n      .replace(/&gt;/gi, '>')\n      .replace(/&lt;/gi, '<');\n  }\n\n  if (value.indexOf('<p>') === -1 && !none) {\n    return value;\n  }\n\n  const html = (value || '')\n    .replace(/&amp;/gi, '&')\n    .replace(/&nbsp;/gi, ' ')\n    .replace(/&quot;/gi, '\"')\n    .replace(/&#39;/gi, \"'\")\n    .replace(/^<p[^>]*>/i, '')\n    .replace(/<p[^>]*>/gi, '\\n')\n    .replace(/<\\/p>/gi, '');\n\n  if (none) {\n    return striptags(html).replace(/&gt;/gi, '>').replace(/&lt;/gi, '<');\n  }\n\n  if (replaceBold) {\n    const processedHtml = convertMention(\n      convertToAscii(\n        html\n          .replace(\n            /<a.*?href=\"([.\\s\\S]*?)\".*?>([.\\s\\S]*?)<\\/a>/g,\n            (match, p1, p2) => {\n              return `<a href=\"${p1}\">${p1}</a>`;\n            }\n          )\n          .replace(/<ul>/, '\\n<ul>')\n          .replace(/<\\/ul>\\n/, '</ul>')\n          .replace(/<li.*?>([.\\s\\S]*?)<\\/li.*?>/gm, (match, p1) => {\n            return `<li><p>- ${p1.replace(/\\n/gm, '')}\\n</p></li>`;\n          })\n      ),\n      convertMentionFunction\n    );\n\n    return striptags(processedHtml)\n      .replace(/&gt;/gi, '>')\n      .replace(/&lt;/gi, '<')\n      .replace(/&𝗹𝘁;/gi, '<')\n      .replace(/&𝗴𝘁;/gi, '>')\n      .replace(/&g̲t̲;/gi, '>')\n      .replace(/&l̲t̲;/gi, '<');\n  }\n\n  // Strip all other tags\n  return striptags(html, ['ul', 'li', 'h1', 'h2', 'h3'])\n    .replace(/&gt;/gi, '>')\n    .replace(/&lt;/gi, '<');\n};\n\nexport const convertMention = (\n  value: string,\n  process?: (idOrHandle: string, name: string) => string\n) => {\n  if (!process) {\n    return value;\n  }\n\n  return value.replace(\n    /<span.*?data-mention-id=\"([.\\s\\S]*?)\"[.\\s\\S]*?>([.\\s\\S]*?)<\\/span>/gi,\n    (match, id, name) => {\n      return `<span>` + process(id, name) + `</span>`;\n    }\n  );\n};\n\nexport const convertToAscii = (value: string): string => {\n  return value\n    .replace(/<strong>(.+?)<\\/strong>/gi, (match, p1) => {\n      const replacer = p1.split('').map((char: string) => {\n        // @ts-ignore\n        return bold?.[char] || char;\n      });\n\n      return match.replace(p1, replacer.join(''));\n    })\n    .replace(/<u>(.+?)<\\/u>/gi, (match, p1) => {\n      const replacer = p1.split('').map((char: string) => {\n        // @ts-ignore\n        return underlineMap?.[char] || char;\n      });\n\n      return match.replace(p1, replacer.join(''));\n    });\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/timer.ts",
    "content": "export const timer = (ms: number) => new Promise((res) => setTimeout(res, ms));\n"
  },
  {
    "path": "libraries/helpers/src/utils/use.fire.events.ts",
    "content": "import { usePlausible } from 'next-plausible';\nimport { useCallback } from 'react';\nimport { usePostHog } from 'posthog-js/react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\n\nexport const useFireEvents = () => {\n  const { billingEnabled } = useVariables();\n  const plausible = usePlausible();\n  const posthog = usePostHog();\n  const user = useUser();\n\n  return useCallback(\n    (name: string, props?: any) => {\n      if (!billingEnabled) {\n        return;\n      }\n\n      if (user) {\n        posthog.identify(user.id, { email: user.email, name: user.name });\n      }\n\n      posthog.capture(name, props);\n      plausible(name, { props });\n    },\n    [user]\n  );\n};\n"
  },
  {
    "path": "libraries/helpers/src/utils/use.wait.for.class.tsx",
    "content": "import { useEffect, useState } from \"react\";\n\n/**\n * useWaitForClass\n *\n * Watches the DOM for the presence of a CSS class and resolves when found.\n *\n * @param className - The class to wait for (without the dot, e.g. \"my-element\")\n * @param root - The root node to observe (defaults to document.body)\n * @returns A boolean indicating if the class is currently present\n */\nexport function useWaitForClass(className: string, root: HTMLElement | null = null): boolean {\n  const [found, setFound] = useState(false);\n\n  useEffect(() => {\n    const target = root ?? document.body;\n\n    if (!target) return;\n\n    // Check immediately in case the element is already present\n    if (target.querySelector(`.${className}`)) {\n      setFound(true);\n      return;\n    }\n\n    const observer = new MutationObserver(() => {\n      if (target.querySelector(`.${className}`)) {\n        setFound(true);\n        observer.disconnect();\n      }\n    });\n\n    observer.observe(target, {\n      childList: true,\n      subtree: true,\n      attributes: true,\n    });\n\n    return () => observer.disconnect();\n  }, [className, root]);\n\n  return found;\n}"
  },
  {
    "path": "libraries/helpers/src/utils/utm.saver.tsx",
    "content": "'use client';\n\nimport { FC, useCallback, useEffect } from 'react';\nimport { useSearchParams } from 'next/navigation';\nimport { useLocalStorage } from '@mantine/hooks';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';\nimport { useTrack } from '@gitroom/react/helpers/use.track';\n\nconst UtmSaver: FC = () => {\n  const query = useSearchParams();\n  const [value, setValue] = useLocalStorage({ key: 'utm', defaultValue: '' });\n  const searchParams = useSearchParams();\n  const fireEvents = useFireEvents();\n  const track = useTrack();\n\n  useEffect(() => {\n    if (searchParams.get('check')) {\n      fireEvents('purchase');\n      track(TrackEnum.StartTrial);\n    }\n  }, []);\n\n  useEffect(() => {\n    const landingUrl = localStorage.getItem('landingUrl');\n    if (landingUrl) {\n      return;\n    }\n\n    localStorage.setItem('landingUrl', window.location.href);\n    localStorage.setItem('referrer', document.referrer);\n  }, []);\n\n  useEffect(() => {\n    const utm = query.get('utm_source') || query.get('utm') || query.get('ref');\n    if (utm && !value) {\n      setValue(utm);\n    }\n  }, [query, value]);\n\n  return <></>;\n};\n\nexport const useUtmUrl = () => {\n  const [value] = useLocalStorage({ key: 'utm', defaultValue: '' });\n  return value || '';\n};\nexport default UtmSaver;\n"
  },
  {
    "path": "libraries/helpers/src/utils/valid.images.ts",
    "content": "import {\n  ValidationArguments,\n  ValidatorConstraintInterface,\n  ValidatorConstraint,\n} from 'class-validator';\nimport striptags from 'striptags';\n\n@ValidatorConstraint({ name: 'validateContent', async: false })\nexport class ValidContent implements ValidatorConstraintInterface {\n  validate(contentRaw: string, args: ValidationArguments) {\n    const content = striptags(contentRaw || '');\n    if (\n      // @ts-ignore\n      (!args?.object?.image || !Array.isArray(args?.object?.image) || !args?.object?.image.length) &&\n      (!content || typeof content !== 'string' || content?.trim() === '')\n    ) {\n      return false;\n    }\n\n    return true;\n  }\n\n  defaultMessage(args: ValidationArguments) {\n    // here you can provide default error message if validation failed\n    return ' If images do not exist, content must be a non-empty string.';\n  }\n}\n"
  },
  {
    "path": "libraries/helpers/src/utils/valid.url.path.ts",
    "content": "import {\n  ValidationArguments,\n  ValidatorConstraintInterface,\n  ValidatorConstraint,\n} from 'class-validator';\n\n@ValidatorConstraint({ name: 'checkValidExtension', async: false })\nexport class ValidUrlExtension implements ValidatorConstraintInterface {\n  validate(text: string, args: ValidationArguments) {\n    return (\n      !!text?.split?.('?')?.[0].endsWith('.png') ||\n      !!text?.split?.('?')?.[0].endsWith('.jpg') ||\n      !!text?.split?.('?')?.[0].endsWith('.jpeg') ||\n      !!text?.split?.('?')?.[0].endsWith('.gif') ||\n      !!text?.split?.('?')?.[0].endsWith('.webp') ||\n      !!text?.split?.('?')?.[0].endsWith('.mp4')\n    );\n  }\n\n  defaultMessage(args: ValidationArguments) {\n    // here you can provide default error message if validation failed\n    return (\n      'File must have a valid extension: .png, .jpg, .jpeg, .gif, .webp, or .mp4'\n    );\n  }\n}\n\n@ValidatorConstraint({ name: 'checkValidPath', async: false })\nexport class ValidUrlPath implements ValidatorConstraintInterface {\n  validate(text: string, args: ValidationArguments) {\n    if (!process.env.RESTRICT_UPLOAD_DOMAINS) {\n      return true;\n    }\n\n    return (\n      (text || 'invalid url').indexOf(process.env.RESTRICT_UPLOAD_DOMAINS) > -1\n    );\n  }\n\n  defaultMessage(args: ValidationArguments) {\n    // here you can provide default error message if validation failed\n    return (\n      'URL must contain the domain: ' + process.env.RESTRICT_UPLOAD_DOMAINS + ' Make sure you first use the upload API route.'\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/.eslintrc.json",
    "content": "{\n  \"extends\": [\"../../.eslintrc.json\"],\n  \"ignorePatterns\": [\"!**/*\"],\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\", \"*.tsx\", \"*.js\", \"*.jsx\"],\n      \"rules\": {}\n    },\n    {\n      \"files\": [\"*.ts\", \"*.tsx\"],\n      \"rules\": {}\n    },\n    {\n      \"files\": [\"*.js\", \"*.jsx\"],\n      \"rules\": {}\n    }\n  ]\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/README.md",
    "content": "# nestjs-libraries\n\nThis library was generated with [Nx](https://nx.dev).\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/3rdparties/heygen/heygen.provider.ts",
    "content": "import {\n  ThirdParty,\n  ThirdPartyAbstract,\n} from '@gitroom/nestjs-libraries/3rdparties/thirdparty.interface';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport { timer } from '@gitroom/helpers/utils/timer';\n\n@ThirdParty({\n  identifier: 'heygen',\n  title: 'HeyGen',\n  description: 'HeyGen is a platform for creating AI-generated avatars videos.',\n  position: 'media',\n  fields: [],\n})\nexport class HeygenProvider extends ThirdPartyAbstract<{\n  voice: string;\n  avatar: string;\n  aspect_ratio: string;\n  captions: string;\n}> {\n  // @ts-ignore\n  constructor(private _openaiService: OpenaiService) {\n    super();\n  }\n\n  async checkConnection(\n    apiKey: string\n  ): Promise<false | { name: string; username: string; id: string }> {\n    const list = await fetch('https://api.heygen.com/v1/user/me', {\n      method: 'GET',\n      headers: {\n        accept: 'application/json',\n        'x-api-key': apiKey,\n      },\n    });\n\n    if (!list.ok) {\n      return false;\n    }\n\n    const { data } = await list.json();\n\n    return {\n      name: data.first_name + ' ' + data.last_name,\n      username: data.username,\n      id: data.username,\n    };\n  }\n\n  async generateVoice(apiKey: string, data: { text: string }) {\n    return {\n      voice: await this._openaiService.generateVoiceFromText(data.text),\n    };\n  }\n\n  async voices(apiKey: string) {\n    const {\n      data: { voices },\n    } = await (\n      await fetch('https://api.heygen.com/v2/voices', {\n        method: 'GET',\n        headers: {\n          accept: 'application/json',\n          'x-api-key': apiKey,\n        },\n      })\n    ).json();\n\n    return voices.slice(0, 20);\n  }\n\n  async avatars(apiKey: string) {\n    const {\n      data: { avatar_group_list },\n    } = await (\n      await fetch(\n        'https://api.heygen.com/v2/avatar_group.list?include_public=false',\n        {\n          method: 'GET',\n          headers: {\n            accept: 'application/json',\n            'x-api-key': apiKey,\n          },\n        }\n      )\n    ).json();\n\n    const loadedAvatars = [];\n    for (const avatar of avatar_group_list) {\n      const {\n        data: { avatar_list },\n      } = await (\n        await fetch(\n          `https://api.heygen.com/v2/avatar_group/${avatar.id}/avatars`,\n          {\n            method: 'GET',\n            headers: {\n              accept: 'application/json',\n              'x-api-key': apiKey,\n            },\n          }\n        )\n      ).json();\n\n      loadedAvatars.push(...avatar_list);\n    }\n\n    return loadedAvatars;\n  }\n\n  async sendData(\n    apiKey: string,\n    data: {\n      voice: string;\n      avatar: string;\n      aspect_ratio: string;\n      captions: string;\n      selectedVoice: string;\n      type: 'talking_photo' | 'avatar';\n    }\n  ): Promise<string> {\n    const {\n      data: { video_id },\n    } = await (\n      await fetch(`https://api.heygen.com/v2/video/generate`, {\n        method: 'POST',\n        body: JSON.stringify({\n          caption: data.captions === 'yes',\n          video_inputs: [\n            {\n              ...(data.type === 'avatar'\n                ? {\n                    character: {\n                      type: 'avatar',\n                      avatar_id: data.avatar,\n                    },\n                  }\n                : {\n                    character: {\n                      type: 'talking_photo',\n                      talking_photo_id: data.avatar,\n                    },\n                  }),\n              voice: {\n                type: 'text',\n                input_text: data.voice,\n                voice_id: data.selectedVoice,\n              },\n            },\n          ],\n          dimension:\n            data.aspect_ratio === 'story'\n              ? {\n                  width: 720,\n                  height: 1280,\n                }\n              : {\n                  width: 1280,\n                  height: 720,\n                },\n        }),\n        headers: {\n          accept: 'application/json',\n          'content-type': 'application/json',\n          'x-api-key': apiKey,\n        },\n      })\n    ).json();\n\n    while (true) {\n      const {\n        data: { status, video_url },\n      } = await (\n        await fetch(\n          `https://api.heygen.com/v1/video_status.get?video_id=${video_id}`,\n          {\n            headers: {\n              accept: 'application/json',\n              'content-type': 'application/json',\n              'x-api-key': apiKey,\n            },\n          }\n        )\n      ).json();\n\n      if (status === 'completed') {\n        return video_url;\n      } else if (status === 'failed') {\n        throw new Error('Video generation failed');\n      }\n\n      await timer(3000);\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/3rdparties/thirdparty.interface.ts",
    "content": "import { Injectable } from '@nestjs/common';\n\nexport abstract class ThirdPartyAbstract<T = any> {\n  abstract checkConnection(\n    apiKey: string\n  ): Promise<false | { name: string; username: string; id: string }>;\n  abstract sendData(apiKey: string, data: T): Promise<string>;\n  [key: string]: ((apiKey: string, data?: any) => Promise<any>) | undefined;\n}\n\nexport interface ThirdPartyParams {\n  identifier: string;\n  title: string;\n  description: string;\n  position: 'media' | 'webhook';\n  fields: {\n    name: string;\n    description: string;\n    type: string;\n    placeholder: string;\n    validation?: RegExp;\n  }[];\n}\n\nexport function ThirdParty(params: ThirdPartyParams) {\n  return function (target: any) {\n    // Apply @Injectable decorator to the target class\n    Injectable()(target);\n\n    // Retrieve existing metadata or initialize an empty array\n    const existingMetadata =\n      Reflect.getMetadata('third:party', ThirdPartyAbstract) || [];\n\n    // Add the metadata information for this method\n    existingMetadata.push({ target, ...params });\n\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata('third:party', existingMetadata, ThirdPartyAbstract);\n  };\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/3rdparties/thirdparty.manager.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport {\n  ThirdPartyAbstract,\n  ThirdPartyParams,\n} from '@gitroom/nestjs-libraries/3rdparties/thirdparty.interface';\nimport { ModuleRef } from '@nestjs/core';\nimport { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.service';\n\n@Injectable()\nexport class ThirdPartyManager {\n  constructor(\n    private _moduleRef: ModuleRef,\n    private _thirdPartyService: ThirdPartyService\n  ) {}\n\n  getAllThirdParties(): any[] {\n    return (Reflect.getMetadata('third:party', ThirdPartyAbstract) || []).map(\n      (p: any) => ({\n        identifier: p.identifier,\n        title: p.title,\n        description: p.description,\n        fields: p.fields || [],\n      })\n    );\n  }\n\n  getThirdPartyByName(\n    identifier: string\n  ): (ThirdPartyParams & { instance: ThirdPartyAbstract }) | undefined {\n    const thirdParty = (\n      Reflect.getMetadata('third:party', ThirdPartyAbstract) || []\n    ).find((p: any) => p.identifier === identifier);\n\n    return { ...thirdParty, instance: this._moduleRef.get(thirdParty.target) };\n  }\n\n  deleteIntegration(org: string, id: string) {\n    return this._thirdPartyService.deleteIntegration(org, id);\n  }\n\n  getIntegrationById(org: string, id: string) {\n    return this._thirdPartyService.getIntegrationById(org, id);\n  }\n\n  getAllThirdPartiesByOrganization(org: string) {\n    return this._thirdPartyService.getAllThirdPartiesByOrganization(org);\n  }\n\n  saveIntegration(\n    org: string,\n    identifier: string,\n    apiKey: string,\n    data: { name: string; username: string; id: string }\n  ) {\n    return this._thirdPartyService.saveIntegration(\n      org,\n      identifier,\n      apiKey,\n      data\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/3rdparties/thirdparty.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { HeygenProvider } from '@gitroom/nestjs-libraries/3rdparties/heygen/heygen.provider';\nimport { ThirdPartyManager } from '@gitroom/nestjs-libraries/3rdparties/thirdparty.manager';\n\n@Global()\n@Module({\n  providers: [HeygenProvider, ThirdPartyManager],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class ThirdPartyModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/agent/agent.categories.ts",
    "content": "export const agentCategories = [\n  'Educational',\n  'Inspirational',\n  'Promotional',\n  'Entertaining',\n  'Interactive',\n  'Behind The Scenes',\n  'Testimonial',\n  'Informative',\n  'Humorous',\n  'Seasonal',\n  'News',\n  'Challenge',\n  'Contest',\n  'Tips',\n  'Tutorial',\n  'Poll',\n  'Survey',\n  'Quote',\n  'Event',\n  'FAQ',\n  'Story',\n  'Meme',\n  'Review',\n  'Announcement',\n  'Highlight',\n  'Celebration',\n  'Reminder',\n  'Debate',\n  'Update',\n  'Trend',\n];\n\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { BaseMessage, HumanMessage } from '@langchain/core/messages';\nimport { END, START, StateGraph } from '@langchain/langgraph';\nimport { ChatOpenAI } from '@langchain/openai';\nimport { ChatPromptTemplate } from '@langchain/core/prompts';\nimport { agentCategories } from '@gitroom/nestjs-libraries/agent/agent.categories';\nimport { z } from 'zod';\nimport { agentTopics } from '@gitroom/nestjs-libraries/agent/agent.topics';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\n\nconst model = new ChatOpenAI({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n  model: 'gpt-4o-2024-08-06',\n  temperature: 0,\n});\n\ninterface WorkflowChannelsState {\n  messages: BaseMessage[];\n  topic?: string;\n  category: string;\n  hook?: string;\n  content?: string;\n}\n\nconst category = z.object({\n  category: z.string().describe('The category for the post'),\n});\n\nconst topic = z.object({\n  topic: z.string().describe('The topic of the post'),\n});\n\nconst hook = z.object({\n  hook: z.string().describe('The hook of the post'),\n});\n\n@Injectable()\nexport class AgentGraphInsertService {\n  constructor(private _postsService: PostsService) {}\n  static state = () =>\n    new StateGraph<WorkflowChannelsState>({\n      channels: {\n        messages: {\n          reducer: (currentState, updateValue) =>\n            currentState.concat(updateValue),\n          default: () => [],\n        },\n        topic: null,\n        category: null,\n        hook: null,\n        content: null,\n      },\n    });\n\n  async findCategory(state: WorkflowChannelsState) {\n    const { messages } = state;\n    const structuredOutput = model.withStructuredOutput(category);\n    return ChatPromptTemplate.fromTemplate(\n      `\nYou are an assistant that get a social media post and categorize it into to one from the following categories:\n{categories}\nHere is the post:\n{post}\n    `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        post: messages[0].content,\n        categories: agentCategories.join(', '),\n      });\n  }\n\n  findTopic(state: WorkflowChannelsState) {\n    const { messages } = state;\n    const structuredOutput = model.withStructuredOutput(topic);\n    return ChatPromptTemplate.fromTemplate(\n      `\nYou are an assistant that get a social media post and categorize it into one of the following topics:\n{topics}\nHere is the post:\n{post}\n    `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        post: messages[0].content,\n        topics: agentTopics.join(', '),\n      });\n  }\n\n  findHook(state: WorkflowChannelsState) {\n    const { messages } = state;\n    const structuredOutput = model.withStructuredOutput(hook);\n    return ChatPromptTemplate.fromTemplate(\n      `\nYou are an assistant that get a social media post and extract the hook, the hook is usually the first or second of both sentence of the post, but can be in a different place, make sure you don't change the wording of the post use the exact text:\n{post}\n    `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        post: messages[0].content,\n      });\n  }\n\n  async savePost(state: WorkflowChannelsState) {\n    await this._postsService.createPopularPosts({\n      category: state.category,\n      topic: state.topic!,\n      hook: state.hook!,\n      content: state.messages[0].content! as string,\n    });\n\n    return {};\n  }\n\n  newPost(post: string) {\n    const state = AgentGraphInsertService.state();\n    const workflow = state\n      .addNode('find-category', this.findCategory)\n      .addNode('find-topic', this.findTopic)\n      .addNode('find-hook', this.findHook)\n      .addNode('save-post', this.savePost.bind(this))\n      .addEdge(START, 'find-category')\n      .addEdge('find-category', 'find-topic')\n      .addEdge('find-topic', 'find-hook')\n      .addEdge('find-hook', 'save-post')\n      .addEdge('save-post', END);\n\n    const app = workflow.compile();\n    return app.invoke({\n      messages: [new HumanMessage(post)],\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/agent/agent.graph.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport {\n  BaseMessage,\n  HumanMessage,\n  ToolMessage,\n} from '@langchain/core/messages';\nimport { END, START, StateGraph } from '@langchain/langgraph';\nimport { ChatOpenAI, DallEAPIWrapper } from '@langchain/openai';\nimport { TavilySearchResults } from '@langchain/community/tools/tavily_search';\nimport { ToolNode } from '@langchain/langgraph/prebuilt';\nimport { ChatPromptTemplate } from '@langchain/core/prompts';\nimport dayjs from 'dayjs';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { z } from 'zod';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';\n\nconst tools = !process.env.TAVILY_API_KEY\n  ? []\n  : [new TavilySearchResults({ maxResults: 3 })];\nconst toolNode = new ToolNode(tools);\n\nconst model = new ChatOpenAI({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n  model: 'gpt-4.1',\n  temperature: 0.7,\n});\n\nconst dalle = new DallEAPIWrapper({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n  model: 'dall-e-3',\n});\n\ninterface WorkflowChannelsState {\n  messages: BaseMessage[];\n  orgId: string;\n  question: string;\n  hook?: string;\n  fresearch?: string;\n  category?: string;\n  topic?: string;\n  date?: string;\n  format: 'one_short' | 'one_long' | 'thread_short' | 'thread_long';\n  tone: 'personal' | 'company';\n  content?: {\n    content: string;\n    website?: string;\n    prompt?: string;\n    image?: string;\n  }[];\n  isPicture?: boolean;\n  popularPosts?: { content: string; hook: string }[];\n}\n\nconst category = z.object({\n  category: z.string().describe('The category for the post'),\n});\n\nconst topic = z.object({\n  topic: z.string().describe('The topic for the post'),\n});\n\nconst hook = z.object({\n  hook: z\n    .string()\n    .describe(\n      'Hook for the new post, don\\'t take it from \"the request of the user\"'\n    ),\n});\n\nconst contentZod = (\n  isPicture: boolean,\n  format: 'one_short' | 'one_long' | 'thread_short' | 'thread_long'\n) => {\n  const content = z.object({\n    content: z.string().describe('Content for the new post'),\n    website: z\n      .string()\n      .nullable()\n      .optional()\n      .describe(\n        \"Website for the new post if exists, If one of the post present a brand, website link must be to the root domain of the brand or don't include it, website url should contain the brand name\"\n      ),\n    ...(isPicture\n      ? {\n          prompt: z\n            .string()\n            .describe(\n              \"Prompt to generate a picture for this post later, make sure it doesn't contain brand names and make it very descriptive in terms of style\"\n            ),\n        }\n      : {}),\n  });\n\n  return z.object({\n    content:\n      format === 'one_short' || format === 'one_long'\n        ? content\n        : z.array(content).min(2).describe(`Content for the new post`),\n  });\n};\n\n@Injectable()\nexport class AgentGraphService {\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _postsService: PostsService,\n    private _mediaService: MediaService\n  ) {}\n  static state = () =>\n    new StateGraph<WorkflowChannelsState>({\n      channels: {\n        messages: {\n          reducer: (currentState, updateValue) =>\n            currentState.concat(updateValue),\n          default: () => [],\n        },\n        fresearch: null,\n        format: null,\n        tone: null,\n        question: null,\n        orgId: null,\n        hook: null,\n        content: null,\n        date: null,\n        category: null,\n        popularPosts: null,\n        topic: null,\n        isPicture: null,\n      },\n    });\n\n  async startCall(state: WorkflowChannelsState) {\n    const runTools = model.bindTools(tools);\n    const response = await ChatPromptTemplate.fromTemplate(\n      `\n    Today is ${dayjs().format()}, You are an assistant that gets a social media post or requests for a social media post.\n    You research should be on the most possible recent data.\n    You concat the text of the request together with an internet research based on the text.\n    {text}\n    `\n    )\n      .pipe(runTools)\n      .invoke({\n        text: state.messages[state.messages.length - 1].content,\n      });\n\n    return { messages: [response] };\n  }\n\n  async saveResearch(state: WorkflowChannelsState) {\n    const content = state.messages.filter((f) => f instanceof ToolMessage);\n    return { fresearch: content };\n  }\n\n  async findCategories(state: WorkflowChannelsState) {\n    const allCategories = await this._postsService.findAllExistingCategories();\n    const structuredOutput = model.withStructuredOutput(category);\n    const { category: outputCategory } = await ChatPromptTemplate.fromTemplate(\n      `\n        You are an assistant that gets a text that will be later summarized into a social media post\n        and classify it to one of the following categories: {categories}\n        text: {text}\n      `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        categories: allCategories.map((p) => p.category).join(', '),\n        text: state.fresearch,\n      });\n\n    return {\n      category: outputCategory,\n    };\n  }\n\n  async findTopic(state: WorkflowChannelsState) {\n    const allTopics = await this._postsService.findAllExistingTopicsOfCategory(\n      state?.category!\n    );\n    if (allTopics.length === 0) {\n      return { topic: null };\n    }\n\n    const structuredOutput = model.withStructuredOutput(topic);\n    const { topic: outputTopic } = await ChatPromptTemplate.fromTemplate(\n      `\n        You are an assistant that gets a text that will be later summarized into a social media post\n        and classify it to one of the following topics: {topics}\n        text: {text}\n      `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        topics: allTopics.map((p) => p.topic).join(', '),\n        text: state.fresearch,\n      });\n\n    return {\n      topic: outputTopic,\n    };\n  }\n\n  async findPopularPosts(state: WorkflowChannelsState) {\n    const popularPosts = await this._postsService.findPopularPosts(\n      state.category!,\n      state.topic\n    );\n    return { popularPosts };\n  }\n\n  async generateHook(state: WorkflowChannelsState) {\n    const structuredOutput = model.withStructuredOutput(hook);\n    const { hook: outputHook } = await ChatPromptTemplate.fromTemplate(\n      `\n        You are an assistant that gets content for a social media post, and generate only the hook.\n        The hook is the 1-2 sentences of the post that will be used to grab the attention of the reader.\n        You will be provided existing hooks you should use as inspiration.\n        - Avoid weird hook that starts with \"Discover the secret...\", \"The best...\", \"The most...\", \"The top...\"\n        - Make sure it sounds ${state.tone}\n        - Use ${state.tone === 'personal' ? '1st' : '3rd'} person mode\n        - Make sure it's engaging\n        - Don't be cringy\n        - Use simple english\n        - Make sure you add \"\\n\" between the lines\n        - Don't take the hook from \"request of the user\"\n\n        <!-- BEGIN request of the user -->\n        {request}\n        <!-- END request of the user -->\n        \n        <!-- BEGIN existing hooks -->\n        {hooks}\n        <!-- END existing hooks -->\n        \n        <!-- BEGIN current content -->\n        {text}\n        <!-- END current content -->\n       \n      `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        request: state.messages[0].content,\n        hooks: state.popularPosts!.map((p) => p.hook).join('\\n'),\n        text: state.fresearch,\n      });\n\n    return {\n      hook: outputHook,\n    };\n  }\n\n  async generateContent(state: WorkflowChannelsState) {\n    const structuredOutput = model.withStructuredOutput(\n      contentZod(!!state.isPicture, state.format)\n    );\n    const { content: outputContent } = await ChatPromptTemplate.fromTemplate(\n      `\n        You are an assistant that gets existing hook of a social media, content and generate only the content.\n        - Don't add any hashtags\n        - Make sure it sounds ${state.tone}\n        - Use ${state.tone === 'personal' ? '1st' : '3rd'} person mode\n        - ${\n          state.format === 'one_short' || state.format === 'thread_short'\n            ? 'Post should be maximum 200 chars to fit twitter'\n            : 'Post should be long'\n        }\n        - ${\n          state.format === 'one_short' || state.format === 'one_long'\n            ? 'Post should have only 1 item'\n            : 'Post should have minimum 2 items'\n        }\n        - Use the hook as inspiration\n        - Make sure it's engaging\n        - Don't be cringy\n        - Use simple english\n        - The Content should not contain the hook\n        - Try to put some call to action at the end of the post\n        - Make sure you add \"\\n\" between the lines\n        - Add \"\\n\" after every \".\"\n        \n        Hook:\n        {hook}\n        \n        User request:\n        {request}\n        \n        current content information:\n        {information}\n      `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        hook: state.hook,\n        request: state.messages[0].content,\n        information: state.fresearch,\n      });\n\n    return {\n      content: outputContent,\n    };\n  }\n\n  async fixArray(state: WorkflowChannelsState) {\n    if (state.format === 'one_short' || state.format === 'one_long') {\n      return {\n        content: [state.content],\n      };\n    }\n\n    return {};\n  }\n\n  async generatePictures(state: WorkflowChannelsState) {\n    if (!state.isPicture) {\n      return {};\n    }\n\n    const newContent = await Promise.all(\n      (state.content || []).map(async (p) => {\n        const image = await dalle.invoke(p.prompt!);\n        return {\n          ...p,\n          image,\n        };\n      })\n    );\n\n    return {\n      content: newContent,\n    };\n  }\n\n  async uploadPictures(state: WorkflowChannelsState) {\n    const all = await Promise.all(\n      (state.content || []).map(async (p) => {\n        if (p.image) {\n          const upload = await this.storage.uploadSimple(p.image);\n          const name = upload.split('/').pop()!;\n          const uploadWithId = await this._mediaService.saveFile(\n            state.orgId,\n            name,\n            upload\n          );\n\n          return {\n            ...p,\n            image: uploadWithId,\n          };\n        }\n\n        return p;\n      })\n    );\n\n    return { content: all };\n  }\n\n  async isGeneratePicture(state: WorkflowChannelsState) {\n    if (state.isPicture) {\n      return 'generate-picture';\n    }\n\n    return 'post-time';\n  }\n\n  async postDateTime(state: WorkflowChannelsState) {\n    return { date: await this._postsService.findFreeDateTime(state.orgId) };\n  }\n\n  start(orgId: string, body: GeneratorDto) {\n    const state = AgentGraphService.state();\n    const workflow = state\n      .addNode('agent', this.startCall.bind(this))\n      .addNode('research', toolNode)\n      .addNode('save-research', this.saveResearch.bind(this))\n      .addNode('find-category', this.findCategories.bind(this))\n      .addNode('find-topic', this.findTopic.bind(this))\n      .addNode('find-popular-posts', this.findPopularPosts.bind(this))\n      .addNode('generate-hook', this.generateHook.bind(this))\n      .addNode('generate-content', this.generateContent.bind(this))\n      .addNode('generate-content-fix', this.fixArray.bind(this))\n      .addNode('generate-picture', this.generatePictures.bind(this))\n      .addNode('upload-pictures', this.uploadPictures.bind(this))\n      .addNode('post-time', this.postDateTime.bind(this))\n      .addEdge(START, 'agent')\n      .addEdge('agent', 'research')\n      .addEdge('research', 'save-research')\n      .addEdge('save-research', 'find-category')\n      .addEdge('find-category', 'find-topic')\n      .addEdge('find-topic', 'find-popular-posts')\n      .addEdge('find-popular-posts', 'generate-hook')\n      .addEdge('generate-hook', 'generate-content')\n      .addEdge('generate-content', 'generate-content-fix')\n      .addConditionalEdges(\n        'generate-content-fix',\n        this.isGeneratePicture.bind(this)\n      )\n      .addEdge('generate-picture', 'upload-pictures')\n      .addEdge('upload-pictures', 'post-time')\n      .addEdge('post-time', END);\n\n    const app = workflow.compile();\n\n    return app.streamEvents(\n      {\n        messages: [new HumanMessage(body.research)],\n        isPicture: body.isPicture,\n        format: body.format,\n        tone: body.tone,\n        orgId,\n      },\n      {\n        streamMode: 'values',\n        version: 'v2',\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/agent/agent.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { AgentGraphService } from '@gitroom/nestjs-libraries/agent/agent.graph.service';\nimport { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service';\n\n@Global()\n@Module({\n  providers: [AgentGraphService, AgentGraphInsertService],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class AgentModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/agent/agent.topics.ts",
    "content": "export const agentTopics = [\n  'Business',\n  'Marketing',\n  'Finance',\n  'Startups',\n  'Networking',\n  'Leadership',\n  'Strategy',\n  'Branding',\n  'Analytics',\n  'Growth',\n  'Drawing',\n  'Painting',\n  'Design',\n  'Photography',\n  'Writing',\n  'Sculpting',\n  'Animation',\n  'Sketching',\n  'Crafting',\n  'Calligraphy',\n  'Mindset',\n  'Productivity',\n  'Motivation',\n  'Education',\n  'Learning',\n  'Skills',\n  'Success',\n  'Wellness',\n  'Goals',\n  'Inspiration',\n  'Fashion',\n  'Travel',\n  'Food',\n  'Fitness',\n  'Health',\n  'Beauty',\n  'Home',\n  'Decor',\n  'Hobbies',\n  'Parenting',\n  'Tech',\n  'Gadgets',\n  'AI',\n  'Coding',\n  'Software',\n  'Innovation',\n  'Apps',\n  'Gaming',\n  'Robotics',\n  'Security',\n  'Music',\n  'Movies',\n  'Sports',\n  'Books',\n  'Theater',\n  'Comedy',\n  'Dance',\n  'Celebrities',\n  'Culture',\n  'Gaming',\n  'Environment',\n  'Equality',\n  'Activism',\n  'Justice',\n  'Diversity',\n  'Sustainability',\n  'Inclusion',\n  'Awareness',\n  'Charity',\n  'Peace',\n  'Holidays',\n  'Festivities',\n  'Seasons',\n  'Trends',\n  'Celebrations',\n  'Anniversaries',\n  'Milestones',\n  'Memories',\n  'Promotions',\n  'Updates',\n];\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/agent.tool.interface.ts",
    "content": "import type { ZodLikeSchema } from '@mastra/core/dist/types/zod-compat';\nimport type {\n  ToolExecutionContext,\n} from '@mastra/core/dist/tools/types';\nimport { Tool } from '@mastra/core/dist/tools/tool';\n\nexport type ToolReturn = Tool<\n  ZodLikeSchema,\n  ZodLikeSchema,\n  ZodLikeSchema,\n  ZodLikeSchema,\n  ToolExecutionContext<ZodLikeSchema, ZodLikeSchema, ZodLikeSchema>\n>;\n\nexport interface AgentToolInterface {\n  name: string;\n  run(): ToolReturn;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/async.storage.ts",
    "content": "// context.ts\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\ntype Ctx = {\n  requestId: string;\n  auth: any; // replace with your org type if you have it, e.g. Organization\n};\n\nconst als = new AsyncLocalStorage<Ctx>();\n\nexport function runWithContext<T>(ctx: Ctx, fn: () => Promise<T> | T) {\n  return als.run(ctx, fn);\n}\n\nexport function getContext(): Ctx | undefined {\n  return als.getStore();\n}\n\nexport function getAuth<T = any>(): T | undefined {\n  return als.getStore()?.auth as T | undefined;\n}\n\nexport function getRequestId(): string | undefined {\n  return als.getStore()?.requestId;\n}"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/auth.context.ts",
    "content": "import { ToolAction } from '@mastra/core/dist/tools/types';\nimport { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';\n\nexport const checkAuth: ToolAction['execute'] = async (\n  { runtimeContext },\n  options\n) => {\n  const auth = getAuth();\n  // @ts-ignore\n  if (options?.extra?.authInfo || auth) {\n    runtimeContext.set(\n      // @ts-ignore\n      'organization',\n      // @ts-ignore\n      JSON.stringify(options?.extra?.authInfo || auth)\n    );\n    // @ts-ignore\n    runtimeContext.set('ui', 'false');\n  }\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/chat.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { LoadToolsService } from '@gitroom/nestjs-libraries/chat/load.tools.service';\nimport { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';\nimport { toolList } from '@gitroom/nestjs-libraries/chat/tools/tool.list';\n\n@Global()\n@Module({\n  providers: [MastraService, LoadToolsService, ...toolList],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class ChatModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/load.tools.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { Agent } from '@mastra/core/agent';\nimport { openai } from '@ai-sdk/openai';\nimport { Memory } from '@mastra/memory';\nimport { pStore } from '@gitroom/nestjs-libraries/chat/mastra.store';\nimport { array, object, string } from 'zod';\nimport { ModuleRef } from '@nestjs/core';\nimport { toolList } from '@gitroom/nestjs-libraries/chat/tools/tool.list';\nimport dayjs from 'dayjs';\n\nexport const AgentState = object({\n  proverbs: array(string()).default([]),\n});\n\nconst renderArray = (list: string[], show: boolean) => {\n  if (!show) return '';\n  return list.map((p) => `- ${p}`).join('\\n');\n};\n\n@Injectable()\nexport class LoadToolsService {\n  constructor(private _moduleRef: ModuleRef) {}\n\n  async loadTools() {\n    return (\n      await Promise.all<{ name: string; tool: any }>(\n        toolList\n          .map((p) => this._moduleRef.get(p, { strict: false }))\n          .map(async (p) => ({\n            name: p.name as string,\n            tool: await p.run(),\n          }))\n      )\n    ).reduce(\n      (all, current) => ({\n        ...all,\n        [current.name]: current.tool,\n      }),\n      {} as Record<string, any>\n    );\n  }\n\n  async agent() {\n    const tools = await this.loadTools();\n    return new Agent({\n      name: 'postiz',\n      description: 'Agent that helps manage and schedule social media posts for users',\n      instructions: ({ runtimeContext }) => {\n        const ui: string = runtimeContext.get('ui' as never);\n        return `\n      Global information:\n        - Date (UTC): ${dayjs().format('YYYY-MM-DD HH:mm:ss')}\n\n      You are an agent that helps manage and schedule social media posts for users, you can:\n        - Schedule posts into the future, or now, adding texts, images and videos\n        - Generate pictures for posts\n        - Generate videos for posts\n        - Generate text for posts\n        - Show global analytics about socials\n        - List integrations (channels)\n      \n      - We schedule posts to different integration like facebook, instagram, etc. but to the user we don't say integrations we say channels as integration is the technical name\n      - When scheduling a post, you must follow the social media rules and best practices.\n      - When scheduling a post, you can pass an array for list of posts for a social media platform, But it has different behavior depending on the platform.\n        - For platforms like Threads, Bluesky and X (Twitter), each post in the array will be a separate post in the thread.\n        - For platforms like LinkedIn and Facebook, second part of the array will be added as \"comments\" to the first post.\n        - If the social media platform has the concept of \"threads\", we need to ask the user if they want to create a thread or one long post.\n        - For X, if you don't have Premium, don't suggest a long post because it won't work.\n        - Platform format will also be passed can be \"normal\", \"markdown\", \"html\", make sure you use the correct format for each platform.\n      \n      - Sometimes 'integrationSchema' will return rules, make sure you follow them (these rules are set in stone, even if the user asks to ignore them)\n      - Each socials media platform has different settings and rules, you can get them by using the integrationSchema tool.\n      - Always make sure you use this tool before you schedule any post.\n      - In every message I will send you the list of needed social medias (id and platform), if you already have the information use it, if not, use the integrationSchema tool to get it.\n      - Make sure you always take the last information I give you about the socials, it might have changed.\n      - Before scheduling a post, always make sure you ask the user confirmation by providing all the details of the post (text, images, videos, date, time, social media platform, account).\n      - Between tools, we will reference things like: [output:name] and [input:name] to set the information right.\n      - When outputting a date for the user, make sure it's human readable with time\n      - The content of the post, HTML, Each line must be wrapped in <p> here is the possible tags: h1, h2, h3, u, strong, li, ul, p (you can\\'t have u and strong together), don't use a \"code\" box\n      ${renderArray(\n        [\n          'If the user confirm, ask if they would like to get a modal with populated content without scheduling the post yet or if they want to schedule it right away.',\n        ],\n        !!ui\n      )}\n`;\n      },\n      model: openai('gpt-5.2'),\n      tools,\n      memory: new Memory({\n        storage: pStore,\n        options: {\n          threads: {\n            generateTitle: true,\n          },\n          workingMemory: {\n            enabled: true,\n            schema: AgentState,\n          },\n        },\n      }),\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/mastra.service.ts",
    "content": "import { Mastra } from '@mastra/core/mastra';\nimport { ConsoleLogger } from '@mastra/core/logger';\nimport { pStore } from '@gitroom/nestjs-libraries/chat/mastra.store';\nimport { Injectable } from '@nestjs/common';\nimport { LoadToolsService } from '@gitroom/nestjs-libraries/chat/load.tools.service';\n\n@Injectable()\nexport class MastraService {\n  static mastra: Mastra;\n  constructor(private _loadToolsService: LoadToolsService) {}\n  async mastra() {\n    MastraService.mastra =\n      MastraService.mastra ||\n      new Mastra({\n        storage: pStore,\n        agents: {\n          postiz: await this._loadToolsService.agent(),\n        },\n        logger: new ConsoleLogger({\n          level: 'info',\n        }),\n      });\n\n    return MastraService.mastra;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/mastra.store.ts",
    "content": "import { PostgresStore, PgVector } from '@mastra/pg';\n\nexport const pStore = new PostgresStore({\n  connectionString: process.env.DATABASE_URL,\n});\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/rules.description.decorator.ts",
    "content": "import 'reflect-metadata';\n\nexport function Rules(description: string) {\n  return function (target: any) {\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata(\n      'custom:rules:description',\n      description,\n      target\n    );\n  };\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/start.mcp.ts",
    "content": "import { INestApplication } from '@nestjs/common';\nimport { Request, Response } from 'express';\nimport { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';\nimport { MCPServer } from '@mastra/mcp';\nimport { randomUUID } from 'crypto';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\nimport { runWithContext } from './async.storage';\nexport const startMcp = async (app: INestApplication) => {\n  const mastraService = app.get(MastraService, { strict: false });\n  const organizationService = app.get(OrganizationService, { strict: false });\n  const oauthService = app.get(OAuthService, { strict: false });\n\n  const resolveAuth = async (token: string) => {\n    if (token.startsWith('pos_')) {\n      const authorization = await oauthService.getOrgByOAuthToken(token);\n      if (!authorization) return null;\n      return authorization.organization;\n    }\n    return organizationService.getOrgByApiKey(token);\n  };\n\n  const mastra = await mastraService.mastra();\n  const agent = mastra.getAgent('postiz');\n  const tools = await agent.getTools();\n\n  const serverConfig = {\n    name: 'Postiz MCP',\n    version: '1.0.0',\n    tools,\n    agents: { postiz: agent },\n  };\n\n  const server = new MCPServer(serverConfig);\n\n  app.use('/mcp', async (req: Request, res: Response, next: () => void) => {\n    // Skip if this is the /mcp/:id route\n    if (req.path !== '/' && req.path !== '') {\n      next();\n      return;\n    }\n\n    // @ts-ignore\n    res.setHeader('Access-Control-Allow-Origin', '*');\n    res.setHeader('Access-Control-Allow-Methods', '*');\n    res.setHeader('Access-Control-Allow-Headers', '*');\n    res.setHeader('Access-Control-Expose-Headers', '*');\n\n    if (req.method === 'OPTIONS') {\n      res.sendStatus(200);\n      return;\n    }\n\n    const token = req.headers.authorization?.replace('Bearer ', '');\n    if (!token) {\n      res.status(401).send('Missing Authorization header');\n      return;\n    }\n\n    // @ts-ignore\n    req.auth = await resolveAuth(token);\n    // @ts-ignore\n    if (!req.auth) {\n      res.status(401).send('Invalid API Key or OAuth token');\n      return;\n    }\n\n    const url = new URL('/mcp', process.env.NEXT_PUBLIC_BACKEND_URL);\n\n    // @ts-ignore\n    await runWithContext({ requestId: token, auth: req.auth }, async () => {\n      await server.startHTTP({\n        url,\n        httpPath: url.pathname,\n        options: {\n          sessionIdGenerator: () => {\n            return randomUUID();\n          },\n        },\n        req,\n        res,\n      });\n    });\n  });\n\n  app.use('/mcp/:id', async (req: Request, res: Response) => {\n    // @ts-ignore\n    res.setHeader('Access-Control-Allow-Origin', '*');\n    res.setHeader('Access-Control-Allow-Methods', '*');\n    res.setHeader('Access-Control-Allow-Headers', '*');\n    res.setHeader('Access-Control-Expose-Headers', '*');\n\n    if (req.method === 'OPTIONS') {\n      res.sendStatus(200);\n      return;\n    }\n\n    // @ts-ignore\n    req.auth = await organizationService.getOrgByApiKey(req.params.id);\n    // @ts-ignore\n    if (!req.auth) {\n      res.status(400).send('Invalid API Key');\n      return;\n    }\n\n    const url = new URL(\n      `/mcp/${req.params.id}`,\n      process.env.NEXT_PUBLIC_BACKEND_URL\n    );\n\n    await runWithContext(\n      // @ts-ignore\n      { requestId: req.params.id, auth: req.auth },\n      async () => {\n        await server.startHTTP({\n          url,\n          httpPath: url.pathname,\n          options: {\n            sessionIdGenerator: () => {\n              return randomUUID();\n            },\n          },\n          req,\n          res,\n        });\n      }\n    );\n  });\n\n  app.use(['/sse/:id', '/message/:id'], async (req: Request, res: Response) => {\n    // @ts-ignore\n    res.setHeader('Access-Control-Allow-Origin', '*');\n    res.setHeader('Access-Control-Allow-Methods', '*');\n    res.setHeader('Access-Control-Allow-Headers', '*');\n    res.setHeader('Access-Control-Expose-Headers', '*');\n\n    if (req.method === 'OPTIONS') {\n      res.sendStatus(200);\n      return;\n    }\n\n    // @ts-ignore\n    req.auth = await organizationService.getOrgByApiKey(req.params.id);\n    // @ts-ignore\n    if (!req.auth) {\n      res.status(400).send('Invalid API Key');\n      return;\n    }\n\n    const url = new URL(req.originalUrl, process.env.NEXT_PUBLIC_BACKEND_URL);\n\n    await runWithContext(\n      // @ts-ignore\n      { requestId: req.params.id, auth: req.auth },\n      async () => {\n        await new MCPServer(serverConfig).startSSE({\n          url,\n          ssePath: `/sse/${req.params.id}`,\n          messagePath: `/message/${req.params.id}`,\n          req,\n          res,\n        });\n      }\n    );\n  });\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/generate.image.tool.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { Injectable } from '@nestjs/common';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\n\n@Injectable()\nexport class GenerateImageTool implements AgentToolInterface {\n  private storage = UploadFactory.createStorage();\n\n  constructor(private _mediaService: MediaService) {}\n  name = 'generateImageTool';\n\n  run() {\n    return createTool({\n      id: 'generateImageTool',\n      description: `Generate image to use in a post,\n                    in case the user specified a platform that requires attachment and attachment was not provided,\n                    ask if they want to generate a picture of a video.\n      `,\n      inputSchema: z.object({\n        prompt: z.string(),\n      }),\n      outputSchema: z.object({\n        id: z.string(),\n        path: z.string(),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        // @ts-ignore\n        const org = JSON.parse(runtimeContext.get('organization') as string);\n        const image = await this._mediaService.generateImage(\n          context.prompt,\n          org\n        );\n\n        const file = await this.storage.uploadSimple(\n          'data:image/png;base64,' + image\n        );\n\n        return this._mediaService.saveFile(org.id, file.split('/').pop(), file);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/generate.video.options.tool.ts",
    "content": "import {\n  AgentToolInterface,\n  ToolReturn,\n} from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { Injectable } from '@nestjs/common';\nimport { getValidationSchemas } from '@gitroom/nestjs-libraries/chat/validation.schemas.helper';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport z from 'zod';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\n\n@Injectable()\nexport class GenerateVideoOptionsTool implements AgentToolInterface {\n  constructor(private _videoManagerService: VideoManager) {}\n  name = 'generateVideoOptions';\n\n  run() {\n    return createTool({\n      id: 'generateVideoOptions',\n      description: `All the options to generate videos, some tools might require another call to generateVideoFunction`,\n      outputSchema: z.object({\n        video: z.array(\n          z.object({\n            type: z.string(),\n            output: z.string(),\n            tools: z.array(\n              z.object({\n                functionName: z.string(),\n                output: z.string(),\n              })\n            ),\n            customParams: z.any(),\n          })\n        ),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        const videos = this._videoManagerService.getAllVideos();\n        console.log(\n          JSON.stringify(\n            {\n              video: videos.map((p) => {\n                return {\n                  type: p.identifier,\n                  output: 'vertical|horizontal',\n                  tools: p.tools,\n                  customParams: getValidationSchemas()[p.dto.name],\n                };\n              }),\n            },\n            null,\n            2\n          )\n        );\n\n        return {\n          video: videos.map((p) => {\n            return {\n              type: p.identifier,\n              output: 'vertical|horizontal',\n              tools: p.tools,\n              customParams: getValidationSchemas()[p.dto.name],\n            };\n          }),\n        };\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/generate.video.tool.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { Injectable } from '@nestjs/common';\nimport {\n  IntegrationManager,\n  socialIntegrationList,\n} from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\n\n@Injectable()\nexport class GenerateVideoTool implements AgentToolInterface {\n  constructor(\n    private _mediaService: MediaService,\n    private _videoManager: VideoManager\n  ) {}\n  name = 'generateVideoTool';\n\n  run() {\n    return createTool({\n      id: 'generateVideoTool',\n      description: `Generate video to use in a post,\n                    in case the user specified a platform that requires attachment and attachment was not provided,\n                    ask if they want to generate a picture of a video.\n                    In many cases 'videoFunctionTool' will need to be called first, to get things like voice id\n                    Here are the type of video that can be generated:\n                    ${this._videoManager\n                      .getAllVideos()\n                      .map((p) => \"-\" + p.title)\n                      .join('\\n')}\n      `,\n      inputSchema: z.object({\n        identifier: z.string(),\n        output: z.enum(['vertical', 'horizontal']),\n        customParams: z.array(\n          z.object({\n            key: z.string().describe('Name of the settings key to pass'),\n            value: z.any().describe('Value of the key'),\n          })\n        ),\n      }),\n      outputSchema: z.object({\n        url: z.string(),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        // @ts-ignore\n        const org = JSON.parse(runtimeContext.get('organization') as string);\n        const value = await this._mediaService.generateVideo(org, {\n          type: context.identifier,\n          output: context.output,\n          customParams: context.customParams.reduce(\n            (all, current) => ({\n              ...all,\n              [current.key]: current.value,\n            }),\n            {}\n          ),\n        });\n\n        return {\n          url: value.path,\n        };\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/integration.list.tool.ts",
    "content": "import {\n  AgentToolInterface,\n  ToolReturn,\n} from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { Injectable } from '@nestjs/common';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport z from 'zod';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\nimport { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';\n\n@Injectable()\nexport class IntegrationListTool implements AgentToolInterface {\n  constructor(private _integrationService: IntegrationService) {}\n  name = 'integrationList';\n\n  run() {\n    return createTool({\n      id: 'integrationList',\n      description: `This tool list available integrations to schedule posts to`,\n      outputSchema: z.object({\n        output: z.array(\n          z.object({\n            id: z.string(),\n            name: z.string(),\n            picture: z.string(),\n            platform: z.string(),\n          })\n        ),\n      }),\n      execute: async (args, options) => {\n        console.log(getAuth());\n        console.log(options);\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        const organizationId = JSON.parse(\n          // @ts-ignore\n          runtimeContext.get('organization') as string\n        ).id;\n\n        return {\n          output: (\n            await this._integrationService.getIntegrationsList(organizationId)\n          ).map((p) => ({\n            name: p.name,\n            id: p.id,\n            disabled: p.disabled,\n            picture: p.picture || '/no-picture.jpg',\n            platform: p.providerIdentifier,\n            display: p.profile,\n            type: p.type,\n          })),\n        };\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { Injectable } from '@nestjs/common';\nimport { socialIntegrationList } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { AllProvidersSettings } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/all.providers.settings';\nimport { validate } from 'class-validator';\nimport { Integration } from '@prisma/client';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { weightedLength } from '@gitroom/helpers/utils/count.length';\n\nfunction countCharacters(text: string, type: string): number {\n  if (type !== 'x') {\n    return text.length;\n  }\n  return weightedLength(text);\n}\n\n@Injectable()\nexport class IntegrationSchedulePostTool implements AgentToolInterface {\n  constructor(\n    private _postsService: PostsService,\n    private _integrationService: IntegrationService\n  ) {}\n  name = 'integrationSchedulePostTool';\n\n  run() {\n    return createTool({\n      id: 'schedulePostTool',\n      description: `\nThis tool allows you to schedule a post to a social media platform, based on integrationSchema tool.\nSo for example:\n\nIf the user want to post a post to LinkedIn with one comment\n- socialPost array length will be one\n- postsAndComments array length will be two (one for the post, one for the comment)\n\nIf the user want to post 20 posts for facebook each in individual days without comments\n- socialPost array length will be 20\n- postsAndComments array length will be one\n\nIf the tools return errors, you would need to rerun it with the right parameters, don't ask again, just run it\n`,\n      inputSchema: z.object({\n        socialPost: z\n          .array(\n            z.object({\n              integrationId: z\n                .string()\n                .describe('The id of the integration (not internal id)'),\n              isPremium: z\n                .boolean()\n                .describe(\n                  \"If the integration is X, return if it's premium or not\"\n                ),\n              date: z.string().describe('The date of the post in UTC time'),\n              shortLink: z\n                .boolean()\n                .describe(\n                  'If the post has a link inside, we can ask the user if they want to add a short link'\n                ),\n              type: z\n                .enum(['draft', 'schedule', 'now'])\n                .describe(\n                  'The type of the post, if we pass now, we should pass the current date also'\n                ),\n              postsAndComments: z\n                .array(\n                  z.object({\n                    content: z\n                      .string()\n                      .describe(\n                        \"The content of the post, HTML, Each line must be wrapped in <p> here is the possible tags: h1, h2, h3, u, strong, li, ul, p (you can't have u and strong together)\"\n                      ),\n                    attachments: z\n                      .array(z.string())\n                      .describe('The image of the post (URLS)'),\n                  })\n                )\n                .describe(\n                  'first item is the post, every other item is the comments'\n                ),\n              settings: z\n                .array(\n                  z.object({\n                    key: z\n                      .string()\n                      .describe('Name of the settings key to pass'),\n                    value: z\n                      .any()\n                      .describe(\n                        'Value of the key, always prefer the id then label if possible'\n                      ),\n                  })\n                )\n                .describe(\n                  'This relies on the integrationSchema tool to get the settings [input:settings]'\n                ),\n            })\n          )\n          .describe('Individual post'),\n      }),\n      outputSchema: z.object({\n        output: z\n          .array(\n            z.object({\n              postId: z.string(),\n              integration: z.string(),\n            })\n          )\n          .or(z.object({ errors: z.string() })),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        const organizationId = JSON.parse(\n          // @ts-ignore\n          runtimeContext.get('organization') as string\n        ).id;\n        const finalOutput = [];\n\n        const integrations = {} as Record<string, Integration>;\n        for (const platform of context.socialPost) {\n          integrations[platform.integrationId] =\n            await this._integrationService.getIntegrationById(\n              organizationId,\n              platform.integrationId\n            );\n\n          const { dto, maxLength, identifier } = socialIntegrationList.find(\n            (p) =>\n              p.identifier ===\n              integrations[platform.integrationId].providerIdentifier\n          )!;\n\n          if (dto) {\n            const newDTO = new dto();\n            const obj = Object.assign(\n              newDTO,\n              platform.settings.reduce(\n                (acc, s) => ({\n                  ...acc,\n                  [s.key]: s.value,\n                }),\n                {} as AllProvidersSettings\n              )\n            );\n            const errors = await validate(obj);\n            if (errors.length) {\n              return {\n                errors: JSON.stringify(errors),\n              };\n            }\n\n            const errorsLength = [];\n            for (const post of platform.postsAndComments) {\n              const maximumCharacters = maxLength(platform.isPremium);\n              const strip = stripHtmlValidation('normal', post.content, true);\n              const weightedLength = countCharacters(strip, identifier || '');\n              const totalCharacters =\n                weightedLength > strip.length ? weightedLength : strip.length;\n\n              if (totalCharacters > (maximumCharacters || 1000000)) {\n                errorsLength.push({\n                  value: post.content,\n                  error: `The maximum characters is ${maximumCharacters}, we got ${totalCharacters}, please fix it, and try integrationSchedulePostTool again.`,\n                });\n              }\n            }\n\n            if (errorsLength.length) {\n              return {\n                errors: JSON.stringify(errorsLength),\n              };\n            }\n          }\n        }\n\n        for (const post of context.socialPost) {\n          const integration = integrations[post.integrationId];\n\n          if (!integration) {\n            throw new Error('Integration not found');\n          }\n\n          const output = await this._postsService.createPost(organizationId, {\n            date: post.date,\n            type: post.type as 'draft' | 'schedule' | 'now',\n            shortLink: post.shortLink,\n            tags: [],\n            posts: [\n              {\n                integration,\n                group: makeId(10),\n                settings: post.settings.reduce(\n                  (acc, s) => ({\n                    ...acc,\n                    [s.key]: s.value,\n                  }),\n                  {\n                    __type: integration.providerIdentifier,\n                  } as AllProvidersSettings\n                ),\n                value: post.postsAndComments.map((p) => ({\n                  content: p.content,\n                  id: makeId(10),\n                  delay: 0,\n                  image: p.attachments.map((p) => ({\n                    id: makeId(10),\n                    path: p,\n                  })),\n                })),\n              },\n            ],\n          });\n          finalOutput.push(...output);\n        }\n\n        return {\n          output: finalOutput,\n        };\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/integration.trigger.tool.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { Injectable } from '@nestjs/common';\nimport {\n  IntegrationManager,\n  socialIntegrationList,\n} from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\n\n@Injectable()\nexport class IntegrationTriggerTool implements AgentToolInterface {\n  constructor(\n    private _integrationManager: IntegrationManager,\n    private _integrationService: IntegrationService,\n    private _refreshIntegrationService: RefreshIntegrationService\n  ) {}\n  name = 'triggerTool';\n\n  run() {\n    return createTool({\n      id: 'triggerTool',\n      description: `After using the integrationSchema, we sometimes miss details we can\\'t ask from the user, like ids.\n      Sometimes this tool requires to user prompt for some settings, like a word to search for. methodName is required [input:callable-tools]`,\n      inputSchema: z.object({\n        integrationId: z.string().describe('The id of the integration'),\n        methodName: z\n          .string()\n          .describe(\n            'The methodName from the `integrationSchema` functions in the tools array, required'\n          ),\n        dataSchema: z.array(\n          z.object({\n            key: z.string().describe('Name of the settings key to pass'),\n            value: z.string().describe('Value of the key'),\n          })\n        ),\n      }),\n      outputSchema: z.object({\n        output: z.array(z.record(z.string(), z.any())),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        console.log('triggerTool', context);\n        const organizationId = JSON.parse(\n          // @ts-ignore\n          runtimeContext.get('organization') as string\n        ).id;\n\n        const getIntegration =\n          await this._integrationService.getIntegrationById(\n            organizationId,\n            context.integrationId\n          );\n\n        if (!getIntegration) {\n          return {\n            output: 'Integration not found',\n          };\n        }\n\n        const integrationProvider = socialIntegrationList.find(\n          (p) => p.identifier === getIntegration.providerIdentifier\n        )!;\n\n        if (!integrationProvider) {\n          return {\n            output: 'Integration not found',\n          };\n        }\n\n        const tools = this._integrationManager.getAllTools();\n        if (\n          // @ts-ignore\n          !tools[integrationProvider.identifier].some(\n            (p) => p.methodName === context.methodName\n          ) ||\n          // @ts-ignore\n          !integrationProvider[context.methodName]\n        ) {\n          return { output: 'tool not found' };\n        }\n\n        while (true) {\n          try {\n            // @ts-ignore\n            const load = await integrationProvider[context.methodName](\n              getIntegration.token,\n              context.dataSchema.reduce(\n                (all, current) => ({\n                  ...all,\n                  [current.key]: current.value,\n                }),\n                {}\n              ),\n              getIntegration.internalId,\n              getIntegration\n            );\n\n            return { output: load };\n          } catch (err) {\n            if (err instanceof RefreshToken) {\n              const data = await this._refreshIntegrationService.refresh(\n                getIntegration\n              );\n\n              if (!data) {\n                await this._integrationService.disconnectChannel(\n                  organizationId,\n                  getIntegration\n                );\n                return {\n                  output:\n                    'We had to disconnect the channel as the token expired',\n                };\n              }\n\n              const { accessToken } = data;\n\n              if (accessToken) {\n                getIntegration.token = accessToken;\n\n                if (integrationProvider.refreshWait) {\n                  await timer(10000);\n                }\n\n                continue;\n              } else {\n              }\n            }\n            return { output: 'Unexpected error' };\n          }\n        }\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/integration.validation.tool.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { Injectable } from '@nestjs/common';\nimport {\n  IntegrationManager,\n  socialIntegrationList,\n} from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { getValidationSchemas } from '@gitroom/nestjs-libraries/chat/validation.schemas.helper';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\n\n@Injectable()\nexport class IntegrationValidationTool implements AgentToolInterface {\n  constructor(private _integrationManager: IntegrationManager) {}\n  name = 'integrationSchema';\n\n  run() {\n    return createTool({\n      id: 'integrationSchema',\n      description: `Everytime we want to schedule a social media post, we need to understand the schema of the integration.\n         This tool helps us get the schema of the integration.\n         Sometimes we might get a schema back the requires some id, for that, you can get information from 'tools'\n         And use the triggerTool function.\n        `,\n      inputSchema: z.object({\n        isPremium: z\n          .boolean()\n          .describe('is this the user premium? if not, set to false'),\n        platform: z\n          .string()\n          .describe(\n            `platform identifier (${socialIntegrationList\n              .map((p) => p.identifier)\n              .join(', ')})`\n          ),\n      }),\n      outputSchema: z.object({\n        output: z.object({\n          rules: z.string(),\n          maxLength: z\n            .number()\n            .describe('The maximum length of a post / comment'),\n          settings: z\n            .any()\n            .describe('List of settings need to be passed to schedule a post'),\n          tools: z\n            .array(\n              z.object({\n                description: z.string().describe('Description of the tool'),\n                methodName: z\n                  .string()\n                  .describe('Method to call to get the information'),\n                dataSchema: z\n                  .array(\n                    z.object({\n                      key: z\n                        .string()\n                        .describe('Name of the settings key to pass'),\n                      description: z\n                        .string()\n                        .describe('Description of the setting key'),\n                      type: z.string(),\n                    })\n                  )\n                  .describe(\n                    'This will be passed to schedulePostTool [output:settings]'\n                  ),\n              })\n            )\n            .describe(\n              \"Sometimes settings require some id, tags and stuff, if you don't have, trigger the `triggerTool` function from the tools list [output:callable-tools]\"\n            ),\n        }),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        const integration = socialIntegrationList.find(\n          (p) => p.identifier === context.platform\n        )!;\n\n        if (!integration) {\n          return {\n            output: { rules: '', maxLength: 0, settings: {}, tools: [] },\n          };\n        }\n\n        const maxLength = integration.maxLength(context.isPremium);\n        const schemas = !integration.dto\n          ? false\n          : getValidationSchemas()[integration.dto.name];\n        const tools = this._integrationManager.getAllTools();\n        const rules = this._integrationManager.getAllRulesDescription();\n\n        return {\n          output: {\n            rules: rules[integration.identifier],\n            maxLength,\n            settings: !schemas ? 'No additional settings required' : schemas,\n            tools: tools[integration.identifier],\n          },\n        };\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/tool.list.ts",
    "content": "import { IntegrationValidationTool } from '@gitroom/nestjs-libraries/chat/tools/integration.validation.tool';\nimport { IntegrationTriggerTool } from '@gitroom/nestjs-libraries/chat/tools/integration.trigger.tool';\nimport { IntegrationSchedulePostTool } from './integration.schedule.post';\nimport { GenerateVideoOptionsTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.options.tool';\nimport { VideoFunctionTool } from '@gitroom/nestjs-libraries/chat/tools/video.function.tool';\nimport { GenerateVideoTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.tool';\nimport { GenerateImageTool } from '@gitroom/nestjs-libraries/chat/tools/generate.image.tool';\nimport { IntegrationListTool } from '@gitroom/nestjs-libraries/chat/tools/integration.list.tool';\n\nexport const toolList = [\n  IntegrationListTool,\n  IntegrationValidationTool,\n  IntegrationTriggerTool,\n  IntegrationSchedulePostTool,\n  GenerateVideoOptionsTool,\n  VideoFunctionTool,\n  GenerateVideoTool,\n  GenerateImageTool,\n];\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/tools/video.function.tool.ts",
    "content": "import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';\nimport { createTool } from '@mastra/core/tools';\nimport { Injectable } from '@nestjs/common';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport z from 'zod';\nimport { ModuleRef } from '@nestjs/core';\nimport { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';\n\n@Injectable()\nexport class VideoFunctionTool implements AgentToolInterface {\n  constructor(\n    private _videoManagerService: VideoManager,\n    private _moduleRef: ModuleRef\n  ) {}\n  name = 'videoFunctionTool';\n\n  run() {\n    return createTool({\n      id: 'videoFunctionTool',\n      description: `Sometimes when we want to generate videos we might need to get some additional information like voice_id, etc`,\n      inputSchema: z.object({\n        identifier: z.string(),\n        functionName: z.string(),\n      }),\n      execute: async (args, options) => {\n        const { context, runtimeContext } = args;\n        checkAuth(args, options);\n        const videos = this._videoManagerService.getAllVideos();\n        const findVideo = videos.find(\n          (p) =>\n            p.identifier === context.identifier &&\n            p.tools.some((p) => p.functionName === context.functionName)\n        );\n\n        if (!findVideo) {\n          return { error: 'Function not found' };\n        }\n\n        const func = await this._moduleRef\n          // @ts-ignore\n          .get(findVideo.target, { strict: false })\n          [context.functionName]();\n        return func;\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/chat/validation.schemas.helper.ts",
    "content": "import {\n  validationMetadatasToSchemas,\n  targetConstructorToSchema,\n} from 'class-validator-jsonschema';\nimport { ValidationTypes } from 'class-validator';\n// @ts-ignore\nimport { defaultMetadataStorage } from 'class-transformer/cjs/storage';\n\nexport function getValidationSchemas() {\n  return validationMetadatasToSchemas({\n    classTransformerMetadataStorage: defaultMetadataStorage,\n    additionalConverters: {\n      [ValidationTypes.NESTED_VALIDATION]: (meta, options) => {\n        if (typeof meta.target === 'function') {\n          const typeMeta = options.classTransformerMetadataStorage\n            ? options.classTransformerMetadataStorage.findTypeMetadata(\n                meta.target,\n                meta.propertyName\n              )\n            : null;\n          if (typeMeta) {\n            const childType = typeMeta.typeFunction();\n            return targetConstructorToSchema(childType, options);\n          }\n        }\n        return {};\n      },\n    },\n  });\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/crypto/nowpayments.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\n\nexport interface ProcessPayment {\n  payment_id: number;\n  payment_status: string;\n  pay_address: string;\n  price_amount: number;\n  price_currency: string;\n  pay_amount: number;\n  actually_paid: number;\n  pay_currency: string;\n  order_id: string;\n  order_description: string;\n  purchase_id: string;\n  created_at: string;\n  updated_at: string;\n  outcome_amount: number;\n  outcome_currency: string;\n}\n\n@Injectable()\nexport class Nowpayments {\n  constructor(private _subscriptionService: SubscriptionService) {}\n\n  async processPayment(path: string, body: ProcessPayment) {\n    const decrypt = AuthService.verifyJWT(path) as any;\n    if (!decrypt || !decrypt.order_id) {\n      return;\n    }\n\n    if (\n      body.payment_status !== 'confirmed' &&\n      body.payment_status !== 'finished'\n    ) {\n      return;\n    }\n\n    const [org, make] = body.order_id.split('_');\n    await this._subscriptionService.lifeTime(org, make, 'PRO');\n    return body;\n  }\n\n  async createPaymentPage(orgId: string) {\n    const onlyId = makeId(5);\n    const make = orgId + '_' + onlyId;\n    const signRequest = AuthService.signJWT({ order_id: make });\n\n    const { id, invoice_url } = await (\n      await fetch('https://api.nowpayments.io/v1/invoice', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          'x-api-key': process.env.NOWPAYMENTS_API_KEY!,\n        },\n        body: JSON.stringify({\n          price_amount: process.env.NOWPAYMENTS_AMOUNT,\n          price_currency: 'USD',\n          order_id: make,\n          pay_currency: 'SOL',\n          order_description: 'Lifetime deal account for Postiz',\n          ipn_callback_url:\n            process.env.NEXT_PUBLIC_BACKEND_URL +\n            `/public/crypto/${signRequest}`,\n          success_url: process.env.FRONTEND_URL + `/launches?check=${onlyId}`,\n          cancel_url: process.env.FRONTEND_URL,\n        }),\n      })\n    ).json();\n\n    return {\n      id,\n      invoice_url,\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/agencies/agencies.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { User } from '@prisma/client';\nimport { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';\n\n@Injectable()\nexport class AgenciesRepository {\n  constructor(\n    private _socialMediaAgencies: PrismaRepository<'socialMediaAgency'>,\n    private _socialMediaAgenciesNiche: PrismaRepository<'socialMediaAgencyNiche'>\n  ) {}\n\n  getAllAgencies() {\n    return this._socialMediaAgencies.model.socialMediaAgency.findMany({\n      where: {\n        deletedAt: null,\n        approved: true,\n      },\n      include: {\n        logo: true,\n        niches: true,\n      },\n      orderBy: {\n        createdAt: 'desc',\n      },\n    });\n  }\n\n  getCount() {\n    return this._socialMediaAgencies.model.socialMediaAgency.count({\n      where: {\n        deletedAt: null,\n        approved: true,\n      },\n    });\n  }\n\n  getAllAgenciesSlug() {\n    return this._socialMediaAgencies.model.socialMediaAgency.findMany({\n      where: {\n        deletedAt: null,\n        approved: true,\n      },\n      select: {\n        slug: true,\n      },\n    });\n  }\n\n  approveOrDecline(action: string, id: string) {\n    return this._socialMediaAgencies.model.socialMediaAgency.update({\n      where: {\n        id,\n      },\n      data: {\n        approved: action === 'approve',\n      },\n    });\n  }\n\n  getAgencyById(id: string) {\n    return this._socialMediaAgencies.model.socialMediaAgency.findFirst({\n      where: {\n        id,\n        deletedAt: null,\n        approved: true,\n      },\n      include: {\n        logo: true,\n        niches: true,\n        user: true,\n      },\n    });\n  }\n\n  getAgencyInformation(agency: string) {\n    return this._socialMediaAgencies.model.socialMediaAgency.findFirst({\n      where: {\n        slug: agency,\n        deletedAt: null,\n        approved: true,\n      },\n      include: {\n        logo: true,\n        niches: true,\n      },\n    });\n  }\n\n  getAgencyByUser(user: User) {\n    return this._socialMediaAgencies.model.socialMediaAgency.findFirst({\n      where: {\n        userId: user.id,\n        deletedAt: null,\n      },\n      include: {\n        logo: true,\n        niches: true,\n      },\n    });\n  }\n\n  async createAgency(user: User, body: CreateAgencyDto) {\n    const insertAgency =\n      await this._socialMediaAgencies.model.socialMediaAgency.upsert({\n        where: {\n          userId: user.id,\n        },\n        update: {\n          userId: user.id,\n          name: body.name,\n          website: body.website,\n          facebook: body.facebook,\n          instagram: body.instagram,\n          twitter: body.twitter,\n          linkedIn: body.linkedIn,\n          youtube: body.youtube,\n          tiktok: body.tiktok,\n          logoId: body.logo.id,\n          shortDescription: body.shortDescription,\n          description: body.description,\n          approved: false,\n        },\n        create: {\n          userId: user.id,\n          name: body.name,\n          website: body.website,\n          facebook: body.facebook,\n          instagram: body.instagram,\n          twitter: body.twitter,\n          linkedIn: body.linkedIn,\n          youtube: body.youtube,\n          tiktok: body.tiktok,\n          logoId: body.logo.id,\n          shortDescription: body.shortDescription,\n          description: body.description,\n          slug: body.name.toLowerCase().replace(/ /g, '-'),\n          approved: false,\n        },\n        select: {\n          id: true,\n        },\n      });\n\n    await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.deleteMany(\n      {\n        where: {\n          agencyId: insertAgency.id,\n          niche: {\n            notIn: body.niches,\n          },\n        },\n      }\n    );\n\n    const currentNiche =\n      await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.findMany(\n        {\n          where: {\n            agencyId: insertAgency.id,\n          },\n          select: {\n            niche: true,\n          },\n        }\n      );\n\n    const addNewNiche = body.niches.filter(\n      (n) => !currentNiche.some((c) => c.niche === n)\n    );\n\n    await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.createMany(\n      {\n        data: addNewNiche.map((n) => ({\n          agencyId: insertAgency.id,\n          niche: n,\n        })),\n      }\n    );\n\n    return insertAgency;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/agencies/agencies.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';\nimport { User } from '@prisma/client';\nimport { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\n\n@Injectable()\nexport class AgenciesService {\n  constructor(\n    private _agenciesRepository: AgenciesRepository,\n    private _notificationService: NotificationService\n  ) {}\n  getAgencyByUser(user: User) {\n    return this._agenciesRepository.getAgencyByUser(user);\n  }\n\n  getCount() {\n    return this._agenciesRepository.getCount();\n  }\n\n  getAllAgencies() {\n    return this._agenciesRepository.getAllAgencies();\n  }\n\n  getAllAgenciesSlug() {\n    return this._agenciesRepository.getAllAgenciesSlug();\n  }\n\n  getAgencyInformation(agency: string) {\n    return this._agenciesRepository.getAgencyInformation(agency);\n  }\n\n  async approveOrDecline(email: string, action: string, id: string) {\n    await this._agenciesRepository.approveOrDecline(action, id);\n    const agency = await this._agenciesRepository.getAgencyById(id);\n\n    if (action === 'approve') {\n      await this._notificationService.sendEmail(\n        agency?.user?.email!,\n        'Your Agency has been approved and added to Postiz 🚀',\n        `\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Your Agency has been approved and added to Postiz 🚀</title>\n</head>\n\n<body style=\"font-family: Arial, sans-serif; margin: 0; padding: 0;\">\n  Hi there, <br /><br />\n  Your agency ${agency?.name} has been added to Postiz!<br />\n  You can <a href=\"https://postiz.com/agencies/${agency?.slug}\">check it here</a><br />\n  It will appear on the main agency of Postiz in the next 24 hours.<br /><br />\n</body>\n</html>`\n      );\n\n      return;\n    }\n\n    await this._notificationService.sendEmail(\n      agency?.user?.email!,\n      'Your Agency has been declined 😔',\n      `\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Your Agency has been declined</title>\n</head>\n\n<body style=\"font-family: Arial, sans-serif; margin: 0; padding: 0;\">\n  Hi there, <br /><br />\n  Your agency ${agency?.name} has been declined to Postiz!<br />\n  If you think we have made a mistake, please reply to this email and let us know\n</body>\n</html>`\n    );\n\n    return;\n  }\n\n  async createAgency(user: User, body: CreateAgencyDto) {\n    const agency = await this._agenciesRepository.createAgency(user, body);\n    await this._notificationService.sendEmail(\n      'nevo@postiz.com',\n      'New agency created',\n      `\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Email Template</title>\n</head>\n\n<body style=\"font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4;\">\n    <table align=\"center\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 600px; width: 100%; background-color: #ffffff; margin-top: 20px; border: 1px solid #ddd;\">\n        <tr>\n            <td style=\"padding: 0 20px 20px 20px; text-align: center;\">\n                <!-- Website -->\n                <a href=\"${\n                  body.website\n                }\" style=\"text-decoration: none; color: #007bff;\">${\n        body.website\n      }</a>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px; text-align: center;\">\n                <!-- Social Media Links -->\n                <p style=\"margin: 10px 0; font-size: 16px;\">\n                    Social Medias:\n                    <a href=\"${\n                      body.facebook\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.facebook\n      }</a><br />\n                    <a href=\"${\n                      body.instagram\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.instagram\n      }</a><br />\n                    <a href=\"${\n                      body.twitter\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.twitter\n      }</a><br />\n                    <a href=\"${\n                      body.linkedIn\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.linkedIn\n      }</a><br />\n                    <a href=\"${\n                      body.youtube\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.youtube\n      }</a><br />\n                    <a href=\"${\n                      body.tiktok\n                    }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">${\n        body.tiktok\n      }</a>\n                </p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px;\">\n                <!-- Short Description -->\n                <h2 style=\"text-align: center; color: #333;\">Logo</h2>\n                <p style=\"text-align: center; color: #555; font-size: 16px;\">\n                  <img src=\"${body.logo.path}\" width=\"60\" height=\"60\" />\n                </p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px;\">\n                <!-- Short Description -->\n                <h2 style=\"text-align: center; color: #333;\">Name</h2>\n                <p style=\"text-align: center; color: #555; font-size: 16px;\">${\n                  body.name\n                }</p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px;\">\n                <!-- Short Description -->\n                <h2 style=\"text-align: center; color: #333;\">Short Description</h2>\n                <p style=\"text-align: center; color: #555; font-size: 16px;\">${\n                  body.shortDescription\n                }</p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px;\">\n                <!-- Description -->\n                <h2 style=\"text-align: center; color: #333;\">Description</h2>\n                <p style=\"text-align: center; color: #555; font-size: 16px;\">${\n                  body.description\n                }</p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px;\">\n                <!-- Niches -->\n                <h2 style=\"text-align: center; color: #333;\">Niches</h2>\n                <p style=\"text-align: center; color: #555; font-size: 16px;\">${body.niches.join(\n                  ','\n                )}</p>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px; text-align: center; background-color: #000;\">\n                <a href=\"https://postiz.com/agencies/action/approve/${\n                  agency.id\n                }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">To approve click here</a><br /><br /><br />\n                <a href=\"https://postiz.com/agencies/action/decline/${\n                  agency.id\n                }\" style=\"margin: 0 10px; text-decoration: none; color: #007bff;\">To decline click here</a><br /><br /><br />\n            </td>\n        </tr>\n        <tr>\n            <td style=\"padding: 20px; text-align: center; background-color: #f4f4f4;\">\n                <p style=\"color: #777; font-size: 14px;\">&copy; 2024 Your Gitroom Limited All rights reserved.</p>\n            </td>\n        </tr>\n    </table>\n</body>\n\n</html>\n    `\n    );\n    return agency;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/autopost/autopost.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { v4 as uuidv4 } from 'uuid';\nimport { AutopostDto } from '@gitroom/nestjs-libraries/dtos/autopost/autopost.dto';\n\n@Injectable()\nexport class AutopostRepository {\n  constructor(private _autoPost: PrismaRepository<'autoPost'>) {}\n\n  getTotal(orgId: string) {\n    return this._autoPost.model.autoPost.count({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  getAutoposts(orgId: string) {\n    return this._autoPost.model.autoPost.findMany({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  deleteAutopost(orgId: string, id: string) {\n    return this._autoPost.model.autoPost.update({\n      where: {\n        id,\n        organizationId: orgId,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  getAutopost(id: string) {\n    return this._autoPost.model.autoPost.findUnique({\n      where: {\n        id,\n        deletedAt: null,\n      },\n    });\n  }\n\n  updateUrl(id: string, url: string) {\n    return this._autoPost.model.autoPost.update({\n      where: {\n        id,\n      },\n      data: {\n        lastUrl: url,\n      },\n    });\n  }\n\n  changeActive(orgId: string, id: string, active: boolean) {\n    return this._autoPost.model.autoPost.update({\n      where: {\n        id,\n        organizationId: orgId,\n      },\n      data: {\n        active,\n      },\n    });\n  }\n\n  async createAutopost(orgId: string, body: AutopostDto, id?: string) {\n    const { id: newId, active } = await this._autoPost.model.autoPost.upsert({\n      where: {\n        id: id || uuidv4(),\n        organizationId: orgId,\n      },\n      create: {\n        organizationId: orgId,\n        url: body.url,\n        title: body.title,\n        integrations: JSON.stringify(body.integrations),\n        active: body.active,\n        content: body.content,\n        generateContent: body.generateContent,\n        addPicture: body.addPicture,\n        syncLast: body.syncLast,\n        onSlot: body.onSlot,\n        lastUrl: body.lastUrl,\n      },\n      update: {\n        url: body.url,\n        title: body.title,\n        integrations: JSON.stringify(body.integrations),\n        active: body.active,\n        content: body.content,\n        generateContent: body.generateContent,\n        addPicture: body.addPicture,\n        syncLast: body.syncLast,\n        onSlot: body.onSlot,\n        lastUrl: body.lastUrl,\n      },\n    });\n\n    return { id: newId, active };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';\nimport { AutopostDto } from '@gitroom/nestjs-libraries/dtos/autopost/autopost.dto';\nimport dayjs from 'dayjs';\nimport { END, START, StateGraph } from '@langchain/langgraph';\nimport { AutoPost, Integration } from '@prisma/client';\nimport { BaseMessage } from '@langchain/core/messages';\nimport striptags from 'striptags';\nimport { ChatOpenAI, DallEAPIWrapper } from '@langchain/openai';\nimport { JSDOM } from 'jsdom';\nimport { z } from 'zod';\nimport { ChatPromptTemplate } from '@langchain/core/prompts';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport Parser from 'rss-parser';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { TemporalService } from 'nestjs-temporal-core';\nimport { TypedSearchAttributes } from '@temporalio/common';\nimport {\n  organizationId,\n} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';\nconst parser = new Parser();\n\ninterface WorkflowChannelsState {\n  messages: BaseMessage[];\n  integrations: Integration[];\n  body: AutoPost;\n  description: string;\n  image: string;\n  id: string;\n  load: {\n    date: string;\n    url: string;\n    description: string;\n  };\n}\n\nconst model = new ChatOpenAI({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n  model: 'gpt-4.1',\n  temperature: 0.7,\n});\n\nconst dalle = new DallEAPIWrapper({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n  model: 'gpt-image-1',\n});\n\nconst generateContent = z.object({\n  socialMediaPostContent: z\n    .string()\n    .describe('Content for social media posts max 120 chars'),\n});\n\nconst dallePrompt = z.object({\n  generatedTextToBeSentToDallE: z\n    .string()\n    .describe('Generated prompt from description to be sent to DallE'),\n});\n\n@Injectable()\nexport class AutopostService {\n  constructor(\n    private _autopostsRepository: AutopostRepository,\n    private _temporalService: TemporalService,\n    private _integrationService: IntegrationService,\n    private _postsService: PostsService\n  ) {}\n\n  async stopAll(org: string) {\n    const getAll = (await this.getAutoposts(org)).filter((f) => f.active);\n    for (const autopost of getAll) {\n      await this.changeActive(org, autopost.id, false);\n    }\n  }\n\n  getAutoposts(orgId: string) {\n    return this._autopostsRepository.getAutoposts(orgId);\n  }\n\n  async createAutopost(orgId: string, body: AutopostDto, id?: string) {\n    const data = await this._autopostsRepository.createAutopost(\n      orgId,\n      body,\n      id\n    );\n\n    await this.processCron(body.active, orgId, data.id);\n\n    return data;\n  }\n\n  async changeActive(orgId: string, id: string, active: boolean) {\n    const data = await this._autopostsRepository.changeActive(\n      orgId,\n      id,\n      active\n    );\n    await this.processCron(active, orgId, id);\n    return data;\n  }\n\n  async processCron(active: boolean, orgId: string, id: string) {\n    if (active) {\n      try {\n        return this._temporalService.client\n          .getRawClient()\n          ?.workflow.start('autoPostWorkflow', {\n            workflowId: `autopost-${id}`,\n            taskQueue: 'main',\n            args: [{ id, immediately: true }],\n            typedSearchAttributes: new TypedSearchAttributes([\n              {\n                key: organizationId,\n                value: orgId,\n              },\n            ]),\n          });\n      } catch (err) {}\n    }\n\n    try {\n      return await this._temporalService.terminateWorkflow(`autopost-${id}`);\n    } catch (err) {\n      return false;\n    }\n  }\n\n  async deleteAutopost(orgId: string, id: string) {\n    const data = await this._autopostsRepository.deleteAutopost(orgId, id);\n    await this.processCron(false, orgId, id);\n    return data;\n  }\n\n  async loadXML(url: string) {\n    try {\n      const { items } = await parser.parseURL(url);\n      const findLast = items.reduce(\n        (all: any, current: any) => {\n          if (dayjs(current.pubDate).isAfter(all.pubDate)) {\n            return current;\n          }\n          return all;\n        },\n        { pubDate: dayjs().subtract(100, 'years') }\n      );\n\n      return {\n        success: true,\n        date: findLast.pubDate,\n        url: findLast.link,\n        description: striptags(\n          findLast?.['content:encoded'] ||\n            findLast?.content ||\n            findLast?.description ||\n            ''\n        )\n          .replace(/\\n/g, ' ')\n          .trim(),\n      };\n    } catch (err) {\n      /** sent **/\n    }\n\n    return { success: false };\n  }\n\n  static state = () =>\n    new StateGraph<WorkflowChannelsState>({\n      channels: {\n        messages: {\n          reducer: (currentState, updateValue) =>\n            currentState.concat(updateValue),\n          default: () => [],\n        },\n        body: null,\n        description: null,\n        load: null,\n        image: null,\n        integrations: null,\n        id: null,\n      },\n    });\n\n  async loadUrl(url: string) {\n    try {\n      const loadDom = new JSDOM(await (await fetch(url)).text());\n      loadDom.window.document\n        .querySelectorAll('script')\n        .forEach((s) => s.remove());\n      loadDom.window.document\n        .querySelectorAll('style')\n        .forEach((s) => s.remove());\n      // remove all html, script and styles\n      return striptags(loadDom.window.document.body.innerHTML);\n    } catch (err) {\n      return '';\n    }\n  }\n\n  async generateDescription(state: WorkflowChannelsState) {\n    if (!state.body.generateContent) {\n      return {\n        ...state,\n        description: state.body.content,\n      };\n    }\n\n    const description =\n      state.load.description || (await this.loadUrl(state.load.url));\n    if (!description) {\n      return {\n        ...state,\n        description: '',\n      };\n    }\n\n    const structuredOutput = model.withStructuredOutput(generateContent);\n    const { socialMediaPostContent } = await ChatPromptTemplate.fromTemplate(\n      `\n        You are an assistant that gets raw 'description' of a content and generate a social media post content.\n        Rules:\n        - Maximum 100 chars\n        - Try to make it a short as possible to fit any social media\n        - Add line breaks between sentences (\\\\n) \n        - Don't add hashtags\n        - Add emojis when needed\n        \n        'description':\n        {content}\n      `\n    )\n      .pipe(structuredOutput)\n      .invoke({\n        content: description,\n      });\n\n    return {\n      ...state,\n      description: socialMediaPostContent,\n    };\n  }\n\n  async generatePicture(state: WorkflowChannelsState) {\n    const structuredOutput = model.withStructuredOutput(dallePrompt);\n    const { generatedTextToBeSentToDallE } =\n      await ChatPromptTemplate.fromTemplate(\n        `\n        You are an assistant that gets description and generate a prompt that will be sent to DallE to generate pictures.\n        \n        content:\n        {content}\n      `\n      )\n        .pipe(structuredOutput)\n        .invoke({\n          content: state.load.description || state.description,\n        });\n\n    const image = await dalle.invoke(generatedTextToBeSentToDallE);\n\n    return { ...state, image };\n  }\n\n  async schedulePost(state: WorkflowChannelsState) {\n    const nextTime = await this._postsService.findFreeDateTime(\n      state.integrations[0].organizationId\n    );\n\n    await this._postsService.createPost(state.integrations[0].organizationId, {\n      date: nextTime + 'Z',\n      order: makeId(10),\n      shortLink: false,\n      type: 'draft',\n      tags: [],\n      posts: state.integrations.map((i) => ({\n        settings: {\n          __type: i.providerIdentifier as any,\n          title: '',\n          tags: [],\n          subreddit: [],\n        },\n        group: makeId(10),\n        integration: { id: i.id },\n        value: [\n          {\n            id: makeId(10),\n            delay: 0,\n            content:\n              state.description.replace(/\\n/g, '\\n\\n') +\n              '\\n\\n' +\n              state.load.url,\n            image: !state.image\n              ? []\n              : [\n                  {\n                    id: makeId(10),\n                    name: makeId(10),\n                    path: state.image,\n                    organizationId: state.integrations[0].organizationId,\n                  },\n                ],\n          },\n        ],\n      })),\n    });\n  }\n\n  async updateUrl(state: WorkflowChannelsState) {\n    await this._autopostsRepository.updateUrl(state.id, state.load.url);\n  }\n\n  async startAutopost(id: string) {\n    const getPost = await this._autopostsRepository.getAutopost(id);\n    if (!getPost || !getPost.active) {\n      return;\n    }\n\n    const load = await this.loadXML(getPost.url);\n    if (!load.success || load.url === getPost.lastUrl) {\n      return;\n    }\n\n    const integrations = await this._integrationService.getIntegrationsList(\n      getPost.organizationId\n    );\n\n    const parseIntegrations = JSON.parse(getPost.integrations || '[]') || [];\n    const neededIntegrations = integrations.filter((i) =>\n      parseIntegrations.some((ii: any) => ii.id === i.id)\n    );\n\n    const integrationsToSend =\n      parseIntegrations.length === 0 ? integrations : neededIntegrations;\n    if (integrationsToSend.length === 0) {\n      return;\n    }\n\n    const state = AutopostService.state();\n    const workflow = state\n      .addNode('generate-description', this.generateDescription.bind(this))\n      .addNode('generate-picture', this.generatePicture.bind(this))\n      .addNode('schedule-post', this.schedulePost.bind(this))\n      .addNode('update-url', this.updateUrl.bind(this))\n      .addEdge(START, 'generate-description')\n      .addConditionalEdges(\n        'generate-description',\n        (state: WorkflowChannelsState) => {\n          if (!state.description) {\n            return 'schedule-post';\n          }\n          if (state.body.addPicture) {\n            return 'generate-picture';\n          }\n          return 'schedule-post';\n        }\n      )\n      .addEdge('generate-picture', 'schedule-post')\n      .addEdge('schedule-post', 'update-url')\n      .addEdge('update-url', END);\n\n    const app = workflow.compile();\n    await app.invoke({\n      messages: [],\n      id,\n      body: getPost,\n      load,\n      integrations: integrationsToSend,\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/database.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { PrismaRepository, PrismaService, PrismaTransaction } from './prisma.service';\nimport { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';\nimport { UsersRepository } from '@gitroom/nestjs-libraries/database/prisma/users/users.repository';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { SubscriptionRepository } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';\nimport { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';\nimport { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.repository';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { MediaRepository } from '@gitroom/nestjs-libraries/database/prisma/media/media.repository';\nimport { NotificationsRepository } from '@gitroom/nestjs-libraries/database/prisma/notifications/notifications.repository';\nimport { EmailService } from '@gitroom/nestjs-libraries/services/email.service';\nimport { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';\nimport { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';\nimport { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';\nimport { TrackService } from '@gitroom/nestjs-libraries/track/track.service';\nimport { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';\nimport { WebhooksRepository } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.repository';\nimport { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';\nimport { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';\nimport { SignatureService } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.service';\nimport { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';\nimport { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';\nimport { SetsService } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.service';\nimport { SetsRepository } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.repository';\nimport { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.repository';\nimport { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.service';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { OAuthRepository } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.repository';\nimport { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';\n\n@Global()\n@Module({\n  imports: [],\n  controllers: [],\n  providers: [\n    PrismaService,\n    PrismaRepository,\n    PrismaTransaction,\n    UsersService,\n    UsersRepository,\n    OrganizationService,\n    OrganizationRepository,\n    SubscriptionService,\n    SubscriptionRepository,\n    NotificationService,\n    NotificationsRepository,\n    WebhooksRepository,\n    WebhooksService,\n    IntegrationService,\n    IntegrationRepository,\n    PostsService,\n    PostsRepository,\n    StripeService,\n    SignatureRepository,\n    AutopostRepository,\n    AutopostService,\n    SignatureService,\n    MediaService,\n    MediaRepository,\n    AgenciesService,\n    AgenciesRepository,\n    IntegrationManager,\n    RefreshIntegrationService,\n    ExtractContentService,\n    OpenaiService,\n    FalService,\n    EmailService,\n    TrackService,\n    ShortLinkService,\n    SetsService,\n    SetsRepository,\n    ThirdPartyRepository,\n    ThirdPartyService,\n    OAuthRepository,\n    OAuthService,\n    VideoManager,\n  ],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class DatabaseModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';\n\n@Injectable()\nexport class IntegrationRepository {\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _integration: PrismaRepository<'integration'>,\n    private _posts: PrismaRepository<'post'>,\n    private _plugs: PrismaRepository<'plugs'>,\n    private _exisingPlugData: PrismaRepository<'exisingPlugData'>,\n    private _customers: PrismaRepository<'customer'>,\n    private _mentions: PrismaRepository<'mentions'>\n  ) {}\n\n  getMentions(platform: string, q: string) {\n    return this._mentions.model.mentions.findMany({\n      where: {\n        platform,\n        OR: [\n          {\n            name: {\n              contains: q,\n              mode: 'insensitive',\n            },\n          },\n          {\n            username: {\n              contains: q,\n              mode: 'insensitive',\n            },\n          },\n        ],\n      },\n      orderBy: {\n        name: 'asc',\n      },\n      take: 100,\n      select: {\n        name: true,\n        username: true,\n        image: true,\n      },\n    });\n  }\n\n  insertMentions(\n    platform: string,\n    mentions: { name: string; username: string; image: string }[]\n  ) {\n    if (mentions.length === 0) {\n      return [] as any[];\n    }\n    return this._mentions.model.mentions.createMany({\n      data: mentions.map((mention) => ({\n        platform,\n        name: mention.name,\n        username: mention.username,\n        image: mention.image,\n      })),\n      skipDuplicates: true,\n    });\n  }\n\n  async checkPreviousConnections(org: string, id: string) {\n    const findIt = await this._integration.model.integration.findMany({\n      where: {\n        rootInternalId: id.split('_').pop(),\n      },\n      select: {\n        organizationId: true,\n        id: true,\n      },\n    });\n\n    if (findIt.some((f) => f.organizationId === org)) {\n      return false;\n    }\n\n    return findIt.length > 0;\n  }\n\n  updateProviderSettings(org: string, id: string, settings: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        additionalSettings: settings,\n      },\n    });\n  }\n\n  async setTimes(org: string, id: string, times: IntegrationTimeDto) {\n    return this._integration.model.integration.update({\n      select: {\n        id: true,\n      },\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        postingTimes: JSON.stringify(times.time),\n      },\n    });\n  }\n\n  getPlug(plugId: string) {\n    return this._plugs.model.plugs.findFirst({\n      where: {\n        id: plugId,\n      },\n      include: {\n        integration: true,\n      },\n    });\n  }\n\n  async getPlugs(orgId: string, integrationId: string) {\n    return this._plugs.model.plugs.findMany({\n      where: {\n        integrationId,\n        organizationId: orgId,\n        activated: true,\n      },\n      include: {\n        integration: {\n          select: {\n            id: true,\n            providerIdentifier: true,\n          },\n        },\n      },\n    });\n  }\n\n  async updateIntegration(id: string, params: Partial<Integration>) {\n    if (\n      params.picture &&\n      (params.picture.indexOf(process.env.CLOUDFLARE_BUCKET_URL!) === -1 ||\n        params.picture.indexOf(process.env.FRONTEND_URL!) === -1)\n    ) {\n      params.picture = await this.storage.uploadSimple(params.picture);\n    }\n\n    const existing = await this._integration.model.integration.findUnique({\n      where: {\n        organizationId_internalId: {\n          organizationId: params.organizationId!,\n          internalId: params.internalId,\n        },\n      },\n    });\n\n    if (existing) {\n      await this._posts.model.post.updateMany({\n        where: {\n          integrationId: id,\n        },\n        data: {\n          deletedAt: new Date(),\n        },\n      });\n\n      await this._integration.model.integration.update({\n        where: {\n          id,\n        },\n        data: {\n          internalId: `deleted_${params.internalId}_${makeId(10)}`,\n          deletedAt: new Date(),\n        },\n      });\n    }\n\n    return this._integration.model.integration.update({\n      where: {\n        ...(existing ? { id: existing.id } : { id }),\n      },\n      data: {\n        ...params,\n        disabled: false,\n        deletedAt: null,\n      },\n    });\n  }\n\n  disconnectChannel(org: string, id: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        refreshNeeded: true,\n      },\n    });\n  }\n\n  async createOrUpdateIntegration(\n    additionalSettings:\n      | {\n          title: string;\n          description: string;\n          type: 'checkbox' | 'text' | 'textarea';\n          value: any;\n          regex?: string;\n        }[]\n      | undefined,\n    oneTimeToken: boolean,\n    org: string,\n    name: string,\n    picture: string | undefined,\n    type: 'article' | 'social',\n    internalId: string,\n    provider: string,\n    token: string,\n    refreshToken = '',\n    expiresIn = 999999999,\n    username?: string,\n    isBetweenSteps = false,\n    refresh?: string,\n    timezone?: number,\n    customInstanceDetails?: string\n  ) {\n    const postTimes = timezone\n      ? {\n          postingTimes: JSON.stringify([\n            { time: 560 - timezone },\n            { time: 850 - timezone },\n            { time: 1140 - timezone },\n          ]),\n        }\n      : {};\n    const upsert = await this._integration.model.integration.upsert({\n      where: {\n        organizationId_internalId: {\n          internalId,\n          organizationId: org,\n        },\n      },\n      create: {\n        type: type as any,\n        name,\n        providerIdentifier: provider,\n        token,\n        profile: username,\n        ...(picture ? { picture } : {}),\n        inBetweenSteps: isBetweenSteps,\n        refreshToken,\n        ...(expiresIn\n          ? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }\n          : {}),\n        internalId,\n        ...postTimes,\n        organizationId: org,\n        refreshNeeded: false,\n        rootInternalId: internalId.split('_').pop(),\n        ...(customInstanceDetails ? { customInstanceDetails } : {}),\n        additionalSettings: additionalSettings\n          ? JSON.stringify(additionalSettings)\n          : '[]',\n      },\n      update: {\n        ...(additionalSettings\n          ? { additionalSettings: JSON.stringify(additionalSettings) }\n          : {}),\n        ...(customInstanceDetails ? { customInstanceDetails } : {}),\n        type: type as any,\n        ...(!refresh\n          ? {\n              inBetweenSteps: isBetweenSteps,\n            }\n          : {}),\n        ...(picture ? { picture } : {}),\n        profile: username,\n        providerIdentifier: provider,\n        token,\n        refreshToken,\n        ...(expiresIn\n          ? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }\n          : {}),\n        internalId,\n        organizationId: org,\n        deletedAt: null,\n        refreshNeeded: false,\n      },\n    });\n\n    if (oneTimeToken) {\n      const rootId =\n        (\n          await this._integration.model.integration.findFirst({\n            where: {\n              organizationId: org,\n              internalId: internalId,\n            },\n          })\n        )?.rootInternalId || internalId.split('_').pop()!;\n\n      await this._integration.model.integration.updateMany({\n        where: {\n          id: {\n            not: upsert.id,\n          },\n          rootInternalId: rootId,\n        },\n        data: {\n          token,\n          refreshToken,\n          refreshNeeded: false,\n          ...(expiresIn\n            ? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }\n            : {}),\n        },\n      });\n    }\n\n    return upsert;\n  }\n\n  needsToBeRefreshed() {\n    return this._integration.model.integration.findMany({\n      where: {\n        tokenExpiration: {\n          lte: dayjs().add(1, 'day').toDate(),\n        },\n        inBetweenSteps: false,\n        deletedAt: null,\n        refreshNeeded: false,\n      },\n    });\n  }\n\n  async setBetweenRefreshSteps(id: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n      },\n      data: {\n        inBetweenSteps: true,\n      },\n    });\n  }\n  refreshNeeded(org: string, id: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        refreshNeeded: true,\n      },\n    });\n  }\n\n  updateNameAndUrl(id: string, name: string, url: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n      },\n      data: {\n        ...(name ? { name } : {}),\n        ...(url ? { picture: url } : {}),\n      },\n    });\n  }\n\n  getIntegrationById(org: string, id: string) {\n    return this._integration.model.integration.findFirst({\n      where: {\n        organizationId: org,\n        id,\n      },\n    });\n  }\n\n  async getIntegrationForOrder(\n    id: string,\n    order: string,\n    user: string,\n    org: string\n  ) {\n    const integration = await this._posts.model.post.findFirst({\n      where: {\n        integrationId: id,\n        submittedForOrder: {\n          id: order,\n          messageGroup: {\n            OR: [\n              { sellerId: user },\n              { buyerId: user },\n              { buyerOrganizationId: org },\n            ],\n          },\n        },\n      },\n      select: {\n        integration: {\n          select: {\n            id: true,\n            name: true,\n            picture: true,\n            inBetweenSteps: true,\n            providerIdentifier: true,\n          },\n        },\n      },\n    });\n\n    return integration?.integration;\n  }\n\n  async updateOnCustomerName(org: string, id: string, name: string) {\n    const customer = !name\n      ? undefined\n      : (await this._customers.model.customer.findFirst({\n          where: {\n            orgId: org,\n            name,\n          },\n        })) ||\n        (await this._customers.model.customer.create({\n          data: {\n            name,\n            orgId: org,\n          },\n        }));\n\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        customer: !customer\n          ? { disconnect: true }\n          : {\n              connect: {\n                id: customer.id,\n              },\n            },\n      },\n    });\n  }\n\n  updateIntegrationGroup(org: string, id: string, group: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: !group\n        ? {\n            customer: {\n              disconnect: true,\n            },\n          }\n        : {\n            customer: {\n              connect: {\n                id: group,\n              },\n            },\n          },\n    });\n  }\n\n  customers(orgId: string) {\n    return this._customers.model.customer.findMany({\n      where: {\n        orgId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  getIntegrationsList(org: string) {\n    return this._integration.model.integration.findMany({\n      where: {\n        organizationId: org,\n        deletedAt: null,\n      },\n      include: {\n        customer: true,\n      },\n    });\n  }\n\n  async disableChannel(org: string, id: string) {\n    await this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        disabled: true,\n      },\n    });\n  }\n\n  async enableChannel(org: string, id: string) {\n    await this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        disabled: false,\n      },\n    });\n  }\n\n  getPostsForChannel(org: string, id: string) {\n    return this._posts.model.post.groupBy({\n      by: ['group'],\n      where: {\n        organizationId: org,\n        integrationId: id,\n        deletedAt: null,\n      },\n    });\n  }\n\n  deleteChannel(org: string, id: string) {\n    return this._integration.model.integration.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  async checkForDeletedOnceAndUpdate(org: string, page: string) {\n    return this._integration.model.integration.updateMany({\n      where: {\n        organizationId: org,\n        internalId: page,\n        deletedAt: {\n          not: null,\n        },\n      },\n      data: {\n        internalId: makeId(10),\n      },\n    });\n  }\n\n  async disableIntegrations(org: string, totalChannels: number) {\n    const getChannels = await this._integration.model.integration.findMany({\n      where: {\n        organizationId: org,\n        disabled: false,\n        deletedAt: null,\n      },\n      take: totalChannels,\n      select: {\n        id: true,\n      },\n    });\n\n    for (const channel of getChannels) {\n      await this._integration.model.integration.update({\n        where: {\n          id: channel.id,\n        },\n        data: {\n          disabled: true,\n        },\n      });\n    }\n  }\n\n  getPlugsByIntegrationId(org: string, id: string) {\n    return this._plugs.model.plugs.findMany({\n      where: {\n        organizationId: org,\n        integrationId: id,\n      },\n    });\n  }\n\n  createOrUpdatePlug(org: string, integrationId: string, body: PlugDto) {\n    return this._plugs.model.plugs.upsert({\n      where: {\n        organizationId: org,\n        plugFunction_integrationId: {\n          integrationId,\n          plugFunction: body.func,\n        },\n      },\n      create: {\n        integrationId,\n        organizationId: org,\n        plugFunction: body.func,\n        data: JSON.stringify(body.fields),\n        activated: true,\n      },\n      update: {\n        data: JSON.stringify(body.fields),\n      },\n      select: {\n        activated: true,\n      },\n    });\n  }\n\n  changePlugActivation(orgId: string, plugId: string, status: boolean) {\n    return this._plugs.model.plugs.update({\n      where: {\n        organizationId: orgId,\n        id: plugId,\n      },\n      data: {\n        activated: !!status,\n      },\n    });\n  }\n\n  async loadExisingData(\n    methodName: string,\n    integrationId: string,\n    id: string[]\n  ) {\n    return this._exisingPlugData.model.exisingPlugData.findMany({\n      where: {\n        integrationId,\n        methodName,\n        value: {\n          in: id,\n        },\n      },\n    });\n  }\n\n  async saveExisingData(\n    methodName: string,\n    integrationId: string,\n    value: string[]\n  ) {\n    return this._exisingPlugData.model.exisingPlugData.createMany({\n      data: value.map((p) => ({\n        integrationId,\n        methodName,\n        value: p,\n      })),\n    });\n  }\n\n  async getPostingTimes(orgId: string, integrationsId?: string) {\n    return this._integration.model.integration.findMany({\n      where: {\n        ...(integrationsId ? { id: integrationsId } : {}),\n        organizationId: orgId,\n        disabled: false,\n        deletedAt: null,\n      },\n      select: {\n        postingTimes: true,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts",
    "content": "import {\n  forwardRef,\n  HttpException,\n  HttpStatus,\n  Inject,\n  Injectable,\n} from '@nestjs/common';\nimport { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport {\n  AnalyticsData,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { Integration, Organization } from '@prisma/client';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport dayjs from 'dayjs';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';\nimport { difference, uniq } from 'lodash';\nimport utc from 'dayjs/plugin/utc';\nimport { AutopostRepository } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.repository';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\nimport { TemporalService } from 'nestjs-temporal-core';\n\ndayjs.extend(utc);\n\n@Injectable()\nexport class IntegrationService {\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _integrationRepository: IntegrationRepository,\n    private _autopostsRepository: AutopostRepository,\n    private _integrationManager: IntegrationManager,\n    private _notificationService: NotificationService,\n    @Inject(forwardRef(() => RefreshIntegrationService))\n    private _refreshIntegrationService: RefreshIntegrationService,\n    private _temporalService: TemporalService\n  ) {}\n\n  async changeActiveCron(orgId: string) {\n    const data = await this._autopostsRepository.getAutoposts(orgId);\n\n    for (const item of data.filter((f) => f.active)) {\n      try {\n        await this._temporalService.terminateWorkflow(`autopost-${item.id}`);\n      } catch (err) {}\n    }\n\n    return true;\n  }\n\n  getMentions(platform: string, q: string) {\n    return this._integrationRepository.getMentions(platform, q);\n  }\n\n  insertMentions(\n    platform: string,\n    mentions: { name: string; username: string; image: string }[]\n  ) {\n    return this._integrationRepository.insertMentions(platform, mentions);\n  }\n\n  async setTimes(\n    orgId: string,\n    integrationId: string,\n    times: IntegrationTimeDto\n  ) {\n    return this._integrationRepository.setTimes(orgId, integrationId, times);\n  }\n\n  updateProviderSettings(org: string, id: string, additionalSettings: string) {\n    return this._integrationRepository.updateProviderSettings(\n      org,\n      id,\n      additionalSettings\n    );\n  }\n\n  checkPreviousConnections(org: string, id: string) {\n    return this._integrationRepository.checkPreviousConnections(org, id);\n  }\n\n  async createOrUpdateIntegration(\n    additionalSettings:\n      | {\n          title: string;\n          description: string;\n          type: 'checkbox' | 'text' | 'textarea';\n          value: any;\n          regex?: string;\n        }[]\n      | undefined,\n    oneTimeToken: boolean,\n    org: string,\n    name: string,\n    picture: string | undefined,\n    type: 'article' | 'social',\n    internalId: string,\n    provider: string,\n    token: string,\n    refreshToken = '',\n    expiresIn?: number,\n    username?: string,\n    isBetweenSteps = false,\n    refresh?: string,\n    timezone?: number,\n    customInstanceDetails?: string\n  ) {\n    const uploadedPicture = picture\n      ? picture?.indexOf('imagedelivery.net') > -1\n        ? picture\n        : await this.storage.uploadSimple(picture)\n      : undefined;\n\n    return this._integrationRepository.createOrUpdateIntegration(\n      additionalSettings,\n      oneTimeToken,\n      org,\n      name,\n      uploadedPicture,\n      type,\n      internalId,\n      provider,\n      token,\n      refreshToken,\n      expiresIn,\n      username,\n      isBetweenSteps,\n      refresh,\n      timezone,\n      customInstanceDetails\n    );\n  }\n\n  updateIntegrationGroup(org: string, id: string, group: string) {\n    return this._integrationRepository.updateIntegrationGroup(org, id, group);\n  }\n\n  updateOnCustomerName(org: string, id: string, name: string) {\n    return this._integrationRepository.updateOnCustomerName(org, id, name);\n  }\n\n  getIntegrationsList(org: string) {\n    return this._integrationRepository.getIntegrationsList(org);\n  }\n\n  getIntegrationForOrder(id: string, order: string, user: string, org: string) {\n    return this._integrationRepository.getIntegrationForOrder(\n      id,\n      order,\n      user,\n      org\n    );\n  }\n\n  updateNameAndUrl(id: string, name: string, url: string) {\n    return this._integrationRepository.updateNameAndUrl(id, name, url);\n  }\n\n  getIntegrationById(org: string, id: string) {\n    return this._integrationRepository.getIntegrationById(org, id);\n  }\n\n  async refreshToken(provider: SocialProvider, refresh: string) {\n    try {\n      const { refreshToken, accessToken, expiresIn } =\n        await provider.refreshToken(refresh);\n\n      if (!refreshToken || !accessToken || !expiresIn) {\n        return false;\n      }\n\n      return { refreshToken, accessToken, expiresIn };\n    } catch (e) {\n      return false;\n    }\n  }\n\n  async disconnectChannel(orgId: string, integration: Integration) {\n    await this._integrationRepository.disconnectChannel(orgId, integration.id);\n    await this.informAboutRefreshError(orgId, integration);\n  }\n\n  async informAboutRefreshError(\n    orgId: string,\n    integration: Integration,\n    err = ''\n  ) {\n    await this._notificationService.inAppNotification(\n      orgId,\n      `Could not refresh your ${integration.providerIdentifier} channel ${err}`,\n      `Could not refresh your ${integration.providerIdentifier} channel ${err}. Please go back to the system and connect it again ${process.env.FRONTEND_URL}/launches`,\n      true,\n      false,\n      'info'\n    );\n  }\n\n  async refreshNeeded(org: string, id: string) {\n    return this._integrationRepository.refreshNeeded(org, id);\n  }\n\n  async setBetweenRefreshSteps(id: string) {\n    return this._integrationRepository.setBetweenRefreshSteps(id);\n  }\n\n  async refreshTokens() {\n    const integrations = await this._integrationRepository.needsToBeRefreshed();\n    for (const integration of integrations) {\n      const provider = this._integrationManager.getSocialIntegration(\n        integration.providerIdentifier\n      );\n\n      const data = await this.refreshToken(provider, integration.refreshToken!);\n\n      if (!data) {\n        await this.informAboutRefreshError(\n          integration.organizationId,\n          integration\n        );\n        await this._integrationRepository.refreshNeeded(\n          integration.organizationId,\n          integration.id\n        );\n        return;\n      }\n\n      const { refreshToken, accessToken, expiresIn } = data;\n\n      await this.createOrUpdateIntegration(\n        undefined,\n        !!provider.oneTimeToken,\n        integration.organizationId,\n        integration.name,\n        undefined,\n        'social',\n        integration.internalId,\n        integration.providerIdentifier,\n        accessToken,\n        refreshToken,\n        expiresIn\n      );\n    }\n  }\n\n  async disableChannel(org: string, id: string) {\n    return this._integrationRepository.disableChannel(org, id);\n  }\n\n  async enableChannel(org: string, totalChannels: number, id: string) {\n    const integrations = (\n      await this._integrationRepository.getIntegrationsList(org)\n    ).filter((f) => !f.disabled);\n    if (\n      !!process.env.STRIPE_PUBLISHABLE_KEY &&\n      integrations.length >= totalChannels\n    ) {\n      throw new Error('You have reached the maximum number of channels');\n    }\n\n    return this._integrationRepository.enableChannel(org, id);\n  }\n\n  async getPostsForChannel(org: string, id: string) {\n    return this._integrationRepository.getPostsForChannel(org, id);\n  }\n\n  async deleteChannel(org: string, id: string) {\n    return this._integrationRepository.deleteChannel(org, id);\n  }\n\n  async disableIntegrations(org: string, totalChannels: number) {\n    return this._integrationRepository.disableIntegrations(org, totalChannels);\n  }\n\n  async checkForDeletedOnceAndUpdate(org: string, page: string) {\n    return this._integrationRepository.checkForDeletedOnceAndUpdate(org, page);\n  }\n\n  async saveProviderPage(org: string, id: string, data: any) {\n    const getIntegration = await this._integrationRepository.getIntegrationById(\n      org,\n      id\n    );\n    if (!getIntegration) {\n      throw new HttpException('Integration not found', HttpStatus.NOT_FOUND);\n    }\n    if (!getIntegration.inBetweenSteps) {\n      throw new HttpException('Invalid request', HttpStatus.BAD_REQUEST);\n    }\n\n    const provider = this._integrationManager.getSocialIntegration(\n      getIntegration.providerIdentifier\n    );\n\n    if (!provider.fetchPageInformation) {\n      throw new HttpException(\n        'Provider does not support page selection',\n        HttpStatus.BAD_REQUEST\n      );\n    }\n\n    const getIntegrationInformation = await provider.fetchPageInformation(\n      getIntegration.token,\n      data\n    );\n\n    await this.checkForDeletedOnceAndUpdate(\n      org,\n      String(getIntegrationInformation.id)\n    );\n    await this._integrationRepository.updateIntegration(id, {\n      picture: getIntegrationInformation.picture,\n      internalId: String(getIntegrationInformation.id),\n      organizationId: org,\n      name: getIntegrationInformation.name,\n      inBetweenSteps: false,\n      token: getIntegrationInformation.access_token,\n      profile: getIntegrationInformation.username,\n    });\n\n    return { success: true };\n  }\n\n  async checkAnalytics(\n    org: Organization,\n    integration: string,\n    date: string,\n    forceRefresh = false\n  ): Promise<AnalyticsData[]> {\n    const getIntegration = await this.getIntegrationById(org.id, integration);\n\n    if (!getIntegration) {\n      throw new Error('Invalid integration');\n    }\n\n    if (getIntegration.type !== 'social') {\n      return [];\n    }\n\n    const integrationProvider = this._integrationManager.getSocialIntegration(\n      getIntegration.providerIdentifier\n    );\n\n    if (\n      dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||\n      forceRefresh\n    ) {\n      const data = await this._refreshIntegrationService.refresh(\n        getIntegration\n      );\n      if (!data) {\n        return [];\n      }\n\n      const { accessToken } = data;\n\n      if (accessToken) {\n        getIntegration.token = accessToken;\n\n        if (integrationProvider.refreshWait) {\n          await timer(10000);\n        }\n      } else {\n        await this.disconnectChannel(org.id, getIntegration);\n        return [];\n      }\n    }\n\n    const getIntegrationData = await ioRedis.get(\n      `integration:${org.id}:${integration}:${date}`\n    );\n    if (getIntegrationData) {\n      return JSON.parse(getIntegrationData);\n    }\n\n    if (integrationProvider.analytics) {\n      try {\n        const loadAnalytics = await integrationProvider.analytics(\n          getIntegration.internalId,\n          getIntegration.token,\n          +date\n        );\n        await ioRedis.set(\n          `integration:${org.id}:${integration}:${date}`,\n          JSON.stringify(loadAnalytics),\n          'EX',\n          !process.env.NODE_ENV || process.env.NODE_ENV === 'development'\n            ? 1\n            : 3600\n        );\n        return loadAnalytics;\n      } catch (e) {\n        if (e instanceof RefreshToken) {\n          return this.checkAnalytics(org, integration, date, true);\n        }\n      }\n    }\n\n    return [];\n  }\n\n  customers(orgId: string) {\n    return this._integrationRepository.customers(orgId);\n  }\n\n  getPlugsByIntegrationId(org: string, integrationId: string) {\n    return this._integrationRepository.getPlugsByIntegrationId(\n      org,\n      integrationId\n    );\n  }\n\n  async processInternalPlug(\n    data: {\n      post: string;\n      originalIntegration: string;\n      integration: string;\n      plugName: string;\n      orgId: string;\n      delay: number;\n      information: any;\n    },\n    forceRefresh = false\n  ): Promise<any> {\n    const originalIntegration =\n      await this._integrationRepository.getIntegrationById(\n        data.orgId,\n        data.originalIntegration\n      );\n\n    const getIntegration = await this._integrationRepository.getIntegrationById(\n      data.orgId,\n      data.integration\n    );\n\n    if (!getIntegration || !originalIntegration) {\n      return;\n    }\n\n    const getAllInternalPlugs = this._integrationManager\n      .getInternalPlugs(getIntegration.providerIdentifier)\n      .internalPlugs.find((p: any) => p.identifier === data.plugName);\n\n    if (!getAllInternalPlugs) {\n      return;\n    }\n\n    const getSocialIntegration = this._integrationManager.getSocialIntegration(\n      getIntegration.providerIdentifier\n    );\n\n    // @ts-ignore\n    await getSocialIntegration?.[getAllInternalPlugs.methodName]?.(\n      getIntegration,\n      originalIntegration,\n      data.post,\n      data.information\n    );\n\n    return;\n  }\n\n  async processPlugs(data: {\n    plugId: string;\n    postId: string;\n    delay: number;\n    totalRuns: number;\n    currentRun: number;\n  }) {\n    const getPlugById = await this._integrationRepository.getPlug(data.plugId);\n    if (!getPlugById) {\n      return true;\n    }\n\n    const integration = this._integrationManager.getSocialIntegration(\n      getPlugById.integration.providerIdentifier\n    );\n\n    // @ts-ignore\n    const process = await integration[getPlugById.plugFunction](\n      getPlugById.integration,\n      data.postId,\n      JSON.parse(getPlugById.data).reduce((all: any, current: any) => {\n        all[current.name] = current.value;\n        return all;\n      }, {})\n    );\n\n    if (process) {\n      return true;\n    }\n\n    if (data.totalRuns === data.currentRun) {\n      return true;\n    }\n\n    return false;\n  }\n\n  async createOrUpdatePlug(\n    orgId: string,\n    integrationId: string,\n    body: PlugDto\n  ) {\n    const { activated } = await this._integrationRepository.createOrUpdatePlug(\n      orgId,\n      integrationId,\n      body\n    );\n\n    return {\n      activated,\n    };\n  }\n\n  async changePlugActivation(orgId: string, plugId: string, status: boolean) {\n    const { id, integrationId, plugFunction } =\n      await this._integrationRepository.changePlugActivation(\n        orgId,\n        plugId,\n        status\n      );\n\n    return { id };\n  }\n\n  async getPlugs(orgId: string, integrationId: string) {\n    return this._integrationRepository.getPlugs(orgId, integrationId);\n  }\n\n  async loadExisingData(\n    methodName: string,\n    integrationId: string,\n    id: string[]\n  ) {\n    const exisingData = await this._integrationRepository.loadExisingData(\n      methodName,\n      integrationId,\n      id\n    );\n    const loadOnlyIds = exisingData.map((p) => p.value);\n    return difference(id, loadOnlyIds);\n  }\n\n  async findFreeDateTime(\n    orgId: string,\n    integrationsId?: string\n  ): Promise<number[]> {\n    const findTimes = await this._integrationRepository.getPostingTimes(\n      orgId,\n      integrationsId\n    );\n    return uniq(\n      findTimes.reduce((all: any, current: any) => {\n        return [\n          ...all,\n          ...JSON.parse(current.postingTimes).map(\n            (p: { time: number }) => p.time\n          ),\n        ];\n      }, [] as number[])\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';\n\n@Injectable()\nexport class MediaRepository {\n  constructor(private _media: PrismaRepository<'media'>) {}\n\n  saveFile(org: string, fileName: string, filePath: string, originalName?: string) {\n    return this._media.model.media.create({\n      data: {\n        organization: {\n          connect: {\n            id: org,\n          },\n        },\n        name: fileName,\n        path: filePath,\n        originalName: originalName || null,\n      },\n      select: {\n        id: true,\n        name: true,\n        originalName: true,\n        path: true,\n        thumbnail: true,\n        alt: true,\n      },\n    });\n  }\n\n  getMediaById(id: string) {\n    return this._media.model.media.findUnique({\n      where: {\n        id,\n      },\n    });\n  }\n\n  deleteMedia(org: string, id: string) {\n    return this._media.model.media.update({\n      where: {\n        id,\n        organizationId: org,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  saveMediaInformation(org: string, data: SaveMediaInformationDto) {\n    return this._media.model.media.update({\n      where: {\n        id: data.id,\n        organizationId: org,\n      },\n      data: {\n        alt: data.alt,\n        thumbnail: data.thumbnail,\n        thumbnailTimestamp: data.thumbnailTimestamp,\n      },\n      select: {\n        id: true,\n        name: true,\n        originalName: true,\n        alt: true,\n        thumbnail: true,\n        path: true,\n        thumbnailTimestamp: true,\n      },\n    });\n  }\n\n  async getMedia(org: string, page: number) {\n    const pageNum = (page || 1) - 1;\n    const query = {\n      where: {\n        organization: {\n          id: org,\n        },\n      },\n    };\n    const pages = Math.ceil((await this._media.model.media.count(query)) / 18);\n    const results = await this._media.model.media.findMany({\n      where: {\n        organizationId: org,\n        deletedAt: null,\n      },\n      orderBy: {\n        createdAt: 'desc',\n      },\n      select: {\n        id: true,\n        name: true,\n        originalName: true,\n        path: true,\n        thumbnail: true,\n        alt: true,\n        thumbnailTimestamp: true,\n      },\n      skip: pageNum * 18,\n      take: 18,\n    });\n\n    return {\n      pages,\n      results,\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/media/media.service.ts",
    "content": "import { HttpException, Injectable } from '@nestjs/common';\nimport { MediaRepository } from '@gitroom/nestjs-libraries/database/prisma/media/media.repository';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { Organization } from '@prisma/client';\nimport { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport {\n  AuthorizationActions,\n  Sections,\n  SubscriptionException,\n} from '@gitroom/backend/services/auth/permissions/permission.exception.class';\n\n@Injectable()\nexport class MediaService {\n  private storage = UploadFactory.createStorage();\n\n  constructor(\n    private _mediaRepository: MediaRepository,\n    private _openAi: OpenaiService,\n    private _subscriptionService: SubscriptionService,\n    private _videoManager: VideoManager\n  ) {}\n\n  async deleteMedia(org: string, id: string) {\n    return this._mediaRepository.deleteMedia(org, id);\n  }\n\n  getMediaById(id: string) {\n    return this._mediaRepository.getMediaById(id);\n  }\n\n  async generateImage(\n    prompt: string,\n    org: Organization,\n    generatePromptFirst?: boolean\n  ) {\n    const generating = await this._subscriptionService.useCredit(\n      org,\n      'ai_images',\n      async () => {\n        if (generatePromptFirst) {\n          prompt = await this._openAi.generatePromptForPicture(prompt);\n          console.log('Prompt:', prompt);\n        }\n        return this._openAi.generateImage(prompt, !!generatePromptFirst);\n      }\n    );\n\n    return generating;\n  }\n\n  saveFile(org: string, fileName: string, filePath: string, originalName?: string) {\n    return this._mediaRepository.saveFile(org, fileName, filePath, originalName);\n  }\n\n  getMedia(org: string, page: number) {\n    return this._mediaRepository.getMedia(org, page);\n  }\n\n  saveMediaInformation(org: string, data: SaveMediaInformationDto) {\n    return this._mediaRepository.saveMediaInformation(org, data);\n  }\n\n  getVideoOptions() {\n    return this._videoManager.getAllVideos();\n  }\n\n  async generateVideoAllowed(org: Organization, type: string) {\n    const video = this._videoManager.getVideoByName(type);\n    if (!video) {\n      throw new Error(`Video type ${type} not found`);\n    }\n\n    if (!video.trial && org.isTrailing) {\n      throw new HttpException('This video is not available in trial mode', 406);\n    }\n\n    return true;\n  }\n\n  async generateVideo(org: Organization, body: VideoDto) {\n    const totalCredits = await this._subscriptionService.checkCredits(\n      org,\n      'ai_videos'\n    );\n\n    if (totalCredits.credits <= 0) {\n      throw new SubscriptionException({\n        action: AuthorizationActions.Create,\n        section: Sections.VIDEOS_PER_MONTH,\n      });\n    }\n\n    const video = this._videoManager.getVideoByName(body.type);\n    if (!video) {\n      throw new Error(`Video type ${body.type} not found`);\n    }\n\n    if (!video.trial && org.isTrailing) {\n      throw new HttpException('This video is not available in trial mode', 406);\n    }\n\n    console.log(body.customParams);\n    await video.instance.processAndValidate(body.customParams);\n    console.log('no err');\n\n    return await this._subscriptionService.useCredit(\n      org,\n      'ai_videos',\n      async () => {\n        const loadedData = await video.instance.process(\n          body.output,\n          body.customParams\n        );\n\n        const file = await this.storage.uploadSimple(loadedData);\n        return this.saveFile(org.id, file.split('/').pop(), file);\n      }\n    );\n  }\n\n  async videoFunction(identifier: string, functionName: string, body: any) {\n    const video = this._videoManager.getVideoByName(identifier);\n    if (!video) {\n      throw new Error(`Video with identifier ${identifier} not found`);\n    }\n\n    // @ts-ignore\n    const functionToCall = video.instance[functionName];\n    if (\n      typeof functionToCall !== 'function' ||\n      this._videoManager.checkAvailableVideoFunction(functionToCall)\n    ) {\n      throw new HttpException(\n        `Function ${functionName} not found on video instance`,\n        400\n      );\n    }\n\n    return functionToCall(body);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/notifications/notification.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { NotificationsRepository } from '@gitroom/nestjs-libraries/database/prisma/notifications/notifications.repository';\nimport { EmailService } from '@gitroom/nestjs-libraries/services/email.service';\nimport { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';\nimport { TemporalService } from 'nestjs-temporal-core';\nimport { TypedSearchAttributes } from '@temporalio/common';\nimport { organizationId } from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';\n\nexport type NotificationType = 'success' | 'fail' | 'info';\n\n@Injectable()\nexport class NotificationService {\n  constructor(\n    private _notificationRepository: NotificationsRepository,\n    private _emailService: EmailService,\n    private _organizationRepository: OrganizationRepository,\n    private _temporalService: TemporalService\n  ) {}\n\n  getMainPageCount(organizationId: string, userId: string) {\n    return this._notificationRepository.getMainPageCount(\n      organizationId,\n      userId\n    );\n  }\n\n  getNotificationsPaginated(organizationId: string, page: number) {\n    return this._notificationRepository.getNotificationsPaginated(\n      organizationId,\n      page\n    );\n  }\n\n  getNotifications(organizationId: string, userId: string) {\n    return this._notificationRepository.getNotifications(\n      organizationId,\n      userId\n    );\n  }\n\n  async inAppNotification(\n    orgId: string,\n    subject: string,\n    message: string,\n    sendEmail = false,\n    digest = false,\n    type: NotificationType = 'success'\n  ) {\n    await this._notificationRepository.createNotification(orgId, message);\n    if (!sendEmail) {\n      return;\n    }\n\n    if (digest) {\n      try {\n        await this._temporalService.client\n          .getRawClient()\n          ?.workflow.signalWithStart('digestEmailWorkflow', {\n            workflowId: 'digest_email_workflow_' + orgId,\n            signal: 'email',\n            signalArgs: [\n              [\n                {\n                  title: subject,\n                  message,\n                  type,\n                },\n              ],\n            ],\n            taskQueue: 'main',\n            workflowIdConflictPolicy: 'USE_EXISTING',\n            args: [{ organizationId: orgId }],\n            typedSearchAttributes: new TypedSearchAttributes([\n              {\n                key: organizationId,\n                value: orgId,\n              },\n            ]),\n          });\n      } catch (err) {}\n\n      return;\n    }\n\n    await this.sendEmailsToOrg(orgId, subject, message, type);\n  }\n\n  async sendEmailsToOrg(\n    orgId: string,\n    subject: string,\n    message: string,\n    type?: NotificationType\n  ) {\n    const userOrg = await this._organizationRepository.getAllUsersOrgs(orgId);\n    for (const user of userOrg?.users || []) {\n      // 'info' type is always sent regardless of preferences\n      if (type !== 'info') {\n        // Filter users based on their email preferences\n        if (type === 'success' && !user.user.sendSuccessEmails) {\n          continue;\n        }\n        if (type === 'fail' && !user.user.sendFailureEmails) {\n          continue;\n        }\n      }\n      await this.sendEmail(user.user.email, subject, message);\n    }\n  }\n\n  async sendEmail(to: string, subject: string, html: string, replyTo?: string) {\n    await this._emailService.sendEmail(to, subject, html, 'top', replyTo);\n  }\n\n  hasEmailProvider() {\n    return this._emailService.hasProvider();\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/notifications/notifications.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class NotificationsRepository {\n  constructor(\n    private _notifications: PrismaRepository<'notifications'>,\n    private _user: PrismaRepository<'user'>\n  ) {}\n\n  getLastReadNotification(userId: string) {\n    return this._user.model.user.findFirst({\n      where: {\n        id: userId,\n      },\n      select: {\n        lastReadNotifications: true,\n      },\n    });\n  }\n\n  async getMainPageCount(organizationId: string, userId: string) {\n    const { lastReadNotifications } = (await this.getLastReadNotification(\n      userId\n    ))!;\n\n    return {\n      total: await this._notifications.model.notifications.count({\n        where: {\n          organizationId,\n          createdAt: {\n            gt: lastReadNotifications!,\n          },\n        },\n      }),\n    };\n  }\n\n  async createNotification(organizationId: string, content: string) {\n    await this._notifications.model.notifications.create({\n      data: {\n        organizationId,\n        content,\n      },\n    });\n  }\n\n  async getNotificationsSince(organizationId: string, since: string) {\n    return this._notifications.model.notifications.findMany({\n      where: {\n        organizationId,\n        createdAt: {\n          gte: new Date(since),\n        },\n      },\n    });\n  }\n\n  async getNotificationsPaginated(organizationId: string, page: number) {\n    const limit = 100;\n    const skip = page * limit;\n\n    const where = {\n      organizationId,\n      deletedAt: null as Date | null,\n    };\n\n    const [notifications, total] = await Promise.all([\n      this._notifications.model.notifications.findMany({\n        where,\n        orderBy: {\n          createdAt: 'desc',\n        },\n        skip,\n        take: limit,\n        select: {\n          id: true,\n          content: true,\n          link: true,\n          createdAt: true,\n        },\n      }),\n      this._notifications.model.notifications.count({ where }),\n    ]);\n\n    return {\n      notifications,\n      total,\n      page,\n      limit,\n      hasMore: skip + notifications.length < total,\n    };\n  }\n\n  async getNotifications(organizationId: string, userId: string) {\n    const { lastReadNotifications } = (await this.getLastReadNotification(\n      userId\n    ))!;\n\n    await this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        lastReadNotifications: new Date(),\n      },\n    });\n\n    return {\n      lastReadNotifications,\n      notifications: await this._notifications.model.notifications.findMany({\n        orderBy: {\n          createdAt: 'desc',\n        },\n        take: 10,\n        where: {\n          organizationId,\n        },\n        select: {\n          createdAt: true,\n          content: true,\n        },\n      }),\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/oauth/oauth.repository.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\n\n@Injectable()\nexport class OAuthRepository {\n  constructor(\n    private _oauthApp: PrismaRepository<'oAuthApp'>,\n    private _oauthAuth: PrismaRepository<'oAuthAuthorization'>\n  ) {}\n\n  getAppByOrgId(orgId: string) {\n    return this._oauthApp.model.oAuthApp.findFirst({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n      include: {\n        picture: true,\n      },\n    });\n  }\n\n  getAppByClientId(clientId: string) {\n    return this._oauthApp.model.oAuthApp.findFirst({\n      where: {\n        clientId,\n        deletedAt: null,\n      },\n      include: {\n        picture: true,\n      },\n    });\n  }\n\n  createApp(\n    orgId: string,\n    data: {\n      name: string;\n      description?: string;\n      pictureId?: string;\n      redirectUrl: string;\n      clientId: string;\n      clientSecret: string;\n    }\n  ) {\n    return this._oauthApp.model.oAuthApp.create({\n      data: {\n        organizationId: orgId,\n        name: data.name,\n        description: data.description,\n        pictureId: data.pictureId,\n        redirectUrl: data.redirectUrl,\n        clientId: data.clientId,\n        clientSecret: data.clientSecret,\n      },\n      include: {\n        picture: true,\n      },\n    });\n  }\n\n  async updateApp(\n    orgId: string,\n    data: {\n      name?: string;\n      description?: string;\n      pictureId?: string;\n      redirectUrl?: string;\n    }\n  ) {\n    const app = await this._oauthApp.model.oAuthApp.findFirst({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n    if (!app) {\n      return null;\n    }\n    return this._oauthApp.model.oAuthApp.update({\n      where: { id: app.id },\n      data,\n      include: {\n        picture: true,\n      },\n    });\n  }\n\n  async deleteApp(orgId: string) {\n    const app = await this._oauthApp.model.oAuthApp.findFirst({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n    if (!app) {\n      return null;\n    }\n    return this._oauthApp.model.oAuthApp.update({\n      where: { id: app.id },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  async updateClientSecret(orgId: string, newSecret: string) {\n    const app = await this._oauthApp.model.oAuthApp.findFirst({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n    if (!app) {\n      return null;\n    }\n    return this._oauthApp.model.oAuthApp.update({\n      where: { id: app.id },\n      data: {\n        clientSecret: newSecret,\n      },\n    });\n  }\n\n  createAuthorization(data: {\n    oauthAppId: string;\n    userId: string;\n    organizationId: string;\n    authorizationCode: string;\n    codeExpiresAt: Date;\n  }) {\n    return this._oauthAuth.model.oAuthAuthorization.upsert({\n      where: {\n        oauthAppId_userId_organizationId: {\n          oauthAppId: data.oauthAppId,\n          userId: data.userId,\n          organizationId: data.organizationId,\n        },\n      },\n      create: {\n        oauthAppId: data.oauthAppId,\n        userId: data.userId,\n        organizationId: data.organizationId,\n        authorizationCode: data.authorizationCode,\n        codeExpiresAt: data.codeExpiresAt,\n      },\n      update: {\n        authorizationCode: data.authorizationCode,\n        codeExpiresAt: data.codeExpiresAt,\n        accessToken: null,\n        revokedAt: null,\n      },\n    });\n  }\n\n  findByCode(encryptedCode: string) {\n    return this._oauthAuth.model.oAuthAuthorization.findFirst({\n      where: {\n        authorizationCode: encryptedCode,\n        revokedAt: null,\n      },\n    });\n  }\n\n  exchangeCodeForToken(id: string, encryptedToken: string) {\n    return this._oauthAuth.model.oAuthAuthorization.update({\n      where: { id },\n      data: {\n        accessToken: encryptedToken,\n        authorizationCode: null,\n        codeExpiresAt: null,\n      },\n    });\n  }\n\n  findByAccessToken(encryptedToken: string) {\n    return this._oauthAuth.model.oAuthAuthorization.findFirst({\n      where: {\n        accessToken: encryptedToken,\n        revokedAt: null,\n      },\n      include: {\n        organization: {\n          include: {\n            subscription: {\n              select: {\n                subscriptionTier: true,\n                totalChannels: true,\n                isLifetime: true,\n              },\n            },\n          },\n        },\n        user: {\n          select: { id: true },\n        },\n      },\n    });\n  }\n\n  getApprovedApps(userId: string) {\n    return this._oauthAuth.model.oAuthAuthorization.findMany({\n      where: {\n        userId,\n        revokedAt: null,\n        accessToken: { not: null },\n      },\n      include: {\n        oauthApp: {\n          include: {\n            picture: true,\n          },\n        },\n      },\n      orderBy: {\n        createdAt: 'desc',\n      },\n    });\n  }\n\n  revokeAuthorization(userId: string, authId: string) {\n    return this._oauthAuth.model.oAuthAuthorization.update({\n      where: {\n        id: authId,\n        userId,\n      },\n      data: {\n        revokedAt: new Date(),\n      },\n    });\n  }\n\n  revokeAllForApp(oauthAppId: string) {\n    return this._oauthAuth.model.oAuthAuthorization.updateMany({\n      where: {\n        oauthAppId,\n        revokedAt: null,\n      },\n      data: {\n        revokedAt: new Date(),\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/oauth/oauth.service.ts",
    "content": "import { HttpException, HttpStatus, Injectable } from '@nestjs/common';\nimport { OAuthRepository } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.repository';\nimport { CreateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/create-oauth-app.dto';\nimport { UpdateOAuthAppDto } from '@gitroom/nestjs-libraries/dtos/oauth/update-oauth-app.dto';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\n\n@Injectable()\nexport class OAuthService {\n  constructor(private _oauthRepository: OAuthRepository) {}\n\n  async getApp(orgId: string) {\n    const app = await this._oauthRepository.getAppByOrgId(orgId);\n    if (!app) return false;\n    const { clientSecret, ...rest } = app;\n    return rest;\n  }\n\n  async createApp(orgId: string, dto: CreateOAuthAppDto) {\n    const existing = await this._oauthRepository.getAppByOrgId(orgId);\n    if (existing) {\n      throw new HttpException(\n        'You can only have one OAuth application per organization',\n        HttpStatus.BAD_REQUEST\n      );\n    }\n\n    const clientId = 'pca_' + makeId(32);\n    const clientSecret = 'pcs_' + makeId(48);\n    const encryptedSecret = AuthService.fixedEncryption(clientSecret);\n\n    const app = await this._oauthRepository.createApp(orgId, {\n      name: dto.name,\n      description: dto.description,\n      pictureId: dto.pictureId,\n      redirectUrl: dto.redirectUrl,\n      clientId,\n      clientSecret: encryptedSecret,\n    });\n\n    return { ...app, clientSecret };\n  }\n\n  async updateApp(orgId: string, dto: UpdateOAuthAppDto) {\n    return this._oauthRepository.updateApp(orgId, {\n      ...(dto.name && { name: dto.name }),\n      ...(dto.description !== undefined && { description: dto.description }),\n      ...(dto.pictureId !== undefined && { pictureId: dto.pictureId }),\n      ...(dto.redirectUrl && { redirectUrl: dto.redirectUrl }),\n    });\n  }\n\n  async deleteApp(orgId: string) {\n    const app = await this._oauthRepository.getAppByOrgId(orgId);\n    if (!app) {\n      throw new HttpException('No OAuth app found', HttpStatus.NOT_FOUND);\n    }\n    await this._oauthRepository.revokeAllForApp(app.id);\n    await this._oauthRepository.deleteApp(orgId);\n    return { success: true };\n  }\n\n  async rotateSecret(orgId: string) {\n    const app = await this._oauthRepository.getAppByOrgId(orgId);\n    if (!app) {\n      throw new HttpException('No OAuth app found', HttpStatus.NOT_FOUND);\n    }\n\n    const newSecret = 'pcs_' + makeId(48);\n    const encrypted = AuthService.fixedEncryption(newSecret);\n    await this._oauthRepository.updateClientSecret(orgId, encrypted);\n    return { clientSecret: newSecret };\n  }\n\n  async validateAuthorizationRequest(clientId: string) {\n    const app = await this._oauthRepository.getAppByClientId(clientId);\n    if (!app) {\n      throw new HttpException('Invalid client_id', HttpStatus.BAD_REQUEST);\n    }\n    return app;\n  }\n\n  async createAuthorizationCode(\n    oauthAppId: string,\n    userId: string,\n    organizationId: string\n  ) {\n    const code = makeId(32);\n    const encryptedCode = AuthService.fixedEncryption(code);\n    const codeExpiresAt = new Date(Date.now() + 10 * 60 * 1000);\n\n    await this._oauthRepository.createAuthorization({\n      oauthAppId,\n      userId,\n      organizationId,\n      authorizationCode: encryptedCode,\n      codeExpiresAt,\n    });\n\n    return code;\n  }\n\n  async exchangeCodeForToken(\n    code: string,\n    clientId: string,\n    clientSecret: string\n  ) {\n    const app = await this._oauthRepository.getAppByClientId(clientId);\n    if (!app) {\n      throw new HttpException(\n        { error: 'invalid_client' },\n        HttpStatus.UNAUTHORIZED\n      );\n    }\n\n    if (app.clientSecret !== AuthService.fixedEncryption(clientSecret)) {\n      throw new HttpException(\n        { error: 'invalid_client' },\n        HttpStatus.UNAUTHORIZED\n      );\n    }\n\n    const encryptedCode = AuthService.fixedEncryption(code);\n    const auth = await this._oauthRepository.findByCode(encryptedCode);\n    if (!auth || auth.oauthAppId !== app.id) {\n      throw new HttpException(\n        { error: 'invalid_grant' },\n        HttpStatus.BAD_REQUEST\n      );\n    }\n\n    if (!auth.codeExpiresAt || new Date() > auth.codeExpiresAt) {\n      throw new HttpException(\n        { error: 'invalid_grant', error_description: 'Code has expired' },\n        HttpStatus.BAD_REQUEST\n      );\n    }\n\n    const token = 'pos_' + makeId(40);\n    const encryptedToken = AuthService.fixedEncryption(token);\n    const { organizationId } = await this._oauthRepository.exchangeCodeForToken(\n      auth.id,\n      encryptedToken\n    );\n\n    return {\n      id: organizationId,\n      access_token: token,\n      token_type: 'bearer',\n    };\n  }\n\n  async getOrgByOAuthToken(token: string) {\n    const encrypted = AuthService.fixedEncryption(token);\n    return this._oauthRepository.findByAccessToken(encrypted);\n  }\n\n  async getApprovedApps(userId: string) {\n    return this._oauthRepository.getApprovedApps(userId);\n  }\n\n  async revokeApp(userId: string, authId: string) {\n    await this._oauthRepository.revokeAuthorization(userId, authId);\n    return { success: true };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Role, ShortLinkPreference, SubscriptionTier } from '@prisma/client';\nimport { Injectable } from '@nestjs/common';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\n\n@Injectable()\nexport class OrganizationRepository {\n  constructor(\n    private _organization: PrismaRepository<'organization'>,\n    private _userOrg: PrismaRepository<'userOrganization'>,\n    private _user: PrismaRepository<'user'>\n  ) {}\n\n  createMaxUser(id: string, name: string, saasName: string, email: string) {\n    return this._organization.model.organization.create({\n      select: {\n        id: true,\n        apiKey: true,\n      },\n      data: {\n        name: name ? `${name}###${id}` : `Unnamed User###${id}`,\n        apiKey: AuthService.fixedEncryption(makeId(20)),\n        isTrailing: false,\n        subscription: {\n          create: {\n            totalChannels: 1000000,\n            subscriptionTier: 'ULTIMATE',\n            isLifetime: true,\n            period: 'YEARLY',\n          },\n        },\n        users: {\n          create: {\n            role: Role.SUPERADMIN,\n            user: {\n              create: {\n                activated: true,\n                email: email\n                  ? email.split('@').join(`+${saasName}@`)\n                  : `${saasName}+` + makeId(10) + '@postiz.com',\n                name: name ? `${name}###${id}` : `Unnamed User###${id}`,\n                providerName: 'LOCAL',\n                password: AuthService.hashPassword(makeId(500)),\n                timezone: 0,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  getOrgByApiKey(api: string) {\n    return this._organization.model.organization.findFirst({\n      where: {\n        apiKey: api,\n      },\n      include: {\n        subscription: {\n          select: {\n            subscriptionTier: true,\n            totalChannels: true,\n            isLifetime: true,\n          },\n        },\n      },\n    });\n  }\n\n  getCount() {\n    return this._organization.model.organization.count();\n  }\n\n  getUserOrg(id: string) {\n    return this._userOrg.model.userOrganization.findFirst({\n      where: {\n        id,\n      },\n      select: {\n        user: true,\n        organization: {\n          include: {\n            users: {\n              select: {\n                id: true,\n                disabled: true,\n                role: true,\n                userId: true,\n              },\n            },\n            subscription: {\n              select: {\n                subscriptionTier: true,\n                totalChannels: true,\n                isLifetime: true,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  getImpersonateUser(name: string) {\n    return this._userOrg.model.userOrganization.findMany({\n      where: {\n        user: {\n          OR: [\n            {\n              name: {\n                contains: name,\n              },\n            },\n            {\n              email: {\n                contains: name,\n              },\n            },\n            {\n              id: {\n                contains: name,\n              },\n            },\n          ],\n        },\n      },\n      select: {\n        id: true,\n        organization: {\n          select: {\n            id: true,\n          },\n        },\n        user: {\n          select: {\n            id: true,\n            name: true,\n            email: true,\n          },\n        },\n      },\n    });\n  }\n\n  updateApiKey(orgId: string) {\n    return this._organization.model.organization.update({\n      where: {\n        id: orgId,\n      },\n      data: {\n        apiKey: AuthService.fixedEncryption(makeId(20)),\n      },\n    });\n  }\n\n  async getOrgsByUserId(userId: string) {\n    return this._organization.model.organization.findMany({\n      where: {\n        users: {\n          some: {\n            userId,\n          },\n        },\n      },\n      include: {\n        users: {\n          where: {\n            userId,\n          },\n          select: {\n            disabled: true,\n            role: true,\n          },\n        },\n        subscription: {\n          select: {\n            subscriptionTier: true,\n            totalChannels: true,\n            isLifetime: true,\n            createdAt: true,\n          },\n        },\n      },\n    });\n  }\n\n  async getOrgById(id: string) {\n    return this._organization.model.organization.findUnique({\n      where: {\n        id,\n      },\n    });\n  }\n\n  async addUserToOrg(\n    userId: string,\n    id: string,\n    orgId: string,\n    role: 'USER' | 'ADMIN'\n  ) {\n    const checkIfInviteExists = await this._user.model.user.findFirst({\n      where: {\n        inviteId: id,\n      },\n    });\n\n    if (checkIfInviteExists) {\n      return false;\n    }\n\n    const checkForSubscription =\n      await this._organization.model.organization.findFirst({\n        where: {\n          id: orgId,\n        },\n        select: {\n          subscription: true,\n        },\n      });\n\n    if (\n      process.env.STRIPE_PUBLISHABLE_KEY &&\n      checkForSubscription?.subscription?.subscriptionTier ===\n        SubscriptionTier.STANDARD\n    ) {\n      return false;\n    }\n\n    const create = await this._userOrg.model.userOrganization.create({\n      data: {\n        role,\n        userId,\n        organizationId: orgId,\n      },\n    });\n\n    await this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        inviteId: id,\n      },\n    });\n\n    return create;\n  }\n\n  async createOrgAndUser(\n    body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },\n    hasEmail: boolean,\n    ip: string,\n    userAgent: string\n  ) {\n    return this._organization.model.organization.create({\n      data: {\n        name: body.company,\n        apiKey: AuthService.fixedEncryption(makeId(20)),\n        allowTrial: true,\n        isTrailing: true,\n        users: {\n          create: {\n            role: Role.SUPERADMIN,\n            user: {\n              create: {\n                activated: body.provider !== 'LOCAL' || !hasEmail,\n                email: body.email,\n                password: body.password\n                  ? AuthService.hashPassword(body.password)\n                  : '',\n                providerName: body.provider,\n                providerId: body.providerId || '',\n                timezone: 0,\n                ip,\n                agent: userAgent,\n              },\n            },\n          },\n        },\n      },\n      select: {\n        id: true,\n        users: {\n          select: {\n            user: true,\n          },\n        },\n      },\n    });\n  }\n\n  getOrgByCustomerId(customerId: string) {\n    return this._organization.model.organization.findFirst({\n      where: {\n        paymentId: customerId,\n      },\n    });\n  }\n\n  async setStreak(organizationId: string, type: 'start' | 'end') {\n    try {\n      await this._organization.model.organization.update({\n        where: {\n          id: organizationId,\n          ...(type === 'start'\n            ? {\n                streakSince: null,\n              }\n            : {}),\n        },\n        data: {\n          ...(type === 'end' ? { streakSince: null } : {}),\n          ...(type === 'start' ? { streakSince: new Date() } : {}),\n        },\n      });\n    } catch (err) {}\n  }\n\n  async getTeam(orgId: string) {\n    return this._organization.model.organization.findUnique({\n      where: {\n        id: orgId,\n      },\n      select: {\n        users: {\n          select: {\n            role: true,\n            user: {\n              select: {\n                email: true,\n                id: true,\n                sendSuccessEmails: true,\n                sendFailureEmails: true,\n                sendStreakEmails: true,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  getAllUsersOrgs(orgId: string) {\n    return this._organization.model.organization.findUnique({\n      where: {\n        id: orgId,\n      },\n      select: {\n        users: {\n          select: {\n            user: {\n              select: {\n                email: true,\n                id: true,\n                sendSuccessEmails: true,\n                sendFailureEmails: true,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  async deleteTeamMember(orgId: string, userId: string) {\n    return this._userOrg.model.userOrganization.delete({\n      where: {\n        userId_organizationId: {\n          userId,\n          organizationId: orgId,\n        },\n      },\n    });\n  }\n\n  disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {\n    return this._userOrg.model.userOrganization.updateMany({\n      where: {\n        organizationId: orgId,\n        role: {\n          not: Role.SUPERADMIN,\n        },\n      },\n      data: {\n        disabled: disable,\n      },\n    });\n  }\n\n  getShortlinkPreference(orgId: string) {\n    return this._organization.model.organization.findUnique({\n      where: {\n        id: orgId,\n      },\n      select: {\n        shortlink: true,\n      },\n    });\n  }\n\n  updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {\n    return this._organization.model.organization.update({\n      where: {\n        id: orgId,\n      },\n      data: {\n        shortlink,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts",
    "content": "import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';\nimport { Injectable } from '@nestjs/common';\nimport { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';\nimport { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';\nimport { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport dayjs from 'dayjs';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { Organization, ShortLinkPreference } from '@prisma/client';\nimport { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';\n\n@Injectable()\nexport class OrganizationService {\n  constructor(\n    private _organizationRepository: OrganizationRepository,\n    private _notificationsService: NotificationService\n  ) {}\n  async createOrgAndUser(\n    body: Omit<CreateOrgUserDto, 'providerToken'> & { providerId?: string },\n    ip: string,\n    userAgent: string\n  ) {\n    return this._organizationRepository.createOrgAndUser(\n      body,\n      this._notificationsService.hasEmailProvider(),\n      ip,\n      userAgent\n    );\n  }\n\n  async getCount() {\n    return this._organizationRepository.getCount();\n  }\n\n  async createMaxUser(id: string, name: string, saasName: string, email: string) {\n    return this._organizationRepository.createMaxUser(id, name, saasName, email);\n  }\n\n  addUserToOrg(\n    userId: string,\n    id: string,\n    orgId: string,\n    role: 'USER' | 'ADMIN'\n  ) {\n    return this._organizationRepository.addUserToOrg(userId, id, orgId, role);\n  }\n\n  getOrgById(id: string) {\n    return this._organizationRepository.getOrgById(id);\n  }\n\n  getOrgByApiKey(api: string) {\n    return this._organizationRepository.getOrgByApiKey(api);\n  }\n\n  getUserOrg(id: string) {\n    return this._organizationRepository.getUserOrg(id);\n  }\n\n  getOrgsByUserId(userId: string) {\n    return this._organizationRepository.getOrgsByUserId(userId);\n  }\n\n  updateApiKey(orgId: string) {\n    return this._organizationRepository.updateApiKey(orgId);\n  }\n\n  getTeam(orgId: string) {\n    return this._organizationRepository.getTeam(orgId);\n  }\n\n  async setStreak(organizationId: string, type: 'start' | 'end') {\n    return this._organizationRepository.setStreak(organizationId, type);\n  }\n\n  getOrgByCustomerId(customerId: string) {\n    return this._organizationRepository.getOrgByCustomerId(customerId);\n  }\n\n  async inviteTeamMember(orgId: string, body: AddTeamMemberDto) {\n    const timeLimit = dayjs().add(1, 'hour').format('YYYY-MM-DD HH:mm:ss');\n    const id = makeId(5);\n    const url =\n      process.env.FRONTEND_URL +\n      `/?org=${AuthService.signJWT({ ...body, orgId, timeLimit, id })}`;\n    if (body.sendEmail) {\n      await this._notificationsService.sendEmail(\n        body.email,\n        'You have been invited to join an organization',\n        `You have been invited to join an organization. Click <a href=\"${url}\">here</a> to join.<br />The link will expire in 1 hour.`\n      );\n    }\n    return { url };\n  }\n\n  async deleteTeamMember(org: Organization, userId: string) {\n    const userOrgs = await this._organizationRepository.getOrgsByUserId(userId);\n    const findOrgToDelete = userOrgs.find((orgUser) => orgUser.id === org.id);\n    if (!findOrgToDelete) {\n      throw new Error('User is not part of this organization');\n    }\n\n    // @ts-ignore\n    const myRole = org.users[0].role;\n    const userRole = findOrgToDelete.users[0].role;\n    const myLevel = myRole === 'USER' ? 0 : myRole === 'ADMIN' ? 1 : 2;\n    const userLevel = userRole === 'USER' ? 0 : userRole === 'ADMIN' ? 1 : 2;\n\n    if (myLevel < userLevel) {\n      throw new Error('You do not have permission to delete this user');\n    }\n\n    return this._organizationRepository.deleteTeamMember(org.id, userId);\n  }\n\n  disableOrEnableNonSuperAdminUsers(orgId: string, disable: boolean) {\n    return this._organizationRepository.disableOrEnableNonSuperAdminUsers(\n      orgId,\n      disable\n    );\n  }\n\n  getShortlinkPreference(orgId: string) {\n    return this._organizationRepository.getShortlinkPreference(orgId);\n  }\n\n  updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {\n    return this._organizationRepository.updateShortlinkPreference(\n      orgId,\n      shortlink\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { Post as PostBody } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';\nimport { APPROVED_SUBMIT_FOR_ORDER, Post, State } from '@prisma/client';\nimport { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';\nimport { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';\nimport dayjs from 'dayjs';\nimport isoWeek from 'dayjs/plugin/isoWeek';\nimport weekOfYear from 'dayjs/plugin/weekOfYear';\nimport isSameOrAfter from 'dayjs/plugin/isSameOrAfter';\nimport utc from 'dayjs/plugin/utc';\nimport { v4 as uuidv4 } from 'uuid';\nimport { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';\n\ndayjs.extend(isoWeek);\ndayjs.extend(weekOfYear);\ndayjs.extend(isSameOrAfter);\ndayjs.extend(utc);\n\n@Injectable()\nexport class PostsRepository {\n  constructor(\n    private _post: PrismaRepository<'post'>,\n    private _popularPosts: PrismaRepository<'popularPosts'>,\n    private _comments: PrismaRepository<'comments'>,\n    private _tags: PrismaRepository<'tags'>,\n    private _tagsPosts: PrismaRepository<'tagsPosts'>,\n    private _errors: PrismaRepository<'errors'>\n  ) {}\n\n  searchForMissingThreeHoursPosts() {\n    return this._post.model.post.findMany({\n      where: {\n        integration: {\n          refreshNeeded: false,\n          inBetweenSteps: false,\n          disabled: false,\n        },\n        publishDate: {\n          gte: dayjs.utc().subtract(2, 'hour').toDate(),\n          lt: dayjs.utc().add(2, 'hour').toDate(),\n        },\n        state: 'QUEUE',\n        deletedAt: null,\n        parentPostId: null,\n      },\n      select: {\n        id: true,\n        organizationId: true,\n        integration: {\n          select: {\n            providerIdentifier: true,\n          },\n        },\n        publishDate: true,\n      },\n    });\n  }\n\n  getOldPosts(orgId: string, date: string) {\n    return this._post.model.post.findMany({\n      where: {\n        integration: {\n          refreshNeeded: false,\n          inBetweenSteps: false,\n          disabled: false,\n        },\n        organizationId: orgId,\n        publishDate: {\n          lte: dayjs(date).toDate(),\n        },\n        deletedAt: null,\n        parentPostId: null,\n      },\n      orderBy: {\n        publishDate: 'desc',\n      },\n      select: {\n        id: true,\n        content: true,\n        publishDate: true,\n        releaseURL: true,\n        state: true,\n        integration: {\n          select: {\n            id: true,\n            name: true,\n            providerIdentifier: true,\n            picture: true,\n            type: true,\n          },\n        },\n      },\n    });\n  }\n\n  updateImages(id: string, images: string) {\n    return this._post.model.post.update({\n      where: {\n        id,\n      },\n      data: {\n        image: images,\n      },\n    });\n  }\n\n  getPostUrls(orgId: string, ids: string[]) {\n    return this._post.model.post.findMany({\n      where: {\n        organizationId: orgId,\n        id: {\n          in: ids,\n        },\n      },\n      select: {\n        id: true,\n        releaseURL: true,\n      },\n    });\n  }\n\n  async getPosts(orgId: string, query: GetPostsDto) {\n    // Use the provided start and end dates directly\n    const startDate = dayjs.utc(query.startDate).toDate();\n    const endDate = dayjs.utc(query.endDate).toDate();\n\n    const list = await this._post.model.post.findMany({\n      where: {\n        AND: [\n          {\n            OR: [\n              {\n                organizationId: orgId,\n              }\n            ],\n          },\n          {\n            OR: [\n              {\n                publishDate: {\n                  gte: startDate,\n                  lte: endDate,\n                },\n              },\n              {\n                intervalInDays: {\n                  not: null,\n                },\n              },\n            ],\n          },\n        ],\n        integration: {\n          deletedAt: null,\n        },\n        deletedAt: null,\n        parentPostId: null,\n        ...(query.customer\n          ? {\n              integration: {\n                customerId: query.customer,\n              },\n            }\n          : {}),\n      },\n      select: {\n        id: true,\n        content: true,\n        publishDate: true,\n        releaseURL: true,\n        releaseId: true,\n        state: true,\n        intervalInDays: true,\n        group: true,\n        tags: {\n          select: {\n            tag: true,\n          },\n        },\n        integration: {\n          select: {\n            id: true,\n            providerIdentifier: true,\n            name: true,\n            picture: true,\n          },\n        },\n      },\n    });\n\n    return list.reduce((all, post) => {\n      if (!post.intervalInDays) {\n        return [...all, post];\n      }\n\n      const addMorePosts = [];\n      let startingDate = dayjs.utc(post.publishDate);\n      while (dayjs.utc(endDate).isSameOrAfter(startingDate)) {\n        if (dayjs(startingDate).isSameOrAfter(dayjs.utc(post.publishDate))) {\n          addMorePosts.push({\n            ...post,\n            publishDate: startingDate.toDate(),\n            actualDate: post.publishDate,\n          });\n        }\n\n        startingDate = startingDate.add(post.intervalInDays, 'days');\n      }\n\n      return [...all, ...addMorePosts];\n    }, [] as any[]);\n  }\n\n  async getPostsList(orgId: string, query: GetPostsListDto) {\n    const page = query.page || 0;\n    const limit = query.limit || 20;\n    const skip = page * limit;\n\n    const where = {\n      AND: [\n        {\n          OR: [\n            {\n              organizationId: orgId,\n            },\n          ],\n        },\n        {\n          publishDate: {\n            gte: dayjs.utc().toDate(),\n          },\n        },\n      ],\n      deletedAt: null as Date | null,\n      parentPostId: null as string | null,\n      intervalInDays: null as number | null,\n      ...(query.customer\n        ? {\n            integration: {\n              customerId: query.customer,\n            },\n          }\n        : {}),\n    };\n\n    const [posts, total] = await Promise.all([\n      this._post.model.post.findMany({\n        where,\n        skip,\n        take: limit,\n        orderBy: {\n          publishDate: 'asc',\n        },\n        select: {\n          id: true,\n          content: true,\n          publishDate: true,\n          releaseURL: true,\n          releaseId: true,\n          state: true,\n          group: true,\n          tags: {\n            select: {\n              tag: true,\n            },\n          },\n          integration: {\n            select: {\n              id: true,\n              providerIdentifier: true,\n              name: true,\n              picture: true,\n            },\n          },\n        },\n      }),\n      this._post.model.post.count({ where }),\n    ]);\n\n    return {\n      posts,\n      total,\n      page,\n      limit,\n      hasMore: skip + posts.length < total,\n    };\n  }\n\n  async deletePost(orgId: string, group: string) {\n    await this._post.model.post.updateMany({\n      where: {\n        organizationId: orgId,\n        group,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n\n    return this._post.model.post.findFirst({\n      where: {\n        organizationId: orgId,\n        group,\n        parentPostId: null,\n      },\n      select: {\n        id: true,\n      },\n    });\n  }\n\n  getPostsByGroup(orgId: string, group: string) {\n    return this._post.model.post.findMany({\n      where: {\n        group,\n        ...(orgId ? { organizationId: orgId } : {}),\n        deletedAt: null,\n      },\n      include: {\n        integration: true,\n        tags: {\n          select: {\n            tag: true,\n          },\n        },\n      },\n    });\n  }\n\n  getPost(\n    id: string,\n    includeIntegration = false,\n    orgId?: string,\n    isFirst?: boolean\n  ) {\n    return this._post.model.post.findUnique({\n      where: {\n        id,\n        ...(orgId ? { organizationId: orgId } : {}),\n        deletedAt: null,\n      },\n      include: {\n        ...(includeIntegration\n          ? {\n              integration: true,\n              tags: {\n                select: {\n                  tag: true,\n                },\n              },\n            }\n          : {}),\n        childrenPost: true,\n      },\n    });\n  }\n\n  updatePost(id: string, postId: string, releaseURL: string) {\n    return this._post.model.post.update({\n      where: {\n        id,\n      },\n      data: {\n        state: 'PUBLISHED',\n        releaseURL,\n        releaseId: postId,\n      },\n    });\n  }\n\n  updateReleaseId(id: string, orgId: string, releaseId: string) {\n    return this._post.model.post.update({\n      where: {\n        id,\n        organizationId: orgId,\n        releaseId: 'missing',\n      },\n      data: {\n        releaseId: String(releaseId),\n      },\n    });\n  }\n\n  async changeState(id: string, state: State, err?: any, body?: any) {\n    const update = await this._post.model.post.update({\n      where: {\n        id,\n      },\n      data: {\n        state,\n        ...(err\n          ? { error: typeof err === 'string' ? err : JSON.stringify(err) }\n          : {}),\n      },\n      include: {\n        integration: {\n          select: {\n            providerIdentifier: true,\n          },\n        },\n      },\n    });\n\n    if (state === 'ERROR' && err && body) {\n      try {\n        await this._errors.model.errors.create({\n          data: {\n            message: typeof err === 'string' ? err : JSON.stringify(err),\n            organizationId: update.organizationId,\n            platform: update.integration.providerIdentifier,\n            postId: update.id,\n            body: typeof body === 'string' ? body : JSON.stringify(body),\n          },\n        });\n      } catch (err) {}\n    }\n\n    return update;\n  }\n\n  async changeDate(\n    orgId: string,\n    id: string,\n    date: string,\n    isDraft: boolean,\n    action: 'schedule' | 'update' = 'schedule'\n  ) {\n    return this._post.model.post.update({\n      where: {\n        organizationId: orgId,\n        id,\n      },\n      data: {\n        publishDate: dayjs(date).toDate(),\n        // schedule: set state to QUEUE (or DRAFT if it was a draft)\n        // update: don't change the state\n        ...(action === 'schedule'\n          ? {\n              state: isDraft ? 'DRAFT' : 'QUEUE',\n              releaseId: null,\n              releaseURL: null,\n            }\n          : {}),\n      },\n    });\n  }\n\n  countPostsFromDay(orgId: string, date: Date) {\n    return this._post.model.post.count({\n      where: {\n        organizationId: orgId,\n        publishDate: {\n          gte: date,\n        },\n        OR: [\n          {\n            deletedAt: null,\n            state: {\n              in: ['QUEUE'],\n            },\n          },\n          {\n            state: 'PUBLISHED',\n          },\n        ],\n      },\n    });\n  }\n\n  async createOrUpdatePost(\n    state: 'draft' | 'schedule' | 'now' | 'update',\n    orgId: string,\n    date: string,\n    body: PostBody,\n    tags: { value: string; label: string }[],\n    inter?: number\n  ) {\n    const posts: Post[] = [];\n    const uuid = uuidv4();\n\n    for (const value of body.value) {\n      const updateData = (type: 'create' | 'update') => ({\n        publishDate: dayjs(date).toDate(),\n        integration: {\n          connect: {\n            id: body.integration.id,\n            organizationId: orgId,\n          },\n        },\n        ...(posts?.[posts.length - 1]?.id\n          ? {\n              parentPost: {\n                connect: {\n                  id: posts[posts.length - 1]?.id,\n                },\n              },\n            }\n          : type === 'update'\n          ? {\n              parentPost: {\n                disconnect: true,\n              },\n            }\n          : {}),\n        content: value.content,\n        delay: value.delay || 0,\n        group: uuid,\n        intervalInDays: inter ? +inter : null,\n        approvedSubmitForOrder: APPROVED_SUBMIT_FOR_ORDER.NO,\n        ...(state === 'update'\n          ? {}\n          : {\n              state:\n                state === 'draft' ? ('DRAFT' as const) : ('QUEUE' as const),\n            }),\n        image: JSON.stringify(value.image),\n        settings: JSON.stringify(body.settings),\n        organization: {\n          connect: {\n            id: orgId,\n          },\n        },\n      });\n\n      posts.push(\n        await this._post.model.post.upsert({\n          where: {\n            id: value.id || uuidv4(),\n          },\n          create: { ...updateData('create') },\n          update: {\n            ...updateData('update'),\n            lastMessage: {\n              disconnect: true,\n            },\n            submittedForOrder: {\n              disconnect: true,\n            },\n          },\n        })\n      );\n\n      if (posts.length === 1) {\n        await this._tagsPosts.model.tagsPosts.deleteMany({\n          where: {\n            post: {\n              id: posts[0].id,\n            },\n          },\n        });\n\n        if (tags.length) {\n          const tagsList = await this._tags.model.tags.findMany({\n            where: {\n              orgId: orgId,\n              name: {\n                in: tags.map((tag) => tag.label).filter((f) => f),\n              },\n            },\n          });\n\n          if (tagsList.length) {\n            await this._post.model.post.update({\n              where: {\n                id: posts[posts.length - 1].id,\n              },\n              data: {\n                tags: {\n                  createMany: {\n                    data: tagsList.map((tag) => ({\n                      tagId: tag.id,\n                    })),\n                  },\n                },\n              },\n            });\n          }\n        }\n      }\n    }\n\n    const previousPost = body.group\n      ? (\n          await this._post.model.post.findFirst({\n            where: {\n              group: body.group,\n              deletedAt: null,\n              parentPostId: null,\n            },\n            select: {\n              id: true,\n            },\n          })\n        )?.id!\n      : undefined;\n\n    if (body.group) {\n      await this._post.model.post.updateMany({\n        where: {\n          group: body.group,\n          deletedAt: null,\n        },\n        data: {\n          parentPostId: null,\n          deletedAt: new Date(),\n        },\n      });\n    }\n\n    return { previousPost, posts };\n  }\n\n  async submit(id: string, order: string, buyerOrganizationId: string) {\n    return this._post.model.post.update({\n      where: {\n        id,\n      },\n      data: {\n        submittedForOrderId: order,\n        approvedSubmitForOrder: 'WAITING_CONFIRMATION',\n        submittedForOrganizationId: buyerOrganizationId,\n      },\n      select: {\n        id: true,\n        description: true,\n        submittedForOrder: {\n          select: {\n            messageGroupId: true,\n          },\n        },\n      },\n    });\n  }\n\n  updateMessage(id: string, messageId: string) {\n    return this._post.model.post.update({\n      where: {\n        id,\n      },\n      data: {\n        lastMessageId: messageId,\n      },\n    });\n  }\n\n  getPostById(id: string, org?: string) {\n    return this._post.model.post.findUnique({\n      where: {\n        id,\n        ...(org ? { organizationId: org } : {}),\n      },\n      include: {\n        integration: true,\n        submittedForOrder: {\n          include: {\n            posts: {\n              where: {\n                state: 'PUBLISHED',\n              },\n            },\n            ordersItems: true,\n            seller: {\n              select: {\n                id: true,\n                account: true,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  findAllExistingCategories() {\n    return this._popularPosts.model.popularPosts.findMany({\n      select: {\n        category: true,\n      },\n      distinct: ['category'],\n    });\n  }\n\n  findAllExistingTopicsOfCategory(category: string) {\n    return this._popularPosts.model.popularPosts.findMany({\n      where: {\n        category,\n      },\n      select: {\n        topic: true,\n      },\n      distinct: ['topic'],\n    });\n  }\n\n  findPopularPosts(category: string, topic?: string) {\n    return this._popularPosts.model.popularPosts.findMany({\n      where: {\n        category,\n        ...(topic ? { topic } : {}),\n      },\n      select: {\n        content: true,\n        hook: true,\n      },\n    });\n  }\n\n  createPopularPosts(post: {\n    category: string;\n    topic: string;\n    content: string;\n    hook: string;\n  }) {\n    return this._popularPosts.model.popularPosts.create({\n      data: {\n        category: 'category',\n        topic: 'topic',\n        content: 'content',\n        hook: 'hook',\n      },\n    });\n  }\n\n  async getPostsCountsByDates(\n    orgId: string,\n    times: number[],\n    date: dayjs.Dayjs\n  ) {\n    const dates = await this._post.model.post.findMany({\n      where: {\n        deletedAt: null,\n        organizationId: orgId,\n        publishDate: {\n          in: times.map((time) => {\n            return date.clone().add(time, 'minutes').toDate();\n          }),\n        },\n      },\n    });\n\n    return times.filter(\n      (time) =>\n        date.clone().add(time, 'minutes').isAfter(dayjs.utc()) &&\n        !dates.find((dateFind) => {\n          return (\n            dayjs\n              .utc(dateFind.publishDate)\n              .diff(date.clone().startOf('day'), 'minutes') == time\n          );\n        })\n    );\n  }\n\n  async getComments(postId: string) {\n    return this._comments.model.comments.findMany({\n      where: {\n        postId,\n      },\n      orderBy: {\n        createdAt: 'asc',\n      },\n    });\n  }\n\n  async getTags(orgId: string) {\n    return this._tags.model.tags.findMany({\n      where: {\n        orgId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  createTag(orgId: string, body: CreateTagDto) {\n    return this._tags.model.tags.create({\n      data: {\n        orgId,\n        name: body.name,\n        color: body.color,\n      },\n    });\n  }\n\n  editTag(id: string, orgId: string, body: CreateTagDto) {\n    return this._tags.model.tags.update({\n      where: {\n        id,\n      },\n      data: {\n        name: body.name,\n        color: body.color,\n      },\n    });\n  }\n\n  deleteTag(id: string, orgId: string) {\n    return this._tags.model.tags.update({\n      where: {\n        id,\n        orgId,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  createComment(\n    orgId: string,\n    userId: string,\n    postId: string,\n    content: string\n  ) {\n    return this._comments.model.comments.create({\n      data: {\n        organizationId: orgId,\n        userId,\n        postId,\n        content,\n      },\n    });\n  }\n\n  async getPostByForWebhookId(postId: string) {\n    return this._post.model.post.findMany({\n      where: {\n        id: postId,\n        deletedAt: null,\n        parentPostId: null,\n      },\n      select: {\n        id: true,\n        content: true,\n        publishDate: true,\n        releaseURL: true,\n        state: true,\n        integration: {\n          select: {\n            id: true,\n            name: true,\n            providerIdentifier: true,\n            picture: true,\n            type: true,\n          },\n        },\n      },\n    });\n  }\n\n  async getPostsSince(orgId: string, since: string) {\n    return this._post.model.post.findMany({\n      where: {\n        organizationId: orgId,\n        publishDate: {\n          gte: new Date(since),\n        },\n        deletedAt: null,\n        parentPostId: null,\n      },\n      select: {\n        id: true,\n        content: true,\n        publishDate: true,\n        releaseURL: true,\n        state: true,\n        integration: {\n          select: {\n            id: true,\n            name: true,\n            providerIdentifier: true,\n            picture: true,\n            type: true,\n          },\n        },\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts",
    "content": "import {\n  BadRequestException,\n  Injectable,\n  ValidationPipe,\n} from '@nestjs/common';\nimport { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.repository';\nimport { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';\nimport dayjs from 'dayjs';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { Integration, Post, Media, From, State } from '@prisma/client';\nimport { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';\nimport { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';\nimport { shuffle } from 'lodash';\nimport { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport utc from 'dayjs/plugin/utc';\nimport { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';\nimport { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';\nimport { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';\nimport { minifyPostsList, minifyPosts } from '@gitroom/helpers/utils/posts.list.minify';\nimport axios from 'axios';\nimport sharp from 'sharp';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { Readable } from 'stream';\nimport { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\ndayjs.extend(utc);\nimport * as Sentry from '@sentry/nestjs';\nimport { TemporalService } from 'nestjs-temporal-core';\nimport { TypedSearchAttributes } from '@temporalio/common';\nimport {\n  organizationId,\n  postId as postIdSearchParam,\n} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';\nimport { AnalyticsData } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';\nimport { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service';\n\ntype PostWithConditionals = Post & {\n  integration?: Integration;\n  childrenPost: Post[];\n};\n\n@Injectable()\nexport class PostsService {\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _postRepository: PostsRepository,\n    private _integrationManager: IntegrationManager,\n    private _integrationService: IntegrationService,\n    private _mediaService: MediaService,\n    private _shortLinkService: ShortLinkService,\n    private _openaiService: OpenaiService,\n    private _temporalService: TemporalService,\n    private _refreshIntegrationService: RefreshIntegrationService\n  ) {}\n\n  searchForMissingThreeHoursPosts() {\n    return this._postRepository.searchForMissingThreeHoursPosts();\n  }\n\n  updatePost(id: string, postId: string, releaseURL: string) {\n    return this._postRepository.updatePost(id, postId, releaseURL);\n  }\n\n  async getMissingContent(\n    orgId: string,\n    postId: string,\n    forceRefresh = false\n  ): Promise<{ id: string; url: string }[]> {\n    const post = await this._postRepository.getPostById(postId, orgId);\n    if (!post || post.releaseId !== 'missing') {\n      return [];\n    }\n\n    const integrationProvider = this._integrationManager.getSocialIntegration(\n      post.integration.providerIdentifier\n    );\n\n    if (!integrationProvider.missing) {\n      return [];\n    }\n\n    const getIntegration = post.integration!;\n\n    if (\n      dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||\n      forceRefresh\n    ) {\n      const data = await this._refreshIntegrationService.refresh(\n        getIntegration\n      );\n      if (!data) {\n        return [];\n      }\n\n      const { accessToken } = data;\n\n      if (accessToken) {\n        getIntegration.token = accessToken;\n\n        if (integrationProvider.refreshWait) {\n          await timer(10000);\n        }\n      } else {\n        await this._integrationService.disconnectChannel(orgId, getIntegration);\n        return [];\n      }\n    }\n\n    try {\n      return await integrationProvider.missing(\n        getIntegration.internalId,\n        getIntegration.token\n      );\n    } catch (e) {\n      console.log(e);\n      if (e instanceof RefreshToken) {\n        return this.getMissingContent(orgId, postId, true);\n      }\n    }\n\n    return [];\n  }\n\n  async updateReleaseId(orgId: string, postId: string, releaseId: string) {\n    return this._postRepository.updateReleaseId(postId, orgId, releaseId);\n  }\n\n  async checkPostAnalytics(\n    orgId: string,\n    postId: string,\n    date: number,\n    forceRefresh = false\n  ): Promise<AnalyticsData[] | { missing: true }> {\n    const post = await this._postRepository.getPostById(postId, orgId);\n    if (!post || !post.releaseId) {\n      return [];\n    }\n\n    if (post.releaseId === 'missing') {\n      return { missing: true };\n    }\n\n    const integrationProvider = this._integrationManager.getSocialIntegration(\n      post.integration.providerIdentifier\n    );\n\n    if (!integrationProvider.postAnalytics) {\n      return [];\n    }\n\n    const getIntegration = post.integration!;\n\n    if (\n      dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||\n      forceRefresh\n    ) {\n      const data = await this._refreshIntegrationService.refresh(\n        getIntegration\n      );\n      if (!data) {\n        return [];\n      }\n\n      const { accessToken } = data;\n\n      if (accessToken) {\n        getIntegration.token = accessToken;\n\n        if (integrationProvider.refreshWait) {\n          await timer(10000);\n        }\n      } else {\n        await this._integrationService.disconnectChannel(orgId, getIntegration);\n        return [];\n      }\n    }\n\n    // const getIntegrationData = await ioRedis.get(\n    //   `integration:${orgId}:${post.id}:${date}`\n    // );\n    // if (getIntegrationData) {\n    //   return JSON.parse(getIntegrationData);\n    // }\n\n    try {\n      const loadAnalytics = await integrationProvider.postAnalytics(\n        getIntegration.internalId,\n        getIntegration.token,\n        post.releaseId,\n        date\n      );\n      await ioRedis.set(\n        `integration:${orgId}:${post.id}:${date}`,\n        JSON.stringify(loadAnalytics),\n        'EX',\n        !process.env.NODE_ENV || process.env.NODE_ENV === 'development'\n          ? 1\n          : 3600\n      );\n      return loadAnalytics;\n    } catch (e) {\n      console.log(e);\n      if (e instanceof RefreshToken) {\n        return this.checkPostAnalytics(orgId, postId, date, true);\n      }\n    }\n\n    return [];\n  }\n\n  async getStatistics(orgId: string, id: string) {\n    const getPost = await this.getPostsRecursively(id, true, orgId, true);\n    const content = getPost.map((p) => p.content);\n    const shortLinksTracking = await this._shortLinkService.getStatistics(\n      content\n    );\n\n    return {\n      clicks: shortLinksTracking,\n    };\n  }\n\n  async mapTypeToPost(\n    body: CreatePostDto,\n    organization: string,\n    replaceDraft: boolean = false\n  ): Promise<CreatePostDto> {\n    if (!body?.posts?.every((p) => p?.integration?.id)) {\n      throw new BadRequestException('All posts must have an integration id');\n    }\n\n    const mappedValues = {\n      ...body,\n      type: replaceDraft ? 'schedule' : body.type,\n      posts: await Promise.all(\n        body.posts.map(async (post) => {\n          const integration = await this._integrationService.getIntegrationById(\n            organization,\n            post.integration.id\n          );\n\n          if (!integration) {\n            throw new BadRequestException(\n              `Integration with id ${post.integration.id} not found`\n            );\n          }\n\n          return {\n            type: replaceDraft ? 'schedule' : body.type,\n            ...post,\n            settings: {\n              ...(post.settings || ({} as any)),\n              __type: integration.providerIdentifier,\n            },\n          };\n        })\n      ),\n    };\n\n    const validationPipe = new ValidationPipe({\n      skipMissingProperties: false,\n      transform: true,\n      transformOptions: {\n        enableImplicitConversion: true,\n      },\n    });\n\n    return await validationPipe.transform(mappedValues, {\n      type: 'body',\n      metatype: CreatePostDto,\n    });\n  }\n\n  async getPostsRecursively(\n    id: string,\n    includeIntegration = false,\n    orgId?: string,\n    isFirst?: boolean\n  ): Promise<PostWithConditionals[]> {\n    const post = await this._postRepository.getPost(\n      id,\n      includeIntegration,\n      orgId,\n      isFirst\n    );\n\n    if (!post) {\n      return [];\n    }\n\n    return [\n      post!,\n      ...(post?.childrenPost?.length\n        ? await this.getPostsRecursively(\n            post?.childrenPost?.[0]?.id,\n            false,\n            orgId,\n            false\n          )\n        : []),\n    ];\n  }\n\n  async getPosts(orgId: string, query: GetPostsDto) {\n    return this._postRepository.getPosts(orgId, query);\n  }\n\n  async getPostsMinified(orgId: string, query: GetPostsDto) {\n    return minifyPosts({\n      posts: await this._postRepository.getPosts(orgId, query),\n    });\n  }\n\n  async getPostsList(orgId: string, query: GetPostsListDto) {\n    return minifyPostsList(\n      await this._postRepository.getPostsList(orgId, query)\n    );\n  }\n\n  async updateMedia(id: string, imagesList: any[], convertToJPEG = false) {\n    try {\n      let imageUpdateNeeded = false;\n      const getImageList = await Promise.all(\n        (\n          await Promise.all(\n            (imagesList || []).map(async (p: any) => {\n              if (!p.path && p.id) {\n                imageUpdateNeeded = true;\n                return this._mediaService.getMediaById(p.id);\n              }\n\n              return p;\n            })\n          )\n        )\n          .map((m) => {\n            return {\n              ...m,\n              url:\n                m.path.indexOf('http') === -1\n                  ? process.env.FRONTEND_URL +\n                    '/' +\n                    process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +\n                    m.path\n                  : m.path,\n              type: 'image',\n              path:\n                m.path.indexOf('http') === -1\n                  ? process.env.UPLOAD_DIRECTORY + m.path\n                  : m.path,\n            };\n          })\n          .map(async (m) => {\n            if (!convertToJPEG) {\n              return m;\n            }\n\n            if (m.path.indexOf('.png') > -1) {\n              imageUpdateNeeded = true;\n              const response = await axios.get(m.url, {\n                responseType: 'arraybuffer',\n              });\n\n              const imageBuffer = Buffer.from(response.data);\n\n              // Use sharp to get the metadata of the image\n              const buffer = await sharp(imageBuffer)\n                .jpeg({ quality: 100 })\n                .toBuffer();\n\n              const { path, originalname } = await this.storage.uploadFile({\n                buffer,\n                mimetype: 'image/jpeg',\n                size: buffer.length,\n                path: '',\n                fieldname: '',\n                destination: '',\n                stream: new Readable(),\n                filename: '',\n                originalname: '',\n                encoding: '',\n              });\n\n              return {\n                ...m,\n                name: originalname,\n                url:\n                  path.indexOf('http') === -1\n                    ? process.env.FRONTEND_URL +\n                      '/' +\n                      process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +\n                      path\n                    : path,\n                type: 'image',\n                path:\n                  path.indexOf('http') === -1\n                    ? process.env.UPLOAD_DIRECTORY + path\n                    : path,\n              };\n            }\n\n            return m;\n          })\n      );\n\n      if (imageUpdateNeeded) {\n        await this._postRepository.updateImages(\n          id,\n          JSON.stringify(getImageList)\n        );\n      }\n\n      return getImageList;\n    } catch (err: any) {\n      return imagesList;\n    }\n  }\n\n  async getPostsByGroup(orgId: string, group: string) {\n    const convertToJPEG = false;\n    const loadAll = await this._postRepository.getPostsByGroup(orgId, group);\n    const posts = this.arrangePostsByGroup(loadAll, undefined);\n\n    return {\n      group: posts?.[0]?.group,\n      posts: await Promise.all(\n        (posts || []).map(async (post) => ({\n          ...post,\n          image: await this.updateMedia(\n            post.id,\n            JSON.parse(post.image || '[]'),\n            convertToJPEG\n          ),\n        }))\n      ),\n      integrationPicture: posts[0]?.integration?.picture,\n      integration: posts[0].integrationId,\n      settings: JSON.parse(posts[0].settings || '{}'),\n    };\n  }\n\n  arrangePostsByGroup(all: any, parent?: string): PostWithConditionals[] {\n    const findAll = all\n      .filter((p: any) =>\n        !parent ? !p.parentPostId : p.parentPostId === parent\n      )\n      .map(({ integration, ...all }: any) => ({\n        ...all,\n        ...(!parent ? { integration } : {}),\n      }));\n\n    return [\n      ...findAll,\n      ...(findAll.length\n        ? findAll.flatMap((p: any) => this.arrangePostsByGroup(all, p.id))\n        : []),\n    ];\n  }\n\n  async getPost(orgId: string, id: string, convertToJPEG = false) {\n    const posts = await this.getPostsRecursively(id, true, orgId, true);\n    const list = {\n      group: posts?.[0]?.group,\n      posts: await Promise.all(\n        (posts || []).map(async (post) => ({\n          ...post,\n          image: await this.updateMedia(\n            post.id,\n            JSON.parse(post.image || '[]'),\n            convertToJPEG\n          ),\n        }))\n      ),\n      integrationPicture: posts[0]?.integration?.picture,\n      integration: posts[0].integrationId,\n      settings: JSON.parse(posts[0].settings || '{}'),\n    };\n\n    return list;\n  }\n\n  async getOldPosts(orgId: string, date: string) {\n    return this._postRepository.getOldPosts(orgId, date);\n  }\n\n  public async updateTags(orgId: string, post: Post[]): Promise<Post[]> {\n    const plainText = JSON.stringify(post);\n    const extract = Array.from(\n      plainText.match(/\\(post:[a-zA-Z0-9-_]+\\)/g) || []\n    );\n    if (!extract.length) {\n      return post;\n    }\n\n    const ids = (extract || []).map((e) =>\n      e.replace('(post:', '').replace(')', '')\n    );\n    const urls = await this._postRepository.getPostUrls(orgId, ids);\n    const newPlainText = ids.reduce((acc, value) => {\n      const findUrl = urls?.find?.((u) => u.id === value)?.releaseURL || '';\n      return acc.replace(\n        new RegExp(`\\\\(post:${value}\\\\)`, 'g'),\n        findUrl.split(',')[0]\n      );\n    }, plainText);\n\n    return this.updateTags(orgId, JSON.parse(newPlainText) as Post[]);\n  }\n\n  public async checkInternalPlug(\n    integration: Integration,\n    orgId: string,\n    id: string,\n    settings: any\n  ) {\n    const plugs = Object.entries(settings).filter(([key]) => {\n      return key.indexOf('plug-') > -1;\n    });\n\n    if (plugs.length === 0) {\n      return [];\n    }\n\n    const parsePlugs = plugs.reduce((all, [key, value]) => {\n      const [_, name, identifier] = key.split('--');\n      all[name] = all[name] || { name };\n      all[name][identifier] = value;\n      return all;\n    }, {} as any);\n\n    const list: {\n      name: string;\n      integrations: { id: string }[];\n      delay: string;\n      active: boolean;\n    }[] = Object.values(parsePlugs);\n\n    return (list || []).flatMap((trigger) => {\n      return (trigger?.integrations || []).flatMap((int) => ({\n        type: 'internal-plug',\n        post: id,\n        originalIntegration: integration.id,\n        integration: int.id,\n        plugName: trigger.name,\n        orgId: orgId,\n        delay: +trigger.delay,\n        information: trigger,\n      }));\n    });\n  }\n\n  public async checkPlugs(\n    orgId: string,\n    providerName: string,\n    integrationId: string\n  ) {\n    const loadAllPlugs = this._integrationManager.getAllPlugs();\n    const getPlugs = await this._integrationService.getPlugs(\n      orgId,\n      integrationId\n    );\n\n    const currentPlug = loadAllPlugs.find((p) => p.identifier === providerName);\n\n    return getPlugs\n      .filter((plug) => {\n        return currentPlug?.plugs?.some(\n          (p: any) => p.methodName === plug.plugFunction\n        );\n      })\n      .map((plug) => {\n        const runPlug = currentPlug?.plugs?.find(\n          (p: any) => p.methodName === plug.plugFunction\n        )!;\n        return {\n          type: 'global',\n          plugId: plug.id,\n          delay: runPlug.runEveryMilliseconds,\n          totalRuns: runPlug.totalRuns,\n        };\n      });\n  }\n\n  async deletePost(orgId: string, group: string) {\n    const post = await this._postRepository.deletePost(orgId, group);\n\n    if (post?.id) {\n      try {\n        const workflows = this._temporalService.client\n          .getRawClient()\n          ?.workflow.list({\n            query: `postId=\"${post.id}\" AND ExecutionStatus=\"Running\"`,\n          });\n\n        for await (const executionInfo of workflows) {\n          try {\n            const workflow =\n              await this._temporalService.client.getWorkflowHandle(\n                executionInfo.workflowId\n              );\n            if (\n              workflow &&\n              (await workflow.describe()).status.name !== 'TERMINATED'\n            ) {\n              await workflow.terminate();\n            }\n          } catch (err) {}\n        }\n      } catch (err) {}\n    }\n\n    return { error: true };\n  }\n\n  async countPostsFromDay(orgId: string, date: Date) {\n    return this._postRepository.countPostsFromDay(orgId, date);\n  }\n\n  getPostByForWebhookId(id: string) {\n    return this._postRepository.getPostByForWebhookId(id);\n  }\n\n  async startWorkflow(\n    taskQueue: string,\n    postId: string,\n    orgId: string,\n    state: State\n  ) {\n    try {\n      const workflows = this._temporalService.client\n        .getRawClient()\n        ?.workflow.list({\n          query: `postId=\"${postId}\" AND ExecutionStatus=\"Running\"`,\n        });\n\n      for await (const executionInfo of workflows) {\n        try {\n          const workflow = await this._temporalService.client.getWorkflowHandle(\n            executionInfo.workflowId\n          );\n          if (\n            workflow &&\n            (await workflow.describe()).status.name !== 'TERMINATED'\n          ) {\n            await workflow.terminate();\n          }\n        } catch (err) {}\n      }\n    } catch (err) {}\n\n    if (state === 'DRAFT') {\n      return;\n    }\n\n    try {\n      await this._temporalService.client\n        .getRawClient()\n        ?.workflow.start('postWorkflowV101', {\n          workflowId: `post_${postId}`,\n          taskQueue: 'main',\n          workflowIdConflictPolicy: 'TERMINATE_EXISTING',\n          args: [\n            {\n              taskQueue: taskQueue,\n              postId: postId,\n              organizationId: orgId,\n            },\n          ],\n          typedSearchAttributes: new TypedSearchAttributes([\n            {\n              key: postIdSearchParam,\n              value: postId,\n            },\n            {\n              key: organizationId,\n              value: orgId,\n            },\n          ]),\n        });\n    } catch (err) {}\n  }\n\n  async createPost(orgId: string, body: CreatePostDto): Promise<any[]> {\n    const postList = [];\n    for (const post of body.posts) {\n      const messages = (post.value || []).map((p) => p.content);\n      const updateContent = !body.shortLink\n        ? messages\n        : await this._shortLinkService.convertTextToShortLinks(orgId, messages);\n\n      post.value = (post.value || []).map((p, i) => ({\n        ...p,\n        content: updateContent[i],\n      }));\n\n      const { posts } = await this._postRepository.createOrUpdatePost(\n        body.type,\n        orgId,\n        body.type === 'now' ? dayjs().format('YYYY-MM-DDTHH:mm:00') : body.date,\n        post,\n        body.tags,\n        body.inter\n      );\n\n      if (!posts?.length) {\n        return [] as any[];\n      }\n\n      if (body.type !== 'update') {\n        this.startWorkflow(\n          post.settings.__type.split('-')[0].toLowerCase(),\n          posts[0].id,\n          orgId,\n          posts[0].state\n        ).catch((err) => {});\n      }\n\n      Sentry.metrics.count('post_created', 1);\n      postList.push({\n        postId: posts[0].id,\n        integration: post.integration.id,\n      });\n    }\n\n    return postList;\n  }\n\n  async separatePosts(content: string, len: number) {\n    return this._openaiService.separatePosts(content, len);\n  }\n\n  async changeState(id: string, state: State, err?: any, body?: any) {\n    return this._postRepository.changeState(id, state, err, body);\n  }\n\n  async changeDate(\n    orgId: string,\n    id: string,\n    date: string,\n    action: 'schedule' | 'update' = 'schedule'\n  ) {\n    const getPostById = await this._postRepository.getPostById(id, orgId);\n\n    // schedule: Set status to QUEUE and change date (reschedule the post)\n    // update: Just change the date without changing the status\n    const newDate = await this._postRepository.changeDate(\n      orgId,\n      id,\n      date,\n      getPostById.state === 'DRAFT',\n      action\n    );\n\n    if (action === 'schedule') {\n      try {\n        await this.startWorkflow(\n          getPostById.integration.providerIdentifier.split('-')[0].toLowerCase(),\n          getPostById.id,\n          orgId,\n          getPostById.state === 'DRAFT' ? 'DRAFT' : 'QUEUE'\n        );\n      } catch (err) {}\n    }\n\n    return newDate;\n  }\n\n  async generatePostsDraft(orgId: string, body: CreateGeneratedPostsDto) {\n    const getAllIntegrations = (\n      await this._integrationService.getIntegrationsList(orgId)\n    ).filter((f) => !f.disabled && f.providerIdentifier !== 'reddit');\n\n    // const posts = chunk(body.posts, getAllIntegrations.length);\n    const allDates = dayjs()\n      .isoWeek(body.week)\n      .year(body.year)\n      .startOf('isoWeek');\n\n    const dates = [...new Array(7)].map((_, i) => {\n      return allDates.add(i, 'day').format('YYYY-MM-DD');\n    });\n\n    const findTime = (): string => {\n      const totalMinutes = Math.floor(Math.random() * 144) * 10;\n\n      // Convert total minutes to hours and minutes\n      const hours = Math.floor(totalMinutes / 60);\n      const minutes = totalMinutes % 60;\n\n      // Format hours and minutes to always be two digits\n      const formattedHours = hours.toString().padStart(2, '0');\n      const formattedMinutes = minutes.toString().padStart(2, '0');\n      const randomDate =\n        shuffle(dates)[0] + 'T' + `${formattedHours}:${formattedMinutes}:00`;\n\n      if (dayjs(randomDate).isBefore(dayjs())) {\n        return findTime();\n      }\n\n      return randomDate;\n    };\n\n    for (const integration of getAllIntegrations) {\n      for (const toPost of body.posts) {\n        const group = makeId(10);\n        const randomDate = findTime();\n\n        await this.createPost(orgId, {\n          type: 'draft',\n          date: randomDate,\n          order: '',\n          shortLink: false,\n          tags: [],\n          posts: [\n            {\n              group,\n              integration: {\n                id: integration.id,\n              },\n              settings: {\n                __type: integration.providerIdentifier as any,\n                title: '',\n                tags: [],\n                subreddit: [],\n              },\n              value: [\n                ...toPost.list.map((l) => ({\n                  id: '',\n                  content: l.post,\n                  delay: 0,\n                  image: [],\n                })),\n                {\n                  id: '',\n                  delay: 0,\n                  content: `Check out the full story here:\\n${\n                    body.postId || body.url\n                  }`,\n                  image: [],\n                },\n              ],\n            },\n          ],\n        });\n      }\n    }\n  }\n\n  findAllExistingCategories() {\n    return this._postRepository.findAllExistingCategories();\n  }\n\n  findAllExistingTopicsOfCategory(category: string) {\n    return this._postRepository.findAllExistingTopicsOfCategory(category);\n  }\n\n  findPopularPosts(category: string, topic?: string) {\n    return this._postRepository.findPopularPosts(category, topic);\n  }\n\n  async findFreeDateTime(orgId: string, integrationId?: string) {\n    const findTimes = await this._integrationService.findFreeDateTime(\n      orgId,\n      integrationId\n    );\n    return this.findFreeDateTimeRecursive(\n      orgId,\n      findTimes,\n      dayjs.utc().startOf('day')\n    );\n  }\n\n  async createPopularPosts(post: {\n    category: string;\n    topic: string;\n    content: string;\n    hook: string;\n  }) {\n    return this._postRepository.createPopularPosts(post);\n  }\n\n  private async findFreeDateTimeRecursive(\n    orgId: string,\n    times: number[],\n    date: dayjs.Dayjs\n  ): Promise<string> {\n    const list = await this._postRepository.getPostsCountsByDates(\n      orgId,\n      times,\n      date\n    );\n\n    if (!list.length) {\n      return this.findFreeDateTimeRecursive(orgId, times, date.add(1, 'day'));\n    }\n\n    const num = list.reduce<null | number>((prev, curr) => {\n      if (prev === null || prev > curr) {\n        return curr;\n      }\n      return prev;\n    }, null) as number;\n\n    return date.clone().add(num, 'minutes').format('YYYY-MM-DDTHH:mm:00');\n  }\n\n  getComments(postId: string) {\n    return this._postRepository.getComments(postId);\n  }\n\n  getTags(orgId: string) {\n    return this._postRepository.getTags(orgId);\n  }\n\n  createTag(orgId: string, body: CreateTagDto) {\n    return this._postRepository.createTag(orgId, body);\n  }\n\n  editTag(id: string, orgId: string, body: CreateTagDto) {\n    return this._postRepository.editTag(id, orgId, body);\n  }\n\n  deleteTag(id: string, orgId: string) {\n    return this._postRepository.deleteTag(id, orgId);\n  }\n\n  createComment(\n    orgId: string,\n    userId: string,\n    postId: string,\n    comment: string\n  ) {\n    return this._postRepository.createComment(orgId, userId, postId, comment);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/prisma.service.ts",
    "content": "import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';\nimport { PrismaClient } from '@prisma/client';\n\n@Injectable()\nexport class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {\n  constructor() {\n    super({\n      log: [\n        {\n          emit: 'event',\n          level: 'query',\n        },\n      ],\n    });\n  }\n  async onModuleInit() {\n    await this.$connect();\n  }\n\n  async onModuleDestroy() {\n    await this.$disconnect();\n  }\n}\n\n@Injectable()\nexport class PrismaRepository<T extends keyof PrismaService> {\n  public model: Pick<PrismaService, T>;\n  constructor(private _prismaService: PrismaService) {\n    this.model = this._prismaService;\n  }\n}\n\n@Injectable()\nexport class PrismaTransaction {\n  public model: Pick<PrismaService, '$transaction'>;\n  constructor(private _prismaService: PrismaService) {\n    this.model = this._prismaService;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/schema.prisma",
    "content": "generator client {\n  provider = \"prisma-client-js\"\n  runtime  = \"nodejs\"\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Organization {\n  id                  String               @id @default(uuid())\n  name                String\n  description         String?\n  apiKey              String?\n  paymentId           String?\n  streakSince         DateTime?\n  createdAt           DateTime             @default(now())\n  updatedAt           DateTime             @updatedAt\n  allowTrial          Boolean              @default(false)\n  isTrailing          Boolean              @default(false)\n  shortlink           ShortLinkPreference  @default(ASK)\n  autoPost            AutoPost[]\n  Comments            Comments[]\n  credits             Credits[]\n  customers           Customer[]\n  errors              Errors[]\n  github              GitHub[]\n  Integration         Integration[]\n  media               Media[]\n  buyerOrganization   MessagesGroup[]\n  notifications       Notifications[]\n  plugs               Plugs[]\n  post                Post[]               @relation(\"organization\")\n  submittedPost       Post[]               @relation(\"submittedForOrg\")\n  sets                Sets[]\n  signatures          Signatures[]\n  subscription        Subscription?\n  tags                Tags[]\n  thirdParty          ThirdParty[]\n  usedCodes           UsedCodes[]\n  users               UserOrganization[]\n  webhooks            Webhooks[]\n  oauthApp            OAuthApp[]\n  oauthAuthorizations OAuthAuthorization[]\n\n  @@index([apiKey])\n  @@index([streakSince])\n  @@index([paymentId])\n}\n\nmodel Tags {\n  id           String       @id @default(uuid())\n  name         String\n  color        String\n  orgId        String\n  deletedAt    DateTime?\n  createdAt    DateTime     @default(now())\n  updatedAt    DateTime     @updatedAt\n  organization Organization @relation(fields: [orgId], references: [id])\n  posts        TagsPosts[]\n\n  @@index([orgId])\n  @@index([deletedAt])\n}\n\nmodel TagsPosts {\n  postId    String\n  tagId     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  post      Post     @relation(fields: [postId], references: [id])\n  tag       Tags     @relation(fields: [tagId], references: [id])\n\n  @@id([postId, tagId])\n  @@unique([postId, tagId])\n}\n\nmodel User {\n  id                    String               @id @default(uuid())\n  email                 String\n  password              String?\n  providerName          Provider\n  name                  String?\n  lastName              String?\n  isSuperAdmin          Boolean              @default(false)\n  bio                   String?\n  audience              Int                  @default(0)\n  pictureId             String?\n  providerId            String?\n  timezone              Int\n  createdAt             DateTime             @default(now())\n  updatedAt             DateTime             @updatedAt\n  lastReadNotifications DateTime             @default(now())\n  inviteId              String?\n  activated             Boolean              @default(true)\n  account               String?\n  connectedAccount      Boolean              @default(false)\n  lastOnline            DateTime             @default(now())\n  ip                    String?\n  agent                 String?\n  comments              Comments[]\n  items                 ItemUser[]\n  groupBuyer            MessagesGroup[]      @relation(\"groupBuyer\")\n  groupSeller           MessagesGroup[]      @relation(\"groupSeller\")\n  orderBuyer            Orders[]             @relation(\"orderBuyer\")\n  orderSeller           Orders[]             @relation(\"orderSeller\")\n  payoutProblems        PayoutProblems[]\n  agencies              SocialMediaAgency?\n  picture               Media?               @relation(fields: [pictureId], references: [id])\n  organizations         UserOrganization[]\n  sendSuccessEmails     Boolean              @default(true)\n  sendFailureEmails     Boolean              @default(true)\n  sendStreakEmails      Boolean              @default(true)\n  oauthAuthorizations   OAuthAuthorization[]\n\n  @@unique([email, providerName])\n  @@index([lastReadNotifications])\n  @@index([inviteId])\n  @@index([account])\n  @@index([lastOnline])\n  @@index([pictureId])\n}\n\nmodel UsedCodes {\n  id           String       @id @default(uuid())\n  code         String\n  orgId        String\n  createdAt    DateTime     @default(now())\n  updatedAt    DateTime     @updatedAt\n  organization Organization @relation(fields: [orgId], references: [id])\n\n  @@index([code])\n}\n\nmodel UserOrganization {\n  id             String       @id @default(uuid())\n  userId         String\n  organizationId String\n  disabled       Boolean      @default(false)\n  role           Role         @default(USER)\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  organization   Organization @relation(fields: [organizationId], references: [id])\n  user           User         @relation(fields: [userId], references: [id])\n\n  @@unique([userId, organizationId])\n  @@index([disabled])\n}\n\nmodel GitHub {\n  id             String       @id @default(uuid())\n  login          String?\n  name           String?\n  token          String\n  jobId          String?\n  organizationId String\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([login])\n  @@index([organizationId])\n}\n\nmodel Trending {\n  id           String   @id @default(uuid())\n  trendingList String\n  language     String?  @unique\n  hash         String\n  date         DateTime\n  createdAt    DateTime @default(now())\n  updatedAt    DateTime @updatedAt\n\n  @@index([hash])\n}\n\nmodel TrendingLog {\n  id       String   @id @default(uuid())\n  language String?\n  date     DateTime\n}\n\nmodel ItemUser {\n  id     String @id @default(uuid())\n  userId String\n  key    String\n  user   User   @relation(fields: [userId], references: [id])\n\n  @@unique([userId, key])\n  @@index([userId])\n  @@index([key])\n}\n\nmodel Star {\n  id         String   @id @default(uuid())\n  stars      Int\n  totalStars Int\n  forks      Int\n  totalForks Int\n  login      String\n  date       DateTime @default(now()) @db.Date\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@unique([login, date])\n}\n\nmodel Media {\n  id                 String              @id @default(uuid())\n  name               String\n  originalName       String?\n  path               String\n  organizationId     String\n  createdAt          DateTime            @default(now())\n  updatedAt          DateTime            @updatedAt\n  deletedAt          DateTime?\n  fileSize           Int                 @default(0)\n  type               String              @default(\"image\")\n  thumbnail          String?\n  alt                String?\n  thumbnailTimestamp Int?\n  organization       Organization        @relation(fields: [organizationId], references: [id])\n  agencies           SocialMediaAgency[]\n  userPicture        User[]\n  oauthApps          OAuthApp[]\n\n  @@index([name])\n  @@index([organizationId])\n  @@index([type])\n}\n\nmodel SocialMediaAgency {\n  id               String                   @id @default(uuid())\n  userId           String                   @unique\n  name             String\n  logoId           String?\n  website          String?\n  slug             String?\n  facebook         String?\n  instagram        String?\n  twitter          String?\n  linkedIn         String?\n  youtube          String?\n  tiktok           String?\n  otherSocialMedia String?\n  shortDescription String\n  description      String\n  approved         Boolean                  @default(false)\n  createdAt        DateTime                 @default(now())\n  updatedAt        DateTime                 @updatedAt\n  deletedAt        DateTime?\n  logo             Media?                   @relation(fields: [logoId], references: [id])\n  user             User                     @relation(fields: [userId], references: [id])\n  niches           SocialMediaAgencyNiche[]\n\n  @@index([userId])\n  @@index([deletedAt])\n  @@index([id])\n}\n\nmodel SocialMediaAgencyNiche {\n  agencyId String\n  niche    String\n  agency   SocialMediaAgency @relation(fields: [agencyId], references: [id])\n\n  @@id([agencyId, niche])\n}\n\nmodel Credits {\n  id             String       @id @default(uuid())\n  organizationId String\n  credits        Int\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  type           String       @default(\"ai_images\")\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([organizationId])\n  @@index([createdAt])\n}\n\nmodel Subscription {\n  id               String           @id @default(cuid())\n  organizationId   String           @unique\n  subscriptionTier SubscriptionTier\n  identifier       String?\n  cancelAt         DateTime?\n  period           Period\n  totalChannels    Int\n  isLifetime       Boolean          @default(false)\n  createdAt        DateTime         @default(now())\n  updatedAt        DateTime         @updatedAt\n  deletedAt        DateTime?\n  organization     Organization     @relation(fields: [organizationId], references: [id])\n\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel Customer {\n  id           String        @id @default(uuid())\n  name         String\n  orgId        String\n  createdAt    DateTime      @default(now())\n  updatedAt    DateTime      @updatedAt\n  deletedAt    DateTime?\n  organization Organization  @relation(fields: [orgId], references: [id])\n  integrations Integration[]\n\n  @@unique([orgId, name, deletedAt])\n}\n\nmodel Integration {\n  id                    String                 @id @default(cuid())\n  internalId            String\n  organizationId        String\n  name                  String\n  picture               String?\n  providerIdentifier    String\n  type                  String\n  token                 String\n  disabled              Boolean                @default(false)\n  tokenExpiration       DateTime?\n  refreshToken          String?\n  profile               String?\n  deletedAt             DateTime?\n  createdAt             DateTime               @default(now())\n  updatedAt             DateTime?              @updatedAt\n  inBetweenSteps        Boolean                @default(false)\n  refreshNeeded         Boolean                @default(false)\n  postingTimes          String                 @default(\"[{\\\"time\\\":120}, {\\\"time\\\":400}, {\\\"time\\\":700}]\")\n  customInstanceDetails String?\n  customerId            String?\n  rootInternalId        String?\n  additionalSettings    String?                @default(\"[]\")\n  exisingPlugData       ExisingPlugData[]\n  customer              Customer?              @relation(fields: [customerId], references: [id])\n  organization          Organization           @relation(fields: [organizationId], references: [id])\n  webhooks              IntegrationsWebhooks[]\n  orderItems            OrderItems[]\n  plugs                 Plugs[]\n  posts                 Post[]\n\n  @@unique([organizationId, internalId])\n  @@index([rootInternalId])\n  @@index([organizationId])\n  @@index([providerIdentifier])\n  @@index([updatedAt])\n  @@index([createdAt])\n  @@index([deletedAt])\n  @@index([customerId])\n  @@index([inBetweenSteps])\n  @@index([refreshNeeded])\n  @@index([disabled])\n}\n\nmodel Signatures {\n  id             String       @id @default(uuid())\n  organizationId String\n  content        String\n  autoAdd        Boolean\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  deletedAt      DateTime?\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([createdAt])\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel Comments {\n  id             String       @id @default(uuid())\n  content        String\n  organizationId String\n  postId         String\n  userId         String\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  deletedAt      DateTime?\n  organization   Organization @relation(fields: [organizationId], references: [id])\n  post           Post         @relation(fields: [postId], references: [id])\n  user           User         @relation(fields: [userId], references: [id])\n\n  @@index([createdAt])\n  @@index([organizationId])\n  @@index([userId])\n  @@index([postId])\n  @@index([deletedAt])\n}\n\nmodel Post {\n  id                         String                    @id @default(cuid())\n  state                      State                     @default(QUEUE)\n  publishDate                DateTime\n  organizationId             String\n  integrationId              String\n  content                    String\n  delay                      Int                       @default(0)\n  group                      String\n  title                      String?\n  description                String?\n  parentPostId               String?\n  releaseId                  String?\n  releaseURL                 String?\n  settings                   String?\n  image                      String?\n  submittedForOrderId        String?\n  submittedForOrganizationId String?\n  approvedSubmitForOrder     APPROVED_SUBMIT_FOR_ORDER @default(NO)\n  lastMessageId              String?\n  intervalInDays             Int?\n  error                      String?\n  createdAt                  DateTime                  @default(now())\n  updatedAt                  DateTime                  @updatedAt\n  deletedAt                  DateTime?\n  comments                   Comments[]\n  errors                     Errors[]\n  payoutProblems             PayoutProblems[]\n  integration                Integration               @relation(fields: [integrationId], references: [id])\n  lastMessage                Messages?                 @relation(fields: [lastMessageId], references: [id])\n  organization               Organization              @relation(\"organization\", fields: [organizationId], references: [id])\n  parentPost                 Post?                     @relation(\"parentPostId\", fields: [parentPostId], references: [id])\n  childrenPost               Post[]                    @relation(\"parentPostId\")\n  submittedForOrder          Orders?                   @relation(fields: [submittedForOrderId], references: [id])\n  submittedForOrganization   Organization?             @relation(\"submittedForOrg\", fields: [submittedForOrganizationId], references: [id])\n  tags                       TagsPosts[]\n\n  @@index([group])\n  @@index([deletedAt])\n  @@index([publishDate])\n  @@index([state])\n  @@index([organizationId])\n  @@index([parentPostId])\n  @@index([submittedForOrderId])\n  @@index([intervalInDays])\n  @@index([approvedSubmitForOrder])\n  @@index([lastMessageId])\n  @@index([createdAt])\n  @@index([updatedAt])\n  @@index([releaseURL])\n  @@index([integrationId])\n}\n\nmodel Notifications {\n  id             String       @id @default(uuid())\n  organizationId String\n  content        String\n  link           String?\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  deletedAt      DateTime?\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([createdAt])\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel MessagesGroup {\n  id                  String       @id @default(uuid())\n  buyerOrganizationId String\n  buyerId             String\n  sellerId            String\n  createdAt           DateTime     @default(now())\n  updatedAt           DateTime     @updatedAt\n  messages            Messages[]\n  buyer               User         @relation(\"groupBuyer\", fields: [buyerId], references: [id])\n  buyerOrganization   Organization @relation(fields: [buyerOrganizationId], references: [id])\n  seller              User         @relation(\"groupSeller\", fields: [sellerId], references: [id])\n  orders              Orders[]\n\n  @@unique([buyerId, sellerId])\n  @@index([createdAt])\n  @@index([updatedAt])\n  @@index([buyerOrganizationId])\n}\n\nmodel PayoutProblems {\n  id        String   @id @default(uuid())\n  status    String\n  orderId   String\n  userId    String\n  postId    String?\n  amount    Int\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  order     Orders   @relation(fields: [orderId], references: [id])\n  post      Post?    @relation(fields: [postId], references: [id])\n  user      User     @relation(fields: [userId], references: [id])\n}\n\nmodel Orders {\n  id             String           @id @default(uuid())\n  buyerId        String\n  sellerId       String\n  status         OrderStatus\n  messageGroupId String\n  captureId      String?\n  createdAt      DateTime         @default(now())\n  updatedAt      DateTime         @updatedAt\n  ordersItems    OrderItems[]\n  buyer          User             @relation(\"orderBuyer\", fields: [buyerId], references: [id])\n  messageGroup   MessagesGroup    @relation(fields: [messageGroupId], references: [id])\n  seller         User             @relation(\"orderSeller\", fields: [sellerId], references: [id])\n  payoutProblems PayoutProblems[]\n  posts          Post[]\n\n  @@index([buyerId])\n  @@index([sellerId])\n  @@index([updatedAt])\n  @@index([createdAt])\n  @@index([messageGroupId])\n}\n\nmodel OrderItems {\n  id            String      @id @default(uuid())\n  orderId       String\n  integrationId String\n  quantity      Int\n  price         Int\n  integration   Integration @relation(fields: [integrationId], references: [id])\n  order         Orders      @relation(fields: [orderId], references: [id])\n\n  @@index([orderId])\n  @@index([integrationId])\n}\n\nmodel Messages {\n  id        String        @id @default(uuid())\n  from      From\n  content   String?\n  groupId   String\n  special   String?\n  createdAt DateTime      @default(now())\n  updatedAt DateTime      @updatedAt\n  deletedAt DateTime?\n  group     MessagesGroup @relation(fields: [groupId], references: [id])\n  posts     Post[]\n\n  @@index([groupId])\n  @@index([createdAt])\n  @@index([deletedAt])\n}\n\nmodel Plugs {\n  id             String       @id @default(uuid())\n  organizationId String\n  plugFunction   String\n  data           String\n  integrationId  String\n  activated      Boolean      @default(true)\n  integration    Integration  @relation(fields: [integrationId], references: [id])\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@unique([plugFunction, integrationId])\n  @@index([organizationId])\n}\n\nmodel ExisingPlugData {\n  id            String      @id @default(uuid())\n  integrationId String\n  methodName    String\n  value         String\n  integration   Integration @relation(fields: [integrationId], references: [id])\n\n  @@unique([integrationId, methodName, value])\n}\n\nmodel PopularPosts {\n  id        String   @id @default(uuid())\n  category  String\n  topic     String\n  content   String\n  hook      String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n}\n\nmodel IntegrationsWebhooks {\n  integrationId String\n  webhookId     String\n  integration   Integration @relation(fields: [integrationId], references: [id])\n  webhook       Webhooks    @relation(fields: [webhookId], references: [id])\n\n  @@id([integrationId, webhookId])\n  @@unique([integrationId, webhookId])\n  @@index([integrationId])\n  @@index([webhookId])\n}\n\nmodel Webhooks {\n  id             String                 @id @default(uuid())\n  name           String\n  organizationId String\n  url            String\n  deletedAt      DateTime?\n  createdAt      DateTime               @default(now())\n  updatedAt      DateTime               @updatedAt\n  integrations   IntegrationsWebhooks[]\n  organization   Organization           @relation(fields: [organizationId], references: [id])\n\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel AutoPost {\n  id              String       @id @default(uuid())\n  organizationId  String\n  title           String\n  content         String?\n  onSlot          Boolean\n  syncLast        Boolean\n  url             String\n  lastUrl         String\n  active          Boolean\n  addPicture      Boolean\n  generateContent Boolean\n  integrations    String\n  deletedAt       DateTime?\n  createdAt       DateTime     @default(now())\n  updatedAt       DateTime     @updatedAt\n  organization    Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([deletedAt])\n}\n\nmodel Sets {\n  id             String       @id @default(uuid())\n  organizationId String\n  name           String\n  content        String\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@index([organizationId])\n}\n\nmodel ThirdParty {\n  id             String       @id @default(uuid())\n  organizationId String\n  identifier     String\n  name           String\n  internalId     String\n  apiKey         String\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  deletedAt      DateTime?\n  organization   Organization @relation(fields: [organizationId], references: [id])\n\n  @@unique([organizationId, internalId])\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel Errors {\n  id             String       @id @default(uuid())\n  message        String\n  platform       String\n  organizationId String\n  createdAt      DateTime     @default(now())\n  updatedAt      DateTime     @updatedAt\n  postId         String\n  body           String       @default(\"{}\")\n  organization   Organization @relation(fields: [organizationId], references: [id])\n  post           Post         @relation(fields: [postId], references: [id])\n\n  @@index([organizationId])\n  @@index([createdAt])\n}\n\nmodel Mentions {\n  name      String\n  username  String\n  platform  String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  image     String\n\n  @@id([name, username, platform, image])\n  @@index([createdAt])\n}\n\n/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.\nmodel mastra_ai_spans {\n  traceId      String\n  spanId       String\n  parentSpanId String?\n  name         String\n  scope        Json?\n  spanType     String\n  attributes   Json?\n  metadata     Json?\n  links        Json?\n  input        Json?\n  output       Json?\n  error        Json?\n  startedAt    DateTime  @db.Timestamp(6)\n  endedAt      DateTime? @db.Timestamp(6)\n  createdAt    DateTime  @db.Timestamp(6)\n  updatedAt    DateTime? @db.Timestamp(6)\n  isEvent      Boolean\n  startedAtZ   DateTime? @default(now()) @db.Timestamptz(6)\n  endedAtZ     DateTime? @default(now()) @db.Timestamptz(6)\n  createdAtZ   DateTime? @default(now()) @db.Timestamptz(6)\n  updatedAtZ   DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@index([name], map: \"public_mastra_ai_spans_name_idx\")\n  @@index([parentSpanId, startedAt(sort: Desc)], map: \"public_mastra_ai_spans_parentspanid_startedat_idx\")\n  @@index([spanType, startedAt(sort: Desc)], map: \"public_mastra_ai_spans_spantype_startedat_idx\")\n  @@index([traceId, startedAt(sort: Desc)], map: \"public_mastra_ai_spans_traceid_startedat_idx\")\n  @@ignore\n}\n\n/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.\nmodel mastra_evals {\n  input         String\n  output        String\n  result        Json\n  agent_name    String\n  metric_name   String\n  instructions  String\n  test_info     Json?\n  global_run_id String\n  run_id        String\n  created_at    DateTime  @db.Timestamp(6)\n  createdAt     DateTime? @db.Timestamp(6)\n  created_atZ   DateTime? @default(now()) @db.Timestamptz(6)\n  createdAtZ    DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@index([agent_name, created_at(sort: Desc)], map: \"public_mastra_evals_agent_name_created_at_idx\")\n  @@ignore\n}\n\nmodel mastra_messages {\n  id         String    @id\n  thread_id  String\n  content    String\n  role       String\n  type       String\n  createdAt  DateTime  @db.Timestamp(6)\n  resourceId String?\n  createdAtZ DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@index([thread_id, createdAt(sort: Desc)], map: \"public_mastra_messages_thread_id_createdat_idx\")\n}\n\nmodel mastra_resources {\n  id            String    @id\n  workingMemory String?\n  metadata      Json?\n  createdAt     DateTime  @db.Timestamp(6)\n  updatedAt     DateTime  @db.Timestamp(6)\n  createdAtZ    DateTime? @default(now()) @db.Timestamptz(6)\n  updatedAtZ    DateTime? @default(now()) @db.Timestamptz(6)\n}\n\nmodel mastra_scorers {\n  id                   String    @id\n  scorerId             String\n  traceId              String?\n  runId                String\n  scorer               Json\n  preprocessStepResult Json?\n  extractStepResult    Json?\n  analyzeStepResult    Json?\n  score                Float\n  reason               String?\n  metadata             Json?\n  preprocessPrompt     String?\n  extractPrompt        String?\n  generateScorePrompt  String?\n  generateReasonPrompt String?\n  analyzePrompt        String?\n  reasonPrompt         String?\n  input                Json\n  output               Json\n  additionalContext    Json?\n  runtimeContext       Json?\n  entityType           String?\n  entity               Json?\n  entityId             String?\n  source               String\n  resourceId           String?\n  threadId             String?\n  createdAt            DateTime  @db.Timestamp(6)\n  updatedAt            DateTime  @db.Timestamp(6)\n  createdAtZ           DateTime? @default(now()) @db.Timestamptz(6)\n  updatedAtZ           DateTime? @default(now()) @db.Timestamptz(6)\n  spanId               String?\n\n  @@index([traceId, spanId, createdAt(sort: Desc)], map: \"public_mastra_scores_trace_id_span_id_created_at_idx\")\n}\n\nmodel mastra_threads {\n  id         String    @id\n  resourceId String\n  title      String\n  metadata   String?\n  createdAt  DateTime  @db.Timestamp(6)\n  updatedAt  DateTime  @db.Timestamp(6)\n  createdAtZ DateTime? @default(now()) @db.Timestamptz(6)\n  updatedAtZ DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@index([resourceId, createdAt(sort: Desc)], map: \"public_mastra_threads_resourceid_createdat_idx\")\n}\n\nmodel mastra_traces {\n  id           String    @id\n  parentSpanId String?\n  name         String\n  traceId      String\n  scope        String\n  kind         Int\n  attributes   Json?\n  status       Json?\n  events       Json?\n  links        Json?\n  other        String?\n  startTime    BigInt\n  endTime      BigInt\n  createdAt    DateTime  @db.Timestamp(6)\n  createdAtZ   DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@index([name, startTime(sort: Desc)], map: \"public_mastra_traces_name_starttime_idx\")\n}\n\nmodel mastra_workflow_snapshot {\n  workflow_name String\n  run_id        String\n  resourceId    String?\n  snapshot      String\n  createdAt     DateTime  @db.Timestamp(6)\n  updatedAt     DateTime  @db.Timestamp(6)\n  createdAtZ    DateTime? @default(now()) @db.Timestamptz(6)\n  updatedAtZ    DateTime? @default(now()) @db.Timestamptz(6)\n\n  @@unique([workflow_name, run_id], map: \"public_mastra_workflow_snapshot_workflow_name_run_id_key\")\n}\n\nmodel OAuthApp {\n  id             String               @id @default(uuid())\n  organizationId String\n  name           String\n  description    String?\n  pictureId      String?\n  redirectUrl    String\n  clientId       String               @unique\n  clientSecret   String\n  createdAt      DateTime             @default(now())\n  updatedAt      DateTime             @updatedAt\n  deletedAt      DateTime?\n  organization   Organization         @relation(fields: [organizationId], references: [id])\n  picture        Media?               @relation(fields: [pictureId], references: [id])\n  authorizations OAuthAuthorization[]\n\n  @@unique([organizationId, deletedAt])\n  @@index([clientId])\n  @@index([organizationId])\n  @@index([deletedAt])\n}\n\nmodel OAuthAuthorization {\n  id                String       @id @default(uuid())\n  oauthAppId        String\n  userId            String\n  organizationId    String\n  accessToken       String?\n  authorizationCode String?\n  codeExpiresAt     DateTime?\n  revokedAt         DateTime?\n  createdAt         DateTime     @default(now())\n  updatedAt         DateTime     @updatedAt\n  oauthApp          OAuthApp     @relation(fields: [oauthAppId], references: [id])\n  user              User         @relation(fields: [userId], references: [id])\n  organization      Organization @relation(fields: [organizationId], references: [id])\n\n  @@unique([oauthAppId, userId, organizationId])\n  @@index([accessToken])\n  @@index([authorizationCode])\n  @@index([oauthAppId])\n  @@index([userId])\n  @@index([organizationId])\n  @@index([revokedAt])\n}\n\nenum OrderStatus {\n  PENDING\n  ACCEPTED\n  CANCELED\n  COMPLETED\n}\n\nenum From {\n  BUYER\n  SELLER\n}\n\nenum State {\n  QUEUE\n  PUBLISHED\n  ERROR\n  DRAFT\n}\n\nenum SubscriptionTier {\n  STANDARD\n  PRO\n  TEAM\n  ULTIMATE\n}\n\nenum Period {\n  MONTHLY\n  YEARLY\n}\n\nenum Provider {\n  LOCAL\n  GITHUB\n  GOOGLE\n  FARCASTER\n  WALLET\n  GENERIC\n}\n\nenum Role {\n  SUPERADMIN\n  ADMIN\n  USER\n}\n\nenum APPROVED_SUBMIT_FOR_ORDER {\n  NO\n  WAITING_CONFIRMATION\n  YES\n}\n\nenum ShortLinkPreference {\n  ASK\n  YES\n  NO\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/sets/sets.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { SetsDto } from '@gitroom/nestjs-libraries/dtos/sets/sets.dto';\nimport { v4 as uuidv4 } from 'uuid';\n\n@Injectable()\nexport class SetsRepository {\n  constructor(private _sets: PrismaRepository<'sets'>) {}\n\n  getTotal(orgId: string) {\n    return this._sets.model.sets.count({\n      where: {\n        organizationId: orgId,\n      },\n    });\n  }\n\n  getSets(orgId: string) {\n    return this._sets.model.sets.findMany({\n      where: {\n        organizationId: orgId,\n      },\n      orderBy: {\n        createdAt: 'desc',\n      },\n    });\n  }\n\n  deleteSet(orgId: string, id: string) {\n    return this._sets.model.sets.delete({\n      where: {\n        id,\n        organizationId: orgId,\n      },\n    });\n  }\n\n  async createSet(orgId: string, body: SetsDto) {\n    const { id } = await this._sets.model.sets.upsert({\n      where: {\n        id: body.id || uuidv4(),\n        organizationId: orgId,\n      },\n      create: {\n        id: body.id || uuidv4(),\n        organizationId: orgId,\n        name: body.name,\n        content: body.content,\n      },\n      update: {\n        name: body.name,\n        content: body.content,\n      },\n    });\n\n    return { id };\n  }\n} "
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/sets/sets.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { SetsRepository } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.repository';\nimport { SetsDto } from '@gitroom/nestjs-libraries/dtos/sets/sets.dto';\n\n@Injectable()\nexport class SetsService {\n  constructor(private _setsRepository: SetsRepository) {}\n\n  getTotal(orgId: string) {\n    return this._setsRepository.getTotal(orgId);\n  }\n\n  getSets(orgId: string) {\n    return this._setsRepository.getSets(orgId);\n  }\n\n  createSet(orgId: string, body: SetsDto) {\n    return this._setsRepository.createSet(orgId, body);\n  }\n\n  deleteSet(orgId: string, id: string) {\n    return this._setsRepository.deleteSet(orgId, id);\n  }\n} "
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/signatures/signature.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { v4 as uuidv4 } from 'uuid';\nimport { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';\n\n@Injectable()\nexport class SignatureRepository {\n  constructor(private _signatures: PrismaRepository<'signatures'>) {}\n\n  getSignaturesByOrgId(orgId: string) {\n    return this._signatures.model.signatures.findMany({\n      where: { organizationId: orgId, deletedAt: null },\n    });\n  }\n\n  getDefaultSignature(orgId: string) {\n    return this._signatures.model.signatures.findFirst({\n      where: { organizationId: orgId, autoAdd: true, deletedAt: null },\n    });\n  }\n\n  async createOrUpdateSignature(\n    orgId: string,\n    signature: SignatureDto,\n    id?: string\n  ) {\n    const values = {\n      organizationId: orgId,\n      content: signature.content,\n      autoAdd: signature.autoAdd,\n    };\n\n    const { id: updatedId } = await this._signatures.model.signatures.upsert({\n      where: { id: id || uuidv4(), organizationId: orgId },\n      update: values,\n      create: values,\n    });\n\n    if (values.autoAdd) {\n      await this._signatures.model.signatures.updateMany({\n        where: { organizationId: orgId, id: { not: updatedId } },\n        data: { autoAdd: false },\n      });\n    }\n\n    return { id: updatedId };\n  }\n\n  deleteSignature(orgId: string, id: string) {\n    return this._signatures.model.signatures.update({\n      where: { id, organizationId: orgId },\n      data: { deletedAt: new Date() },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/signatures/signature.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { SignatureRepository } from '@gitroom/nestjs-libraries/database/prisma/signatures/signature.repository';\nimport { SignatureDto } from '@gitroom/nestjs-libraries/dtos/signature/signature.dto';\n\n@Injectable()\nexport class SignatureService {\n  constructor(private _signatureRepository: SignatureRepository) {}\n\n  getSignaturesByOrgId(orgId: string) {\n    return this._signatureRepository.getSignaturesByOrgId(orgId);\n  }\n\n  getDefaultSignature(orgId: string) {\n    return this._signatureRepository.getDefaultSignature(orgId);\n  }\n\n  createOrUpdateSignature(orgId: string, signature: SignatureDto, id?: string) {\n    return this._signatureRepository.createOrUpdateSignature(\n      orgId,\n      signature,\n      id\n    );\n  }\n\n  deleteSignature(orgId: string, id: string) {\n    return this._signatureRepository.deleteSignature(orgId, id);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts",
    "content": "export interface PricingInnerInterface {\n  current: string;\n  month_price: number;\n  year_price: number;\n  channel?: number;\n  posts_per_month: number;\n  team_members: boolean;\n  community_features: boolean;\n  featured_by_gitroom: boolean;\n  ai: boolean;\n  import_from_channels: boolean;\n  image_generator?: boolean;\n  image_generation_count: number;\n  generate_videos: number;\n  public_api: boolean;\n  webhooks: number;\n  autoPost: boolean;\n}\nexport interface PricingInterface {\n  [key: string]: PricingInnerInterface;\n}\nexport const pricing: PricingInterface = {\n  FREE: {\n    current: 'FREE',\n    month_price: 0,\n    year_price: 0,\n    channel: 0,\n    image_generation_count: 0,\n    posts_per_month: 0,\n    team_members: false,\n    community_features: false,\n    featured_by_gitroom: false,\n    ai: false,\n    import_from_channels: false,\n    image_generator: false,\n    public_api: false,\n    webhooks: 0,\n    autoPost: false,\n    generate_videos: 0,\n  },\n  STANDARD: {\n    current: 'STANDARD',\n    month_price: 29,\n    year_price: 278,\n    channel: 5,\n    posts_per_month: 400,\n    image_generation_count: 20,\n    team_members: false,\n    ai: true,\n    community_features: false,\n    featured_by_gitroom: false,\n    import_from_channels: true,\n    image_generator: false,\n    public_api: true,\n    webhooks: 2,\n    autoPost: false,\n    generate_videos: 3,\n  },\n  TEAM: {\n    current: 'TEAM',\n    month_price: 39,\n    year_price: 374,\n    channel: 10,\n    posts_per_month: 1000000,\n    image_generation_count: 100,\n    community_features: true,\n    team_members: true,\n    featured_by_gitroom: true,\n    ai: true,\n    import_from_channels: true,\n    image_generator: true,\n    public_api: true,\n    webhooks: 10,\n    autoPost: true,\n    generate_videos: 10,\n  },\n  PRO: {\n    current: 'PRO',\n    month_price: 49,\n    year_price: 470,\n    channel: 30,\n    posts_per_month: 1000000,\n    image_generation_count: 300,\n    community_features: true,\n    team_members: true,\n    featured_by_gitroom: true,\n    ai: true,\n    import_from_channels: true,\n    image_generator: true,\n    public_api: true,\n    webhooks: 30,\n    autoPost: true,\n    generate_videos: 30,\n  },\n  ULTIMATE: {\n    current: 'ULTIMATE',\n    month_price: 99,\n    year_price: 950,\n    channel: 100,\n    posts_per_month: 1000000,\n    image_generation_count: 500,\n    community_features: true,\n    team_members: true,\n    featured_by_gitroom: true,\n    ai: true,\n    import_from_channels: true,\n    image_generator: true,\n    public_api: true,\n    webhooks: 10000,\n    autoPost: true,\n    generate_videos: 60,\n  },\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport {\n  PrismaRepository,\n  PrismaTransaction,\n} from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport dayjs from 'dayjs';\nimport { Organization } from '@prisma/client';\n\n@Injectable()\nexport class SubscriptionRepository {\n  constructor(\n    private readonly _subscription: PrismaRepository<'subscription'>,\n    private readonly _organization: PrismaRepository<'organization'>,\n    private readonly _user: PrismaRepository<'user'>,\n    private readonly _credits: PrismaRepository<'credits'>,\n    private _usedCodes: PrismaRepository<'usedCodes'>\n  ) {}\n\n  getUserAccount(userId: string) {\n    return this._user.model.user.findFirst({\n      where: {\n        id: userId,\n      },\n      select: {\n        account: true,\n        connectedAccount: true,\n      },\n    });\n  }\n\n  getCode(code: string) {\n    return this._usedCodes.model.usedCodes.findFirst({\n      where: {\n        code,\n      },\n    });\n  }\n\n  updateAccount(userId: string, account: string) {\n    return this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        account,\n      },\n    });\n  }\n\n  getSubscriptionByOrganizationId(organizationId: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        organizationId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  updateConnectedStatus(account: string, accountCharges: boolean) {\n    return this._user.model.user.updateMany({\n      where: {\n        account,\n      },\n      data: {\n        connectedAccount: accountCharges,\n      },\n    });\n  }\n\n  getCustomerIdByOrgId(organizationId: string) {\n    return this._organization.model.organization.findFirst({\n      where: {\n        id: organizationId,\n      },\n      select: {\n        paymentId: true,\n      },\n    });\n  }\n\n  checkSubscription(organizationId: string, subscriptionId: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        organizationId,\n        identifier: subscriptionId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  deleteSubscriptionByCustomerId(customerId: string) {\n    return this._subscription.model.subscription.deleteMany({\n      where: {\n        organization: {\n          paymentId: customerId,\n        },\n      },\n    });\n  }\n\n  updateCustomerId(organizationId: string, customerId: string) {\n    return this._organization.model.organization.update({\n      where: {\n        id: organizationId,\n      },\n      data: {\n        paymentId: customerId,\n      },\n    });\n  }\n\n  async getSubscriptionByOrgId(orgId: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        organizationId: orgId,\n      },\n    });\n  }\n\n  async getSubscriptionByCustomerId(customerId: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        organization: {\n          paymentId: customerId,\n        },\n      },\n    });\n  }\n\n  async getOrganizationByCustomerId(customerId: string) {\n    return this._organization.model.organization.findFirst({\n      where: {\n        paymentId: customerId,\n      },\n    });\n  }\n\n  async createOrUpdateSubscription(\n    isTrailing: boolean,\n    identifier: string,\n    customerId: string,\n    totalChannels: number,\n    billing: 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE',\n    period: 'MONTHLY' | 'YEARLY',\n    cancelAt: number | null,\n    code?: string,\n    org?: { id: string }\n  ) {\n    const findOrg =\n      org || (await this.getOrganizationByCustomerId(customerId))!;\n\n    if (!findOrg) {\n      return;\n    }\n\n    await this._subscription.model.subscription.upsert({\n      where: {\n        organizationId: findOrg.id,\n        ...(!code\n          ? {\n              organization: {\n                paymentId: customerId,\n              },\n            }\n          : {}),\n      },\n      update: {\n        subscriptionTier: billing,\n        totalChannels,\n        period,\n        identifier,\n        isLifetime: !!code,\n        cancelAt: cancelAt ? new Date(cancelAt * 1000) : null,\n        deletedAt: null,\n      },\n      create: {\n        organizationId: findOrg.id,\n        subscriptionTier: billing,\n        isLifetime: !!code,\n        totalChannels,\n        period,\n        cancelAt: cancelAt ? new Date(cancelAt * 1000) : null,\n        identifier,\n        deletedAt: null,\n      },\n    });\n\n    await this._organization.model.organization.update({\n      where: {\n        id: findOrg.id,\n      },\n      data: {\n        isTrailing,\n        allowTrial: false,\n      },\n    });\n\n    if (code) {\n      await this._usedCodes.model.usedCodes.create({\n        data: {\n          code,\n          orgId: findOrg.id,\n        },\n      });\n    }\n  }\n\n  getSubscriptionByIdentifier(identifier: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        identifier,\n        deletedAt: null,\n      },\n      include: {\n        organization: true,\n      },\n    });\n  }\n\n  getSubscription(organizationId: string) {\n    return this._subscription.model.subscription.findFirst({\n      where: {\n        organizationId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  async getCreditsFrom(\n    organizationId: string,\n    from: dayjs.Dayjs,\n    type = 'ai_images'\n  ) {\n    const load = await this._credits.model.credits.groupBy({\n      by: ['organizationId'],\n      where: {\n        organizationId,\n        type,\n        createdAt: {\n          gte: from.toDate(),\n        },\n      },\n      _sum: {\n        credits: true,\n      },\n    });\n\n    return load?.[0]?._sum?.credits || 0;\n  }\n\n  async useCredit<T>(\n    org: Organization,\n    type = 'ai_images',\n    func: () => Promise<T>\n  ) {\n    const data = await this._credits.model.credits.create({\n      data: {\n        organizationId: org.id,\n        credits: 1,\n        type,\n      },\n    });\n\n    try {\n      return await func();\n    } catch (err) {\n      await this._credits.model.credits.delete({\n        where: {\n          id: data.id,\n        },\n      });\n      throw err;\n    }\n  }\n\n  setCustomerId(orgId: string, customerId: string) {\n    return this._organization.model.organization.update({\n      where: {\n        id: orgId,\n      },\n      data: {\n        paymentId: customerId,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { SubscriptionRepository } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.repository';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { Organization } from '@prisma/client';\nimport dayjs from 'dayjs';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\n\n@Injectable()\nexport class SubscriptionService {\n  constructor(\n    private readonly _subscriptionRepository: SubscriptionRepository,\n    private readonly _integrationService: IntegrationService,\n    private readonly _organizationService: OrganizationService\n  ) {}\n\n  getSubscriptionByOrganizationId(organizationId: string) {\n    return this._subscriptionRepository.getSubscriptionByOrganizationId(\n      organizationId\n    );\n  }\n\n  useCredit<T>(\n    organization: Organization,\n    type = 'ai_images',\n    func: () => Promise<T>\n  ): Promise<T> {\n    return this._subscriptionRepository.useCredit(organization, type, func);\n  }\n\n  getCode(code: string) {\n    return this._subscriptionRepository.getCode(code);\n  }\n\n  async deleteSubscription(customerId: string) {\n    await this.modifySubscription(\n      customerId,\n      pricing.FREE.channel || 0,\n      'FREE'\n    );\n    return this._subscriptionRepository.deleteSubscriptionByCustomerId(\n      customerId\n    );\n  }\n\n  updateCustomerId(organizationId: string, customerId: string) {\n    return this._subscriptionRepository.updateCustomerId(\n      organizationId,\n      customerId\n    );\n  }\n\n  async checkSubscription(organizationId: string, subscriptionId: string) {\n    return await this._subscriptionRepository.checkSubscription(\n      organizationId,\n      subscriptionId\n    );\n  }\n\n  async modifySubscriptionByOrg(\n    organizationId: string,\n    totalChannels: number,\n    billing: 'FREE' | 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE'\n  ) {\n    if (!organizationId) {\n      return false;\n    }\n\n    const getCurrentSubscription =\n      (await this._subscriptionRepository.getSubscriptionByOrgId(\n        organizationId\n      ))!;\n\n    const from = pricing[getCurrentSubscription?.subscriptionTier || 'FREE'];\n    const to = pricing[billing];\n\n    const currentTotalChannels = (\n      await this._integrationService.getIntegrationsList(organizationId)\n    ).filter((f) => !f.disabled);\n\n    if (currentTotalChannels.length > totalChannels) {\n      await this._integrationService.disableIntegrations(\n        organizationId,\n        currentTotalChannels.length - totalChannels\n      );\n    }\n\n    if (from.team_members && !to.team_members) {\n      await this._organizationService.disableOrEnableNonSuperAdminUsers(\n        organizationId,\n        true\n      );\n    }\n\n    if (!from.team_members && to.team_members) {\n      await this._organizationService.disableOrEnableNonSuperAdminUsers(\n        organizationId,\n        false\n      );\n    }\n\n    if (billing === 'FREE') {\n      await this._integrationService.changeActiveCron(organizationId);\n    }\n\n    return true;\n  }\n\n  async modifySubscription(\n    customerId: string,\n    totalChannels: number,\n    billing: 'FREE' | 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE'\n  ) {\n    if (!customerId) {\n      return false;\n    }\n\n    const getOrgByCustomerId =\n      await this._subscriptionRepository.getOrganizationByCustomerId(\n        customerId\n      );\n\n    const getCurrentSubscription =\n      (await this._subscriptionRepository.getSubscriptionByCustomerId(\n        customerId\n      ))!;\n\n    if (\n      !getOrgByCustomerId ||\n      (getCurrentSubscription && getCurrentSubscription?.isLifetime)\n    ) {\n      return false;\n    }\n\n    const from = pricing[getCurrentSubscription?.subscriptionTier || 'FREE'];\n    const to = pricing[billing];\n\n    const currentTotalChannels = (\n      await this._integrationService.getIntegrationsList(\n        getOrgByCustomerId?.id!\n      )\n    ).filter((f) => !f.disabled);\n\n    if (currentTotalChannels.length > totalChannels) {\n      await this._integrationService.disableIntegrations(\n        getOrgByCustomerId?.id!,\n        currentTotalChannels.length - totalChannels\n      );\n    }\n\n    if (from.team_members && !to.team_members) {\n      await this._organizationService.disableOrEnableNonSuperAdminUsers(\n        getOrgByCustomerId?.id!,\n        true\n      );\n    }\n\n    if (!from.team_members && to.team_members) {\n      await this._organizationService.disableOrEnableNonSuperAdminUsers(\n        getOrgByCustomerId?.id!,\n        false\n      );\n    }\n\n    if (billing === 'FREE') {\n      await this._integrationService.changeActiveCron(getOrgByCustomerId?.id!);\n    }\n\n    return true;\n  }\n\n  async createOrUpdateSubscription(\n    isTrailing: boolean,\n    identifier: string,\n    customerId: string,\n    totalChannels: number,\n    billing: 'STANDARD' | 'TEAM' | 'PRO' | 'ULTIMATE',\n    period: 'MONTHLY' | 'YEARLY',\n    cancelAt: number | null,\n    code?: string,\n    org?: string\n  ) {\n    if (!code) {\n      try {\n        const load = await this.modifySubscription(\n          customerId,\n          totalChannels,\n          billing\n        );\n        if (!load) {\n          return {};\n        }\n      } catch (e) {\n        return {};\n      }\n    }\n    return this._subscriptionRepository.createOrUpdateSubscription(\n      isTrailing,\n      identifier,\n      customerId,\n      totalChannels,\n      billing,\n      period,\n      cancelAt,\n      code,\n      org ? { id: org } : undefined\n    );\n  }\n\n  getSubscriptionByIdentifier(identifier: string) {\n    return this._subscriptionRepository.getSubscriptionByIdentifier(identifier);\n  }\n\n  async getSubscription(organizationId: string) {\n    return this._subscriptionRepository.getSubscription(organizationId);\n  }\n\n  async checkCredits(organization: Organization, checkType = 'ai_images') {\n    // @ts-ignore\n    const type = organization?.subscription?.subscriptionTier || 'FREE';\n\n    if (type === 'FREE') {\n      return { credits: 0 };\n    }\n\n    // @ts-ignore\n    let date = dayjs(organization.subscription.createdAt);\n    while (date.isBefore(dayjs())) {\n      date = date.add(1, 'month');\n    }\n\n    const checkFromMonth = date.subtract(1, 'month');\n    const imageGenerationCount =\n      checkType === 'ai_images'\n        ? pricing[type].image_generation_count\n        : pricing[type].generate_videos;\n\n    const totalUse = await this._subscriptionRepository.getCreditsFrom(\n      organization.id,\n      checkFromMonth,\n      checkType\n    );\n\n    return {\n      credits: imageGenerationCount - totalUse,\n    };\n  }\n\n  async lifeTime(orgId: string, identifier: string, subscription: any) {\n    return this.createOrUpdateSubscription(\n      false,\n      identifier,\n      identifier,\n      pricing[subscription].channel!,\n      subscription,\n      'YEARLY',\n      null,\n      identifier,\n      orgId\n    );\n  }\n\n  async addSubscription(orgId: string, userId: string, subscription: any) {\n    await this._subscriptionRepository.setCustomerId(orgId, userId);\n    return this.createOrUpdateSubscription(\n      false,\n      makeId(5),\n      userId,\n      pricing[subscription].channel!,\n      subscription,\n      'MONTHLY',\n      null,\n      undefined,\n      orgId\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/third-party/third-party.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\n\n@Injectable()\nexport class ThirdPartyRepository {\n  constructor(private _thirdParty: PrismaRepository<'thirdParty'>) {}\n\n  getAllThirdPartiesByOrganization(org: string) {\n    return this._thirdParty.model.thirdParty.findMany({\n      where: { organizationId: org, deletedAt: null },\n      select: {\n        id: true,\n        name: true,\n        identifier: true,\n      },\n    });\n  }\n\n  deleteIntegration(org: string, id: string) {\n    return this._thirdParty.model.thirdParty.update({\n      where: { id, organizationId: org },\n      data: { deletedAt: new Date() },\n    });\n  }\n\n  getIntegrationById(org: string, id: string) {\n    return this._thirdParty.model.thirdParty.findFirst({\n      where: { id, organizationId: org, deletedAt: null },\n    });\n  }\n\n  saveIntegration(\n    org: string,\n    identifier: string,\n    apiKey: string,\n    data: { name: string; username: string; id: string }\n  ) {\n    return this._thirdParty.model.thirdParty.upsert({\n      where: {\n        organizationId_internalId: {\n          internalId: data.id,\n          organizationId: org,\n        },\n      },\n      create: {\n        organizationId: org,\n        name: data.name,\n        internalId: data.id,\n        identifier,\n        apiKey: AuthService.fixedEncryption(apiKey),\n        deletedAt: null,\n      },\n      update: {\n        organizationId: org,\n        name: data.name,\n        internalId: data.id,\n        identifier,\n        apiKey: AuthService.fixedEncryption(apiKey),\n        deletedAt: null,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/third-party/third-party.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.repository';\n\n@Injectable()\nexport class ThirdPartyService {\n  constructor(private _thirdPartyRepository: ThirdPartyRepository) {}\n\n  getAllThirdPartiesByOrganization(org: string) {\n    return this._thirdPartyRepository.getAllThirdPartiesByOrganization(org);\n  }\n\n  deleteIntegration(org: string, id: string) {\n    return this._thirdPartyRepository.deleteIntegration(org, id);\n  }\n\n  getIntegrationById(org: string, id: string) {\n    return this._thirdPartyRepository.getIntegrationById(org, id);\n  }\n\n  saveIntegration(\n    org: string,\n    identifier: string,\n    apiKey: string,\n    data: { name: string; username: string; id: string }\n  ) {\n    return this._thirdPartyRepository.saveIntegration(org, identifier, apiKey, data);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/users/users.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { Provider } from '@prisma/client';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';\nimport { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto';\n\n@Injectable()\nexport class UsersRepository {\n  constructor(private _user: PrismaRepository<'user'>) {}\n\n  getImpersonateUser(name: string) {\n    return this._user.model.user.findMany({\n      where: {\n        OR: [\n          {\n            name: {\n              contains: name,\n            },\n          },\n          {\n            email: {\n              contains: name,\n            },\n          },\n          {\n            id: {\n              contains: name,\n            },\n          },\n        ],\n      },\n      select: {\n        id: true,\n        name: true,\n        email: true,\n      },\n      take: 10,\n    });\n  }\n\n  getUserById(id: string) {\n    return this._user.model.user.findFirst({\n      where: {\n        id,\n      },\n    });\n  }\n\n  getUserByEmail(email: string) {\n    return this._user.model.user.findFirst({\n      where: {\n        email,\n        providerName: Provider.LOCAL,\n      },\n      include: {\n        picture: {\n          select: {\n            id: true,\n            path: true,\n          },\n        },\n      },\n    });\n  }\n\n  activateUser(id: string) {\n    return this._user.model.user.update({\n      where: {\n        id,\n      },\n      data: {\n        activated: true,\n      },\n    });\n  }\n\n  getUserByProvider(providerId: string, provider: Provider) {\n    return this._user.model.user.findFirst({\n      where: {\n        providerId,\n        providerName: provider,\n      },\n    });\n  }\n\n  updatePassword(id: string, password: string) {\n    return this._user.model.user.update({\n      where: {\n        id,\n        providerName: Provider.LOCAL,\n      },\n      data: {\n        password: AuthService.hashPassword(password),\n      },\n    });\n  }\n\n  changeAudienceSize(userId: string, audience: number) {\n    return this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        audience,\n      },\n    });\n  }\n\n  async getPersonal(userId: string) {\n    const user = await this._user.model.user.findUnique({\n      where: {\n        id: userId,\n      },\n      select: {\n        id: true,\n        name: true,\n        bio: true,\n        picture: {\n          select: {\n            id: true,\n            path: true,\n          },\n        },\n      },\n    });\n\n    return user;\n  }\n\n  async changePersonal(userId: string, body: UserDetailDto) {\n    await this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        name: body.fullname,\n        bio: body.bio,\n        picture: body.picture\n          ? {\n              connect: {\n                id: body.picture.id,\n              },\n            }\n          : {\n              disconnect: true,\n            },\n      },\n    });\n  }\n\n  async getEmailNotifications(userId: string) {\n    return this._user.model.user.findUnique({\n      where: {\n        id: userId,\n      },\n      select: {\n        sendSuccessEmails: true,\n        sendFailureEmails: true,\n        sendStreakEmails: true,\n      },\n    });\n  }\n\n  async updateEmailNotifications(userId: string, body: EmailNotificationsDto) {\n    await this._user.model.user.update({\n      where: {\n        id: userId,\n      },\n      data: {\n        sendSuccessEmails: body.sendSuccessEmails,\n        sendFailureEmails: body.sendFailureEmails,\n        sendStreakEmails: body.sendStreakEmails,\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/users/users.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { UsersRepository } from '@gitroom/nestjs-libraries/database/prisma/users/users.repository';\nimport { Provider } from '@prisma/client';\nimport { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';\nimport { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto';\nimport { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';\n\n@Injectable()\nexport class UsersService {\n  constructor(\n    private _usersRepository: UsersRepository,\n    private _organizationRepository: OrganizationRepository\n  ) {}\n\n  getUserByEmail(email: string) {\n    return this._usersRepository.getUserByEmail(email);\n  }\n\n  getUserById(id: string) {\n    return this._usersRepository.getUserById(id);\n  }\n\n  getImpersonateUser(name: string) {\n    return this._organizationRepository.getImpersonateUser(name);\n  }\n\n  getUserByProvider(providerId: string, provider: Provider) {\n    return this._usersRepository.getUserByProvider(providerId, provider);\n  }\n\n  activateUser(id: string) {\n    return this._usersRepository.activateUser(id);\n  }\n\n  updatePassword(id: string, password: string) {\n    return this._usersRepository.updatePassword(id, password);\n  }\n\n  getPersonal(userId: string) {\n    return this._usersRepository.getPersonal(userId);\n  }\n\n  changePersonal(userId: string, body: UserDetailDto) {\n    return this._usersRepository.changePersonal(userId, body);\n  }\n\n  getEmailNotifications(userId: string) {\n    return this._usersRepository.getEmailNotifications(userId);\n  }\n\n  updateEmailNotifications(userId: string, body: EmailNotificationsDto) {\n    return this._usersRepository.updateEmailNotifications(userId, body);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.repository.ts",
    "content": "import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';\nimport { Injectable } from '@nestjs/common';\nimport { WebhooksDto } from '@gitroom/nestjs-libraries/dtos/webhooks/webhooks.dto';\nimport { v4 as uuidv4 } from 'uuid';\n\n@Injectable()\nexport class WebhooksRepository {\n  constructor(private _webhooks: PrismaRepository<'webhooks'>) {}\n\n  getTotal(orgId: string) {\n    return this._webhooks.model.webhooks.count({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n    });\n  }\n\n  getWebhooks(orgId: string) {\n    return this._webhooks.model.webhooks.findMany({\n      where: {\n        organizationId: orgId,\n        deletedAt: null,\n      },\n      include: {\n        integrations: {\n          select: {\n            integration: {\n              select: {\n                id: true,\n                picture: true,\n                name: true,\n              },\n            },\n          },\n        },\n      },\n    });\n  }\n\n  deleteWebhook(orgId: string, id: string) {\n    return this._webhooks.model.webhooks.update({\n      where: {\n        id,\n        organizationId: orgId,\n      },\n      data: {\n        deletedAt: new Date(),\n      },\n    });\n  }\n\n  async createWebhook(orgId: string, body: WebhooksDto) {\n    const { id } = await this._webhooks.model.webhooks.upsert({\n      where: {\n        id: body.id || uuidv4(),\n        organizationId: orgId,\n      },\n      create: {\n        organizationId: orgId,\n        url: body.url,\n        name: body.name,\n      },\n      update: {\n        url: body.url,\n        name: body.name,\n      },\n    });\n\n    await this._webhooks.model.webhooks.update({\n      where: {\n        id,\n        organizationId: orgId,\n      },\n      data: {\n        integrations: {\n          deleteMany: {},\n          create: body.integrations.map((integration) => ({\n            integrationId: integration.id,\n          })),\n        },\n      },\n    });\n\n    return { id };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/database/prisma/webhooks/webhooks.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { WebhooksRepository } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.repository';\nimport { WebhooksDto } from '@gitroom/nestjs-libraries/dtos/webhooks/webhooks.dto';\n\n@Injectable()\nexport class WebhooksService {\n  constructor(private _webhooksRepository: WebhooksRepository) {}\n\n  getTotal(orgId: string) {\n    return this._webhooksRepository.getTotal(orgId);\n  }\n\n  getWebhooks(orgId: string) {\n    return this._webhooksRepository.getWebhooks(orgId);\n  }\n\n  createWebhook(orgId: string, body: WebhooksDto) {\n    return this._webhooksRepository.createWebhook(orgId, body);\n  }\n\n  deleteWebhook(orgId: string, id: string) {\n    return this._webhooksRepository.deleteWebhook(orgId, id);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/agencies/create.agency.dto.ts",
    "content": "import {\n  ArrayMaxSize,\n  ArrayMinSize,\n  IsDefined,\n  IsIn,\n  IsOptional,\n  IsString,\n  IsUrl,\n  MinLength,\n  ValidateIf,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class CreateAgencyLogoDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  path: string;\n}\nexport class CreateAgencyDto {\n  @IsString()\n  @MinLength(3)\n  name: string;\n\n  @IsUrl()\n  @IsDefined()\n  website: string;\n\n  @IsUrl()\n  @ValidateIf((o) => o.facebook)\n  facebook: string;\n\n  @IsString()\n  @IsOptional()\n  instagram: string;\n\n  @IsString()\n  @IsOptional()\n  twitter: string;\n\n  @IsUrl()\n  @ValidateIf((o) => o.linkedIn)\n  linkedIn: string;\n\n  @IsUrl()\n  @ValidateIf((o) => o.youtube)\n  youtube: string;\n\n  @IsString()\n  @IsOptional()\n  tiktok: string;\n\n  @Type(() => CreateAgencyLogoDto)\n  logo: CreateAgencyLogoDto;\n\n  @IsString()\n  shortDescription: string;\n\n  @IsString()\n  description: string;\n\n  @IsString({\n    each: true,\n  })\n  @ArrayMinSize(1)\n  @ArrayMaxSize(3)\n  @IsIn(\n    [\n      'Real Estate',\n      'Fashion',\n      'Health and Fitness',\n      'Beauty',\n      'Travel',\n      'Food',\n      'Tech',\n      'Gaming',\n      'Parenting',\n      'Education',\n      'Business',\n      'Finance',\n      'DIY',\n      'Pets',\n      'Lifestyle',\n      'Sports',\n      'Entertainment',\n      'Art',\n      'Photography',\n      'Sustainability',\n    ],\n    {\n      each: true,\n    }\n  )\n  niches: string[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/analytics/stars.list.dto.ts",
    "content": "import { IsDefined, IsIn, IsNumber, IsOptional } from 'class-validator';\n\nexport class StarsListDto {\n  @IsNumber()\n  @IsDefined()\n  page: number;\n\n  @IsOptional()\n  @IsIn(['login', 'totalStars', 'stars', 'date', 'forks', 'totalForks'])\n  key: 'login' | 'date' | 'stars' | 'totalStars';\n\n  @IsOptional()\n  @IsIn(['desc', 'asc'])\n  state: 'desc' | 'asc';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts",
    "content": "import {\n  IsDefined,\n  IsEmail,\n  IsString,\n  MaxLength,\n  MinLength,\n  ValidateIf,\n} from 'class-validator';\nimport { Provider } from '@prisma/client';\n\nexport class CreateOrgUserDto {\n  @IsString()\n  @MinLength(3)\n  @MaxLength(64)\n  @IsDefined()\n  @ValidateIf((o) => !o.providerToken)\n  password: string;\n\n  @IsString()\n  @IsDefined()\n  provider: Provider;\n\n  @IsString()\n  @IsDefined()\n  @ValidateIf((o) => !o.password)\n  providerToken: string;\n\n  @IsEmail()\n  @IsDefined()\n  @ValidateIf((o) => !o.providerToken)\n  email: string;\n\n  @IsString()\n  @IsDefined()\n  @MinLength(3)\n  @MaxLength(128)\n  company: string;\n\n  datafast_visitor_id: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/auth/forgot-return.password.dto.ts",
    "content": "import {\n  IsDefined,\n  IsIn,\n  IsString,\n  MinLength,\n  ValidateIf,\n} from 'class-validator';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\n\nexport class ForgotReturnPasswordDto {\n  @IsString()\n  @IsDefined()\n  @MinLength(3)\n  password: string;\n\n  @IsString()\n  @IsDefined()\n  @IsIn([makeId(10)], {\n    message: 'Passwords do not match',\n  })\n  @ValidateIf((o) => o.password !== o.repeatPassword)\n  repeatPassword: string;\n\n  @IsString()\n  @IsDefined()\n  @MinLength(5)\n  token: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/auth/forgot.password.dto.ts",
    "content": "import { IsDefined, IsEmail, IsString } from 'class-validator';\n\nexport class ForgotPasswordDto {\n  @IsString()\n  @IsDefined()\n  @IsEmail()\n  email: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/auth/login.user.dto.ts",
    "content": "import {\n  IsDefined,\n  IsEmail,\n  IsString,\n  MinLength,\n  ValidateIf,\n} from 'class-validator';\nimport { Provider } from '@prisma/client';\n\nexport class LoginUserDto {\n  @IsString()\n  @IsDefined()\n  @ValidateIf((o) => !o.providerToken)\n  @MinLength(3)\n  password: string;\n\n  @IsString()\n  @IsDefined()\n  provider: Provider;\n\n  @IsString()\n  @IsDefined()\n  @ValidateIf((o) => !o.password)\n  providerToken: string;\n\n  @IsEmail()\n  @IsDefined()\n  email: string;\n\n  datafast_visitor_id: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/auth/resend-activation.dto.ts",
    "content": "import { IsDefined, IsEmail, IsString } from 'class-validator';\n\nexport class ResendActivationDto {\n  @IsString()\n  @IsDefined()\n  @IsEmail()\n  email: string;\n}\n\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/autopost/autopost.dto.ts",
    "content": "import {\n  IsArray,\n  IsBoolean,\n  IsDefined,\n  IsOptional,\n  IsString,\n  IsUrl,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class Integrations {\n  @IsString()\n  @IsDefined()\n  id: string;\n}\n\nexport class AutopostDto {\n  @IsString()\n  @IsDefined()\n  title: string;\n\n  @IsString()\n  @IsOptional()\n  content: string;\n\n  @IsString()\n  @IsOptional()\n  lastUrl: string;\n\n  @IsBoolean()\n  @IsDefined()\n  onSlot: boolean;\n\n  @IsBoolean()\n  @IsDefined()\n  syncLast: boolean;\n\n  @IsUrl()\n  @IsDefined()\n  url: string;\n\n  @IsBoolean()\n  @IsDefined()\n  active: boolean;\n\n  @IsBoolean()\n  @IsDefined()\n  addPicture: boolean;\n\n  @IsBoolean()\n  @IsDefined()\n  generateContent: boolean;\n\n  @IsArray()\n  @Type(() => Integrations)\n  @ValidateNested({ each: true })\n  integrations: Integrations[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts",
    "content": "import { IsIn } from 'class-validator';\n\nexport class BillingSubscribeDto {\n  @IsIn(['MONTHLY', 'YEARLY'])\n  period: 'MONTHLY' | 'YEARLY';\n\n  @IsIn(['STANDARD', 'PRO', 'TEAM', 'ULTIMATE'])\n  billing: 'STANDARD' | 'PRO' | 'TEAM' | 'ULTIMATE';\n\n  utm: string;\n\n  dub: string;\n\n  datafast_session_id: string;\n  datafast_visitor_id: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/comments/add.comment.dto.ts",
    "content": "export class AddCommentDto {\n  content: string;\n  date: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/generator/create.generated.posts.dto.ts",
    "content": "import {\n  ArrayMinSize,\n  IsArray,\n  IsDefined,\n  IsNumber,\n  IsString,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\n\nclass InnerPost {\n  @IsString()\n  @IsDefined()\n  post: string;\n}\n\nclass PostGroup {\n  @IsArray()\n  @ArrayMinSize(1)\n  @ValidateNested({ each: true })\n  @Type(() => InnerPost)\n  @IsDefined()\n  list: InnerPost[];\n}\n\nexport class CreateGeneratedPostsDto {\n  @IsArray()\n  @ArrayMinSize(1)\n  @ValidateNested({ each: true })\n  @Type(() => PostGroup)\n  @IsDefined()\n  posts: PostGroup[];\n\n  @IsNumber()\n  @IsDefined()\n  week: number;\n\n  @IsNumber()\n  @IsDefined()\n  year: number;\n\n  @IsString()\n  @IsDefined()\n  @ValidateIf((o) => !o.url)\n  url: string;\n\n  @IsString()\n  @IsDefined()\n  @ValidateIf((o) => !o.url)\n  postId: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/generator/generator.dto.ts",
    "content": "import { IsBoolean, IsIn, IsString, MinLength } from 'class-validator';\n\nexport class GeneratorDto {\n  @IsString()\n  @MinLength(10)\n  research: string;\n\n  @IsBoolean()\n  isPicture: boolean;\n\n  @IsString()\n  @IsIn(['one_short', 'one_long', 'thread_short', 'thread_long'])\n  format: 'one_short' | 'one_long' | 'thread_short' | 'thread_long';\n\n  @IsString()\n  @IsIn(['personal', 'company'])\n  tone: 'personal' | 'company';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/integrations/api.key.dto.ts",
    "content": "import { IsString, MinLength } from 'class-validator';\n\nexport class ApiKeyDto {\n  @IsString()\n  @MinLength(4, {\n    message: 'Must be at least 4 characters',\n  })\n  api: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/integrations/connect.integration.dto.ts",
    "content": "import { IsDefined, IsOptional, IsString } from 'class-validator';\n\nexport class ConnectIntegrationDto {\n  @IsString()\n  @IsDefined()\n  state: string;\n\n  @IsString()\n  @IsDefined()\n  code: string;\n\n  @IsString()\n  @IsDefined()\n  timezone: string;\n\n  @IsString()\n  @IsOptional()\n  refresh?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/integrations/integration.function.dto.ts",
    "content": "import { IsDefined, IsString } from 'class-validator';\n\nexport class IntegrationFunctionDto {\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  data: any;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/integrations/integration.time.dto.ts",
    "content": "import { IsArray, IsDefined, IsNumber, ValidateNested } from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class IntegrationValidateTimeDto {\n  @IsDefined()\n  @IsNumber()\n  time: number;\n}\nexport class IntegrationTimeDto {\n  @Type(() => IntegrationValidateTimeDto)\n  @IsArray()\n  @IsDefined()\n  @ValidateNested({ each: true })\n  time: IntegrationValidateTimeDto[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/media/media.dto.ts",
    "content": "import { IsDefined, IsString, IsUrl, ValidateIf, Validate } from 'class-validator';\nimport { ValidUrlExtension, ValidUrlPath } from '@gitroom/helpers/utils/valid.url.path';\n\nexport class MediaDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  @IsString()\n  @IsDefined()\n  @Validate(ValidUrlPath)\n  @Validate(ValidUrlExtension)\n  path: string;\n\n  @ValidateIf((o) => o.alt)\n  @IsString()\n  alt?: string;\n\n  @ValidateIf((o) => o.thumbnail)\n  @IsUrl()\n  thumbnail?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/media/save.media.information.dto.ts",
    "content": "import { IsNumber, IsOptional, IsString, IsUrl, ValidateIf } from 'class-validator';\n\nexport class SaveMediaInformationDto {\n  @IsString()\n  id: string;\n\n  @IsString()\n  alt: string;\n\n  @IsUrl()\n  @ValidateIf((o) => !!o.thumbnail)\n  thumbnail: string;\n\n  @IsNumber()\n  @ValidateIf((o) => !!o.thumbnailTimestamp)\n  thumbnailTimestamp: number;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/media/upload.dto.ts",
    "content": "import { IsDefined, IsString, Validate } from 'class-validator';\nimport { ValidUrlExtension } from '@gitroom/helpers/utils/valid.url.path';\n\nexport class UploadDto {\n  @IsString()\n  @IsDefined()\n  @Validate(ValidUrlExtension)\n  url: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/notifications/get.notifications.dto.ts",
    "content": "import { IsOptional, IsNumber, Min } from 'class-validator';\nimport { Transform } from 'class-transformer';\n\nexport class GetNotificationsDto {\n  @IsOptional()\n  @IsNumber()\n  @Min(0)\n  @Transform(({ value }) => parseInt(value, 10))\n  page?: number = 0;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/oauth/authorize-oauth.dto.ts",
    "content": "import { IsDefined, IsIn, IsOptional, IsString } from 'class-validator';\n\nexport class AuthorizeOAuthQueryDto {\n  @IsString()\n  @IsDefined()\n  client_id: string;\n\n  @IsString()\n  @IsDefined()\n  @IsIn(['code'])\n  response_type: string;\n\n  @IsString()\n  @IsOptional()\n  state?: string;\n}\n\nexport class ApproveOAuthDto {\n  @IsString()\n  @IsDefined()\n  client_id: string;\n\n  @IsString()\n  @IsOptional()\n  state?: string;\n\n  @IsString()\n  @IsDefined()\n  @IsIn(['approve', 'deny'])\n  action: 'approve' | 'deny';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/oauth/create-oauth-app.dto.ts",
    "content": "import { IsDefined, IsOptional, IsString, IsUrl, MaxLength } from 'class-validator';\n\nexport class CreateOAuthAppDto {\n  @IsString()\n  @IsDefined()\n  @MaxLength(100)\n  name: string;\n\n  @IsString()\n  @IsOptional()\n  @MaxLength(500)\n  description?: string;\n\n  @IsString()\n  @IsOptional()\n  pictureId?: string;\n\n  @IsString()\n  @IsDefined()\n  @IsUrl()\n  redirectUrl: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/oauth/token-exchange.dto.ts",
    "content": "import { IsDefined, IsString } from 'class-validator';\n\nexport class TokenExchangeDto {\n  @IsString()\n  @IsDefined()\n  grant_type: string;\n\n  @IsString()\n  @IsDefined()\n  code: string;\n\n  @IsString()\n  @IsDefined()\n  client_id: string;\n\n  @IsString()\n  @IsDefined()\n  client_secret: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/oauth/update-oauth-app.dto.ts",
    "content": "import { IsOptional, IsString, IsUrl, MaxLength } from 'class-validator';\n\nexport class UpdateOAuthAppDto {\n  @IsString()\n  @IsOptional()\n  @MaxLength(100)\n  name?: string;\n\n  @IsString()\n  @IsOptional()\n  @MaxLength(500)\n  description?: string;\n\n  @IsString()\n  @IsOptional()\n  pictureId?: string;\n\n  @IsString()\n  @IsOptional()\n  @IsUrl()\n  redirectUrl?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/plugs/plug.dto.ts",
    "content": "import { IsDefined, IsString, ValidateNested } from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class FieldsDto {\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsDefined()\n  value: string;\n}\n\nexport class PlugDto {\n  @IsString()\n  @IsDefined()\n  func: string;\n\n  @Type(() => FieldsDto)\n  @ValidateNested({ each: true })\n  @IsDefined()\n  fields: FieldsDto[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts",
    "content": "import {\n  ArrayMinSize,\n  IsArray,\n  IsBoolean,\n  IsDateString,\n  IsDefined,\n  IsIn,\n  IsNumber,\n  IsOptional,\n  IsString,\n  Validate,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\nimport { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\nimport {\n  allProviders,\n  type AllProvidersSettings,\n  EmptySettings,\n} from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/all.providers.settings';\nimport { ValidContent } from '@gitroom/helpers/utils/valid.images';\n\nexport class Integration {\n  @IsDefined()\n  @IsString()\n  id: string;\n}\n\nexport class PostContent {\n  @IsDefined()\n  @IsString()\n  @Validate(ValidContent)\n  content: string;\n\n  @IsOptional()\n  @IsString()\n  id: string;\n\n  @IsOptional()\n  @IsNumber()\n  delay: number;\n\n  @IsArray()\n  @Type(() => MediaDto)\n  @ValidateNested({ each: true })\n  image: MediaDto[];\n}\n\nexport class Post {\n  type?: string;\n\n  @IsDefined()\n  @Type(() => Integration)\n  @ValidateNested()\n  integration: Integration;\n\n  @IsDefined()\n  @ArrayMinSize(1)\n  @IsArray()\n  @Type(() => PostContent)\n  @ValidateNested({ each: true })\n  value: PostContent[];\n\n  @IsOptional()\n  @IsString()\n  group: string;\n\n  @ValidateIf((o) => o.type !== 'draft')\n  @ValidateNested()\n  @Type(() => EmptySettings, {\n    keepDiscriminatorProperty: true,\n    discriminator: {\n      property: '__type',\n      subTypes: allProviders(EmptySettings),\n    },\n  })\n  settings: AllProvidersSettings;\n}\n\nclass Tags {\n  @IsDefined()\n  @IsString()\n  value: string;\n\n  @IsDefined()\n  @IsString()\n  label: string;\n}\n\nexport class CreatePostDto {\n  @IsDefined()\n  @IsIn(['draft', 'schedule', 'now', 'update'])\n  type: 'draft' | 'schedule' | 'now' | 'update';\n\n  @IsOptional()\n  @IsString()\n  order?: string;\n\n  @IsDefined()\n  @IsBoolean()\n  shortLink: boolean;\n\n  @IsOptional()\n  @IsNumber()\n  inter?: number;\n\n  @IsDefined()\n  @IsDateString()\n  date: string;\n\n  @IsArray()\n  @IsDefined()\n  @ValidateNested({ each: true })\n  tags: Tags[];\n\n  @IsDefined()\n  @Type(() => Post)\n  @IsArray()\n  @ValidateNested({ each: true })\n  @ArrayMinSize(1)\n  posts: Post[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/create.tag.dto.ts",
    "content": "import { IsString } from 'class-validator';\n\nexport class CreateTagDto {\n  @IsString()\n  name: string;\n\n  @IsString()\n  color: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts",
    "content": "import {\n  IsOptional,\n  IsString,\n  IsDateString,\n} from 'class-validator';\n\nexport class GetPostsDto {\n  @IsDateString()\n  startDate: string;\n\n  @IsDateString()\n  endDate: string;\n\n  @IsOptional()\n  @IsString()\n  customer: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/get.posts.list.dto.ts",
    "content": "import {\n  IsOptional,\n  IsString,\n  IsNumber,\n  Min,\n  Max,\n} from 'class-validator';\nimport { Transform } from 'class-transformer';\n\nexport class GetPostsListDto {\n  @IsOptional()\n  @IsNumber()\n  @Min(0)\n  @Transform(({ value }) => parseInt(value, 10))\n  page?: number = 0;\n\n  @IsOptional()\n  @IsNumber()\n  @Min(1)\n  @Max(100)\n  @Transform(({ value }) => parseInt(value, 10))\n  limit?: number = 20;\n\n  @IsOptional()\n  @IsString()\n  customer?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts",
    "content": "import { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto';\nimport { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';\nimport { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';\nimport { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto';\nimport { XDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/x.dto';\nimport { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/lemmy.dto';\nimport { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';\nimport { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto';\nimport { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto';\nimport { KickDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/kick.dto';\nimport { TwitchDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/twitch.dto';\nimport { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';\nimport { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto';\nimport { IsIn } from 'class-validator';\nimport { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';\nimport { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto';\nimport { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';\nimport { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';\nimport { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';\nimport { GmbSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/gmb.settings.dto';\nimport { FarcasterDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/farcaster.dto';\nimport { FacebookDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/facebook.dto';\nimport { MoltbookDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/moltbook.dto';\nimport { SkoolDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/skool.dto';\nimport { WhopDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/whop.dto';\nimport { MeweDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/mewe.dto';\n\nexport type ProviderExtension<T extends string, M> = { __type: T } & M;\nexport type AllProvidersSettings =\n  | ProviderExtension<'reddit', RedditSettingsDto>\n  | ProviderExtension<'lemmy', LemmySettingsDto>\n  | ProviderExtension<'youtube', YoutubeSettingsDto>\n  | ProviderExtension<'pinterest', PinterestSettingsDto>\n  | ProviderExtension<'dribbble', DribbbleDto>\n  | ProviderExtension<'tiktok', TikTokDto>\n  | ProviderExtension<'discord', DiscordDto>\n  | ProviderExtension<'slack', SlackDto>\n  | ProviderExtension<'kick', KickDto>\n  | ProviderExtension<'twitch', TwitchDto>\n  | ProviderExtension<'x', XDto>\n  | ProviderExtension<'linkedin', LinkedinDto>\n  | ProviderExtension<'linkedin-page', LinkedinDto>\n  | ProviderExtension<'instagram', InstagramDto>\n  | ProviderExtension<'instagram-standalone', InstagramDto>\n  | ProviderExtension<'medium', MediumSettingsDto>\n  | ProviderExtension<'devto', DevToSettingsDto>\n  | ProviderExtension<'hashnode', HashnodeSettingsDto>\n  | ProviderExtension<'wordpress', WordpressDto>\n  | ProviderExtension<'listmonk', ListmonkDto>\n  | ProviderExtension<'gmb', GmbSettingsDto>\n  | ProviderExtension<'facebook', FacebookDto>\n  | ProviderExtension<'wrapcast', FarcasterDto>\n  | ProviderExtension<'threads', None>\n  | ProviderExtension<'mastodon', None>\n  | ProviderExtension<'bluesky', None>\n  | ProviderExtension<'telegram', None>\n  | ProviderExtension<'nostr', None>\n  | ProviderExtension<'moltbook', MoltbookDto>\n  | ProviderExtension<'vk', None>\n  | ProviderExtension<'skool', SkoolDto>\n  | ProviderExtension<'mewe', MeweDto>\n  | ProviderExtension<'whop', WhopDto>;\n\ntype None = NonNullable<unknown>;\n\nexport const allProviders = (setEmpty?: any) => {\n  return [\n    { value: RedditSettingsDto, name: 'reddit' },\n    { value: LemmySettingsDto, name: 'lemmy' },\n    { value: YoutubeSettingsDto, name: 'youtube' },\n    { value: PinterestSettingsDto, name: 'pinterest' },\n    { value: DribbbleDto, name: 'dribbble' },\n    { value: TikTokDto, name: 'tiktok' },\n    { value: DiscordDto, name: 'discord' },\n    { value: SlackDto, name: 'slack' },\n    { value: KickDto, name: 'kick' },\n    { value: TwitchDto, name: 'twitch' },\n    { value: XDto, name: 'x' },\n    { value: LinkedinDto, name: 'linkedin' },\n    { value: LinkedinDto, name: 'linkedin-page' },\n    { value: InstagramDto, name: 'instagram' },\n    { value: InstagramDto, name: 'instagram-standalone' },\n    { value: MediumSettingsDto, name: 'medium' },\n    { value: DevToSettingsDto, name: 'devto' },\n    { value: WordpressDto, name: 'wordpress' },\n    { value: HashnodeSettingsDto, name: 'hashnode' },\n    { value: ListmonkDto, name: 'listmonk' },\n    { value: GmbSettingsDto, name: 'gmb' },\n    { value: FarcasterDto, name: 'wrapcast' },\n    { value: FacebookDto, name: 'facebook' },\n    { value: setEmpty, name: 'threads' },\n    { value: setEmpty, name: 'mastodon' },\n    { value: setEmpty, name: 'bluesky' },\n    { value: setEmpty, name: 'telegram' },\n    { value: setEmpty, name: 'nostr' },\n    { value: setEmpty, name: 'vk' },\n    { value: MoltbookDto, name: 'moltbook' },\n    { value: SkoolDto, name: 'skool' },\n    { value: WhopDto, name: 'whop' },\n    { value: MeweDto, name: 'mewe' },\n  ].filter((f) => f.value);\n};\n\nexport class EmptySettings {\n  @IsIn(allProviders(EmptySettings).map((p) => p.name), {\n    message: `\"__type\" must be ${allProviders(EmptySettings)\n      .map((p) => p.name)\n      .join(', ')}`,\n  })\n  __type: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/dev.to.settings.dto.ts",
    "content": "import {\n  ArrayMaxSize,\n  IsArray,\n  IsDefined,\n  IsOptional,\n  IsString,\n  Matches,\n  MinLength,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\nimport { Type } from 'class-transformer';\nimport { DevToTagsSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.tags.settings.dto';\n\nexport class DevToSettingsDto {\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  title: string;\n\n  @IsOptional()\n  @ValidateNested()\n  @Type(() => MediaDto)\n  main_image?: MediaDto;\n\n  @IsOptional()\n  @IsString()\n  @ValidateIf((o) => o.canonical && o.canonical.indexOf('(post:') === -1)\n  @Matches(\n    /^(|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})$/,\n    {\n      message: 'Invalid URL',\n    }\n  )\n  canonical?: string;\n\n  @IsString()\n  @IsOptional()\n  organization?: string;\n\n  @IsArray()\n  @ArrayMaxSize(4)\n  @Type(() => DevToTagsSettingsDto)\n  @ValidateNested({ each: true })\n  tags: DevToTagsSettingsDto[] = [];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/dev.to.tags.settings.dto.ts",
    "content": "import { IsNumber, IsString } from 'class-validator';\n\nexport class DevToTagsSettingsDto {\n  @IsNumber()\n  value: number;\n\n  @IsString()\n  label: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/discord.dto.ts",
    "content": "import { IsDefined, IsString, MinLength } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class DiscordDto {\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n    @JSONSchema({\n    description: 'Channel must be an id',\n  })\n  channel: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/dribbble.dto.ts",
    "content": "import {\n  IsDefined,\n  IsOptional,\n  IsString,\n  IsUrl,\n  MinLength,\n} from 'class-validator';\n\nexport class DribbbleDto {\n  @IsString()\n  @IsDefined()\n  @MinLength(1, {\n    message: 'Title is required',\n  })\n  title: string;\n\n  @IsString()\n  @IsOptional()\n  @IsUrl()\n  team: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/facebook.dto.ts",
    "content": "import { IsOptional, ValidateIf, IsUrl } from 'class-validator';\n\nexport class FacebookDto {\n  @IsOptional()\n  @ValidateIf(p => p.url)\n  @IsUrl()\n  url?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/farcaster.dto.ts",
    "content": "import { Type } from 'class-transformer';\nimport { IsString, ValidateNested } from 'class-validator';\n\nexport class FarcasterId {\n  @IsString()\n  id: string;\n}\nexport class FarcasterValue {\n  @ValidateNested()\n  @Type(() => FarcasterId)\n  value: FarcasterId;\n}\nexport class FarcasterDto {\n  @ValidateNested({ each: true })\n  @Type(() => FarcasterValue)\n  subreddit: FarcasterValue[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/gmb.settings.dto.ts",
    "content": "import { IsOptional, IsString, IsIn, IsUrl, ValidateIf } from 'class-validator';\n\nexport class GmbSettingsDto {\n  @IsOptional()\n  @IsIn(['STANDARD', 'EVENT', 'OFFER'])\n  topicType?: 'STANDARD' | 'EVENT' | 'OFFER';\n\n  @IsOptional()\n  @IsIn([\n    'NONE',\n    'BOOK',\n    'ORDER',\n    'SHOP',\n    'LEARN_MORE',\n    'SIGN_UP',\n    'GET_OFFER',\n    'CALL',\n  ])\n  callToActionType?:\n    | 'NONE'\n    | 'BOOK'\n    | 'ORDER'\n    | 'SHOP'\n    | 'LEARN_MORE'\n    | 'SIGN_UP'\n    | 'GET_OFFER'\n    | 'CALL';\n\n  @IsOptional()\n  @ValidateIf((o) => o.callToActionType)\n  @IsUrl()\n  callToActionUrl?: string;\n\n  // Event-specific fields\n  @IsOptional()\n  @ValidateIf((o) => o.topicType === 'EVENT')\n  @IsString()\n  eventTitle?: string;\n\n  @IsOptional()\n  @IsString()\n  eventStartDate?: string;\n\n  @IsOptional()\n  @IsString()\n  eventEndDate?: string;\n\n  @IsOptional()\n  @IsString()\n  eventStartTime?: string;\n\n  @IsOptional()\n  @IsString()\n  eventEndTime?: string;\n\n  // Offer-specific fields\n  @IsOptional()\n  @IsString()\n  offerCouponCode?: string;\n\n  @IsOptional()\n  @ValidateIf((o) => o.offerRedeemUrl)\n  @IsUrl()\n  offerRedeemUrl?: string;\n\n  @IsOptional()\n  @IsString()\n  offerTerms?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/hashnode.settings.dto.ts",
    "content": "import {\n  ArrayMinSize,\n  IsArray,\n  IsDefined,\n  IsOptional,\n  IsString,\n  Matches,\n  MinLength,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\nimport { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\n\nexport class HashnodeTagsSettings {\n  @IsString()\n  value: string;\n\n  @IsString()\n  label: string;\n}\n\nexport class HashnodeSettingsDto {\n  @IsString()\n  @MinLength(6)\n  @IsDefined()\n  title: string;\n\n  @IsString()\n  @MinLength(2)\n  @IsOptional()\n  subtitle: string;\n\n  @IsOptional()\n  @ValidateNested()\n  @Type(() => MediaDto)\n  main_image?: MediaDto;\n\n  @IsOptional()\n  @IsString()\n  @ValidateIf((o) => o.canonical && o.canonical.indexOf('(post:') === -1)\n  @Matches(\n    /^(|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})$/,\n    {\n      message: 'Invalid URL',\n    }\n  )\n  canonical?: string;\n\n  @IsString()\n  @IsDefined()\n  publication?: string;\n\n  @IsArray()\n  @ArrayMinSize(1)\n  @Type(() => HashnodeTagsSettings)\n  @ValidateNested({ each: true })\n  tags: HashnodeTagsSettings[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/instagram.dto.ts",
    "content": "import { Type } from 'class-transformer';\nimport {\n  IsArray,\n  IsDefined,\n  IsIn,\n  IsString,\n  ValidateNested,\n  IsOptional,\n} from 'class-validator';\n\nexport class Collaborators {\n  @IsDefined()\n  @IsString()\n  label: string;\n}\nexport class InstagramDto {\n  @IsIn(['post', 'story'])\n  @IsDefined()\n  post_type: 'post' | 'story';\n\n  @IsOptional()\n  is_trial_reel?: boolean;\n\n  @IsIn(['MANUAL', 'SS_PERFORMANCE'])\n  @IsOptional()\n  graduation_strategy?: 'MANUAL' | 'SS_PERFORMANCE';\n\n  @Type(() => Collaborators)\n  @ValidateNested({ each: true })\n  @IsArray()\n  @IsOptional()\n  collaborators: Collaborators[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/kick.dto.ts",
    "content": "export class KickDto {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/lemmy.dto.ts",
    "content": "import {\n  ArrayMinSize,\n  IsDefined,\n  IsOptional,\n  IsString,\n  IsUrl,\n  MinLength,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class LemmySettingsDtoInner {\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  subreddit: string;\n\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  title: string;\n\n  @ValidateIf((o) => o.url)\n  @IsOptional()\n  @IsUrl()\n  url: string;\n}\n\nexport class LemmySettingsValueDto {\n  @Type(() => LemmySettingsDtoInner)\n  @IsDefined()\n  @ValidateNested()\n  value: LemmySettingsDtoInner;\n}\n\nexport class LemmySettingsDto {\n  @Type(() => LemmySettingsValueDto)\n  @ValidateNested({ each: true })\n  @ArrayMinSize(1)\n  subreddit: LemmySettingsValueDto[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/linkedin.dto.ts",
    "content": "import { IsBoolean, IsOptional, IsString } from 'class-validator';\n\nexport class LinkedinDto {\n  @IsBoolean()\n  @IsOptional()\n  post_as_images_carousel: boolean;\n\n  @IsString()\n  @IsOptional()\n  carousel_name?: string;\n}"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/listmonk.dto.ts",
    "content": "import { IsOptional, IsString, MinLength } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class ListmonkDto {\n  @IsString()\n  @MinLength(1)\n  subject: string;\n\n  @IsString()\n  preview: string;\n\n  @IsString()\n  @JSONSchema({\n    description: 'List must be an id',\n  })\n  list: string;\n\n  @IsString()\n  @IsOptional()\n  @JSONSchema({\n    description: 'Template must be an id',\n  })\n  template: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/medium.settings.dto.ts",
    "content": "import {\n  ArrayMaxSize,\n  IsArray,\n  IsDefined,\n  IsOptional,\n  IsString,\n  Matches,\n  MinLength,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class MediumTagsSettings {\n  @IsString()\n  value: string;\n\n  @IsString()\n  label: string;\n}\n\nexport class MediumSettingsDto {\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  title: string;\n\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  subtitle: string;\n\n  @IsOptional()\n  @IsString()\n  @ValidateIf((o) => o.canonical && o.canonical.indexOf('(post:') === -1)\n  @Matches(\n    /^(|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})$/,\n    {\n      message: 'Invalid URL',\n    }\n  )\n  canonical?: string;\n\n  @IsString()\n  @IsOptional()\n  publication?: string;\n\n  @IsArray()\n  @ArrayMaxSize(4)\n  @IsOptional()\n  @ValidateNested({ each: true })\n  @Type(p => MediumTagsSettings)\n  tags: MediumTagsSettings[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/mewe.dto.ts",
    "content": "import { IsIn, IsOptional, IsString, MinLength, ValidateIf } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class MeweDto {\n  @IsIn(['timeline', 'group'])\n  @JSONSchema({\n    description: 'Where to post: timeline or group',\n  })\n  postType: 'timeline' | 'group';\n\n  @ValidateIf((o) => o.postType === 'group')\n  @MinLength(1)\n  @IsString()\n  @JSONSchema({\n    description: 'Group must be an id',\n  })\n  @IsOptional()\n  group?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/moltbook.dto.ts",
    "content": "import { IsDefined, IsString, MinLength } from 'class-validator';\n\nexport class MoltbookDto {\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  submolt: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/pinterest.dto.ts",
    "content": "import {\n  IsDefined, IsOptional, IsString, IsUrl, MaxLength, MinLength, ValidateIf\n} from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class PinterestSettingsDto {\n  @IsString()\n  @ValidateIf((o) => !!o.title)\n  @MaxLength(100)\n  title: string;\n\n  @IsString()\n  @ValidateIf((o) => !!o.link)\n  @IsUrl()\n  link: string;\n\n  @IsString()\n  @ValidateIf((o) => !!o.dominant_color)\n  dominant_color: string;\n\n  @IsDefined({\n    message: 'Board is required',\n  })\n  @IsString({\n    message: 'Board is required',\n  })\n  @MinLength(1, {\n    message: 'Board is required',\n  })\n    @JSONSchema({\n    description: 'board must be an id',\n  })\n  board: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts",
    "content": "import {\n  ArrayMinSize,\n  IsBoolean,\n  IsDefined,\n  IsString,\n  IsUrl,\n  Matches,\n  MinLength,\n  ValidateIf,\n  ValidateNested,\n} from 'class-validator';\nimport { Type } from 'class-transformer';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class RedditFlairDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  @IsString()\n  @IsDefined()\n  name: string;\n}\n\nexport class RedditSettingsDtoInner {\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  @JSONSchema({\n    description: 'Subreddit must start with /r',\n  })\n  subreddit: string;\n\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  title: string;\n\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  type: string;\n\n  @IsUrl()\n  @IsDefined()\n  @ValidateIf((o) => o.type === 'link' && o?.url?.indexOf('(post:') === -1)\n  @Matches(\n    /^(|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})$/,\n    {\n      message: 'Invalid URL',\n    }\n  )\n  url: string;\n\n  @IsBoolean()\n  @IsDefined()\n  is_flair_required: boolean;\n\n  @ValidateIf((e) => e.is_flair_required)\n  @IsDefined()\n  @ValidateNested()\n  @Type(() => RedditFlairDto)\n  flair: RedditFlairDto;\n}\n\nexport class RedditSettingsValueDto {\n  @Type(() => RedditSettingsDtoInner)\n  @IsDefined()\n  @ValidateNested()\n  value: RedditSettingsDtoInner;\n}\n\nexport class RedditSettingsDto {\n  @Type(() => RedditSettingsValueDto)\n  @ValidateNested({ each: true })\n  @ArrayMinSize(1)\n  subreddit: RedditSettingsValueDto[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/skool.dto.ts",
    "content": "import { IsDefined, IsString, MinLength } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class SkoolDto {\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Group must be an id',\n  })\n  group: string;\n\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Label must be an id',\n  })\n  label: string;\n\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Title of the post',\n  })\n  title: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/slack.dto.ts",
    "content": "import { IsDefined, IsString, MinLength } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class SlackDto {\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Channel must be an id',\n  })\n  channel: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts",
    "content": "import {\n  IsBoolean, ValidateIf, IsIn, IsString, MaxLength, IsOptional\n} from 'class-validator';\n\nexport class TikTokDto {\n  @ValidateIf((p) => p.title)\n  @MaxLength(90)\n  title: string;\n\n  @IsIn([\n    'PUBLIC_TO_EVERYONE',\n    'MUTUAL_FOLLOW_FRIENDS',\n    'FOLLOWER_OF_CREATOR',\n    'SELF_ONLY',\n  ])\n  @IsString()\n  privacy_level:\n    | 'PUBLIC_TO_EVERYONE'\n    | 'MUTUAL_FOLLOW_FRIENDS'\n    | 'FOLLOWER_OF_CREATOR'\n    | 'SELF_ONLY';\n\n  @IsBoolean()\n  duet: boolean;\n\n  @IsBoolean()\n  stitch: boolean;\n\n  @IsBoolean()\n  comment: boolean;\n\n  @IsIn(['yes', 'no'])\n  autoAddMusic: 'yes' | 'no';\n\n  @IsBoolean()\n  brand_content_toggle: boolean;\n\n  @IsBoolean()\n  @IsOptional()\n  video_made_with_ai: boolean;\n\n  @IsBoolean()\n  brand_organic_toggle: boolean;\n\n  @IsIn(['DIRECT_POST', 'UPLOAD'])\n  @IsString()\n  content_posting_method: 'DIRECT_POST' | 'UPLOAD';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/twitch.dto.ts",
    "content": "import { IsIn, IsOptional, IsString } from 'class-validator';\n\nexport class TwitchDto {\n  @IsIn(['message', 'announcement'])\n  @IsOptional()\n  messageType?: 'message' | 'announcement';\n\n  @IsIn(['primary', 'blue', 'green', 'orange', 'purple'])\n  @IsOptional()\n  announcementColor?: 'primary' | 'blue' | 'green' | 'orange' | 'purple';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/whop.dto.ts",
    "content": "import { IsDefined, IsOptional, IsString, MinLength } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\n\nexport class WhopDto {\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Company ID',\n  })\n  company: string;\n\n  @MinLength(1)\n  @IsDefined()\n  @IsString()\n  @JSONSchema({\n    description: 'Experience ID for the Whop forum',\n  })\n  experience: string;\n\n  @IsOptional()\n  @IsString()\n  title?: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/wordpress.dto.ts",
    "content": "import {\n  IsDefined,\n  IsOptional,\n  IsString,\n  MinLength,\n  ValidateNested,\n} from 'class-validator';\nimport { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\nimport { Type } from 'class-transformer';\n\nexport class WordpressDto {\n  @IsString()\n  @MinLength(2)\n  @IsDefined()\n  title: string;\n\n  @IsOptional()\n  @ValidateNested()\n  @Type(() => MediaDto)\n  main_image?: MediaDto;\n\n  @IsString()\n  @IsDefined()\n  type: string;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/x.dto.ts",
    "content": "import { IsIn, IsOptional, Matches } from 'class-validator';\n\nexport class XDto {\n  @IsOptional()\n  @Matches(/^(https:\\/\\/x\\.com\\/i\\/communities\\/\\d+)?$/, {\n    message:\n      'Invalid X community URL. It should be in the format: https://x.com/i/communities/1493446837214187523',\n  })\n  community?: string;\n\n  @IsIn(['everyone', 'following', 'mentionedUsers', 'subscribers', 'verified'])\n  who_can_reply_post:\n    | 'everyone'\n    | 'following'\n    | 'mentionedUsers'\n    | 'subscribers'\n    | 'verified';\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/providers-settings/youtube.settings.dto.ts",
    "content": "import {\n  IsArray, IsDefined, IsIn, IsOptional, IsString, MaxLength, MinLength, ValidateNested\n} from 'class-validator';\nimport { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\nimport { Type } from 'class-transformer';\n\nexport class YoutubeTagsSettings {\n  @IsString()\n  value: string;\n\n  @IsString()\n  label: string;\n}\n\nexport class YoutubeSettingsDto {\n  @IsString()\n  @MinLength(2)\n  @MaxLength(100)\n  @IsDefined()\n  title: string;\n\n  @IsIn(['public', 'private', 'unlisted'])\n  @IsDefined()\n  type: string;\n\n  @IsIn(['yes', 'no'])\n  @IsOptional()\n  selfDeclaredMadeForKids: 'no' | 'yes';\n\n  @IsOptional()\n  @ValidateNested()\n  @Type(() => MediaDto)\n  thumbnail?: MediaDto;\n\n  @IsArray()\n  @IsOptional()\n  @ValidateNested()\n  @Type(() => YoutubeTagsSettings)\n  tags: YoutubeTagsSettings[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/posts/transformers/integration.settings.transformer.ts",
    "content": "import { Transform, Type } from 'class-transformer';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class IntegrationSettingsTransformer {\n  constructor(private integrationService: IntegrationService) {}\n\n  async transformPost(post: any, orgId: string) {\n    if (!post.integration?.id || !post.settings) {\n      return post;\n    }\n\n    try {\n      // Get the integration from the database\n      const integration = await this.integrationService.getIntegrationById(\n        orgId,\n        post.integration.id\n      );\n\n      if (integration?.providerIdentifier) {\n        // Set the __type field based on the provider identifier\n        post.settings.__type = integration.providerIdentifier;\n      }\n    } catch (error) {\n      // If there's an error fetching the integration, we'll let validation handle it\n      console.error('Error fetching integration for settings transform:', error);\n    }\n\n    return post;\n  }\n}\n\n// Custom property transformer for individual Post objects\nexport const TransformIntegrationSettings = (orgId: string) => {\n  return Transform(({ value, obj }) => {\n    // This will be handled by the service layer instead of transformer\n    // since we need async database access\n    return value;\n  });\n}; "
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/sets/sets.dto.ts",
    "content": "import { IsDefined, IsOptional, IsString } from 'class-validator';\n\nexport class SetsDto {\n  @IsOptional()\n  @IsString()\n  id?: string;\n\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsDefined()\n  content: string;\n}\n\nexport class UpdateSetsDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsDefined()\n  content: string;\n} "
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/settings/add.team.member.dto.ts",
    "content": "import {\n  IsBoolean,\n  IsDefined,\n  IsEmail,\n  IsIn,\n  IsString,\n  ValidateIf,\n} from 'class-validator';\n\nexport class AddTeamMemberDto {\n  @IsDefined()\n  @IsEmail()\n  @ValidateIf((o) => o.sendEmail)\n  email: string;\n\n  @IsString()\n  @IsIn(['USER', 'ADMIN'])\n  role: string;\n\n  @IsDefined()\n  @IsBoolean()\n  sendEmail: boolean;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/settings/shortlink-preference.dto.ts",
    "content": "import { IsEnum } from 'class-validator';\nimport { ShortLinkPreference } from '@prisma/client';\n\nexport class ShortlinkPreferenceDto {\n  @IsEnum(ShortLinkPreference)\n  shortlink: ShortLinkPreference;\n}\n\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/signature/signature.dto.ts",
    "content": "import { IsBoolean, IsDefined, IsString } from 'class-validator';\n\nexport class SignatureDto {\n  @IsString()\n  @IsDefined()\n  content: string;\n\n  @IsBoolean()\n  @IsDefined()\n  autoAdd: boolean;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/users/email-notifications.dto.ts",
    "content": "import { IsBoolean } from 'class-validator';\n\nexport class EmailNotificationsDto {\n  @IsBoolean()\n  sendSuccessEmails: boolean;\n\n  @IsBoolean()\n  sendFailureEmails: boolean;\n\n  @IsBoolean()\n  sendStreakEmails: boolean;\n}\n\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/users/user.details.dto.ts",
    "content": "import { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';\nimport {\n  IsOptional,\n  IsString,\n  MinLength,\n  ValidateNested,\n} from 'class-validator';\n\nexport class UserDetailDto {\n  @IsString()\n  @MinLength(3)\n  fullname: string;\n\n  @IsString()\n  @IsOptional()\n  bio: string;\n\n  @IsOptional()\n  @ValidateNested()\n  picture: MediaDto;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/videos/video.dto.ts",
    "content": "import {\n  IsIn, Validate, ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface\n} from 'class-validator';\nimport { VideoAbstract } from '@gitroom/nestjs-libraries/videos/video.interface';\n\n@ValidatorConstraint({ name: 'checkInRuntime', async: false })\nexport class ValidIn implements ValidatorConstraintInterface {\n  private _load() {\n    return (Reflect.getMetadata('video', VideoAbstract) || [])\n      .filter((f: any) => f.available)\n      .map((p: any) => p.identifier);\n  }\n\n  validate(text: string, args: ValidationArguments) {\n    // Check if the text is in the list of valid video types\n    const validTypes = this._load();\n    return validTypes.includes(text);\n  }\n\n  defaultMessage(args: ValidationArguments) {\n    // here you can provide default error message if validation failed\n    return 'type must be any of: ' + this._load().join(', ');\n  }\n}\n\nexport class VideoDto {\n  @Validate(ValidIn)\n  type: string;\n\n  @IsIn(['vertical', 'horizontal'])\n  output: 'vertical' | 'horizontal';\n\n  customParams: any;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/videos/video.function.dto.ts",
    "content": "import { IsString } from 'class-validator';\n\nexport class VideoFunctionDto {\n  @IsString()\n  identifier: string;\n\n  @IsString()\n  functionName: string;\n\n  params: any;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/dtos/webhooks/webhooks.dto.ts",
    "content": "import { IsDefined, IsOptional, IsString, IsUrl } from 'class-validator';\nimport { Type } from 'class-transformer';\n\nexport class WebhooksIntegrationDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n}\n\nexport class WebhooksDto {\n  id: string;\n\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsUrl()\n  @IsDefined()\n  url: string;\n\n  @Type(() => WebhooksIntegrationDto)\n  @IsDefined()\n  integrations: WebhooksIntegrationDto[];\n}\n\nexport class UpdateDto {\n  @IsString()\n  @IsDefined()\n  id: string;\n\n  @IsString()\n  @IsDefined()\n  name: string;\n\n  @IsString()\n  @IsUrl()\n  @IsDefined()\n  url: string;\n\n  @Type(() => WebhooksIntegrationDto)\n  @IsDefined()\n  integrations: WebhooksIntegrationDto[];\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/emails/email.interface.ts",
    "content": "export interface EmailInterface {\n  name: string;\n  validateEnvKeys: string[];\n  sendEmail(\n    to: string,\n    subject: string,\n    html: string,\n    emailFromName: string,\n    emailFromAddress: string,\n    replyTo?: string\n  ): Promise<any>;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/emails/empty.provider.ts",
    "content": "import { EmailInterface } from './email.interface';\n\nexport class EmptyProvider implements EmailInterface {\n  name = 'no provider';\n  validateEnvKeys = [];\n  async sendEmail(to: string, subject: string, html: string) {\n    return `No email provider found, email was supposed to be sent to ${to} with subject: ${subject} and ${html}, html`;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/emails/node.mailer.provider.ts",
    "content": "import nodemailer from 'nodemailer';\nimport { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';\n\nconst transporter = nodemailer.createTransport({\n  host: process.env.EMAIL_HOST,\n  port: +process.env.EMAIL_PORT!,\n  secure: process.env.EMAIL_SECURE === 'true',\n  auth: {\n    user: process.env.EMAIL_USER,\n    pass: process.env.EMAIL_PASS,\n  },\n});\n\nexport class NodeMailerProvider implements EmailInterface {\n  name = 'nodemailer';\n  validateEnvKeys = [\n    'EMAIL_HOST',\n    'EMAIL_PORT',\n    'EMAIL_SECURE',\n    'EMAIL_USER',\n    'EMAIL_PASS',\n  ];\n  async sendEmail(\n    to: string,\n    subject: string,\n    html: string,\n    emailFromName: string,\n    emailFromAddress: string\n  ) {\n    const sends = await transporter.sendMail({\n      from: `${emailFromName} <${emailFromAddress}>`, // sender address\n      to: to, // list of receivers\n      subject: subject, // Subject line\n      text: html, // plain text body\n      html: html, // html body\n    });\n\n    return sends;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/emails/resend.provider.ts",
    "content": "import { Resend } from 'resend';\nimport { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';\n\nconst resend = new Resend(process.env.RESEND_API_KEY || 're_132');\n\nexport class ResendProvider implements EmailInterface {\n  name = 'resend';\n  validateEnvKeys = ['RESEND_API_KEY'];\n  async sendEmail(\n    to: string,\n    subject: string,\n    html: string,\n    emailFromName: string,\n    emailFromAddress: string,\n    replyTo?: string\n  ) {\n    try {\n      const sends = await resend.emails.send({\n        from: `${emailFromName} <${emailFromAddress}>`,\n        to,\n        subject,\n        html,\n        ...(replyTo && { reply_to: replyTo }),\n      });\n\n      return sends;\n    } catch (err) {\n      console.log(err);\n    }\n\n    return { sent: false };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/integration.manager.ts",
    "content": "import 'reflect-metadata';\n\nimport { Injectable } from '@nestjs/common';\nimport { XProvider } from '@gitroom/nestjs-libraries/integrations/social/x.provider';\nimport { SocialProvider } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { LinkedinProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.provider';\nimport { RedditProvider } from '@gitroom/nestjs-libraries/integrations/social/reddit.provider';\nimport { DevToProvider } from '@gitroom/nestjs-libraries/integrations/social/dev.to.provider';\nimport { HashnodeProvider } from '@gitroom/nestjs-libraries/integrations/social/hashnode.provider';\nimport { MediumProvider } from '@gitroom/nestjs-libraries/integrations/social/medium.provider';\nimport { FacebookProvider } from '@gitroom/nestjs-libraries/integrations/social/facebook.provider';\nimport { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider';\nimport { YoutubeProvider } from '@gitroom/nestjs-libraries/integrations/social/youtube.provider';\nimport { TiktokProvider } from '@gitroom/nestjs-libraries/integrations/social/tiktok.provider';\nimport { PinterestProvider } from '@gitroom/nestjs-libraries/integrations/social/pinterest.provider';\nimport { DribbbleProvider } from '@gitroom/nestjs-libraries/integrations/social/dribbble.provider';\nimport { LinkedinPageProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.page.provider';\nimport { ThreadsProvider } from '@gitroom/nestjs-libraries/integrations/social/threads.provider';\nimport { DiscordProvider } from '@gitroom/nestjs-libraries/integrations/social/discord.provider';\nimport { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/slack.provider';\nimport { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider';\nimport { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider';\nimport { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lemmy.provider';\nimport { InstagramStandaloneProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.standalone.provider';\nimport { FarcasterProvider } from '@gitroom/nestjs-libraries/integrations/social/farcaster.provider';\nimport { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';\nimport { NostrProvider } from '@gitroom/nestjs-libraries/integrations/social/nostr.provider';\nimport { VkProvider } from '@gitroom/nestjs-libraries/integrations/social/vk.provider';\nimport { WordpressProvider } from '@gitroom/nestjs-libraries/integrations/social/wordpress.provider';\nimport { ListmonkProvider } from '@gitroom/nestjs-libraries/integrations/social/listmonk.provider';\nimport { GmbProvider } from '@gitroom/nestjs-libraries/integrations/social/gmb.provider';\nimport { KickProvider } from '@gitroom/nestjs-libraries/integrations/social/kick.provider';\nimport { TwitchProvider } from '@gitroom/nestjs-libraries/integrations/social/twitch.provider';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { MoltbookProvider } from '@gitroom/nestjs-libraries/integrations/social/moltbook.provider';\nimport { SkoolProvider } from '@gitroom/nestjs-libraries/integrations/social/skool.provider';\nimport { WhopProvider } from '@gitroom/nestjs-libraries/integrations/social/whop.provider';\nimport { MeweProvider } from '@gitroom/nestjs-libraries/integrations/social/mewe.provider';\n\nexport const socialIntegrationList: Array<SocialAbstract & SocialProvider> = [\n  new XProvider(),\n  new LinkedinProvider(),\n  new LinkedinPageProvider(),\n  new RedditProvider(),\n  new InstagramProvider(),\n  new InstagramStandaloneProvider(),\n  new FacebookProvider(),\n  new ThreadsProvider(),\n  new YoutubeProvider(),\n  new GmbProvider(),\n  new TiktokProvider(),\n  new PinterestProvider(),\n  new DribbbleProvider(),\n  new DiscordProvider(),\n  new SlackProvider(),\n  new KickProvider(),\n  new TwitchProvider(),\n  new MastodonProvider(),\n  new BlueskyProvider(),\n  new LemmyProvider(),\n  new FarcasterProvider(),\n  new TelegramProvider(),\n  new NostrProvider(),\n  new VkProvider(),\n  new MediumProvider(),\n  new DevToProvider(),\n  new HashnodeProvider(),\n  new WordpressProvider(),\n  new ListmonkProvider(),\n  new MoltbookProvider(),\n  new WhopProvider(),\n  new SkoolProvider(),\n  new MeweProvider(),\n  // new MastodonCustomProvider(),\n];\n\n@Injectable()\nexport class IntegrationManager {\n  async getAllIntegrations() {\n    return {\n      social: await Promise.all(\n        socialIntegrationList.map(async (p) => ({\n          name: p.name,\n          identifier: p.identifier,\n          toolTip: p.toolTip,\n          editor: p.editor,\n          isExternal: !!p.externalUrl,\n          isWeb3: !!p.isWeb3,\n          isChromeExtension: !!p.isChromeExtension,\n          ...(p.extensionCookies ? { extensionCookies: p.extensionCookies } : {}),\n          ...(p.customFields ? { customFields: await p.customFields() } : {}),\n        }))\n      ),\n      article: [] as any[],\n    };\n  }\n\n  getAllTools(): {\n    [key: string]: {\n      description: string;\n      dataSchema: any;\n      methodName: string;\n    }[];\n  } {\n    return socialIntegrationList.reduce(\n      (all, current) => ({\n        ...all,\n        [current.identifier]:\n          Reflect.getMetadata('custom:tool', current.constructor.prototype) ||\n          [],\n      }),\n      {}\n    );\n  }\n\n  getAllRulesDescription(): {\n    [key: string]: string;\n  } {\n    return socialIntegrationList.reduce(\n      (all, current) => ({\n        ...all,\n        [current.identifier]:\n          Reflect.getMetadata(\n            'custom:rules:description',\n            current.constructor\n          ) || '',\n      }),\n      {}\n    );\n  }\n\n  getAllPlugs() {\n    return socialIntegrationList\n      .map((p) => {\n        return {\n          name: p.name,\n          identifier: p.identifier,\n          plugs: (\n            Reflect.getMetadata('custom:plug', p.constructor.prototype) || []\n          )\n            .filter((f: any) => !f.disabled)\n            .map((p: any) => ({\n              ...p,\n              fields: p.fields.map((c: any) => ({\n                ...c,\n                validation: c?.validation?.toString(),\n              })),\n            })),\n        };\n      })\n      .filter((f) => f.plugs.length);\n  }\n\n  getInternalPlugs(providerName: string) {\n    const p = socialIntegrationList.find((p) => p.identifier === providerName)!;\n    return {\n      internalPlugs:\n        (\n          Reflect.getMetadata(\n            'custom:internal_plug',\n            p.constructor.prototype\n          ) || []\n        ).filter((f: any) => !f.disabled) || [],\n    };\n  }\n\n  getAllowedSocialsIntegrations() {\n    return socialIntegrationList.map((p) => p.identifier);\n  }\n  getSocialIntegration(integration: string): SocialProvider {\n    return socialIntegrationList.find((i) => i.identifier === integration)!;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/integration.missing.scopes.ts",
    "content": "import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';\nimport { Response } from 'express';\nimport { NotEnoughScopes } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { HttpStatusCode } from 'axios';\n\n@Catch(NotEnoughScopes)\nexport class NotEnoughScopesFilter implements ExceptionFilter {\n  catch(exception: NotEnoughScopes, host: ArgumentsHost) {\n    const ctx = host.switchToHttp();\n    const response = ctx.getResponse<Response>();\n\n    response\n      .status(HttpStatusCode.Conflict)\n      .json({ msg: exception.message });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/refresh.integration.service.ts",
    "content": "import { forwardRef, Inject, Injectable } from '@nestjs/common';\nimport { Integration } from '@prisma/client';\nimport { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';\nimport { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';\nimport {\n  AuthTokenDetails,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { TemporalService } from 'nestjs-temporal-core';\n\n@Injectable()\nexport class RefreshIntegrationService {\n  constructor(\n    private _integrationManager: IntegrationManager,\n    @Inject(forwardRef(() => IntegrationService))\n    private _integrationService: IntegrationService,\n    private _temporalService: TemporalService\n  ) {}\n  async refresh(integration: Integration): Promise<false | AuthTokenDetails> {\n    const socialProvider = this._integrationManager.getSocialIntegration(\n      integration.providerIdentifier\n    );\n\n    const refresh = await this.refreshProcess(integration, socialProvider);\n\n    if (!refresh) {\n      return false as const;\n    }\n\n    await this._integrationService.createOrUpdateIntegration(\n      undefined,\n      !!socialProvider.oneTimeToken,\n      integration.organizationId,\n      integration.name,\n      integration.picture!,\n      'social',\n      integration.internalId,\n      integration.providerIdentifier,\n      refresh.accessToken,\n      refresh.refreshToken,\n      refresh.expiresIn\n    );\n\n    return refresh;\n  }\n\n  public async setBetweenSteps(integration: Integration) {\n    await this._integrationService.setBetweenRefreshSteps(integration.id);\n    await this._integrationService.informAboutRefreshError(\n      integration.organizationId,\n      integration\n    );\n  }\n\n  public async startRefreshWorkflow(orgId: string, id: string, integration: SocialProvider) {\n    if (!integration.refreshCron) {\n      return false;\n    }\n\n    return this._temporalService.client\n      .getRawClient()\n      ?.workflow.start(`refreshTokenWorkflow`, {\n        workflowId: `refresh_${id}`,\n        args: [{integrationId: id, organizationId: orgId}],\n        taskQueue: 'main',\n        workflowIdConflictPolicy: 'TERMINATE_EXISTING',\n      });\n  }\n\n  private async refreshProcess(\n    integration: Integration,\n    socialProvider: SocialProvider\n  ): Promise<AuthTokenDetails | false> {\n    const refresh: false | AuthTokenDetails = await socialProvider\n      .refreshToken(integration.refreshToken)\n      .catch((err) => false);\n\n    if (!refresh || !refresh.accessToken) {\n      await this._integrationService.refreshNeeded(\n        integration.organizationId,\n        integration.id\n      );\n\n      await this._integrationService.informAboutRefreshError(\n        integration.organizationId,\n        integration\n      );\n\n      await this._integrationService.disconnectChannel(\n        integration.organizationId,\n        integration\n      );\n\n      return false;\n    }\n\n    if (\n      !socialProvider.reConnect ||\n      integration.rootInternalId === integration.internalId\n    ) {\n      return refresh;\n    }\n\n    const reConnect = await socialProvider.reConnect(\n      integration.rootInternalId,\n      integration.internalId,\n      refresh.accessToken\n    );\n\n    return {\n      ...refresh,\n      ...reConnect,\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport {\n  BadBody,\n  RefreshToken,\n  SocialAbstract,\n} from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport {\n  BskyAgent,\n  RichText,\n  AppBskyEmbedVideo,\n  AppBskyVideoDefs,\n  AtpAgent,\n  BlobRef,\n} from '@atproto/api';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport sharp from 'sharp';\nimport { Plug } from '@gitroom/helpers/decorators/plug.decorator';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport axios from 'axios';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\nasync function reduceImageBySize(url: string, maxSizeKB = 976) {\n  try {\n    // Fetch the image from the URL\n    const response = await axios.get(url, { responseType: 'arraybuffer' });\n    let imageBuffer = Buffer.from(response.data);\n\n    // Use sharp to get the metadata of the image\n    const metadata = await sharp(imageBuffer).metadata();\n    let width = metadata.width!;\n    let height = metadata.height!;\n\n    // Resize iteratively until the size is below the threshold\n    while (imageBuffer.length / 1024 > maxSizeKB) {\n      width = Math.floor(width * 0.9); // Reduce dimensions by 10%\n      height = Math.floor(height * 0.9);\n\n      // Resize the image\n      const resizedBuffer = await sharp(imageBuffer)\n        .resize({ width, height })\n        .toBuffer();\n\n      imageBuffer = resizedBuffer;\n\n      if (width < 10 || height < 10) break; // Prevent overly small dimensions\n    }\n\n    return { width, height, buffer: imageBuffer };\n  } catch (error) {\n    console.error('Error processing image:', error);\n    throw error;\n  }\n}\n\nasync function uploadVideo(\n  agent: AtpAgent,\n  videoPath: string\n): Promise<AppBskyEmbedVideo.Main> {\n  const { data: serviceAuth } = await agent.com.atproto.server.getServiceAuth({\n    aud: `did:web:${agent.dispatchUrl.host}`,\n    lxm: 'com.atproto.repo.uploadBlob',\n    exp: Date.now() / 1000 + 60 * 30, // 30 minutes\n  });\n\n  async function downloadVideo(\n    url: string\n  ): Promise<{ video: Buffer; size: number }> {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch video: ${response.statusText}`);\n    }\n    const arrayBuffer = await response.arrayBuffer();\n    const video = Buffer.from(arrayBuffer);\n    const size = video.length;\n    return { video, size };\n  }\n\n  const video = await downloadVideo(videoPath);\n\n  console.log('Downloaded video', videoPath, video.size);\n\n  const uploadUrl = new URL(\n    'https://video.bsky.app/xrpc/app.bsky.video.uploadVideo'\n  );\n  uploadUrl.searchParams.append('did', agent.session!.did);\n  uploadUrl.searchParams.append('name', videoPath.split('/').pop()!);\n\n  const uploadResponse = await fetch(uploadUrl, {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${serviceAuth.token}`,\n      'Content-Type': 'video/mp4',\n      'Content-Length': video.size.toString(),\n    },\n    body: video.video,\n  });\n\n  const jobStatus = (await uploadResponse.json()) as AppBskyVideoDefs.JobStatus;\n  console.log('JobId:', jobStatus.jobId);\n  let blob: BlobRef | undefined = jobStatus.blob;\n  const videoAgent = new AtpAgent({ service: 'https://video.bsky.app' });\n\n  while (!blob) {\n    const { data: status } = await videoAgent.app.bsky.video.getJobStatus({\n      jobId: jobStatus.jobId,\n    });\n    console.log(\n      'Status:',\n      status.jobStatus.state,\n      status.jobStatus.progress || ''\n    );\n    if (status.jobStatus.blob) {\n      blob = status.jobStatus.blob;\n    }\n\n    if (status.jobStatus.state === 'JOB_STATE_FAILED') {\n      throw new BadBody(\n        'bluesky',\n        JSON.stringify({}),\n        {} as any,\n        'Could not upload video, job failed'\n      );\n    }\n\n    await timer(30000);\n  }\n\n  console.log('posting video...');\n\n  return {\n    $type: 'app.bsky.embed.video',\n    video: blob,\n  } satisfies AppBskyEmbedVideo.Main;\n}\n\n@Rules(\n  'Bluesky can have maximum 1 video or 4 pictures in one post, it can also be without attachments'\n)\nexport class BlueskyProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 2; // Bluesky has moderate rate limits\n  identifier = 'bluesky';\n  name = 'Bluesky';\n  toolTip = \"We don’t currently support two-factor authentication. If it’s enabled on Bluesky, you’ll need to disable it.\"\n  isBetweenSteps = false;\n  scopes = ['write:statuses', 'profile', 'write:media'];\n  editor = 'normal' as const;\n  maxLength() {\n    return 300;\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'service',\n        label: 'Service',\n        defaultValue: 'https://bsky.social',\n        validation: `/^(https?:\\\\/\\\\/)?((([a-zA-Z0-9\\\\-_]{1,256}\\\\.[a-zA-Z]{2,6})|(([0-9]{1,3}\\\\.){3}[0-9]{1,3}))(:[0-9]{1,5})?)(\\\\/[^\\\\s]*)?$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'identifier',\n        label: 'Identifier',\n        validation: `/^.+$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'password',\n        label: 'Password',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n\n    try {\n      const agent = new BskyAgent({\n        service: body.service,\n      });\n\n      const {\n        data: { accessJwt, refreshJwt, handle, did },\n      } = await agent.login({\n        identifier: body.identifier,\n        password: body.password,\n      });\n\n      const profile = await agent.getProfile({\n        actor: did,\n      });\n\n      return {\n        refreshToken: refreshJwt,\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: accessJwt,\n        id: did,\n        name: profile.data.displayName!,\n        picture: profile?.data?.avatar || '',\n        username: profile.data.handle!,\n      };\n    } catch (e) {\n      console.log(e);\n      return 'Invalid credentials';\n    }\n  }\n\n  private async getAgent(integration: Integration) {\n    const body = JSON.parse(\n      AuthService.fixedDecryption(integration.customInstanceDetails!)\n    );\n    const agent = new BskyAgent({\n      service: body.service,\n    });\n\n    try {\n      await agent.login({\n        identifier: body.identifier,\n        password: body.password,\n      });\n    } catch (err) {\n      throw new RefreshToken('bluesky', JSON.stringify(err), {} as BodyInit);\n    }\n\n    return agent;\n  }\n\n  private async uploadMediaForPost(\n    agent: BskyAgent,\n    post: PostDetails\n  ): Promise<{ embed: any; images: any[] }> {\n    // Separate images and videos\n    const imageMedia =\n      post.media?.filter((p) => p.path.indexOf('mp4') === -1) || [];\n    const videoMedia =\n      post.media?.filter((p) => p.path.indexOf('mp4') !== -1) || [];\n\n    // Upload images\n    const images = await Promise.all(\n      imageMedia.map(async (p) => {\n        const { buffer, width, height } = await reduceImageBySize(p.path);\n        return {\n          width,\n          height,\n          buffer: await agent.uploadBlob(new Blob([buffer])),\n        };\n      })\n    );\n\n    // Upload videos (only one video per post is supported by Bluesky)\n    let videoEmbed: AppBskyEmbedVideo.Main | null = null;\n    if (videoMedia.length > 0) {\n      videoEmbed = await uploadVideo(agent, videoMedia[0].path);\n    }\n\n    // Determine embed based on media types\n    let embed: any = {};\n    if (videoEmbed) {\n      embed = videoEmbed;\n    } else if (images.length > 0) {\n      embed = {\n        $type: 'app.bsky.embed.images',\n        images: images.map((p, index) => ({\n          alt: imageMedia?.[index]?.alt || '',\n          image: p.buffer.data.blob,\n          aspectRatio: {\n            width: p.width,\n            height: p.height,\n          },\n        })),\n      };\n    }\n\n    return { embed, images };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const agent = await this.getAgent(integration);\n    const [firstPost] = postDetails;\n\n    const { embed } = await this.uploadMediaForPost(agent, firstPost);\n\n    const rt = new RichText({\n      text: firstPost.message,\n    });\n\n    await rt.detectFacets(agent);\n\n    // @ts-ignore\n    const { cid, uri, commit } = await agent.post({\n      text: rt.text,\n      facets: rt.facets,\n      createdAt: new Date().toISOString(),\n      ...(Object.keys(embed).length > 0 ? { embed } : {}),\n    });\n\n    return [\n      {\n        id: firstPost.id,\n        postId: uri,\n        status: 'completed',\n        releaseURL: `https://bsky.app/profile/${id}/post/${uri.split('/').pop()}`,\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const agent = await this.getAgent(integration);\n    const [commentPost] = postDetails;\n\n    const { embed } = await this.uploadMediaForPost(agent, commentPost);\n\n    const rt = new RichText({\n      text: commentPost.message,\n    });\n\n    await rt.detectFacets(agent);\n\n    // Get the parent post info to get its CID\n    const parentUri = lastCommentId || postId;\n\n    // Fetch the parent post to get its CID\n    const parentThread = await agent.getPostThread({\n      uri: parentUri,\n      depth: 0,\n    });\n\n    // @ts-ignore\n    const parentCid = parentThread.data.thread.post?.cid;\n    // @ts-ignore\n    const rootUri = parentThread.data.thread.post?.record?.reply?.root?.uri || postId;\n    // @ts-ignore\n    const rootCid = parentThread.data.thread.post?.record?.reply?.root?.cid || parentCid;\n\n    // @ts-ignore\n    const { cid, uri, commit } = await agent.post({\n      text: rt.text,\n      facets: rt.facets,\n      createdAt: new Date().toISOString(),\n      ...(Object.keys(embed).length > 0 ? { embed } : {}),\n      reply: {\n        root: {\n          uri: rootUri,\n          cid: rootCid,\n        },\n        parent: {\n          uri: parentUri,\n          cid: parentCid,\n        },\n      },\n    });\n\n    return [\n      {\n        id: commentPost.id,\n        postId: uri,\n        status: 'completed',\n        releaseURL: `https://bsky.app/profile/${id}/post/${uri.split('/').pop()}`,\n      },\n    ];\n  }\n\n  @Plug({\n    identifier: 'bluesky-autoRepostPost',\n    title: 'Auto Repost Posts',\n    description:\n      'When a post reached a certain number of likes, repost it to increase engagement (1 week old posts)',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n    ],\n  })\n  async autoRepostPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string }\n  ) {\n    const body = JSON.parse(\n      AuthService.fixedDecryption(integration.customInstanceDetails!)\n    );\n    const agent = new BskyAgent({\n      service: body.service,\n    });\n\n    await agent.login({\n      identifier: body.identifier,\n      password: body.password,\n    });\n\n    const getThread = await agent.getPostThread({\n      uri: id,\n      depth: 0,\n    });\n\n    // @ts-ignore\n    if (getThread.data.thread.post?.likeCount >= +fields.likesAmount) {\n      await timer(2000);\n      await agent.repost(\n        // @ts-ignore\n        getThread.data.thread.post?.uri,\n        // @ts-ignore\n        getThread.data.thread.post?.cid\n      );\n      return true;\n    }\n\n    return true;\n  }\n\n  @Plug({\n    identifier: 'bluesky-autoPlugPost',\n    title: 'Auto plug post',\n    description:\n      'When a post reached a certain number of likes, add another post to it so you followers get a notification about your promotion',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n      {\n        name: 'post',\n        type: 'richtext',\n        placeholder: 'Post to plug',\n        description: 'Message content to plug',\n        validation: /^[\\s\\S]{3,}$/g,\n      },\n    ],\n  })\n  async autoPlugPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string; post: string }\n  ) {\n    const body = JSON.parse(\n      AuthService.fixedDecryption(integration.customInstanceDetails!)\n    );\n    const agent = new BskyAgent({\n      service: body.service,\n    });\n\n    await agent.login({\n      identifier: body.identifier,\n      password: body.password,\n    });\n\n    const getThread = await agent.getPostThread({\n      uri: id,\n      depth: 0,\n    });\n\n    // @ts-ignore\n    if (getThread.data.thread.post?.likeCount >= +fields.likesAmount) {\n      await timer(2000);\n      const rt = new RichText({\n        text: stripHtmlValidation('normal', fields.post, true),\n      });\n\n      await agent.post({\n        text: rt.text,\n        facets: rt.facets,\n        createdAt: new Date().toISOString(),\n        reply: {\n          root: {\n            // @ts-ignore\n            uri: getThread.data.thread.post?.uri,\n            // @ts-ignore\n            cid: getThread.data.thread.post?.cid,\n          },\n          parent: {\n            // @ts-ignore\n            uri: getThread.data.thread.post?.uri,\n            // @ts-ignore\n            cid: getThread.data.thread.post?.cid,\n          },\n        },\n      });\n      return true;\n    }\n\n    return true;\n  }\n\n  override async mention(\n    token: string,\n    d: { query: string },\n    id: string,\n    integration: Integration\n  ) {\n    const body = JSON.parse(\n      AuthService.fixedDecryption(integration.customInstanceDetails!)\n    );\n\n    const agent = new BskyAgent({\n      service: body.service,\n    });\n\n    await agent.login({\n      identifier: body.identifier,\n      password: body.password,\n    });\n\n    const list = await agent.searchActors({\n      q: d.query,\n    });\n\n    return list.data.actors.map((p) => ({\n      label: p.displayName,\n      id: p.handle,\n      image: p.avatar,\n    }));\n  }\n\n  mentionFormat(idOrHandle: string, name: string) {\n    return `@${idOrHandle}`;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class DevToProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Dev.to has moderate publishing limits\n  identifier = 'devto';\n  name = 'Dev.to';\n  isBetweenSteps = false;\n  editor = 'markdown' as const;\n  scopes = [] as string[];\n  maxLength() {\n    return 100000;\n  }\n  dto = DevToSettingsDto;\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  override handleErrors(body: string) {\n    if (body.indexOf('Canonical url has already been taken') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Canonical URL already exists',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'apiKey',\n        label: 'API key',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n    try {\n      const { name, id, profile_image, username } = await (\n        await fetch('https://dev.to/api/users/me', {\n          headers: {\n            'api-key': body.apiKey,\n          },\n        })\n      ).json();\n\n      return {\n        refreshToken: '',\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: body.apiKey,\n        id,\n        name,\n        picture: profile_image || '',\n        username,\n      };\n    } catch (err) {\n      return 'Invalid credentials';\n    }\n  }\n\n  @Tool({ description: 'Tag list', dataSchema: [] })\n  async tags(token: string) {\n    const tags = await (\n      await fetch('https://dev.to/api/tags?per_page=1000&page=1', {\n        headers: {\n          'api-key': token,\n        },\n      })\n    ).json();\n\n    return tags.map((p: any) => ({ value: p.id, label: p.name }));\n  }\n\n  @Tool({ description: 'Organization list', dataSchema: [] })\n  async organizations(token: string) {\n    const orgs = await (\n      await fetch('https://dev.to/api/articles/me/all?per_page=1000', {\n        headers: {\n          'api-key': token,\n        },\n      })\n    ).json();\n\n    const allOrgs: string[] = [\n      ...new Set(\n        orgs\n          .flatMap((org: any) => org?.organization?.username)\n          .filter((f: string) => f)\n      ),\n    ] as string[];\n    const fullDetails = await Promise.all(\n      allOrgs.map(async (org: string) => {\n        return (\n          await fetch(`https://dev.to/api/organizations/${org}`, {\n            headers: {\n              'api-key': token,\n            },\n          })\n        ).json();\n      })\n    );\n\n    return fullDetails.map((org: any) => ({\n      id: org.id,\n      name: org.name,\n      username: org.username,\n    }));\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { settings } = postDetails?.[0] || { settings: {} };\n    const { id: postId, url } = await (\n      await this.fetch(`https://dev.to/api/articles`, {\n        method: 'POST',\n        body: JSON.stringify({\n          article: {\n            title: settings.title,\n            body_markdown: postDetails?.[0].message,\n            published: true,\n            ...(settings?.main_image?.path\n              ? { main_image: settings?.main_image?.path }\n              : {}),\n            tags: settings?.tags?.map((t: any) => t.label),\n            organization_id: settings.organization,\n            ...(settings.canonical\n              ? { canonical_url: settings.canonical }\n              : {}),\n          },\n        }),\n        headers: {\n          'Content-Type': 'application/json',\n          'api-key': accessToken,\n        },\n      })\n    ).json();\n\n    return [\n      {\n        id: postDetails?.[0].id,\n        status: 'completed',\n        postId: String(postId),\n        releaseURL: url,\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/discord.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { Integration } from '@prisma/client';\nimport { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class DiscordProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 5; // Discord has generous rate limits for webhook posting\n  identifier = 'discord';\n  name = 'Discord';\n  isBetweenSteps = false;\n  editor = 'markdown' as const;\n  scopes = ['identify', 'guilds'];\n  maxLength() {\n    return 1980;\n  }\n  dto = DiscordDto;\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const { access_token, expires_in, refresh_token } = await (\n      await this.fetch('https://discord.com/api/oauth2/token', {\n        method: 'POST',\n        body: new URLSearchParams({\n          refresh_token: refreshToken,\n          grant_type: 'refresh_token',\n        }),\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            process.env.DISCORD_CLIENT_ID +\n              ':' +\n              process.env.DISCORD_CLIENT_SECRET\n          ).toString('base64')}`,\n        },\n      })\n    ).json();\n\n    const { application } = await (\n      await fetch('https://discord.com/api/oauth2/@me', {\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      accessToken: access_token,\n      id: '',\n      name: application.name,\n      picture: '',\n      username: '',\n    };\n  }\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: `https://discord.com/oauth2/authorize?client_id=${\n        process.env.DISCORD_CLIENT_ID\n      }&permissions=377957124096&response_type=code&redirect_uri=${encodeURIComponent(\n        `${process.env.FRONTEND_URL}/integrations/social/discord`\n      )}&integration_type=0&scope=bot+identify+guilds&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const { access_token, expires_in, refresh_token, scope, guild } = await (\n      await this.fetch('https://discord.com/api/oauth2/token', {\n        method: 'POST',\n        body: new URLSearchParams({\n          code: params.code,\n          grant_type: 'authorization_code',\n          redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/discord`,\n        }),\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            process.env.DISCORD_CLIENT_ID +\n              ':' +\n              process.env.DISCORD_CLIENT_SECRET\n          ).toString('base64')}`,\n        },\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope.split(' '));\n\n    const { application } = await (\n      await fetch('https://discord.com/api/oauth2/@me', {\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: guild.id,\n      name: application.name,\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      picture: `https://cdn.discordapp.com/avatars/${application.bot.id}/${application.bot.avatar}.png`,\n      username: application.bot.username,\n    };\n  }\n\n  @Tool({ description: 'Channels', dataSchema: [] })\n  async channels(accessToken: string, params: any, id: string) {\n    const list = await (\n      await fetch(`https://discord.com/api/guilds/${id}/channels`, {\n        headers: {\n          Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n        },\n      })\n    ).json();\n\n    return list\n      .filter((p: any) => p.type === 0 || p.type === 5 || p.type === 15)\n      .map((p: any) => ({\n        id: String(p.id),\n        name: p.name,\n      }));\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const channel = firstPost.settings.channel;\n\n    const form = new FormData();\n    form.append(\n      'payload_json',\n      JSON.stringify({\n        content: firstPost.message.replace(/\\[\\[\\[(@.*?)]]]/g, (match, p1) => {\n          return `<${p1}>`;\n        }),\n        attachments: firstPost.media?.map((p, index) => ({\n          id: index,\n          description: `Picture ${index}`,\n          filename: p.path.split('/').pop(),\n        })),\n      })\n    );\n\n    let index = 0;\n    for (const media of firstPost.media || []) {\n      const loadMedia = await fetch(media.path);\n\n      form.append(\n        `files[${index}]`,\n        await loadMedia.blob(),\n        media.path.split('/').pop()\n      );\n      index++;\n    }\n\n    const data = await (\n      await fetch(`https://discord.com/api/channels/${channel}/messages`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n        },\n        body: form,\n      })\n    ).json();\n\n    return [\n      {\n        id: firstPost.id,\n        releaseURL: `https://discord.com/channels/${id}/${channel}/${data.id}`,\n        postId: data.id,\n        status: 'success',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const channel = commentPost.settings.channel;\n\n    // For Discord, we create a thread from the original message for comments\n    // If we don't have a thread yet, create one\n    let threadChannel = channel;\n\n    // Create thread if this is the first comment\n    if (!lastCommentId) {\n      const { id: threadId } = await (\n        await fetch(\n          `https://discord.com/api/channels/${channel}/messages/${postId}/threads`,\n          {\n            method: 'POST',\n            headers: {\n              Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({\n              name: 'Thread',\n              auto_archive_duration: 1440,\n            }),\n          }\n        )\n      ).json();\n      threadChannel = threadId;\n    } else {\n      // Extract thread channel from the last comment's URL or use channel directly\n      threadChannel = channel;\n    }\n\n    const form = new FormData();\n    form.append(\n      'payload_json',\n      JSON.stringify({\n        content: commentPost.message.replace(/\\[\\[\\[(@.*?)]]]/g, (match, p1) => {\n          return `<${p1}>`;\n        }),\n        attachments: commentPost.media?.map((p, index) => ({\n          id: index,\n          description: `Picture ${index}`,\n          filename: p.path.split('/').pop(),\n        })),\n      })\n    );\n\n    let index = 0;\n    for (const media of commentPost.media || []) {\n      const loadMedia = await fetch(media.path);\n\n      form.append(\n        `files[${index}]`,\n        await loadMedia.blob(),\n        media.path.split('/').pop()\n      );\n      index++;\n    }\n\n    const data = await (\n      await fetch(\n        `https://discord.com/api/channels/${threadChannel}/messages`,\n        {\n          method: 'POST',\n          headers: {\n            Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n          },\n          body: form,\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        releaseURL: `https://discord.com/channels/${id}/${threadChannel}/${data.id}`,\n        postId: data.id,\n        status: 'success',\n      },\n    ];\n  }\n\n  async changeNickname(id: string, accessToken: string, name: string) {\n    await (\n      await fetch(`https://discord.com/api/guilds/${id}/members/@me`, {\n        method: 'PATCH',\n        headers: {\n          Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          nick: name,\n        }),\n      })\n    ).json();\n\n    return {\n      name,\n    };\n  }\n\n  override async mention(\n    token: string,\n    data: { query: string },\n    id: string,\n    integration: Integration\n  ) {\n    const allRoles = await (\n      await fetch(`https://discord.com/api/guilds/${id}/roles`, {\n        headers: {\n          Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n          'Content-Type': 'application/json',\n        },\n      })\n    ).json();\n\n    const matching = allRoles\n      .filter((role: any) =>\n        role.name.toLowerCase().includes(data.query.toLowerCase())\n      )\n      .filter((f: any) => f.name !== '@everyone' && f.name !== '@here');\n\n    const list = await (\n      await fetch(\n        `https://discord.com/api/guilds/${id}/members/search?query=${data.query}`,\n        {\n          headers: {\n            Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN_ID}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      )\n    ).json();\n\n    return [\n      ...[\n        {\n          id: String('here'),\n          label: 'here',\n          image: '',\n          doNotCache: true,\n        },\n        {\n          id: String('everyone'),\n          label: 'everyone',\n          image: '',\n          doNotCache: true,\n        },\n      ].filter((role: any) => {\n        return role.label.toLowerCase().includes(data.query.toLowerCase());\n      }),\n      ...matching.map((p: any) => ({\n        id: String('&' + p.id),\n        label: p.name.split('@')[1],\n        image: '',\n        doNotCache: true,\n      })),\n      ...list.map((p: any) => ({\n        id: String(p.user.id),\n        label: p.user.global_name || p.user.username,\n        image: `https://cdn.discordapp.com/avatars/${p.user.id}/${p.user.avatar}.png`,\n      })),\n    ];\n  }\n\n  mentionFormat(idOrHandle: string, name: string) {\n    if (name === '@here' || name === '@everyone') {\n      return name;\n    }\n    return `[[[@${idOrHandle.replace('@', '')}]]]`;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport axios from 'axios';\nimport FormData from 'form-data';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';\nimport mime from 'mime-types';\nimport { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class DribbbleProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Dribbble has moderate API limits\n  identifier = 'dribbble';\n  name = 'Dribbble';\n  isBetweenSteps = false;\n  scopes = ['public', 'upload'];\n  editor = 'normal' as const;\n  maxLength() {\n    return 40000;\n  }\n  dto = DribbbleDto;\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const { access_token, expires_in } = await (\n      await this.fetch('https://api-sandbox.pinterest.com/v5/oauth/token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            `${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`\n          ).toString('base64')}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n          scope: `${this.scopes.join(',')}`,\n          redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,\n        }),\n      })\n    ).json();\n\n    const { id, profile_image, username } = await (\n      await this.fetch('https://api-sandbox.pinterest.com/v5/user_account', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: id,\n      name: username,\n      accessToken: access_token,\n      refreshToken: refreshToken,\n      expiresIn: expires_in,\n      picture: profile_image || '',\n      username,\n    };\n  }\n\n  @Tool({ description: 'Teams list', dataSchema: [] })\n  async teams(accessToken: string) {\n    const { teams } = await (\n      await this.fetch('https://api.dribbble.com/v2/user', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return (\n      teams?.map((team: any) => ({\n        id: team.id,\n        name: team.name,\n      })) || []\n    );\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: `https://dribbble.com/oauth/authorize?client_id=${\n        process.env.DRIBBBLE_CLIENT_ID\n      }&redirect_uri=${encodeURIComponent(\n        `${process.env.FRONTEND_URL}/integrations/social/dribbble`\n      )}&response_type=code&scope=${this.scopes.join('+')}&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh: string;\n  }) {\n    const { access_token, scope } = await (\n      await this.fetch(\n        `https://dribbble.com/oauth/token?client_id=${process.env.DRIBBBLE_CLIENT_ID}&client_secret=${process.env.DRIBBBLE_CLIENT_SECRET}&code=${params.code}&redirect_uri=${process.env.FRONTEND_URL}/integrations/social/dribbble`,\n        {\n          method: 'POST',\n        }\n      )\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const { id, name, avatar_url, login } = await (\n      await this.fetch('https://api.dribbble.com/v2/user', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: id,\n      name,\n      accessToken: access_token,\n      refreshToken: '',\n      expiresIn: 999999999,\n      picture: avatar_url,\n      username: login,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<DribbbleDto>[]\n  ): Promise<PostResponse[]> {\n    const { data, status } = await axios.get(\n      postDetails?.[0]?.media?.[0]?.path!,\n      {\n        responseType: 'stream',\n      }\n    );\n\n    const slash = postDetails?.[0]?.media?.[0]?.path.split('/').at(-1);\n\n    const formData = new FormData();\n    formData.append('image', data, {\n      filename: slash,\n      contentType: mime.lookup(slash!) || '',\n    });\n\n    formData.append('title', postDetails[0].settings.title);\n    formData.append('description', postDetails[0].message);\n\n    const data2 = await axios.post(\n      'https://api.dribbble.com/v2/shots',\n      formData,\n      {\n        headers: {\n          ...formData.getHeaders(),\n          Authorization: `Bearer ${accessToken}`,\n        },\n      }\n    );\n\n    const location = data2.headers['location'];\n    const newId = location.split('/').at(-1);\n\n    return [\n      {\n        id: postDetails?.[0]?.id,\n        status: 'completed',\n        postId: newId,\n        releaseURL: `https://dribbble.com/shots/${newId}`,\n      },\n    ];\n  }\n\n  analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    return Promise.resolve([]);\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    // Dribbble doesn't provide detailed post-level analytics via their API\n    return [];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { FacebookDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/facebook.dto';\nimport { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto';\nimport { Integration } from '@prisma/client';\n\nexport class FacebookProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'facebook';\n  name = 'Facebook Page';\n  isBetweenSteps = true;\n  scopes = [\n    'pages_show_list',\n    'business_management',\n    'pages_manage_posts',\n    'pages_manage_engagement',\n    'pages_read_engagement',\n    'read_insights',\n  ];\n  override maxConcurrentJob = 100; // Facebook has reasonable rate limits\n  editor = 'normal' as const;\n  maxLength() {\n    return 63206;\n  }\n  dto = FacebookDto;\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    // Access token validation errors - require re-authentication\n    if (body.indexOf('Error validating access token') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Please re-authenticate your Facebook account',\n      };\n    }\n\n    if (body.indexOf('490') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Access token expired, please re-authenticate',\n      };\n    }\n\n    if (body.indexOf('REVOKED_ACCESS_TOKEN') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Access token has been revoked, please re-authenticate',\n      };\n    }\n\n    if (body.indexOf('1366046') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Photos should be smaller than 4 MB and saved as JPG, PNG',\n      };\n    }\n\n    if (body.indexOf('1390008') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'You are posting too fast, please slow down',\n      };\n    }\n\n    // Content policy violations\n    if (body.indexOf('1346003') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Content flagged as abusive by Facebook',\n      };\n    }\n\n    if (body.indexOf('1404006') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          \"We couldn't post your comment, A security check in facebook required to proceed.\",\n      };\n    }\n\n    if (body.indexOf('1404102') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Content violates Facebook Community Standards',\n      };\n    }\n\n    // Permission errors\n    if (body.indexOf('1404078') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Page publishing authorization required, please re-authenticate',\n      };\n    }\n\n    if (body.indexOf('1609008') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Cannot post Facebook.com links',\n      };\n    }\n\n    // Parameter validation errors\n    if (body.indexOf('2061006') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid URL format in post content',\n      };\n    }\n\n    if (body.indexOf('1349125') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid content format',\n      };\n    }\n\n    if (body.indexOf('1404112') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'For security reasons, your account has limited access to the site for a few days',\n      };\n    }\n\n    if (body.indexOf('Name parameter too long') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Post content is too long',\n      };\n    }\n\n    // Service errors - checking specific subcodes first\n    if (body.indexOf('1363047') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Facebook service temporarily unavailable',\n      };\n    }\n\n    if (body.indexOf('1609010') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Facebook service temporarily unavailable',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url:\n        'https://www.facebook.com/v20.0/dialog/oauth' +\n        `?client_id=${process.env.FACEBOOK_APP_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${process.env.FRONTEND_URL}/integrations/social/facebook`\n        )}` +\n        `&state=${state}` +\n        `&scope=${this.scopes.join(',')}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async reConnect(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {\n    const information = await this.fetchPageInformation(accessToken, {\n      page: requiredId,\n    });\n\n    return {\n      id: information.id,\n      name: information.name,\n      accessToken: information.access_token,\n      picture: information.picture,\n      username: information.username,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const getAccessToken = await (\n      await fetch(\n        'https://graph.facebook.com/v20.0/oauth/access_token' +\n          `?client_id=${process.env.FACEBOOK_APP_ID}` +\n          `&redirect_uri=${encodeURIComponent(\n            `${process.env.FRONTEND_URL}/integrations/social/facebook${\n              params.refresh ? `?refresh=${params.refresh}` : ''\n            }`\n          )}` +\n          `&client_secret=${process.env.FACEBOOK_APP_SECRET}` +\n          `&code=${params.code}`\n      )\n    ).json();\n\n    const { access_token } = await (\n      await fetch(\n        'https://graph.facebook.com/v20.0/oauth/access_token' +\n          '?grant_type=fb_exchange_token' +\n          `&client_id=${process.env.FACEBOOK_APP_ID}` +\n          `&client_secret=${process.env.FACEBOOK_APP_SECRET}` +\n          `&fb_exchange_token=${getAccessToken.access_token}&fields=access_token,expires_in`\n      )\n    ).json();\n\n    const { data } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/me/permissions?access_token=${access_token}`\n      )\n    ).json();\n\n    const permissions = data\n      .filter((d: any) => d.status === 'granted')\n      .map((p: any) => p.permission);\n    this.checkScopes(this.scopes, permissions);\n\n    const { id, name, picture } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}`\n      )\n    ).json();\n\n    return {\n      id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),\n      picture: picture?.data?.url || '',\n      username: '',\n    };\n  }\n\n  async pages(accessToken: string) {\n    const seenIds = new Set<string>();\n    const allPages: any[] = [];\n\n    const fetchPaginated = async (startUrl: string) => {\n      let nextUrl: string | undefined = startUrl;\n      while (nextUrl) {\n        const response = await (await fetch(nextUrl)).json();\n        if (response.data) {\n          for (const page of response.data) {\n            if (!seenIds.has(page.id)) {\n              seenIds.add(page.id);\n              allPages.push(page);\n            }\n          }\n        }\n        nextUrl = response.paging?.next;\n      }\n    };\n\n    // Fetch pages the user explicitly shared during the OAuth dialog\n    await fetchPaginated(\n      `https://graph.facebook.com/v20.0/me/accounts?fields=id,username,name,access_token,picture.type(large)&limit=100&access_token=${accessToken}`\n    );\n\n    // Also fetch pages via Business Manager API to discover pages\n    // not selected during the OAuth page selection step\n    try {\n      let bizUrl: string | undefined =\n        `https://graph.facebook.com/v20.0/me/businesses?access_token=${accessToken}`;\n\n      while (bizUrl) {\n        const bizResponse = await (await fetch(bizUrl)).json();\n        if (bizResponse.data) {\n          for (const business of bizResponse.data) {\n            try {\n              await fetchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/owned_pages?fields=id,username,name,access_token,picture.type(large)&limit=100&access_token=${accessToken}`\n              );\n            } catch {\n              // Continue with other businesses\n            }\n\n            try {\n              await fetchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/client_pages?fields=id,username,name,access_token,picture.type(large)&limit=100&access_token=${accessToken}`\n              );\n            } catch {\n              // Continue with other businesses\n            }\n          }\n        }\n        bizUrl = bizResponse.paging?.next;\n      }\n    } catch {\n      // Business Manager API not available for all users\n    }\n\n    return allPages;\n  }\n\n  async fetchPageInformation(accessToken: string, data: { page: string }) {\n    const pageId = data.page;\n    const fields = 'id,username,name,access_token,picture.type(large)';\n\n    const searchPaginated = async (startUrl: string) => {\n      let url: string | undefined = startUrl;\n      while (url) {\n        const response = await (await fetch(url)).json();\n        if (response.data) {\n          const page = response.data.find(\n            (p: any) => String(p.id) === String(pageId)\n          );\n          if (page) {\n            return {\n              id: page.id,\n              name: page.name,\n              access_token: page.access_token,\n              picture: page.picture?.data?.url || '',\n              username: page.username,\n            };\n          }\n        }\n        url = response.paging?.next;\n      }\n      return null;\n    };\n\n    // 1. Check /me/accounts\n    const fromAccounts = await searchPaginated(\n      `https://graph.facebook.com/v20.0/me/accounts?fields=${fields}&limit=100&access_token=${accessToken}`\n    );\n    if (fromAccounts) return fromAccounts;\n\n    // 2. Check Business Manager owned_pages and client_pages\n    try {\n      let bizUrl: string | undefined =\n        `https://graph.facebook.com/v20.0/me/businesses?access_token=${accessToken}`;\n\n      while (bizUrl) {\n        const bizResponse = await (await fetch(bizUrl)).json();\n        if (bizResponse.data) {\n          for (const business of bizResponse.data) {\n            try {\n              const fromOwned = await searchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/owned_pages?fields=${fields}&limit=100&access_token=${accessToken}`\n              );\n              if (fromOwned) return fromOwned;\n            } catch {\n              // Continue with other businesses\n            }\n\n            try {\n              const fromClient = await searchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/client_pages?fields=${fields}&limit=100&access_token=${accessToken}`\n              );\n              if (fromClient) return fromClient;\n            } catch {\n              // Continue with other businesses\n            }\n          }\n        }\n        bizUrl = bizResponse.paging?.next;\n      }\n    } catch {\n      // Business Manager API not available for all users\n    }\n\n    throw new Error('Page not found in your accounts');\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<FacebookDto>[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n\n    let finalId = '';\n    let finalUrl = '';\n    if ((firstPost?.media?.[0]?.path?.indexOf('mp4') || -2) > -1) {\n      const {\n        id: videoId,\n        permalink_url,\n        ...all\n      } = await (\n        await this.fetch(\n          `https://graph.facebook.com/v20.0/${id}/videos?access_token=${accessToken}&fields=id,permalink_url`,\n          {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({\n              file_url: firstPost?.media?.[0]?.path!,\n              description: firstPost.message,\n              published: true,\n            }),\n          },\n          'upload mp4'\n        )\n      ).json();\n\n      finalUrl = 'https://www.facebook.com/reel/' + videoId;\n      finalId = videoId;\n    } else {\n      const uploadPhotos = !firstPost?.media?.length\n        ? []\n        : await Promise.all(\n            firstPost.media.map(async (media) => {\n              const { id: photoId } = await (\n                await this.fetch(\n                  `https://graph.facebook.com/v20.0/${id}/photos?access_token=${accessToken}`,\n                  {\n                    method: 'POST',\n                    headers: {\n                      'Content-Type': 'application/json',\n                    },\n                    body: JSON.stringify({\n                      url: media.path,\n                      published: false,\n                    }),\n                  },\n                  'upload images slides'\n                )\n              ).json();\n\n              return { media_fbid: photoId };\n            })\n          );\n\n      const {\n        id: postId,\n        permalink_url,\n        ...all\n      } = await (\n        await this.fetch(\n          `https://graph.facebook.com/v20.0/${id}/feed?access_token=${accessToken}&fields=id,permalink_url`,\n          {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({\n              ...(uploadPhotos?.length ? { attached_media: uploadPhotos } : {}),\n              ...(firstPost?.settings?.url\n                ? { link: firstPost.settings.url }\n                : {}),\n              message: firstPost.message,\n              published: true,\n            }),\n          },\n          'finalize upload'\n        )\n      ).json();\n\n      finalUrl = permalink_url;\n      finalId = postId;\n    }\n\n    return [\n      {\n        id: firstPost.id,\n        postId: finalId,\n        releaseURL: finalUrl,\n        status: 'success',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<FacebookDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const replyToId = lastCommentId || postId;\n\n    const data = await (\n      await this.fetch(\n        `https://graph.facebook.com/v20.0/${replyToId}/comments?access_token=${accessToken}&fields=id,permalink_url`,\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            ...(commentPost.media?.length\n              ? { attachment_url: commentPost.media[0].path }\n              : {}),\n            message: commentPost.message,\n          }),\n        },\n        'add comment'\n      )\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: data.id,\n        releaseURL: data.permalink_url,\n        status: 'success',\n      },\n    ];\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const until = dayjs().endOf('day').unix();\n    const since = dayjs().subtract(date, 'day').unix();\n\n    const { data } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/${id}/insights?metric=page_impressions_unique,page_posts_impressions_unique,page_post_engagements,page_daily_follows,page_video_views&access_token=${accessToken}&period=day&since=${since}&until=${until}`\n      )\n    ).json();\n\n    return (\n      data?.map((d: any) => ({\n        label:\n          d.name === 'page_impressions_unique'\n            ? 'Page Impressions'\n            : d.name === 'page_post_engagements'\n            ? 'Posts Engagement'\n            : d.name === 'page_daily_follows'\n            ? 'Page followers'\n            : d.name === 'page_video_views'\n            ? 'Videos views'\n            : 'Posts Impressions',\n        percentageChange: 5,\n        data: d?.values?.map((v: any) => ({\n          total: v.value,\n          date: dayjs(v.end_time).format('YYYY-MM-DD'),\n        })),\n      })) || []\n    );\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    try {\n      // Fetch post insights from Facebook Graph API\n      const { data } = await (\n        await this.fetch(\n          `https://graph.facebook.com/v20.0/${postId}/insights?metric=post_impressions_unique,post_reactions_by_type_total,post_clicks,post_clicks_by_type&access_token=${accessToken}`\n        )\n      ).json();\n\n      if (!data || data.length === 0) {\n        return [];\n      }\n\n      const result: AnalyticsData[] = [];\n\n      for (const metric of data) {\n        const value = metric.values?.[0]?.value;\n        if (value === undefined) continue;\n\n        let label = '';\n        let total = '';\n\n        switch (metric.name) {\n          case 'post_impressions_unique':\n            label = 'Impressions';\n            total = String(value);\n            break;\n          case 'post_clicks':\n            label = 'Clicks';\n            total = String(value);\n            break;\n          case 'post_clicks_by_type':\n            // This returns an object with click types\n            if (typeof value === 'object') {\n              const totalClicks = Object.values(\n                value as Record<string, number>\n              ).reduce((sum: number, v: number) => sum + v, 0);\n              label = 'Clicks by Type';\n              total = String(totalClicks);\n            }\n            break;\n          case 'post_reactions_by_type_total':\n            // This returns an object with reaction types\n            if (typeof value === 'object') {\n              const totalReactions = Object.values(\n                value as Record<string, number>\n              ).reduce((sum: number, v: number) => sum + v, 0);\n              label = 'Reactions';\n              total = String(totalReactions);\n            }\n            break;\n        }\n\n        if (label) {\n          result.push({\n            label,\n            percentageChange: 0,\n            data: [{ total, date: today }],\n          });\n        }\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching Facebook post analytics:', err);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { NeynarAPIClient } from '@neynar/nodejs-sdk';\nimport { Integration } from '@prisma/client';\nimport { FarcasterDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/farcaster.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\nconst client = new NeynarAPIClient({\n  apiKey: process.env.NEYNAR_SECRET_KEY || '00000000-000-0000-000-000000000000',\n});\n\n@Rules(\n  'Farcaster/Warpcast can only accept pictures'\n)\nexport class FarcasterProvider\n  extends SocialAbstract\n  implements SocialProvider\n{\n  identifier = 'wrapcast';\n  name = 'Farcaster';\n  isBetweenSteps = false;\n  isWeb3 = true;\n  scopes = [] as string[];\n  override maxConcurrentJob = 3; // Farcaster has moderate limits\n  editor = 'normal' as const;\n  maxLength() {\n    return 800;\n  }\n  dto = FarcasterDto;\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(17);\n    return {\n      url: `${process.env.NEYNAR_CLIENT_ID}||${state}` || '',\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const data = JSON.parse(Buffer.from(params.code, 'base64').toString());\n    return {\n      id: String(data.fid),\n      name: data.display_name,\n      accessToken: data.signer_uuid,\n      refreshToken: '',\n      expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(),\n      picture: data?.pfp_url || '',\n      username: data.username,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<FarcasterDto>[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const ids: { releaseURL: string; postId: string }[] = [];\n\n    const channels =\n      !firstPost?.settings?.subreddit ||\n      firstPost?.settings?.subreddit.length === 0\n        ? [undefined]\n        : firstPost?.settings?.subreddit;\n\n    for (const channel of channels) {\n      const data = await client.publishCast({\n        embeds:\n          firstPost?.media?.map((media) => ({\n            url: media.path,\n          })) || [],\n        signerUuid: accessToken,\n        text: firstPost.message,\n        ...(channel?.value?.id ? { channelId: channel?.value?.id } : {}),\n      });\n\n      ids.push({\n        // @ts-ignore\n        releaseURL: `https://warpcast.com/${data.cast.author.username}/${data.cast.hash}`,\n        postId: data.cast.hash,\n      });\n    }\n\n    return [\n      {\n        id: firstPost.id,\n        postId: ids.map((p) => p.postId).join(','),\n        releaseURL: ids.map((p) => p.releaseURL).join(','),\n        status: 'published',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<FarcasterDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const ids: { releaseURL: string; postId: string }[] = [];\n\n    // postId can be comma-separated if posted to multiple channels\n    const parentIds = (lastCommentId || postId).split(',');\n\n    for (const parentHash of parentIds) {\n      const data = await client.publishCast({\n        embeds:\n          commentPost?.media?.map((media) => ({\n            url: media.path,\n          })) || [],\n        signerUuid: accessToken,\n        text: commentPost.message,\n        parent: parentHash,\n      });\n\n      ids.push({\n        // @ts-ignore\n        releaseURL: `https://warpcast.com/${data.cast.author.username}/${data.cast.hash}`,\n        postId: data.cast.hash,\n      });\n    }\n\n    return [\n      {\n        id: commentPost.id,\n        postId: ids.map((p) => p.postId).join(','),\n        releaseURL: ids.map((p) => p.releaseURL).join(','),\n        status: 'published',\n      },\n    ];\n  }\n\n  @Tool({\n    description: 'Search channels',\n    dataSchema: [{ key: 'word', type: 'string', description: 'Search word' }],\n  })\n  async subreddits(\n    accessToken: string,\n    data: any,\n    id: string,\n    integration: Integration\n  ) {\n    const search = await client.searchChannels({\n      q: data.word,\n      limit: 10,\n    });\n\n    return search.channels.map((p) => {\n      return {\n        title: p.name,\n        name: p.name,\n        id: p.id,\n      };\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/gmb.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { google } from 'googleapis';\nimport { OAuth2Client } from 'google-auth-library/build/src/auth/oauth2client';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport * as process from 'node:process';\nimport dayjs from 'dayjs';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\nimport { GmbSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/gmb.settings.dto';\n\nconst clientAndGmb = () => {\n  const client = new google.auth.OAuth2({\n    clientId: process.env.GOOGLE_GMB_CLIENT_ID || process.env.YOUTUBE_CLIENT_ID,\n    clientSecret:\n      process.env.GOOGLE_GMB_CLIENT_SECRET || process.env.YOUTUBE_CLIENT_SECRET,\n    redirectUri: `${process.env.FRONTEND_URL}/integrations/social/gmb`,\n  });\n\n  const oauth2 = (newClient: OAuth2Client) =>\n    google.oauth2({\n      version: 'v2',\n      auth: newClient,\n    });\n\n  return { client, oauth2 };\n};\n\n@Rules(\n  'Google My Business posts can have text content and optionally one image. Posts can be updates, events, or offers.'\n)\nexport class GmbProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3;\n  identifier = 'gmb';\n  name = 'Google My Business';\n  isBetweenSteps = true;\n  scopes = [\n    'https://www.googleapis.com/auth/userinfo.profile',\n    'https://www.googleapis.com/auth/userinfo.email',\n    'https://www.googleapis.com/auth/business.manage',\n  ];\n  editor = 'normal' as const;\n  dto = GmbSettingsDto;\n\n  maxLength() {\n    return 1500;\n  }\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    if (body.includes('UNAUTHENTICATED') || body.includes('invalid_grant')) {\n      return {\n        type: 'refresh-token',\n        value: 'Please re-authenticate your Google My Business account',\n      };\n    }\n\n    if (body.includes('Unauthorized')) {\n      return {\n        type: 'refresh-token',\n        value:\n          'Token expired or invalid, please reconnect your YouTube account.',\n      };\n    }\n\n    if (body.includes('PERMISSION_DENIED')) {\n      return {\n        type: 'refresh-token',\n        value:\n          'Permission denied. Please ensure you have access to this business location.',\n      };\n    }\n\n    if (body.includes('NOT_FOUND')) {\n      return {\n        type: 'bad-body',\n        value: 'Business location not found. It may have been deleted.',\n      };\n    }\n\n    if (body.includes('INVALID_ARGUMENT')) {\n      return {\n        type: 'bad-body',\n        value: 'Invalid post content. Please check your post details.',\n      };\n    }\n\n    if (body.includes('RESOURCE_EXHAUSTED')) {\n      return {\n        type: 'bad-body',\n        value: 'Rate limit exceeded. Please try again later.',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    const { client, oauth2 } = clientAndGmb();\n    client.setCredentials({ refresh_token });\n    const { credentials } = await client.refreshAccessToken();\n    const user = oauth2(client);\n    const expiryDate = new Date(credentials.expiry_date!);\n    const unixTimestamp =\n      Math.floor(expiryDate.getTime() / 1000) -\n      Math.floor(new Date().getTime() / 1000);\n\n    const { data } = await user.userinfo.get();\n\n    return {\n      accessToken: credentials.access_token!,\n      expiresIn: unixTimestamp!,\n      refreshToken: credentials.refresh_token || refresh_token,\n      id: data.id!,\n      name: data.name!,\n      picture: data?.picture || '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(7);\n    const { client } = clientAndGmb();\n    return {\n      url: client.generateAuthUrl({\n        access_type: 'offline',\n        prompt: 'consent',\n        state,\n        redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/gmb`,\n        scope: this.scopes.slice(0),\n      }),\n      codeVerifier: makeId(11),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const { client, oauth2 } = clientAndGmb();\n    const { tokens } = await client.getToken(params.code);\n    client.setCredentials(tokens);\n    const { scopes } = await client.getTokenInfo(tokens.access_token!);\n    this.checkScopes(this.scopes, scopes);\n\n    const user = oauth2(client);\n    const { data } = await user.userinfo.get();\n\n    const expiryDate = new Date(tokens.expiry_date!);\n    const unixTimestamp =\n      Math.floor(expiryDate.getTime() / 1000) -\n      Math.floor(new Date().getTime() / 1000);\n\n    return {\n      accessToken: tokens.access_token!,\n      expiresIn: unixTimestamp,\n      refreshToken: tokens.refresh_token!,\n      id: data.id!,\n      name: data.name!,\n      picture: data?.picture || '',\n      username: '',\n    };\n  }\n\n  async pages(accessToken: string) {\n    // Get all accounts with pagination\n    const allAccounts: any[] = [];\n    let accountsPageToken: string | undefined;\n\n    do {\n      const params = new URLSearchParams();\n      if (accountsPageToken) {\n        params.set('pageToken', accountsPageToken);\n      }\n      const url = `https://mybusinessaccountmanagement.googleapis.com/v1/accounts${params.toString() ? `?${params}` : ''}`;\n\n      const accountsResponse = await fetch(url, {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      });\n      const accountsData = await accountsResponse.json();\n\n      if (accountsData.accounts) {\n        allAccounts.push(...accountsData.accounts);\n      }\n      accountsPageToken = accountsData.nextPageToken;\n    } while (accountsPageToken);\n\n    if (allAccounts.length === 0) {\n      return [];\n    }\n\n    // Get locations for each account\n    const allLocations: Array<{\n      id: string;\n      name: string;\n      picture: { data: { url: string } };\n      accountName: string;\n      locationName: string;\n    }> = [];\n\n    for (const account of allAccounts) {\n      const accountName = account.name; // format: accounts/{accountId}\n\n      try {\n        // Get all locations with pagination\n        let locationsPageToken: string | undefined;\n\n        do {\n          const params = new URLSearchParams({\n            readMask: 'name,title,storefrontAddress,metadata',\n          });\n          if (locationsPageToken) {\n            params.set('pageToken', locationsPageToken);\n          }\n\n          const locationsResponse = await fetch(\n            `https://mybusinessbusinessinformation.googleapis.com/v1/${accountName}/locations?${params}`,\n            {\n              headers: {\n                Authorization: `Bearer ${accessToken}`,\n              },\n            }\n          );\n          const locationsData = await locationsResponse.json();\n\n          if (locationsData.locations) {\n            for (const location of locationsData.locations) {\n              // location.name is in format: locations/{locationId}\n              // We need the full path: accounts/{accountId}/locations/{locationId}\n              const locationId = location.name.replace('locations/', '');\n              const fullResourceName = `${accountName}/locations/${locationId}`;\n\n              // Get profile photo if available\n              let photoUrl = '';\n              try {\n                const mediaResponse = await fetch(\n                  `https://mybusinessbusinessinformation.googleapis.com/v1/${location.name}/media`,\n                  {\n                    headers: {\n                      Authorization: `Bearer ${accessToken}`,\n                    },\n                  }\n                );\n                const mediaData = await mediaResponse.json();\n                if (mediaData.mediaItems && mediaData.mediaItems.length > 0) {\n                  const profilePhoto = mediaData.mediaItems.find(\n                    (m: any) =>\n                      m.mediaFormat === 'PHOTO' &&\n                      m.locationAssociation?.category === 'PROFILE'\n                  );\n                  if (profilePhoto?.googleUrl) {\n                    photoUrl = profilePhoto.googleUrl;\n                  } else if (mediaData.mediaItems[0]?.googleUrl) {\n                    photoUrl = mediaData.mediaItems[0].googleUrl;\n                  }\n                }\n              } catch {\n                // Ignore media fetch errors\n              }\n\n              allLocations.push({\n                // id is the full resource path for the v4 API: accounts/{accountId}/locations/{locationId}\n                id: fullResourceName,\n                name: location.title || 'Unnamed Location',\n                picture: { data: { url: photoUrl } },\n                accountName: accountName,\n                locationName: location.name,\n              });\n            }\n          }\n          locationsPageToken = locationsData.nextPageToken;\n        } while (locationsPageToken);\n      } catch (error) {\n        // Continue with other accounts if one fails\n        console.error(\n          `Failed to fetch locations for account ${accountName}:`,\n          error\n        );\n      }\n    }\n\n    return allLocations;\n  }\n\n  async fetchPageInformation(\n    accessToken: string,\n    data: { id: string; accountName: string; locationName: string }\n  ) {\n    // data.id is the full resource path: accounts/{accountId}/locations/{locationId}\n    // data.locationName is the v1 API format: locations/{locationId}\n    // Fetch location details using the v1 API format\n    const locationResponse = await fetch(\n      `https://mybusinessbusinessinformation.googleapis.com/v1/${data.locationName}?readMask=name,title,storefrontAddress,metadata`,\n      {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      }\n    );\n    const locationData = await locationResponse.json();\n\n    // Try to get profile photo\n    let photoUrl = '';\n    try {\n      const mediaResponse = await fetch(\n        `https://mybusinessbusinessinformation.googleapis.com/v1/${data.locationName}/media`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n      const mediaData = await mediaResponse.json();\n      if (mediaData.mediaItems && mediaData.mediaItems.length > 0) {\n        const profilePhoto = mediaData.mediaItems.find(\n          (m: any) =>\n            m.mediaFormat === 'PHOTO' &&\n            m.locationAssociation?.category === 'PROFILE'\n        );\n        if (profilePhoto?.googleUrl) {\n          photoUrl = profilePhoto.googleUrl;\n        } else if (mediaData.mediaItems[0]?.googleUrl) {\n          photoUrl = mediaData.mediaItems[0].googleUrl;\n        }\n      }\n    } catch {\n      // Ignore media fetch errors\n    }\n\n    return {\n      // Return the full resource path as id (for v4 Local Posts API)\n      id: data.id,\n      name: locationData.title || 'Unnamed Location',\n      access_token: accessToken,\n      picture: photoUrl,\n      username: '',\n    };\n  }\n\n  async reConnect(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {\n    const pages = await this.pages(accessToken);\n    const findPage = pages.find((p) => p.id === requiredId);\n\n    if (!findPage) {\n      throw new Error('Location not found');\n    }\n\n    const information = await this.fetchPageInformation(accessToken, {\n      id: requiredId,\n      accountName: findPage.accountName,\n      locationName: findPage.locationName,\n    });\n\n    return {\n      id: information.id,\n      name: information.name,\n      accessToken: information.access_token,\n      picture: information.picture,\n      username: information.username,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<GmbSettingsDto>[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const { settings } = firstPost;\n\n    // Build the local post request body\n    const postBody: any = {\n      languageCode: 'en',\n      summary: firstPost.message,\n      topicType: settings?.topicType || 'STANDARD',\n    };\n\n    // Add call to action if provided (and not NONE)\n    if (\n      settings?.callToActionType &&\n      settings.callToActionType !== 'NONE' &&\n      settings?.callToActionUrl\n    ) {\n      postBody.callToAction = {\n        actionType: settings.callToActionType,\n        url: settings.callToActionUrl,\n      };\n    }\n\n    // Add media if provided\n    if (firstPost.media && firstPost.media.length > 0) {\n      const mediaItem = firstPost.media[0];\n      postBody.media = [\n        {\n          mediaFormat: mediaItem.type === 'video' ? 'VIDEO' : 'PHOTO',\n          sourceUrl: mediaItem.path,\n        },\n      ];\n    }\n\n    // Add event details if it's an event post\n    if (settings?.topicType === 'EVENT' && settings?.eventTitle) {\n      postBody.event = {\n        title: settings.eventTitle,\n        schedule: {\n          startDate: this.formatDate(settings.eventStartDate),\n          endDate: this.formatDate(settings.eventEndDate),\n          ...(settings.eventStartTime && {\n            startTime: this.formatTime(settings.eventStartTime),\n          }),\n          ...(settings.eventEndTime && {\n            endTime: this.formatTime(settings.eventEndTime),\n          }),\n        },\n      };\n    }\n\n    // Add offer details if it's an offer post\n    if (settings?.topicType === 'OFFER') {\n      postBody.offer = {\n        couponCode: settings?.offerCouponCode || undefined,\n        redeemOnlineUrl: settings?.offerRedeemUrl || undefined,\n        termsConditions: settings?.offerTerms || undefined,\n      };\n    }\n\n    // Create the local post\n    const response = await this.fetch(\n      `https://mybusiness.googleapis.com/v4/${id}/localPosts`,\n      {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(postBody),\n      },\n      'create local post'\n    );\n\n    const postData = await response.json();\n\n    // Extract the post ID and construct the URL\n    const postId = postData.name || '';\n    const locationId = id.split('/').pop();\n\n    // GMB posts don't have direct URLs, but we can link to the business profile\n    const releaseURL = `https://business.google.com/locations/${locationId}`;\n\n    return [\n      {\n        id: firstPost.id,\n        postId: postId,\n        releaseURL: releaseURL,\n        status: 'success',\n      },\n    ];\n  }\n\n  private formatDate(dateString?: string): any {\n    if (!dateString) {\n      return {\n        year: dayjs().year(),\n        month: dayjs().month() + 1,\n        day: dayjs().date(),\n      };\n    }\n    const date = dayjs(dateString);\n    return {\n      year: date.year(),\n      month: date.month() + 1,\n      day: date.date(),\n    };\n  }\n\n  private formatTime(timeString?: string): any {\n    if (!timeString) {\n      return undefined;\n    }\n    const [hours, minutes] = timeString.split(':').map(Number);\n    return {\n      hours: hours || 0,\n      minutes: minutes || 0,\n      seconds: 0,\n      nanos: 0,\n    };\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    try {\n      const endDate = dayjs().format('YYYY-MM-DD');\n      const startDate = dayjs().subtract(date, 'day').format('YYYY-MM-DD');\n\n      // id is in format: accounts/{accountId}/locations/{locationId}\n      // Business Profile Performance API expects: locations/{locationId}\n      const locationId = id.split('/locations/')[1];\n      const locationPath = `locations/${locationId}`;\n\n      // Use the Business Profile Performance API\n      const response = await fetch(\n        `https://businessprofileperformance.googleapis.com/v1/${locationPath}:fetchMultiDailyMetricsTimeSeries?dailyMetrics=WEBSITE_CLICKS&dailyMetrics=CALL_CLICKS&dailyMetrics=BUSINESS_DIRECTION_REQUESTS&dailyMetrics=BUSINESS_IMPRESSIONS_DESKTOP_MAPS&dailyMetrics=BUSINESS_IMPRESSIONS_MOBILE_MAPS&dailyRange.startDate.year=${dayjs(\n          startDate\n        ).year()}&dailyRange.startDate.month=${\n          dayjs(startDate).month() + 1\n        }&dailyRange.startDate.day=${dayjs(\n          startDate\n        ).date()}&dailyRange.endDate.year=${dayjs(\n          endDate\n        ).year()}&dailyRange.endDate.month=${\n          dayjs(endDate).month() + 1\n        }&dailyRange.endDate.day=${dayjs(endDate).date()}`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n\n      const data = await response.json();\n\n      // Response structure: { multiDailyMetricTimeSeries: [{ dailyMetricTimeSeries: [...] }] }\n      const dailyMetricTimeSeries =\n        data.multiDailyMetricTimeSeries?.[0]?.dailyMetricTimeSeries;\n\n      if (!dailyMetricTimeSeries || dailyMetricTimeSeries.length === 0) {\n        return [];\n      }\n\n      const metricLabels: { [key: string]: string } = {\n        WEBSITE_CLICKS: 'Website Clicks',\n        CALL_CLICKS: 'Phone Calls',\n        BUSINESS_DIRECTION_REQUESTS: 'Direction Requests',\n        BUSINESS_IMPRESSIONS_DESKTOP_MAPS: 'Desktop Map Views',\n        BUSINESS_IMPRESSIONS_MOBILE_MAPS: 'Mobile Map Views',\n      };\n\n      const analytics: AnalyticsData[] = [];\n\n      for (const series of dailyMetricTimeSeries) {\n        const metricName = series.dailyMetric;\n        const label = metricLabels[metricName] || metricName;\n\n        const datedValues = series.timeSeries?.datedValues || [];\n\n        const dataPoints = datedValues.map((dv: any) => ({\n          total: parseInt(dv.value || '0', 10),\n          date: `${dv.date.year}-${String(dv.date.month).padStart(\n            2,\n            '0'\n          )}-${String(dv.date.day).padStart(2, '0')}`,\n        }));\n\n        if (dataPoints.length > 0) {\n          analytics.push({\n            label,\n            percentageChange: 0,\n            data: dataPoints,\n          });\n        }\n      }\n\n      return analytics;\n    } catch (error) {\n      console.error('Error fetching GMB analytics:', error);\n      return [];\n    }\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    // Google My Business local posts don't have detailed individual post analytics\n    // The API focuses on location-level metrics rather than post-level metrics\n    return [];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { tags } from '@gitroom/nestjs-libraries/integrations/social/hashnode.tags';\nimport { jsonToGraphQLQuery } from 'json-to-graphql-query';\nimport { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class HashnodeProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Hashnode has lenient publishing limits\n  identifier = 'hashnode';\n  name = 'Hashnode';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'markdown' as const;\n  maxLength() {\n    return 10000;\n  }\n  dto = HashnodeSettingsDto;\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'apiKey',\n        label: 'API key',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n    try {\n      const {\n        data: {\n          me: { name, id, profilePicture, username },\n        },\n      } = await (\n        await fetch('https://gql.hashnode.com', {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `${body.apiKey}`,\n          },\n          body: JSON.stringify({\n            query: `\n                    query {\n                      me {\n                        name,\n                        id,\n                        profilePicture\n                        username\n                      }\n                    }\n                `,\n          }),\n        })\n      ).json();\n\n      return {\n        refreshToken: '',\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: body.apiKey,\n        id,\n        name,\n        picture: profilePicture || '',\n        username,\n      };\n    } catch (err) {\n      return 'Invalid credentials';\n    }\n  }\n\n  async tags() {\n    return tags.map((tag) => ({ value: tag.objectID, label: tag.name }));\n  }\n\n  @Tool({ description: 'Tags', dataSchema: [] })\n  tagsList() {\n    return tags;\n  }\n\n  @Tool({ description: 'Publications', dataSchema: [] })\n  async publications(accessToken: string) {\n    const {\n      data: {\n        me: {\n          publications: { edges },\n        },\n      },\n    } = await (\n      await fetch('https://gql.hashnode.com', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: `${accessToken}`,\n        },\n        body: JSON.stringify({\n          query: `\n            query {\n              me {\n                publications (first: 50) {\n                  edges{\n                    node {\n                      id\n                      title\n                    }\n                  }\n                }\n              }\n            }\n                `,\n        }),\n      })\n    ).json();\n\n    return edges.map(\n      ({ node: { id, title } }: { node: { id: string; title: string } }) => ({\n        id,\n        name: title,\n      })\n    );\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { settings } = postDetails?.[0] || { settings: {} };\n    const query = jsonToGraphQLQuery(\n      {\n        mutation: {\n          publishPost: {\n            __args: {\n              input: {\n                title: settings.title,\n                publicationId: settings.publication,\n                ...(settings.canonical\n                  ? { originalArticleURL: settings.canonical }\n                  : {}),\n                contentMarkdown: postDetails?.[0].message,\n                tags: settings.tags.map((tag: any) => ({ id: tag.value })),\n                ...(settings.subtitle ? { subtitle: settings.subtitle } : {}),\n                ...(settings.main_image\n                  ? {\n                      coverImageOptions: {\n                        coverImageURL: `${\n                          settings?.main_image?.path?.indexOf('http') === -1\n                            ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY}`\n                            : ``\n                        }${settings?.main_image?.path}`,\n                      },\n                    }\n                  : {}),\n              },\n            },\n            post: {\n              id: true,\n              url: true,\n            },\n          },\n        },\n      },\n      { pretty: true }\n    );\n\n    const {\n      data: {\n        publishPost: {\n          post: { id: postId, url },\n        },\n      },\n    } = await (\n      await this.fetch('https://gql.hashnode.com', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: `${accessToken}`,\n        },\n        body: JSON.stringify({\n          query,\n        }),\n      })\n    ).json();\n\n    return [\n      {\n        id: postDetails?.[0].id,\n        status: 'completed',\n        postId: postId,\n        releaseURL: url,\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/hashnode.tags.ts",
    "content": "export const tags = [\n  {\n    name: 'JavaScript',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513320898547/BJjpblWfG.png',\n    slug: 'javascript',\n    objectID: '56744721958ef13879b94cad',\n  },\n  {\n    name: 'General Programming',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1535648192079/H1daWiBvQ.png',\n    slug: 'programming',\n    objectID: '56744721958ef13879b94c7e',\n  },\n  {\n    name: 'React',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513321478077/ByCWNxZMf.png',\n    slug: 'reactjs',\n    objectID: '56744723958ef13879b95434',\n  },\n  {\n    name: 'Web Development',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450469658/vdxecajl3uwbprclsctm.jpg',\n    slug: 'web-development',\n    objectID: '56744722958ef13879b94f1b',\n  },\n  {\n    name: 'Python',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512408213/rJeQpSNIX.png',\n    slug: 'python',\n    objectID: '56744721958ef13879b94d67',\n  },\n  {\n    name: 'Node.js',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513321388034/SJV3QgWfz.png',\n    slug: 'nodejs',\n    objectID: '56744722958ef13879b94ffb',\n  },\n  {\n    name: 'CSS',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513316949083/By6UMkbfG.png',\n    slug: 'css',\n    objectID: '56744721958ef13879b94b91',\n  },\n  {\n    name: 'beginners',\n    slug: 'beginners',\n    objectID: '56744723958ef13879b955a9',\n  },\n  {\n    name: 'Java',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512378322/H1gM-pH4UQ.png',\n    slug: 'java',\n    objectID: '56744721958ef13879b94c9f',\n  },\n  {\n    name: 'Developer',\n    slug: 'developer',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1554321431158/MqVqSHr8Q.jpeg',\n    objectID: '56744723958ef13879b952d7',\n  },\n  {\n    name: 'HTML5',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513322217442/SkZlDeWzz.png',\n    slug: 'html5',\n    objectID: '56744723958ef13879b95483',\n  },\n  {\n    name: '2Articles1Week',\n    slug: '2articles1week',\n    logo: '',\n    objectID: '5f058ab0c9763d47e2d2eedc',\n  },\n  {\n    name: 'learning',\n    slug: 'learning',\n    objectID: '56744723958ef13879b9532b',\n  },\n  {\n    name: 'PHP',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513177307594/rJ4Jba0-G.png',\n    slug: 'php',\n    objectID: '56744722958ef13879b94fd9',\n  },\n  {\n    name: 'AWS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468151/vmrnzobr1lonnigttn3c.png',\n    slug: 'aws',\n    objectID: '56744721958ef13879b94bc5',\n  },\n  {\n    name: 'Tutorial',\n    slug: 'tutorial',\n    objectID: '56744720958ef13879b947ce',\n  },\n  {\n    name: 'programming blogs',\n    slug: 'programming-blogs',\n    objectID: '56744721958ef13879b94ae7',\n  },\n  {\n    name: 'coding',\n    slug: 'coding',\n    objectID: '56744723958ef13879b954c1',\n  },\n  {\n    name: 'Go Language',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512687168/S1D40rVLm.png',\n    slug: 'go',\n    objectID: '56744721958ef13879b94bd0',\n  },\n  {\n    name: 'Frontend Development',\n    slug: 'frontend-development',\n    objectID: '56a399f292921b8f79d3633c',\n  },\n  {\n    name: 'GitHub',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513321555902/BkhLElZMG.png',\n    slug: 'github',\n    objectID: '56744721958ef13879b94c63',\n  },\n  {\n    name: 'Hashnode',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1619605440273/S3_X4Rf7V.jpeg',\n    slug: 'hashnode',\n    objectID: '567ae5a72b926c3063c3061a',\n  },\n  {\n    name: 'Python 3',\n    slug: 'python3',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1503468096/axvqxfbcm0b7ourhshj7.jpg',\n    objectID: '56744723958ef13879b95342',\n  },\n  {\n    name: 'Codenewbies',\n    slug: 'codenewbies',\n    objectID: '5f22b52283e4e9440619af83',\n  },\n  {\n    name: 'webdev',\n    slug: 'webdev',\n    objectID: '56744723958ef13879b952af',\n  },\n  {\n    name: 'Machine Learning',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513321644252/Sk43El-fz.png',\n    slug: 'machine-learning',\n    objectID: '56744722958ef13879b950a8',\n  },\n  {\n    name: 'General Advice',\n    slug: 'general-advice',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516183731966/B13heohVM.jpeg',\n    objectID: '56fe3b2e7a82968f9f7d51c1',\n  },\n  {\n    name: 'software development',\n    slug: 'software-development',\n    objectID: '56744721958ef13879b94ad1',\n  },\n  {\n    name: 'CSS3',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513316988840/r1Htz1Wzz.png',\n    slug: 'css3',\n    objectID: '56744721958ef13879b94b21',\n  },\n  {\n    name: 'Android',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468271/qbj34hxd8981nfdugyph.png',\n    slug: 'android',\n    objectID: '56744723958ef13879b953d0',\n  },\n  {\n    name: 'Productivity',\n    slug: 'productivity',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1497250361/v3sij4jc8hz9xoic22eq.png',\n    objectID: '56744721958ef13879b94a60',\n  },\n  {\n    name: 'React Native',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235386/rkij45wit50lfpkbte5q.jpg',\n    slug: 'react-native',\n    objectID: '56744722958ef13879b94f4d',\n  },\n  {\n    name: '100DaysOfCode',\n    slug: '100daysofcode',\n    objectID: '576ab68f152618ad1dc938ad',\n  },\n  {\n    name: 'Design',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513324674454/r1qtxW-zf.png',\n    slug: 'design',\n    objectID: '56744722958ef13879b94e89',\n  },\n  {\n    name: 'Devops',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496913014/cnvm0znfqcrwelhgtblb.png',\n    slug: 'devops',\n    objectID: '56744723958ef13879b9550d',\n  },\n  {\n    name: 'Open Source',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496913431/hdg1q4zbmobhrq0csomm.png',\n    slug: 'opensource',\n    objectID: '56744722958ef13879b94f32',\n  },\n  {\n    name: 'Git',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1473706112/l2hom2y5xxpgwlgg0sz0.jpg',\n    slug: 'git',\n    objectID: '56744723958ef13879b9526c',\n  },\n  {\n    name: 'HTML',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513322147587/Hk2jIxZGG.png',\n    slug: 'html',\n    objectID: '56744722958ef13879b94f96',\n  },\n  {\n    name: 'data science',\n    slug: 'data-science',\n    objectID: '56744721958ef13879b94e35',\n  },\n  {\n    name: 'Testing',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450619295/xszq3zb8t6rmgg6regon.png',\n    slug: 'testing',\n    objectID: '56744723958ef13879b9549b',\n  },\n  {\n    name: 'Linux',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450641462/ogpsvoxw5kt8aksuiptj.png',\n    slug: 'linux',\n    objectID: '56744721958ef13879b94b55',\n  },\n  {\n    name: 'Security',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472744837/bnzk4gvspiy66dsmw9ku.png',\n    slug: 'security',\n    objectID: '56744722958ef13879b94fb7',\n  },\n  {\n    name: 'Laravel',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1454754733/exjubzyuvwz0pvvpxxwv.jpg',\n    slug: 'laravel',\n    objectID: '56744721958ef13879b94a83',\n  },\n  {\n    name: 'TypeScript',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1470054384/fuy3ypcjuj4cwdz4qpxn.jpg',\n    slug: 'typescript',\n    objectID: '56744723958ef13879b954e0',\n  },\n  {\n    name: 'APIs',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468334/jirjz7cc54l2mstzpaab.png',\n    slug: 'apis',\n    objectID: '56744723958ef13879b95245',\n  },\n  {\n    name: 'Ruby',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512722989/BksL0SELm.png',\n    slug: 'ruby',\n    objectID: '56744721958ef13879b94c0a',\n  },\n  {\n    name: 'Vue.js',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1505294440/t5igqu22z1s86xa7nkqi.png',\n    slug: 'vuejs',\n    objectID: '56744722958ef13879b950e4',\n  },\n  {\n    name: 'technology',\n    slug: 'technology',\n    objectID: '56744721958ef13879b94d26',\n  },\n  {\n    name: 'Docker',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1453789075/ryxk99vk41tdn8bo28m4.png',\n    slug: 'docker',\n    objectID: '56744721958ef13879b94b77',\n  },\n  {\n    name: 'programming languages',\n    slug: 'programming-languages',\n    objectID: '579a67e2cec33eafc07249c7',\n  },\n  {\n    name: 'Programming Tips',\n    slug: 'programming-tips',\n    objectID: '5f398753c4d5973f55c912fb',\n  },\n  {\n    name: 'Cloud',\n    slug: 'cloud',\n    objectID: '56744721958ef13879b94938',\n  },\n  {\n    name: 'Blogging',\n    slug: 'blogging',\n    objectID: '56744721958ef13879b949aa',\n  },\n  {\n    name: 'newbie',\n    slug: 'newbie',\n    objectID: '56744720958ef13879b947e8',\n  },\n  {\n    name: 'Career',\n    slug: 'career',\n    objectID: '56aa13e5f28f9d9d99e3a5de',\n  },\n  {\n    name: 'Swift',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512662717/Sy1XAHNLQ.png',\n    slug: 'swift',\n    objectID: '56744722958ef13879b94ead',\n  },\n  {\n    name: 'Flutter Community',\n    slug: 'flutter',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1560841840250/KhofPXnAk.jpeg',\n    objectID: '56744722958ef13879b9507c',\n  },\n  {\n    name: 'python beginner',\n    slug: 'python-beginner',\n    objectID: '5f3867d1c4d5973f55c90b8b',\n  },\n  {\n    name: 'Software Engineering',\n    slug: 'software-engineering',\n    objectID: '569d22c892921b8f79d35f68',\n  },\n  {\n    name: 'learn coding',\n    slug: 'learn-coding',\n    objectID: '5f3f40bfdfbb4247f7c14d4c',\n  },\n  {\n    name: 'MongoDB',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450467711/awgzya1xei3pgch5b8xu.png',\n    slug: 'mongodb',\n    objectID: '56744722958ef13879b94f6f',\n  },\n  {\n    name: 'iOS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468231/t4x2aoglmhhz9yw3ezry.png',\n    slug: 'ios',\n    objectID: '56744722958ef13879b94f11',\n  },\n  {\n    name: 'algorithms',\n    slug: 'algorithms',\n    objectID: '56744721958ef13879b94a8d',\n  },\n  {\n    name: 'Web Design',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450622407/deczahnypldw1ftbdxog.png',\n    slug: 'web-design',\n    objectID: '56744721958ef13879b94d32',\n  },\n  {\n    name: 'Databases',\n    slug: 'databases',\n    objectID: '56744722958ef13879b950eb',\n  },\n  {\n    name: 'ES6',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512767931/S1dYCSNIm.png',\n    slug: 'es6',\n    objectID: '56744723958ef13879b954cb',\n  },\n  {\n    name: 'Learning Journey',\n    slug: 'learning-journey',\n    objectID: '5f9435c7fbdce372c9a56fb6',\n  },\n  {\n    name: 'Blockchain',\n    slug: 'blockchain',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1540281064342/rkle7U3sQ.png',\n    objectID: '5690224191716a2d1dbadbc1',\n  },\n  {\n    name: 'data structures',\n    slug: 'data-structures',\n    objectID: '56744722958ef13879b951bb',\n  },\n  {\n    name: 'Redux',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513322046756/HyPSUgWMG.png',\n    slug: 'redux',\n    objectID: '56744723958ef13879b95567',\n  },\n  {\n    name: 'backend',\n    slug: 'backend',\n    objectID: '56744722958ef13879b950bd',\n  },\n  {\n    name: 'C#',\n    slug: 'csharp',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512595400/HkoATH48Q.png',\n    objectID: '56744721958ef13879b94a30',\n  },\n  {\n    name: 'Startups',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1459504275/iksgbnwvscz6zjzk5nhe.jpg',\n    slug: 'startups',\n    objectID: '56744721958ef13879b94b5b',\n  },\n  {\n    name: 'Django',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235489/g7q2vh5igqcxo8jlfwl9.jpg',\n    slug: 'django',\n    objectID: '56744722958ef13879b94e81',\n  },\n  {\n    name: 'UX',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1474023086/dnrwfr6sxylhx60mp26j.png',\n    slug: 'ux',\n    objectID: '56744722958ef13879b94e9d',\n  },\n  {\n    name: 'interview',\n    slug: 'interview',\n    objectID: '56744720958ef13879b947e1',\n  },\n  {\n    name: 'Visual Studio Code',\n    slug: 'vscode',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1497045716/r3myqwr6m8olahqaxl5x.png',\n    objectID: '57323a8bae9d49b5a5a5b39c',\n  },\n  {\n    name: 'internships',\n    slug: 'internships',\n    objectID: '56744720958ef13879b94811',\n  },\n  {\n    name: 'Next.js',\n    slug: 'nextjs',\n    objectID: '584879f0c0aaf085e2012086',\n  },\n  {\n    name: 'Kubernetes',\n    slug: 'kubernetes',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1554318943530/J-r4NJeEi.png',\n    objectID: '56744723958ef13879b9522c',\n  },\n  {\n    name: 'Computer Science',\n    slug: 'computer-science',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1514959838703/BkDJVxqQM.jpeg',\n    objectID: '56744722958ef13879b9512b',\n  },\n  {\n    name: 'REST API',\n    slug: 'rest-api',\n    objectID: '56b1208d04f0061506b360ff',\n  },\n  {\n    name: 'business',\n    slug: 'business',\n    objectID: '56744723958ef13879b952a1',\n  },\n  {\n    name: 'automation',\n    slug: 'automation',\n    objectID: '56744723958ef13879b9535d',\n  },\n  {\n    name: 'Kotlin',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1458728299/fuo7n9epkkxyafihrlhz.jpg',\n    slug: 'kotlin',\n    objectID: '56c2f39e850906a7da47cdeb',\n  },\n  {\n    name: 'Google',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450469897/djpesw0ajrbxvmlyoezx.png',\n    slug: 'google',\n    objectID: '56744723958ef13879b95470',\n  },\n  {\n    name: 'app development',\n    slug: 'app-development',\n    objectID: '56744720958ef13879b947c4',\n  },\n  {\n    name: 'Azure',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1524473475544/B1ntAzsnM.jpeg',\n    slug: 'azure',\n    objectID: '56744721958ef13879b94d89',\n  },\n  {\n    name: 'Game Development',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1473275923/lhhroyopcm9gpfvqxe44.jpg',\n    slug: 'game-development',\n    objectID: '56744723958ef13879b953f2',\n  },\n  {\n    name: 'C++',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512626199/BkcgCSNUm.png',\n    slug: 'cpp',\n    objectID: '56744721958ef13879b948b7',\n  },\n  {\n    name: 'js',\n    slug: 'js',\n    objectID: '56744721958ef13879b94bf5',\n  },\n  {\n    name: 'UI',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1487144606/jy8ee18buuag2zbsbqai.png',\n    slug: 'ui',\n    objectID: '56744723958ef13879b954f5',\n  },\n  {\n    name: 'Mobile Development',\n    slug: 'mobile-development',\n    objectID: '568a9b8ce4c4e23aef243c1f',\n  },\n  {\n    name: 'Cloud Computing',\n    slug: 'cloud-computing',\n    objectID: '56744723958ef13879b9533a',\n  },\n  {\n    name: 'frontend',\n    slug: 'frontend',\n    objectID: '56744721958ef13879b94d0f',\n  },\n  {\n    name: 'Artificial Intelligence',\n    slug: 'artificial-intelligence',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496737518/sgflljcm3hidlvipsriq.png',\n    objectID: '56744721958ef13879b94927',\n  },\n  {\n    name: 'npm',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1460372304/ovff2sszokeskrwdfjjv.png',\n    slug: 'npm',\n    objectID: '56744723958ef13879b95322',\n  },\n  {\n    name: 'development',\n    slug: 'development',\n    objectID: '56744721958ef13879b94d9b',\n  },\n  {\n    name: 'Ruby on Rails',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235552/twnpcxcm29mub2gez4yf.jpg',\n    slug: 'ruby-on-rails',\n    objectID: '56744722958ef13879b94ff1',\n  },\n  {\n    name: 'WordPress',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450732925/vndlqh4zwgqoy6kbcs0j.jpg',\n    slug: 'wordpress',\n    objectID: '56744721958ef13879b94beb',\n  },\n  {\n    name: 'tips',\n    slug: 'tips',\n    objectID: '56744723958ef13879b95319',\n  },\n  {\n    name: 'javascript framework',\n    slug: 'javascript-framework',\n    objectID: '56744723958ef13879b95527',\n  },\n  {\n    name: 'Technical writing ',\n    slug: 'technical-writing-1',\n    objectID: '5f3330322a23d9080d17a0da',\n  },\n  {\n    name: 'Express',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513936680781/HJbEP8qzM.png',\n    slug: 'express',\n    objectID: '56744721958ef13879b9487d',\n  },\n  {\n    name: 'serverless',\n    slug: 'serverless',\n    objectID: '57979f8dcec33eafc07247a2',\n  },\n  {\n    name: 'Angular',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450469536/svgqrg8jtoqihqdffiai.jpg',\n    slug: 'angular',\n    objectID: '56744722958ef13879b94f59',\n  },\n  {\n    name: 'DevLife',\n    slug: 'devlife',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516175805632/HkL6WK3Ez.jpeg',\n    objectID: '592fe1bf8515388d7dfc2650',\n  },\n  {\n    name: 'Functional Programming',\n    slug: 'functional-programming',\n    objectID: '568f5c6beea132481d017c36',\n  },\n  {\n    name: 'programmer',\n    slug: 'programmer',\n    objectID: '568409636b179c61d167f05d',\n  },\n  {\n    name: 'python projects',\n    slug: 'python-projects',\n    objectID: '5f76046e37eb052c1b80da9f',\n  },\n  {\n    name: 'MySQL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496912606/hclufcmqr2btz24a6egj.png',\n    slug: 'mysql',\n    objectID: '56744721958ef13879b94dff',\n  },\n  {\n    name: 'Dart',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450601337/v7ng3klyzehzxtbjoym9.png',\n    slug: 'dart',\n    objectID: '56744721958ef13879b94df0',\n  },\n  {\n    name: 'Firebase',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1464240463/xo1rbiqimh25bmlgwb3g.jpg',\n    slug: 'firebase',\n    objectID: '56744722958ef13879b94e99',\n  },\n  {\n    name: 'Windows',\n    slug: 'windows',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1554134710664/jnLVaVy-N.png',\n    objectID: '56744723958ef13879b953f7',\n  },\n  {\n    name: 'code',\n    slug: 'code',\n    objectID: '56744721958ef13879b94982',\n  },\n  {\n    name: 'GraphQL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235506/qbofja8kwx8cw8nuyaqg.jpg',\n    slug: 'graphql',\n    objectID: '56744723958ef13879b9555c',\n  },\n  {\n    name: 'SEO',\n    slug: 'seo',\n    objectID: '56744722958ef13879b9519c',\n  },\n  {\n    name: 'ReactHooks',\n    slug: 'reacthooks',\n    objectID: '5f8523be6ad92638db4944a9',\n  },\n  {\n    name: '#beginners #learningtocode #100daysofcode',\n    slug: 'beginners-learningtocode-100daysofcode',\n    objectID: '5f789ec19c3b6e410121699a',\n  },\n  {\n    name: 'hacking',\n    slug: 'hacking',\n    objectID: '56744723958ef13879b9553a',\n  },\n  {\n    name: 'Cryptocurrency',\n    slug: 'cryptocurrency',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512988849862/SJ5heynZG.png',\n    objectID: '58e4c1144d64a3de3e94b31b',\n  },\n  {\n    name: 'SQL',\n    slug: 'sql',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1501338352/cv7owxtxvr39rjzxoolr.png',\n    objectID: '56744723958ef13879b953ed',\n  },\n  {\n    name: 'hashnodebootcamp',\n    slug: 'hashnodebootcamp',\n    objectID: '5f75f322b7a1d82bf9b34c6d',\n  },\n  {\n    name: 'Tailwind CSS',\n    slug: 'tailwind-css',\n    objectID: '5f4ebbb150b5c61ec6ef4ad2',\n  },\n  {\n    name: 'webpack',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1457865805/st9hz4f5ufmpxhizmfpk.jpg',\n    slug: 'webpack',\n    objectID: '56744722958ef13879b95055',\n  },\n  {\n    name: 'learn',\n    slug: 'learn',\n    objectID: '56a2235672ca04ea5d7a00c2',\n  },\n  {\n    name: 'first post',\n    slug: 'first-post-1',\n    objectID: '5f08ee681981c53c4987f2b3',\n  },\n  {\n    name: 'design patterns',\n    slug: 'design-patterns',\n    objectID: '56744721958ef13879b94968',\n  },\n  {\n    name: 'ai',\n    slug: 'ai',\n    objectID: '56744721958ef13879b9488e',\n  },\n  {\n    name: 'Microservices',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1479724330/cpcqfxm9af8d8esgo8wp.jpg',\n    slug: 'microservices',\n    objectID: '56744721958ef13879b948a2',\n  },\n  {\n    name: 'data analysis',\n    slug: 'data-analysis',\n    objectID: '56744722958ef13879b951ac',\n  },\n  {\n    name: 'best practices',\n    slug: 'best-practices',\n    objectID: '56744723958ef13879b95598',\n  },\n  {\n    name: 'beginner',\n    slug: 'beginner',\n    objectID: '56744723958ef13879b952b6',\n  },\n  {\n    name: 'Deep Learning',\n    slug: 'deep-learning',\n    objectID: '578f611523e94ba91a5bebd8',\n  },\n  {\n    name: 'Ubuntu',\n    slug: 'ubuntu',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496151690/x8g04hsjiekjgkkuhrk7.png',\n    objectID: '56744721958ef13879b94988',\n  },\n  {\n    name: 'C',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235467/zfbdpx1pe00glfy6lc6b.jpg',\n    slug: 'c',\n    objectID: '56744721958ef13879b9492c',\n  },\n  {\n    name: 'MERN Stack',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512793459/Hk-s0B4Im.png',\n    slug: 'mern',\n    objectID: '56c32d8c316f8ee15e9e0fde',\n  },\n  {\n    name: 'education',\n    slug: 'education',\n    objectID: '56b631c8e6740d0959b6f3ef',\n  },\n  {\n    name: 'authentication',\n    slug: 'authentication',\n    objectID: '56744721958ef13879b94b00',\n  },\n  {\n    name: 'community',\n    slug: 'community',\n    objectID: '56744722958ef13879b9514c',\n  },\n  {\n    name: 'marketing',\n    slug: 'marketing',\n    objectID: '57449fa89ade925885158d1e',\n  },\n  {\n    name: 'Hello World',\n    slug: 'hello-world',\n    objectID: '591d0f67b5bbb96606f07af4',\n  },\n  {\n    name: 'tools',\n    slug: 'tools',\n    objectID: '56744721958ef13879b94e0c',\n  },\n  {\n    name: 'ecommerce',\n    slug: 'ecommerce',\n    objectID: '56744722958ef13879b95041',\n  },\n  {\n    name: 'news',\n    slug: 'news',\n    objectID: '56744721958ef13879b9493e',\n  },\n  {\n    name: 'Microsoft',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450622053/ioqfwklxmzqwwy7jrxmj.png',\n    slug: 'microsoft',\n    objectID: '56744721958ef13879b94d1d',\n  },\n  {\n    name: 'jQuery',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450815745/cd8sl0j2hkeuoq2isuc3.png',\n    slug: 'jquery',\n    objectID: '56744721958ef13879b94c2b',\n  },\n  {\n    name: 'Javascript library',\n    slug: 'javascript-library',\n    objectID: '568fa207525da8063d08fb68',\n  },\n  {\n    name: 'data',\n    slug: 'data',\n    objectID: '56744721958ef13879b949d3',\n  },\n  {\n    name: 'clean code',\n    slug: 'clean-code',\n    objectID: '573504d39835efadc8742016',\n  },\n  {\n    name: 'web',\n    slug: 'web',\n    objectID: '56744722958ef13879b94f40',\n  },\n  {\n    name: 'programing',\n    slug: 'programing',\n    objectID: '56ab1a78f28f9d9d99e3a6d1',\n  },\n  {\n    name: 'tech ',\n    slug: 'tech',\n    objectID: '5677de7c7dd5d4174dcc2073',\n  },\n  {\n    name: 'Mobile apps',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450619495/qiwhbnoxoas2b5dtb6cx.png',\n    slug: 'mobile-apps',\n    objectID: '56744721958ef13879b94c5b',\n  },\n  {\n    name: 'performance',\n    slug: 'performance',\n    objectID: '56744721958ef13879b94dc4',\n  },\n  {\n    name: 'UI Design',\n    slug: 'ui-design',\n    objectID: '5682df44aeae5c9e229cf9f9',\n  },\n  {\n    name: 'PostgreSQL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1460706552/iwl62ldvrzgf4k9rhame.jpg',\n    slug: 'postgresql',\n    objectID: '56744721958ef13879b949b5',\n  },\n  {\n    name: 'Rust',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512703511/HJDSCr4UQ.png',\n    slug: 'rust',\n    objectID: '5684bee6bf03be7d4a9ed853',\n  },\n  {\n    name: 'motivation',\n    slug: 'motivation',\n    objectID: '56b0ba4604f0061506b35fae',\n  },\n  {\n    name: 'software architecture',\n    slug: 'software-architecture',\n    objectID: '56744722958ef13879b950c9',\n  },\n  {\n    name: 'introduction',\n    slug: 'introduction',\n    objectID: '56744721958ef13879b948cc',\n  },\n  {\n    name: 'Bootstrap',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450470158/wpi0t8fj9kr8on9v6jmd.jpg',\n    slug: 'bootstrap',\n    objectID: '56744721958ef13879b94be1',\n  },\n  {\n    name: 'networking',\n    slug: 'networking',\n    objectID: '56ffbb5d5861692778050361',\n  },\n  {\n    name: 'blog',\n    slug: 'blog',\n    objectID: '56744721958ef13879b948ac',\n  },\n  {\n    name: 'jobs',\n    slug: 'jobs',\n    objectID: '56a77939281161e11972fdd7',\n  },\n  {\n    name: 'terminal',\n    slug: 'terminal',\n    objectID: '56744721958ef13879b94da6',\n  },\n  {\n    name: 'command line',\n    slug: 'command-line',\n    objectID: '56744723958ef13879b9539a',\n  },\n  {\n    name: 'website',\n    slug: 'website',\n    objectID: '5674471d958ef13879b94785',\n  },\n  {\n    name: 'Developer Tools',\n    slug: 'developer-tools',\n    objectID: '57ebac0bd9b08ec06a77be05',\n  },\n  {\n    name: 'aws lambda',\n    slug: 'aws-lambda',\n    objectID: '57c7ea36e53060955aa8c0c0',\n  },\n  {\n    name: 'Ethereum',\n    slug: 'ethereum',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512988945062/HJKfZJhWf.png',\n    objectID: '58e4c1144d64a3de3e94b31d',\n  },\n  {\n    name: 'System Architecture',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496913335/duppaieikvyvepmoj6uz.png',\n    slug: 'system-architecture',\n    objectID: '56744723958ef13879b955b0',\n  },\n  {\n    name: '#cybersecurity',\n    slug: 'cybersecurity-1',\n    objectID: '5f2e70c0b8ac395b1f23a6cb',\n  },\n  {\n    name: 'linux for beginners',\n    slug: 'linux-for-beginners',\n    objectID: '5fa5022a3e634314b5179cf5',\n  },\n  {\n    name: 'Games',\n    slug: 'games',\n    objectID: '578f6a105460288cdeb6f2ab',\n  },\n  {\n    name: 'developers',\n    slug: 'developers',\n    objectID: '56744722958ef13879b94f05',\n  },\n  {\n    name: 'internet',\n    slug: 'internet',\n    objectID: '56f260f15ec781bb472f83af',\n  },\n  {\n    name: 'android app development',\n    slug: 'android-app-development',\n    objectID: '56744721958ef13879b94890',\n  },\n  {\n    name: 'full stack',\n    slug: 'full-stack',\n    objectID: '56744723958ef13879b95387',\n  },\n  {\n    name: 'server',\n    slug: 'server',\n    objectID: '56744721958ef13879b94e17',\n  },\n  {\n    name: 'projects',\n    slug: 'projects',\n    objectID: '56744722958ef13879b95074',\n  },\n  {\n    name: 'macOS',\n    slug: 'macos',\n    objectID: '576a1d6e13cc2eb2d90e2383',\n  },\n  {\n    name: 'project management',\n    slug: 'project-management',\n    objectID: '569d22af46dfdb8479aa6921',\n  },\n  {\n    name: 'writing',\n    slug: 'writing',\n    objectID: '5674471d958ef13879b9477e',\n  },\n  {\n    name: 'Flutter Examples',\n    slug: 'flutter-examples',\n    objectID: '5f08f6a1b0bf5b3c273ea78b',\n  },\n  {\n    name: 'guide',\n    slug: 'guide',\n    objectID: '56744723958ef13879b955a7',\n  },\n  {\n    name: 'deployment',\n    slug: 'deployment',\n    objectID: '56744721958ef13879b94dad',\n  },\n  {\n    name: 'array',\n    slug: 'array',\n    objectID: '578e290c5460288cdeb6f187',\n  },\n  {\n    name: 'Bash',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1464705930/i6dyhkbiwqezfwbsq4c2.jpg',\n    slug: 'bash',\n    objectID: '56744722958ef13879b95119',\n  },\n  {\n    name: 'Bitcoin',\n    slug: 'bitcoin',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512988974934/rJwNWJhZz.png',\n    objectID: '5697e90f46dfdb8479aa6708',\n  },\n  {\n    name: 'Google Chrome',\n    slug: 'chrome',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502183259/uhcfgovkcf3pm66xjsl0.png',\n    objectID: '56744722958ef13879b94f68',\n  },\n  {\n    name: '.NET',\n    slug: 'net',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1515075179602/rkEdLho7G.jpeg',\n    objectID: '56744723958ef13879b9556e',\n  },\n  {\n    name: 'dotnet',\n    slug: 'dotnet',\n    objectID: '5794f65abecb9ebac0d5fc55',\n  },\n  {\n    name: 'life',\n    slug: 'life',\n    objectID: '57bc257693309a25047c5e43',\n  },\n  {\n    name: 'Twitter',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1464240092/ysal5yejuviop7p7bvbl.png',\n    slug: 'twitter',\n    objectID: '56744721958ef13879b949ad',\n  },\n  {\n    name: 'Object Oriented Programming',\n    slug: 'object-oriented-programming',\n    objectID: '591e9732ab184fdc3bcd9185',\n  },\n  {\n    name: 'iot',\n    slug: 'iot',\n    objectID: '56744723958ef13879b9532f',\n  },\n  {\n    name: 'json',\n    slug: 'json',\n    objectID: '56744721958ef13879b94dec',\n  },\n  {\n    name: 'api',\n    slug: 'api',\n    objectID: '56744721958ef13879b94c20',\n  },\n  {\n    name: 'Express.js',\n    slug: 'expressjs-cilb5apda0066e053g7td7q24',\n    objectID: '56d729602c0ee8a839b966f1',\n  },\n  {\n    name: 'basics',\n    slug: 'basics',\n    objectID: '57b75ddd51da93ffde24c7d9',\n  },\n  {\n    name: 'http',\n    slug: 'http',\n    objectID: '56744721958ef13879b94c04',\n  },\n  {\n    name: 'Self Improvement ',\n    slug: 'self-improvement-1',\n    objectID: '5f2e55763b12e25afe3e4d05',\n  },\n  {\n    name: 'GitLab',\n    slug: 'gitlab',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1506019761/umvea3aqsquj7z9ighjt.png',\n    objectID: '56bb10616bd8ce129b0bcc6c',\n  },\n  {\n    name: 'google cloud',\n    slug: 'google-cloud',\n    objectID: '56744722958ef13879b951dd',\n  },\n  {\n    name: 'Spring',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1458677282/mtezd0wf8jhhmbgkzo1g.jpg',\n    slug: 'spring',\n    objectID: '5674471d958ef13879b94772',\n  },\n  {\n    name: 'selenium',\n    slug: 'selenium',\n    objectID: '56a1bb2a92921b8f79d3620f',\n  },\n  {\n    name: 'Gatsby',\n    slug: 'gatsby',\n    objectID: '58a37012803129b7f158f514',\n  },\n  {\n    name: 'containers',\n    slug: 'containers',\n    objectID: '571f798917ae2452d9887631',\n  },\n  {\n    name: 'resources',\n    slug: 'resources',\n    objectID: '56744721958ef13879b94d55',\n  },\n  {\n    name: 'operating system',\n    slug: 'operating-system',\n    objectID: '56744721958ef13879b94b09',\n  },\n  {\n    name: 'product',\n    slug: 'product',\n    objectID: '577f7bc442d3fa70a37e450e',\n  },\n  {\n    name: 'cms',\n    slug: 'cms',\n    objectID: '56744723958ef13879b953ff',\n  },\n  {\n    name: 'ui ux designer',\n    slug: 'ui-ux-designer',\n    objectID: '5f7af8bd9c3b6e4101218399',\n  },\n  {\n    name: 'hosting',\n    slug: 'hosting',\n    objectID: '56744721958ef13879b94b0f',\n  },\n  {\n    name: 'social media',\n    slug: 'social-media',\n    objectID: '5775ff2c57675ec2fcfd086e',\n  },\n  {\n    name: 'debugging',\n    slug: 'debugging',\n    objectID: '56744723958ef13879b95372',\n  },\n  {\n    name: 'Heroku',\n    slug: 'heroku',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496175418/k6pahvykel6hcfqtkk3d.jpg',\n    objectID: '568935c69a4538cecc3ae55f',\n  },\n  {\n    name: 'software',\n    slug: 'software',\n    objectID: '56744721958ef13879b9481e',\n  },\n  {\n    name: 'asp.net core',\n    slug: 'aspnet-core',\n    objectID: '56bad3b76bd8ce129b0bcc04',\n  },\n  {\n    name: 'hackathon',\n    slug: 'hackathon',\n    objectID: '56744720958ef13879b947d4',\n  },\n  {\n    name: 'framework',\n    slug: 'framework',\n    objectID: '56744721958ef13879b94b4d',\n  },\n  {\n    name: 'cli',\n    slug: 'cli',\n    objectID: '56744723958ef13879b953a7',\n  },\n  {\n    name: 'array methods',\n    slug: 'array-methods',\n    objectID: '5f397a30c4d5973f55c91219',\n  },\n  {\n    name: 'Electron',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1473241164/mqhaydhn8fhejzrsxofr.png',\n    slug: 'electron',\n    objectID: '56744723958ef13879b95419',\n  },\n  {\n    name: 'challenge',\n    slug: 'challenge',\n    objectID: '56744721958ef13879b949c9',\n  },\n  {\n    name: 'Freelancing',\n    slug: 'freelancing',\n    objectID: '56744723958ef13879b953cc',\n  },\n  {\n    name: 'linux-basics',\n    slug: 'linux-basics',\n    objectID: '5fb01c1fc03b0e471014f758',\n  },\n  {\n    name: 'portfolio',\n    slug: 'portfolio',\n    objectID: '5690e78091716a2d1dbadc0f',\n  },\n  {\n    name: 'functions',\n    slug: 'functions',\n    objectID: '56744721958ef13879b94a01',\n  },\n  {\n    name: 'Springboot',\n    slug: 'springboot',\n    objectID: '58646144cc0caec55e2fd1d1',\n  },\n  {\n    name: 'youtube',\n    slug: 'youtube',\n    objectID: '56ced112f0ec33085f1cc5ab',\n  },\n  {\n    name: 'Browsers',\n    slug: 'browsers',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502183382/psbzjcrqxjjndian3nph.png',\n    objectID: '56744721958ef13879b94d63',\n  },\n  {\n    name: 'vue',\n    slug: 'vue',\n    objectID: '570e5021115103c3b09785e1',\n  },\n  {\n    name: 'Flask Framework',\n    slug: 'flask',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1518503935975/S1_-_WePM.png',\n    objectID: '56744723958ef13879b95588',\n  },\n  {\n    name: 'HashnodeCommunity',\n    slug: 'hashnodecommunity',\n    objectID: '5f3272264332ee07eb55c4bd',\n  },\n  {\n    name: 'Apple',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1465890893/iickievhb3ymoyga6wyw.png',\n    slug: 'apple',\n    objectID: '56744721958ef13879b948ba',\n  },\n  {\n    name: 'Business and Finance ',\n    slug: 'business-and-finance',\n    objectID: '5f253857669da9610ee1771d',\n  },\n  {\n    name: 'CSS Animation',\n    slug: 'css-animation',\n    objectID: '567c03e03f1768f6bf48a678',\n  },\n  {\n    name: 'books',\n    slug: 'books',\n    objectID: '56744721958ef13879b94d2a',\n  },\n  {\n    name: 'Technical interview',\n    slug: 'technical-interview',\n    objectID: '5f0725a8570e2e29ce255012',\n  },\n  {\n    name: 'PHP7',\n    slug: 'php7',\n    objectID: '5680fde5aeae5c9e229cf8e2',\n  },\n  {\n    name: 'side project',\n    slug: 'side-project',\n    objectID: '576fa8aca245bcf2e2e91044',\n  },\n  {\n    name: 'personal',\n    slug: 'personal',\n    objectID: '56b41c593f1e4ff03c56b4e4',\n  },\n  {\n    name: 'github-actions',\n    slug: 'github-actions-1',\n    objectID: '5f4f0f5850b5c61ec6ef4eb4',\n  },\n  {\n    name: 'Facebook',\n    slug: 'facebook',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496176300/khcjk48ycpejt19sfgav.png',\n    objectID: '56744721958ef13879b94da0',\n  },\n  {\n    name: 'code review',\n    slug: 'code-review',\n    objectID: '56744721958ef13879b949f9',\n  },\n  {\n    name: 'elasticsearch',\n    slug: 'elasticsearch',\n    objectID: '56744723958ef13879b95430',\n  },\n  {\n    name: 'TDD (Test-driven development)',\n    slug: 'tdd',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502441836/zcgicxrtkoquz67dlkgs.png',\n    objectID: '56744721958ef13879b94898',\n  },\n  {\n    name: 'Svelte',\n    slug: 'svelte',\n    objectID: '583d0951f533d193a2e694d1',\n  },\n  {\n    name: 'Sass',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1490082271/p0drxprfhz9qmkm0txrf.png',\n    slug: 'sass',\n    objectID: '56744721958ef13879b94df7',\n  },\n  {\n    name: 'Entrepreneurship',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496913550/abhdc0juuwrn1kfjk966.png',\n    slug: 'entrepreneurship',\n    objectID: '567a50052b926c3063c305c9',\n  },\n  {\n    name: 'Bugs and Errors',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1465891233/e2dpkrprraf8jitcvc3i.png',\n    slug: 'bugs-and-errors',\n    objectID: '575f9bc3da600b8ef43e5263',\n  },\n  {\n    name: 'android apps',\n    slug: 'android-apps',\n    objectID: '590c655dd7c4344afe6c3241',\n  },\n  {\n    name: 'Flutter Widgets',\n    slug: 'flutter-widgets',\n    objectID: '5f08f6a1b0bf5b3c273ea78c',\n  },\n  {\n    name: 'documentation',\n    slug: 'documentation',\n    objectID: '56744722958ef13879b950f8',\n  },\n  {\n    name: 'Continuous Integration',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1460096831/jeyz4slnhjuflhkqbanb.png',\n    slug: 'continuous-integration',\n    objectID: '56744721958ef13879b94de0',\n  },\n  {\n    name: 'version control',\n    slug: 'version-control',\n    objectID: '56744722958ef13879b9506b',\n  },\n  {\n    name: 'asynchronous',\n    slug: 'asynchronous',\n    objectID: '56744722958ef13879b94e66',\n  },\n  {\n    name: 'Magento',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1487144361/a8xaya1bv8advcuoj90m.png',\n    slug: 'magento',\n    objectID: '56eadc94bcca2d711e191c4c',\n  },\n  {\n    name: 'Netlify',\n    slug: 'netlify',\n    objectID: '57ce27e495368c463b098050',\n  },\n  {\n    name: 'nginx',\n    slug: 'nginx',\n    objectID: '56744722958ef13879b94f8b',\n  },\n  {\n    name: 'web scraping',\n    slug: 'web-scraping',\n    objectID: '58dfb250eb0ffea9e764936d',\n  },\n  {\n    name: 'ios app development',\n    slug: 'ios-app-development',\n    objectID: '584a50f7e1ffd7084c8b1e6c',\n  },\n  {\n    name: 'Redis',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513324425585/r1M9y-bMM.png',\n    slug: 'redis',\n    objectID: '56744721958ef13879b94c41',\n  },\n  {\n    name: 'infrastructure',\n    slug: 'infrastructure',\n    objectID: '56a4e1d28e1dd6d05014efdb',\n  },\n  {\n    name: 'shell',\n    slug: 'shell',\n    objectID: '56744723958ef13879b95561',\n  },\n  {\n    name: 'CSS Frameworks',\n    slug: 'css-frameworks',\n    objectID: '56744721958ef13879b94b82',\n  },\n  {\n    name: 'Responsive Web Design',\n    slug: 'responsive-web-design',\n    objectID: '574dc610be8cff2ed6571a40',\n  },\n  {\n    name: 'bootcamp',\n    slug: 'bootcamp',\n    objectID: '58d54af36047f98ddcae780b',\n  },\n  {\n    name: 'Competitive programming',\n    slug: 'competitive-programming',\n    objectID: '56fb79d4da7018d48c208e91',\n  },\n  {\n    name: 'podcast',\n    slug: 'podcast',\n    objectID: '56744722958ef13879b950d3',\n  },\n  {\n    name: 'email',\n    slug: 'email',\n    objectID: '56744722958ef13879b95038',\n  },\n  {\n    name: 'Material Design',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1474050455/stm5thxo0n1evvzpl7np.png',\n    slug: 'material-design',\n    objectID: '56744722958ef13879b95029',\n  },\n  {\n    name: 'NoSQL',\n    slug: 'nosql',\n    objectID: '56744721958ef13879b94b41',\n  },\n  {\n    name: 'markdown',\n    slug: 'markdown',\n    objectID: '56744722958ef13879b950b2',\n  },\n  {\n    name: 'components',\n    slug: 'components',\n    objectID: '571c5374fc5b53a1ace37ce8',\n  },\n  {\n    name: 'unit testing',\n    slug: 'unit-testing',\n    objectID: '56744721958ef13879b94ac4',\n  },\n  {\n    name: 'management',\n    slug: 'management',\n    objectID: '56744721958ef13879b948d1',\n  },\n  {\n    name: 'research',\n    slug: 'research',\n    objectID: '56744723958ef13879b952cb',\n  },\n  {\n    name: 'Ionic Framework',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1513839196650/HyrD9A_fM.jpeg',\n    slug: 'ionic',\n    objectID: '56744721958ef13879b94b62',\n  },\n  {\n    name: 'vim',\n    slug: 'vim',\n    objectID: '56744722958ef13879b95126',\n  },\n  {\n    name: 'Accessibility',\n    slug: 'accessibility',\n    objectID: '56744723958ef13879b95230',\n  },\n  {\n    name: 'remote',\n    slug: 'remote',\n    objectID: '56744721958ef13879b94841',\n  },\n  {\n    name: 'agile',\n    slug: 'agile',\n    objectID: '56744723958ef13879b9551b',\n  },\n  {\n    name: 'analytics',\n    slug: 'analytics',\n    objectID: '56744721958ef13879b9495b',\n  },\n  {\n    name: 'vscode extensions',\n    slug: 'vscode-extensions',\n    objectID: '5f5c6d4213599a5f2e33f00f',\n  },\n  {\n    name: 'statistics',\n    slug: 'statistics',\n    objectID: '56744721958ef13879b949ea',\n  },\n  {\n    name: 'react router',\n    slug: 'react-router',\n    objectID: '56744721958ef13879b949bc',\n  },\n  {\n    name: 'IDEs',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468381/x5vcqb3xxe7wdheuopww.png',\n    slug: 'ides',\n    objectID: '56744722958ef13879b94eff',\n  },\n  {\n    name: 'forms',\n    slug: 'forms',\n    objectID: '56744721958ef13879b948fa',\n  },\n  {\n    name: 'Terraform',\n    slug: 'terraform',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1617121672721/6r0bN-GSK.png',\n    objectID: '57bf546693309a25047c6206',\n  },\n  {\n    name: 'animation',\n    slug: 'animation',\n    objectID: '56744723958ef13879b95338',\n  },\n  {\n    name: 'Developer Blogging',\n    slug: 'developer-blogging',\n    objectID: '5f1c1e25e8769101a9ef64d2',\n  },\n  {\n    name: 'PWA',\n    slug: 'pwa',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496404433/rlgbcgsuycivf0ukgxrl.png',\n    objectID: '57cbc5d49b3eb82e014a0320',\n  },\n  {\n    name: 'JAMstack',\n    slug: 'jamstack',\n    objectID: '58f9253e01cb858c63429c31',\n  },\n  {\n    name: 'Elixir',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1452000435/svfntjev0f681f6oiptm.png',\n    slug: 'elixir',\n    objectID: '56744723958ef13879b95392',\n  },\n  {\n    name: 'dotnetcore',\n    slug: 'dotnetcore',\n    objectID: '5794f65abecb9ebac0d5fc56',\n  },\n  {\n    name: 'ShowHashnode',\n    slug: 'showhashnode',\n    objectID: '5d946e601971c92f3298b280',\n  },\n  {\n    name: 'coding challenge',\n    slug: 'coding-challenge',\n    objectID: '5f16831dfefe35614464e44b',\n  },\n  {\n    name: 'Android Studio',\n    slug: 'android-studio',\n    objectID: '5868042db99398bc30c43e77',\n  },\n  {\n    name: 'variables',\n    slug: 'variables',\n    objectID: '56744721958ef13879b94863',\n  },\n  {\n    name: 'ci-cd',\n    slug: 'ci-cd',\n    objectID: '5f0ed0dd7611e111fbd7194f',\n  },\n  {\n    name: 'nlp',\n    slug: 'nlp',\n    objectID: '573a8e38a5dc678fc9090d31',\n  },\n  {\n    name: '#howtos',\n    slug: 'howtos',\n    objectID: '5f18178960b5d372e20d5a86',\n  },\n  {\n    name: 'Web Hosting',\n    slug: 'web-hosting',\n    objectID: '571faab486b33947d9bdbab2',\n  },\n  {\n    name: 'oop',\n    slug: 'oop',\n    objectID: '5674471d958ef13879b94779',\n  },\n  {\n    name: 'DigitalOcean',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1491567594/mtbp3w2posceqcdj8rx5.jpg',\n    slug: 'digitalocean',\n    objectID: '56744721958ef13879b948c3',\n  },\n  {\n    name: 'SVG',\n    slug: 'svg',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1500325020/kp4vjytdfuqbhaibiqm7.png',\n    objectID: '56744723958ef13879b95469',\n  },\n  {\n    name: 'promises',\n    slug: 'promises',\n    objectID: '56744722958ef13879b951d9',\n  },\n  {\n    name: 'womenwhocode',\n    slug: 'womenwhocode',\n    objectID: '5f1fdd28ed20ff21a11e7126',\n  },\n  {\n    name: 'Flutter SDK',\n    slug: 'flutter-sdk',\n    objectID: '5f08f6a1b0bf5b3c273ea78a',\n  },\n  {\n    name: 'optimization',\n    slug: 'optimization',\n    objectID: '56744721958ef13879b94821',\n  },\n  {\n    name: 'work',\n    slug: 'work',\n    objectID: '56a361abff99ae055eeffd33',\n  },\n  {\n    name: 'database',\n    slug: 'database',\n    objectID: '56744722958ef13879b950ef',\n  },\n  {\n    name: 'pandas',\n    slug: 'pandas',\n    objectID: '56744723958ef13879b953e6',\n  },\n  {\n    name: 'chrome extension',\n    slug: 'chrome-extension',\n    objectID: '56b1945b04f0061506b361db',\n  },\n  {\n    name: 'privacy',\n    slug: 'privacy',\n    objectID: '56744723958ef13879b952fc',\n  },\n  {\n    name: 'events',\n    slug: 'events',\n    objectID: '575d75e2da600b8ef43e506d',\n  },\n  {\n    name: 'ansible',\n    slug: 'ansible',\n    objectID: '56744722958ef13879b95152',\n  },\n  {\n    name: 'Mathematics',\n    slug: 'mathematics',\n    objectID: '592d60cb8a6f7b0a1195412a',\n  },\n  {\n    name: 'startup',\n    slug: 'startup',\n    objectID: '56744721958ef13879b94bbb',\n  },\n  {\n    name: 'music',\n    slug: 'music',\n    objectID: '56744721958ef13879b949c6',\n  },\n  {\n    name: 'problem solving skills',\n    slug: 'problem-solving-skills',\n    objectID: '5f8560a8e83ccb407537a1ee',\n  },\n  {\n    name: 'review',\n    slug: 'review',\n    objectID: '56744723958ef13879b953b4',\n  },\n  {\n    name: 'GIS',\n    slug: 'gis',\n    objectID: '57fb4f226849a80ac266ca71',\n  },\n  {\n    name: 'unity',\n    slug: 'unity',\n    objectID: '56744721958ef13879b94885',\n  },\n  {\n    name: 'test',\n    slug: 'test',\n    objectID: '56744722958ef13879b951d6',\n  },\n  {\n    name: 'TIL',\n    slug: 'til',\n    objectID: '5d93238ce235795f6eb6dd79',\n  },\n  {\n    name: 'Auth0',\n    slug: 'auth0',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1627916134204/sEaEU0wiP.png',\n    objectID: '56fb1506ea33a5b266f2ffc3',\n  },\n  {\n    name: 'Certification',\n    slug: 'certification',\n    objectID: '57d4461ed17cab545cab66de',\n  },\n  {\n    name: 'webdevelopment',\n    slug: 'webdevelopment',\n    objectID: '56744721958ef13879b94b20',\n  },\n  {\n    name: 'lifestyle',\n    slug: 'lifestyle',\n    objectID: '56744721958ef13879b948f2',\n  },\n  {\n    name: 'course',\n    slug: 'course',\n    objectID: '575150c412a8cb07bb842118',\n  },\n  {\n    name: 'Story',\n    slug: 'story',\n    objectID: '57348ce934963cba3535abb4',\n  },\n  {\n    name: 'job search',\n    slug: 'job-search',\n    objectID: '5f08ee681981c53c4987f2b4',\n  },\n  {\n    name: 'Raspberry Pi',\n    slug: 'raspberry-pi',\n    objectID: '56d2cbb4099859fa044d68c0',\n  },\n  {\n    name: 'Amazon Web Services',\n    slug: 'amazon-web-services',\n    objectID: '56a6742dc84f2c6913b8eac3',\n  },\n  {\n    name: 'tutorials',\n    slug: 'tutorials',\n    objectID: '56744721958ef13879b94dcc',\n  },\n  {\n    slug: 'flutter-cjx3aa7op001jims1kuwl3ekz',\n    objectID: '5d0a3b36c7de780e772aff0a',\n  },\n  {\n    name: '#data visualisation',\n    slug: 'data-visualisation-1',\n    objectID: '5f4b7d61f540845bb26f0291',\n  },\n  {\n    name: 'continuous deployment',\n    slug: 'continuous-deployment',\n    objectID: '56744722958ef13879b94f92',\n  },\n  {\n    name: 'video',\n    slug: 'video',\n    objectID: '56744723958ef13879b954e9',\n  },\n  {\n    name: 'DOM',\n    slug: 'dom',\n    objectID: '56744723958ef13879b95376',\n  },\n  {\n    name: 'search',\n    slug: 'search',\n    objectID: '56744721958ef13879b9497b',\n  },\n  {\n    name: 'JWT',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1464240237/bqu9k0lklrg7xxvk2pzq.jpg',\n    slug: 'jwt',\n    objectID: '56744723958ef13879b9536e',\n  },\n  {\n    name: 'Interviews',\n    slug: 'interviews',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496318621/g6yz7ukrqftqat2y3ycn.png',\n    objectID: '56744721958ef13879b948b1',\n  },\n  {\n    name: 'vanilla-js',\n    slug: 'vanilla-js-1',\n    objectID: '5f9aaeaba1658252d1a7b620',\n  },\n  {\n    name: 'monitoring',\n    slug: 'monitoring',\n    objectID: '56744723958ef13879b95361',\n  },\n  {\n    name: 'Text Editors',\n    slug: 'text-editors',\n    objectID: '571459a7162bdaad9f92b0d7',\n  },\n  {\n    name: 'gaming',\n    slug: 'gaming',\n    objectID: '57e951b155544e5132a4d5df',\n  },\n  {\n    name: 'mongoose',\n    slug: 'mongoose',\n    objectID: '56744723958ef13879b9540c',\n  },\n  {\n    name: 'SaaS',\n    slug: 'saas',\n    objectID: '56744722958ef13879b950a5',\n  },\n  {\n    name: 'content',\n    slug: 'content',\n    objectID: '56744721958ef13879b94849',\n  },\n  {\n    name: 'apache',\n    slug: 'apache',\n    objectID: '56744723958ef13879b95513',\n  },\n  {\n    name: 'engineering',\n    slug: 'engineering',\n    objectID: '56744722958ef13879b950b5',\n  },\n  {\n    name: 'headless cms',\n    slug: 'headless-cms',\n    objectID: '5914be36db93b4aae8008897',\n  },\n  {\n    name: 'newsletter',\n    slug: 'newsletter',\n    objectID: '56744722958ef13879b9516a',\n  },\n  {\n    name: 'network',\n    slug: 'network',\n    objectID: '56744721958ef13879b94923',\n  },\n  {\n    name: 'IT',\n    slug: 'it',\n    objectID: '57628dcd820dd45f3fbd8eb5',\n  },\n  {\n    name: 'mobile app development',\n    slug: 'mobile-app-development',\n    objectID: '56744723958ef13879b95222',\n  },\n  {\n    name: 'freeCodeCamp.org',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1518534240940/ByFDRugwf.jpeg',\n    slug: 'freecodecamp',\n    objectID: '57039f98f950faa9ab7ec552',\n  },\n  {\n    name: 'Cryptography',\n    slug: 'cryptography',\n    objectID: '58426a8997063da359fe2cf4',\n  },\n  {\n    name: 'Augmented Reality',\n    slug: 'augmented-reality',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1506666999/lnnrwwh9td4xm87lh13d.png',\n    objectID: '57ce29fde5e41a2a5c24fa98',\n  },\n  {\n    name: 'training',\n    slug: 'training',\n    objectID: '56b0a1600a7ca0c6f70c3703',\n  },\n  {\n    name: 'Objects',\n    slug: 'objects',\n    objectID: '57e793cdef99cf03582fe42b',\n  },\n  {\n    name: 'flexbox',\n    slug: 'flexbox',\n    objectID: '56744721958ef13879b94afb',\n  },\n  {\n    name: 'SSL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450712342/u2tfvtrfojyne6qzaflq.jpg',\n    slug: 'ssl',\n    objectID: '56744721958ef13879b94912',\n  },\n  {\n    name: 'ASP.NET',\n    slug: 'aspnet',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1515075607732/ByxXu3jQG.jpeg',\n    objectID: '567e2a600db88211bac0a032',\n  },\n  {\n    name: 'distributed system',\n    slug: 'distributed-system',\n    objectID: '568c90725e7a940b3d3e08ed',\n  },\n  {\n    name: 'logging',\n    slug: 'logging',\n    objectID: '568bb9dbe99c5444f3233893',\n  },\n  {\n    name: 'Applications',\n    slug: 'applications',\n    objectID: '56ea7aebbcca2d711e191c02',\n  },\n  {\n    name: 'user experience',\n    slug: 'user-experience',\n    objectID: '56744721958ef13879b948d4',\n  },\n  {\n    name: 'architecture',\n    slug: 'architecture',\n    objectID: '56744723958ef13879b9529a',\n  },\n  {\n    name: 'package',\n    slug: 'package',\n    objectID: '56744723958ef13879b9533c',\n  },\n  {\n    name: 'tricks',\n    slug: 'tricks',\n    objectID: '56744721958ef13879b94b19',\n  },\n  {\n    name: 'R Language',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1490864688/fiw7ngemmxkumjpkntdp.png',\n    slug: 'r',\n    objectID: '56744722958ef13879b95111',\n  },\n  {\n    name: 'css flexbox',\n    slug: 'css-flexbox',\n    objectID: '56744721958ef13879b94c3a',\n  },\n  {\n    name: 'Xcode',\n    slug: 'xcode',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502355718/vddmkshskl3sbl4xogwn.jpg',\n    objectID: '56744720958ef13879b947ff',\n  },\n  {\n    name: 'Monetization',\n    slug: 'monetization',\n    objectID: '5736a1db6a4640415dc89e28',\n  },\n  {\n    name: 'async',\n    slug: 'async',\n    objectID: '56cbdb23b70682283f9edeb8',\n  },\n  {\n    name: 'SQL Server',\n    slug: 'sql-server',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1551380133166/kHlXAcxdU.jpeg',\n    objectID: '56744720958ef13879b947b6',\n  },\n  {\n    name: 'tensorflow',\n    slug: 'tensorflow',\n    objectID: '56744722958ef13879b9518a',\n  },\n  {\n    name: 'Vercel Hashnode Hackathon',\n    slug: 'vercelhashnode',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1610701406772/nrAD-f_i6.png',\n    objectID: '6001530cf611a365208ad66a',\n  },\n  {\n    name: 'extension',\n    slug: 'extension',\n    objectID: '569f6b4492921b8f79d36061',\n  },\n  {\n    name: 'free',\n    slug: 'free',\n    objectID: '56744723958ef13879b95214',\n  },\n  {\n    name: 'kotlin beginner',\n    slug: 'kotlin-beginner',\n    objectID: '5f081e73b587713318b74a42',\n  },\n  {\n    name: 'SurviveJS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1461251276/bmmcz554bnl0zk83l1iz.png',\n    slug: 'survivejs',\n    objectID: '5718ec0fc4b104334fad928e',\n  },\n  {\n    name: 'Rails',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1453793832/qypx8pjpm7tybpcbfhif.jpg',\n    slug: 'rails',\n    objectID: '56744722958ef13879b94eb5',\n  },\n  {\n    name: 'Web Perf',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472485302/gvihfpia52e0l5r3rau9.jpg',\n    slug: 'webperf',\n    objectID: '56744722958ef13879b950c6',\n  },\n  {\n    name: 'big data',\n    slug: 'big-data',\n    objectID: '56744721958ef13879b94e3b',\n  },\n  {\n    name: 'communication',\n    slug: 'communication',\n    objectID: '57d2d92415ae0c65b80ace44',\n  },\n  {\n    name: 'Solidity',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512988916861/ryTxWknbG.png',\n    slug: 'solidity',\n    objectID: '595ab8b5a3e02ebe146b2f2a',\n  },\n  {\n    name: 'Experience ',\n    slug: 'experience',\n    objectID: '587dbc32d40f782e50cf92e0',\n  },\n  {\n    name: 'Amazon S3',\n    slug: 'amazon-s3',\n    objectID: '569d145446dfdb8479aa690d',\n  },\n  {\n    name: 'Meteor',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450467991/aaskzxstfaadd1sbhxj2.png',\n    slug: 'meteor',\n    objectID: '56744722958ef13879b94fa7',\n  },\n  {\n    name: 'agile development',\n    slug: 'agile-development',\n    objectID: '56744721958ef13879b94dba',\n  },\n  {\n    name: 'Oracle',\n    slug: 'oracle',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516182996908/ByaA6q2NG.jpeg',\n    objectID: '56744721958ef13879b9498a',\n  },\n  {\n    name: 'scss',\n    slug: 'scss',\n    objectID: '56744722958ef13879b951f1',\n  },\n  {\n    name: 'GCP',\n    slug: 'gcp',\n    objectID: '58d4d1fbcfc5bd6596a0a6b5',\n  },\n  {\n    name: 'domain',\n    slug: 'domain',\n    objectID: '5714fe4e151fa7c4488cc1ae',\n  },\n  {\n    name: 'Regex',\n    slug: 'regex',\n    objectID: '56f6aef0aa013a5f87413615',\n  },\n  {\n    name: 'Symfony',\n    slug: 'symfony',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1541163459741/BknTF6t3m.png',\n    objectID: '572d6c67bf97af427dd07f13',\n  },\n  {\n    name: 'app',\n    slug: 'app',\n    objectID: '56744721958ef13879b94a0e',\n  },\n  {\n    name: 'Junior developer ',\n    slug: 'junior-developer',\n    objectID: '5f071caa6e04d8269a566170',\n  },\n  {\n    name: 'advice',\n    slug: 'advice',\n    objectID: '56744723958ef13879b95333',\n  },\n  {\n    name: 'Powershell',\n    slug: 'powershell',\n    objectID: '56f7871ffc7154468758edb7',\n  },\n  {\n    name: 'Babel',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1504815622/wo9hjfe0klgxj8mahf6j.png',\n    slug: 'babel',\n    objectID: '56744722958ef13879b95045',\n  },\n  {\n    name: 'Reactive Programming',\n    slug: 'reactive-programming',\n    objectID: '56744721958ef13879b94aee',\n  },\n  {\n    name: 'Smart Contracts',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512989048807/S1WtW1hZz.png',\n    slug: 'smart-contracts',\n    objectID: '5a2e407a5b9ed1636662b8f9',\n  },\n  {\n    name: 'string',\n    slug: 'string',\n    objectID: '57448e2a9ade925885158cfe',\n  },\n  {\n    name: 'images',\n    slug: 'images',\n    objectID: '56744723958ef13879b95229',\n  },\n  {\n    name: 'hiring',\n    slug: 'hiring',\n    objectID: '56744721958ef13879b9497e',\n  },\n  {\n    name: 'Christmas Hackathon',\n    slug: 'christmashackathon',\n    logo: null,\n    objectID: '5fe187955620145ec6e3a5c2',\n  },\n  {\n    name: 'services',\n    slug: 'services',\n    objectID: '5682e64e2c29f7e0c86d024b',\n  },\n  {\n    name: 'aws-cdk',\n    slug: 'aws-cdk',\n    objectID: '5f743910a3a6d515f7142eb4',\n  },\n  {\n    name: 'Laravel 5',\n    slug: 'laravel-5',\n    objectID: '56ec06ac5edec9d7189a0ad6',\n  },\n  {\n    name: 'crypto',\n    slug: 'crypto',\n    objectID: '57b188c971be21426cb4916e',\n  },\n  {\n    name: 'instagram',\n    slug: 'instagram',\n    objectID: '56744721958ef13879b94aec',\n  },\n  {\n    name: 'questions',\n    slug: 'questions',\n    objectID: '56744723958ef13879b952fe',\n  },\n  {\n    name: 'bot',\n    slug: 'bot',\n    objectID: '56744721958ef13879b948df',\n  },\n  {\n    name: 'chatbot',\n    slug: 'chatbot',\n    objectID: '57444f35468ae9e479434fac',\n  },\n  {\n    name: 'risingstack',\n    slug: 'risingstack',\n    objectID: '587745676b985e96ec6d48b7',\n  },\n  {\n    name: 'trends',\n    slug: 'trends',\n    objectID: '56744721958ef13879b94a2a',\n  },\n  {\n    name: 'Jest',\n    slug: 'jest',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496389933/s2t8atgotu6wvjgojpn6.png',\n    objectID: '56cfe81bfa28f5fe7f74d215',\n  },\n  {\n    name: 'refactoring',\n    slug: 'refactoring',\n    objectID: '56744720958ef13879b947df',\n  },\n  {\n    name: 'frameworks',\n    slug: 'frameworks',\n    objectID: '56744721958ef13879b94db1',\n  },\n  {\n    name: 'arrays',\n    slug: 'arrays',\n    objectID: '579350e1d87e23e5efe30d84',\n  },\n  {\n    name: 'cheatsheet',\n    slug: 'cheatsheet',\n    objectID: '56cc66fff978c91273a36237',\n  },\n  {\n    name: 'team',\n    slug: 'team',\n    objectID: '56744723958ef13879b952e7',\n  },\n  {\n    name: 'docker images',\n    slug: 'docker-images',\n    objectID: '5f442ff51b2ea309b7529267',\n  },\n  {\n    name: 'classes',\n    slug: 'classes',\n    objectID: '56744723958ef13879b955a3',\n  },\n  {\n    name: 'workflow',\n    slug: 'workflow',\n    objectID: '56744722958ef13879b94e77',\n  },\n  {\n    name: 'ML',\n    slug: 'ml',\n    objectID: '57c6e7bdb274bac7e601abe2',\n  },\n  {\n    name: 'neural networks',\n    slug: 'neural-networks',\n    objectID: '56af3b4ccc975f0cc6878c8a',\n  },\n  {\n    name: 'javascript modules',\n    slug: 'javascript-modules',\n    objectID: '56cbdab9b70682283f9edeae',\n  },\n  {\n    name: 'skills',\n    slug: 'skills',\n    objectID: '576b3918decdd3bf3610c80b',\n  },\n  {\n    name: 'Internet of Things',\n    slug: 'internet-of-things',\n    objectID: '58f8acb0e928dad5e4c7ab2b',\n  },\n  {\n    name: 'dns',\n    slug: 'dns',\n    objectID: '5674471d958ef13879b94798',\n  },\n  {\n    name: 'Blazor ',\n    slug: 'blazor-1',\n    objectID: '5f219f52ef20f63bcf9822c6',\n  },\n  {\n    name: 'Script',\n    slug: 'script',\n    objectID: '56a294beff99ae055eeffcea',\n  },\n  {\n    name: 'Help Needed',\n    slug: 'help',\n    objectID: '5674471d958ef13879b94764',\n  },\n  {\n    name: 'mobile',\n    slug: 'mobile',\n    objectID: '56744723958ef13879b9524e',\n  },\n  {\n    name: 'Amplify Hashnode',\n    slug: 'amplifyhashnode',\n    logo: null,\n    objectID: '60223d4f281265375d643d83',\n  },\n  {\n    name: 'ssh',\n    slug: 'ssh',\n    objectID: '5677ff6aec7aa67e51f1e096',\n  },\n  {\n    name: 'Software Testing',\n    slug: 'software-testing',\n    objectID: '56b54dae8dabdc6142c1ac86',\n  },\n  {\n    name: 'dev tools',\n    slug: 'dev-tools',\n    objectID: '56744723958ef13879b9527c',\n  },\n  {\n    name: 'https',\n    slug: 'https',\n    objectID: '56744722958ef13879b94e73',\n  },\n  {\n    name: 'Inspiration',\n    slug: 'inspiration',\n    objectID: '57de56e3c61e5b59729da2a8',\n  },\n  {\n    name: 'Ajax',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1459504130/huzynvc2g3hd5w8sjw6w.jpg',\n    slug: 'ajax',\n    objectID: '56744722958ef13879b95140',\n  },\n  {\n    name: 'DEVCommunity',\n    slug: 'devcommunity',\n    objectID: '5f1ccb30f4016901885cc50f',\n  },\n  {\n    name: 'oauth',\n    slug: 'oauth',\n    objectID: '56744722958ef13879b951b1',\n  },\n  {\n    name: 'design principles',\n    slug: 'design-principles',\n    objectID: '5f965c1c40346172a86c2c4b',\n  },\n  {\n    name: 'mentalhealth',\n    slug: 'mentalhealth-1',\n    objectID: '5f7e39240e5d207780d949e9',\n  },\n  {\n    name: '#hacktoberfest ',\n    slug: 'hacktoberfest-1',\n    objectID: '5f6629266dfc523d0a89357b',\n  },\n  {\n    name: 'MobX',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1534512814483/SJUnRS4U7.jpeg',\n    slug: 'mobx',\n    objectID: '5729bc14faa06f875ef32e95',\n  },\n  {\n    name: 'ec2',\n    slug: 'ec2',\n    objectID: '56744721958ef13879b94a18',\n  },\n  {\n    name: 'setup',\n    slug: 'setup',\n    objectID: '57a37bf75bfdd08aeffb5832',\n  },\n  {\n    name: 'devtools',\n    slug: 'devtools',\n    objectID: '56744722958ef13879b950fe',\n  },\n  {\n    name: 'ecmascript',\n    slug: 'ecmascript',\n    objectID: '56744722958ef13879b9511f',\n  },\n  {\n    name: 'styled-components',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1486104606/jbhiqodxlyhaqogfuqwy.png',\n    slug: 'styled-components',\n    objectID: '58900d47afa2b4bce2efb44f',\n  },\n  {\n    name: 'REST',\n    slug: 'rest',\n    objectID: '56744721958ef13879b949f6',\n  },\n  {\n    name: 'caching',\n    slug: 'caching',\n    objectID: '56744723958ef13879b9540f',\n  },\n  {\n    name: '7daystreak',\n    slug: '7daystreak',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1626280769878/xaxdZgS0N.png',\n    objectID: '60ed9e18fc37a15ec15683b3',\n  },\n  {\n    name: 'image processing',\n    slug: 'image-processing',\n    objectID: '5674471d958ef13879b94776',\n  },\n  {\n    name: 'Web API',\n    slug: 'web-api',\n    objectID: '5894ec2f47e4163deb72c252',\n  },\n  {\n    name: 'ideas',\n    slug: 'ideas',\n    objectID: '56744721958ef13879b948f6',\n  },\n  {\n    name: 'hack',\n    slug: 'hack',\n    objectID: '56744723958ef13879b95426',\n  },\n  {\n    name: 'hardware',\n    slug: 'hardware',\n    objectID: '568439646b179c61d167f08d',\n  },\n  {\n    name: 'web application',\n    slug: 'web-application',\n    objectID: '56744723958ef13879b952c2',\n  },\n  {\n    name: 'library',\n    slug: 'library',\n    objectID: '56744721958ef13879b94d94',\n  },\n  {\n    name: 'opencv',\n    slug: 'opencv',\n    objectID: '587745676b985e96ec6d48b8',\n  },\n  {\n    name: 'AWS Certified Solutions Architect Associate',\n    slug: 'aws-certified-solutions-architect-associate',\n    objectID: '5f71b762eb14b172f1d4bc39',\n  },\n  {\n    name: 'CSS Grid',\n    slug: 'css-grid',\n    objectID: '58becf402a99d222c65c24d8',\n  },\n  {\n    name: 'job',\n    slug: 'job',\n    objectID: '56744721958ef13879b94a46',\n  },\n  {\n    name: 'leadership',\n    slug: 'leadership',\n    objectID: '57c15e52387df20e0b9f94a0',\n  },\n  {\n    name: 'Jenkins',\n    slug: 'jenkins',\n    objectID: '57d6d71cf72dd3705c15ffcf',\n  },\n  {\n    name: 'eslint',\n    slug: 'eslint',\n    objectID: '570f716a115103c3b0978698',\n  },\n  {\n    name: 'time',\n    slug: 'time',\n    objectID: '58f7bab0e1eb1bd4e45f05f0',\n  },\n  {\n    name: 'realtime',\n    slug: 'realtime',\n    objectID: '56744721958ef13879b94bdf',\n  },\n  {\n    name: 'Math',\n    slug: 'math',\n    objectID: '581ad086c055bbfb46d8811b',\n  },\n  {\n    name: 'conference',\n    slug: 'conference',\n    objectID: '56744721958ef13879b9493b',\n  },\n  {\n    name: 'general',\n    slug: 'general',\n    objectID: '56fd6444404be5549d3de51b',\n  },\n  {\n    name: 'encryption',\n    slug: 'encryption',\n    objectID: '56744723958ef13879b9528d',\n  },\n  {\n    name: 'files',\n    slug: 'files',\n    objectID: '57f7bbb9813841efc19c3488',\n  },\n  {\n    name: 'error handling',\n    slug: 'error-handling',\n    objectID: '56744722958ef13879b95084',\n  },\n  {\n    name: 'Auth0Hackathon',\n    slug: 'auth0hackathon',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1627916253311/DuFTo1seC.png',\n    objectID: '6108059fb97c436d241bddc5',\n  },\n  {\n    name: 'numpy',\n    slug: 'numpy',\n    objectID: '57c7c7c7e53060955aa8c018',\n  },\n  {\n    name: 'D3.js',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1459873316/fdlqr3pk587gddsrirxe.jpg',\n    slug: 'd3js',\n    objectID: '56744721958ef13879b94d8c',\n  },\n  {\n    name: 'Apollo GraphQL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1467922175/sbxeze75uotah3qeqhbh.png',\n    slug: 'apollo',\n    objectID: '57053ef1115103c3b0977fb0',\n  },\n  {\n    name: 'Nuxt',\n    slug: 'nuxt',\n    objectID: '591c5a1956856e7d71046403',\n  },\n  {\n    name: 'DDD',\n    slug: 'ddd',\n    objectID: '576b14ad41d2cbca360cf875',\n  },\n  {\n    name: 'excel',\n    slug: 'excel',\n    objectID: '591414b39e2b75ff7c5fa62d',\n  },\n  {\n    name: 'branding',\n    slug: 'branding',\n    objectID: '56b71ac92894c38346c06670',\n  },\n  {\n    name: 'Web Components',\n    slug: 'web-components',\n    objectID: '56744723958ef13879b95564',\n  },\n  {\n    name: 'dynamodb',\n    slug: 'dynamodb',\n    objectID: '56744722958ef13879b950d8',\n  },\n  {\n    name: 'College',\n    slug: 'college',\n    objectID: '587dbc32d40f782e50cf92df',\n  },\n  {\n    name: 'journal',\n    slug: 'journal',\n    objectID: '5674471d958ef13879b94791',\n  },\n  {\n    name: 'state',\n    slug: 'state',\n    objectID: '584ac47b9747b36ae2a28c8a',\n  },\n  {\n    name: 'impostor syndrome',\n    slug: 'impostor-syndrome',\n    objectID: '56744723958ef13879b95306',\n  },\n  {\n    name: 'creativity',\n    slug: 'creativity',\n    objectID: '56744721958ef13879b94829',\n  },\n  {\n    name: 'SheCodeAfrica ',\n    slug: 'shecodeafrica',\n    objectID: '5f115a51d6c58d29e0240e45',\n  },\n  {\n    name: 'SocketIO',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472485355/zsypm63fq6998mc1pqvl.png',\n    slug: 'socketio',\n    objectID: '56744721958ef13879b94b52',\n  },\n  {\n    name: 'HTML Canvas',\n    slug: 'html-canvas',\n    objectID: '5692580fcad8946e563c570a',\n  },\n  {\n    name: 'QA',\n    slug: 'qa',\n    objectID: '56a20c4d92921b8f79d36276',\n  },\n  {\n    name: 'linux kernel',\n    slug: 'linux-kernel',\n    objectID: '5faadc16d6009557c49f5bbb',\n  },\n  {\n    name: 'Travel',\n    slug: 'travel',\n    objectID: '58859588abf4ad10c6ac08b6',\n  },\n  {\n    name: 'authorization',\n    slug: 'authorization',\n    objectID: '56744722958ef13879b9518c',\n  },\n  {\n    name: 'Scrum',\n    slug: 'scrum',\n    objectID: '570a9a273aeb5317437380e4',\n  },\n  {\n    name: 'Validation',\n    slug: 'validation',\n    objectID: '56c093923ddee41359169468',\n  },\n  {\n    name: 'messaging',\n    slug: 'messaging',\n    objectID: '57d832bbd17cab545cab9dbf',\n  },\n  {\n    name: 'Computer Vision',\n    slug: 'computer-vision',\n    objectID: '57534dab82cbbab8dcd475b9',\n  },\n  {\n    name: 'ios app developer',\n    slug: 'ios-app-developer',\n    objectID: '56744723958ef13879b9542c',\n  },\n  {\n    name: 'Xamarin',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1464701189/ms3lwj8fdp2agynrxrly.jpg',\n    slug: 'xamarin',\n    objectID: '56744721958ef13879b94825',\n  },\n  {\n    name: 'mvc',\n    slug: 'mvc',\n    objectID: '56744721958ef13879b94995',\n  },\n  {\n    name: 'fonts',\n    slug: 'fonts',\n    objectID: '56744721958ef13879b9499e',\n  },\n  {\n    name: 'video streaming',\n    slug: 'video-streaming',\n    objectID: '590c71fe1ae3d06072e8956c',\n  },\n  {\n    name: 'closure',\n    slug: 'closure',\n    objectID: '56744721958ef13879b94b1e',\n  },\n  {\n    name: 'HarperDB Hackathon',\n    slug: 'harperdbhackathon',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1623401171709/jCXKCcOIl.png',\n    objectID: '60b7952425dc276ffb940618',\n  },\n  {\n    name: 'axios',\n    slug: 'axios',\n    objectID: '58887bba81421379798066f5',\n  },\n  {\n    name: 'mentorship',\n    slug: 'mentorship',\n    objectID: '575c2d212f07b512c4dce579',\n  },\n  {\n    name: 'code smell ',\n    slug: 'code-smell-1',\n    objectID: '5fa7f4cac0d56c5ae62e3471',\n  },\n  {\n    name: 'Web Accessibility',\n    slug: 'web-accessibility',\n    objectID: '5f3f1dcc5b3ac8481821c47c',\n  },\n  {\n    name: '#growth',\n    slug: 'growth-1',\n    objectID: '5f21ee72ef20f63bcf98250b',\n  },\n  {\n    name: 'shopify',\n    slug: 'shopify',\n    objectID: '57d2f8b8739df23de32d9a0b',\n  },\n  {\n    name: 'dailydev',\n    slug: 'dailydev',\n    objectID: '5f4e6e6de613341d6f8cd33e',\n  },\n  {\n    name: 'expressjs',\n    slug: 'expressjs',\n    objectID: '56744721958ef13879b94d81',\n  },\n  {\n    name: 'fun',\n    slug: 'fun',\n    objectID: '56744723958ef13879b954b1',\n  },\n  {\n    name: 'android development',\n    slug: 'android-development',\n    objectID: '56744722958ef13879b95086',\n  },\n  {\n    name: 'DevBlogging',\n    slug: 'devblogging',\n    objectID: '5f323f334332ee07eb55c25e',\n  },\n  {\n    name: 'Scala',\n    slug: 'scala',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496318498/u1ogtyiakscd683ar63g.png',\n    objectID: '56744723958ef13879b952a7',\n  },\n  {\n    name: 'repository',\n    slug: 'repository',\n    objectID: '56744721958ef13879b94932',\n  },\n  {\n    name: 'Gulp',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1455107024/ymnvwdrzghdaupgnh1pa.png',\n    slug: 'gulp',\n    objectID: '56744723958ef13879b954b9',\n  },\n  {\n    name: 'CodePen',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1490300926/zpeedkxkcyorvxzepwdq.png',\n    slug: 'codepen',\n    objectID: '56744722958ef13879b94f3e',\n  },\n  {\n    name: 'front-end',\n    slug: 'front-end-cik5w32oi016zos53hitiymhh',\n    objectID: '56b118e610979efc2b9a8d91',\n  },\n  {\n    name: 'Salesforce',\n    slug: 'salesforce',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1542629160319/SkxND7eC7.jpeg',\n    objectID: '578d40c45460288cdeb6f094',\n  },\n  {\n    name: 'Auth ',\n    slug: 'auth',\n    objectID: '5762d998d163d06a3fca2d8d',\n  },\n  {\n    name: 'sorting',\n    slug: 'sorting',\n    objectID: '56e79a12c10bbcfb0ce541b1',\n  },\n  {\n    name: 'slack',\n    slug: 'slack',\n    objectID: '56744723958ef13879b952bc',\n  },\n  {\n    name: 'languages',\n    slug: 'languages',\n    objectID: '56744723958ef13879b95347',\n  },\n  {\n    name: 'Amazon',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1469216724/nxiuwpm6dybqbn9dhybc.png',\n    slug: 'amazon',\n    objectID: '56744721958ef13879b94906',\n  },\n  {\n    name: 'storage',\n    slug: 'storage',\n    objectID: '5708ff9c115103c3b09782d7',\n  },\n  {\n    name: 'algorithm',\n    slug: 'algorithm',\n    objectID: '56744721958ef13879b94de3',\n  },\n  {\n    name: 'pdf',\n    slug: 'pdf',\n    objectID: '57962622bdb2f5db657ae6c3',\n  },\n  {\n    name: 'fetch',\n    slug: 'fetch',\n    objectID: '5758618112a8cb07bb8426d2',\n  },\n  {\n    name: 'dependency injection',\n    slug: 'dependency-injection',\n    objectID: '56e6d5598c0bb8288a559c95',\n  },\n  {\n    name: 'template',\n    slug: 'template',\n    objectID: '56c4cd6eedfec14f66f81d98',\n  },\n  {\n    name: 'RxJS',\n    slug: 'rxjs',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1512113179321/HJXmNY0lf.jpeg',\n    objectID: '56744723958ef13879b95559',\n  },\n  {\n    name: 'WebAssembly',\n    slug: 'webassembly',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1510296821/n90fqabiufcs8kbridxm.png',\n    objectID: '56744722958ef13879b95043',\n  },\n  {\n    name: 'game',\n    slug: 'game',\n    objectID: '56744721958ef13879b9496d',\n  },\n  {\n    name: 'lambda',\n    slug: 'lambda',\n    objectID: '56744721958ef13879b94867',\n  },\n  {\n    name: 'JSX',\n    slug: 'jsx',\n    objectID: '577b65e0a1ac2f52aea75814',\n  },\n  {\n    name: 'GUI',\n    slug: 'gui',\n    objectID: '574dd005be8cff2ed6571a4f',\n  },\n  {\n    name: 'theme',\n    slug: 'theme',\n    objectID: '58e1a2b84200d85d6bfc1457',\n  },\n  {\n    name: 'routing',\n    slug: 'routing',\n    objectID: '56744721958ef13879b949fb',\n  },\n  {\n    name: 'Firefox',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1511214305890/HJ9ka6gxM.jpeg',\n    slug: 'firefox',\n    objectID: '56744721958ef13879b94929',\n  },\n  {\n    name: 'visual studio',\n    slug: 'visual-studio',\n    objectID: '56744723958ef13879b953df',\n  },\n  {\n    name: 'migration',\n    slug: 'migration',\n    objectID: '56744723958ef13879b9534f',\n  },\n  {\n    name: 'Foundation',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450470022/sfgwosxc2dgxo9yslapu.png',\n    slug: 'foundation',\n    objectID: '56744722958ef13879b94fc2',\n  },\n  {\n    name: 'LinkedIn',\n    slug: 'linkedin',\n    objectID: '575ebcbada600b8ef43e51c4',\n  },\n  {\n    name: 'planning',\n    slug: 'planning',\n    objectID: '57ed528897eba84632db5b88',\n  },\n  {\n    name: 'static',\n    slug: 'static',\n    objectID: '57cbff559b3eb82e014a0364',\n  },\n  {\n    name: 'Indie Maker',\n    slug: 'indie-maker',\n    objectID: '5f1edf42cf3e61138dbef956',\n  },\n  {\n    name: 'ThreeJS',\n    slug: 'threejs',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1520160600492/Bklw1UKOM.jpeg',\n    objectID: '571fa589cfc14de85d6aca42',\n  },\n  {\n    name: 'Yarn',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1477030779/nbhawthd7lervqjdiwrz.jpg',\n    slug: 'yarn',\n    objectID: '5801b9c24c0f5aee780a3883',\n  },\n  {\n    name: 'User Interface',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1462773835/uvhdwekyfkkh1tldkew7.jpg',\n    slug: 'user-interface',\n    objectID: '56744721958ef13879b94823',\n  },\n  {\n    name: 'fullstack',\n    slug: 'fullstack',\n    objectID: '56744721958ef13879b94a6c',\n  },\n  {\n    name: 'web performance',\n    slug: 'web-performance',\n    objectID: '56744721958ef13879b94950',\n  },\n  {\n    name: 'websockets',\n    slug: 'websockets',\n    objectID: '56744721958ef13879b94a0f',\n  },\n  {\n    name: 'SEO for Developers',\n    slug: 'seo-for-developers',\n    objectID: '5f58d1c9ffbb8f35dd030cdd',\n  },\n  {\n    name: 'graphic design',\n    slug: 'graphic-design',\n    objectID: '56ab4801960088c21db4d845',\n  },\n  {\n    name: 'bootstrap 4',\n    slug: 'bootstrap-4',\n    objectID: '56744723958ef13879b953a4',\n  },\n  {\n    name: 'push notifications',\n    slug: 'push-notifications',\n    objectID: '577d40e61e03c69a78fb0dac',\n  },\n  {\n    name: 'color',\n    slug: 'color',\n    objectID: '5774aa8157675ec2fcfd0744',\n  },\n  {\n    name: 'Scope',\n    slug: 'scope',\n    objectID: '56f16b6cea857e0c6af05a4c',\n  },\n  {\n    name: 'create-react-app',\n    slug: 'create-react-app',\n    objectID: '58ec8cb535aeeb5330e71961',\n  },\n  {\n    name: 'scalability',\n    slug: 'scalability',\n    objectID: '5691193ecad8946e563c56e9',\n  },\n  {\n    name: 'server hosting',\n    slug: 'server-hosting',\n    objectID: '56744723958ef13879b9553e',\n  },\n  {\n    name: 'login',\n    slug: 'login',\n    objectID: '56b45894500fd79e29bd7bf4',\n  },\n  {\n    name: 'Chat',\n    slug: 'chat',\n    objectID: '575e6494ed4fa39df4f9af08',\n  },\n  {\n    name: 'Culture',\n    slug: 'culture',\n    objectID: '568a70511f77b14a93d83737',\n  },\n  {\n    name: 'Recursion',\n    slug: 'recursion',\n    objectID: '56903d0e91716a2d1dbadbca',\n  },\n  {\n    name: 'cloudflare',\n    slug: 'cloudflare',\n    objectID: '56744720958ef13879b947e6',\n  },\n  {\n    name: 'whatsapp',\n    slug: 'whatsapp',\n    objectID: '5732da8af311f7ed13dddcb3',\n  },\n  {\n    name: 'Off Topic',\n    slug: 'off-topic',\n    objectID: '575ab7852f07b512c4dce46e',\n  },\n  {\n    name: 'passwords',\n    slug: 'passwords',\n    objectID: '578395f816a33191db0432f4',\n  },\n  {\n    name: 'map',\n    slug: 'map',\n    objectID: '56fd21cd770db0f14a63ee67',\n  },\n  {\n    slug: 'go-cjffccfnf0024tjs1mcwab09t',\n    objectID: '5abf7c154496b1f745e95fce',\n  },\n  {\n    name: 'Tailwind CSS Tutorial',\n    slug: 'tailwind-css-tutorial',\n    objectID: '5f76e2947d160d41227d65b9',\n  },\n  {\n    name: 'SQLite',\n    slug: 'sqlite',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516183050940/BJXG0cn4z.jpeg',\n    objectID: '56d9e25a4aa5f35f09dd6c98',\n  },\n  {\n    name: 'WebGL',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472831587/ajchcv7sjghl7p5k1tgm.jpg',\n    slug: 'webgl',\n    objectID: '56744721958ef13879b94a3f',\n  },\n  {\n    name: 'Phoenix framework',\n    slug: 'phoenix',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1522051540209/Hy20FXI5G.jpeg',\n    objectID: '56744721958ef13879b94abc',\n  },\n  {\n    name: 'magento 2',\n    slug: 'magento-2',\n    objectID: '587789c03c79514bec516060',\n  },\n  {\n    name: 'editors',\n    slug: 'editors',\n    objectID: '56744723958ef13879b95262',\n  },\n  {\n    name: 'google sheets',\n    slug: 'google-sheets',\n    objectID: '56e669b622f645300192ed17',\n  },\n  {\n    name: 'kafka',\n    slug: 'kafka',\n    objectID: '572527cf5ec4095ed6f48bf3',\n  },\n  {\n    name: 'Art',\n    slug: 'art',\n    objectID: '56efa81abcca2d711e191eb9',\n  },\n  {\n    name: 'generators',\n    slug: 'generators',\n    objectID: '56744722958ef13879b950b8',\n  },\n  {\n    name: 'Company',\n    slug: 'company',\n    objectID: '572ca231bf97af427dd07e6c',\n  },\n  {\n    name: 'console',\n    slug: 'console',\n    objectID: '56744723958ef13879b952e1',\n  },\n  {\n    name: 'Virtual Reality',\n    slug: 'virtual-reality',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1506666919/d9j2ku0cjlhrzrboojit.png',\n    objectID: '56c87d289b87edaf6e25f825',\n  },\n  {\n    name: 'apps',\n    slug: 'apps',\n    objectID: '56744721958ef13879b94ac2',\n  },\n  {\n    name: 'plugins',\n    slug: 'plugins',\n    objectID: '56744723958ef13879b95204',\n  },\n  {\n    name: 'terminal command',\n    slug: 'terminal-command',\n    objectID: '5f6afc44cbf0b22e6d444142',\n  },\n  {\n    name: 'arduino',\n    slug: 'arduino',\n    objectID: '56744722958ef13879b951db',\n  },\n  {\n    name: 'email marketing',\n    slug: 'email-marketing',\n    objectID: '57b76044a629e4147b4251d5',\n  },\n  {\n    name: 'project',\n    slug: 'project',\n    objectID: '56744721958ef13879b94aae',\n  },\n  {\n    name: '3d',\n    slug: '3d',\n    objectID: '56744721958ef13879b94ad9',\n  },\n  {\n    name: 'charts',\n    slug: 'charts',\n    objectID: '56744720958ef13879b947d1',\n  },\n  {\n    name: 'e-learning',\n    slug: 'e-learning',\n    objectID: '569c9b4c72ca04ea5d79fc6c',\n  },\n  {\n    name: 'browser',\n    slug: 'browser',\n    objectID: '56744721958ef13879b94a11',\n  },\n  {\n    name: 'snippets',\n    slug: 'snippets',\n    objectID: '56744721958ef13879b948ae',\n  },\n  {\n    name: 'Flux',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468113/cariy62rvjvlnz8ks7qw.png',\n    slug: 'flux',\n    objectID: '56744721958ef13879b94d46',\n  },\n  {\n    name: 'mac',\n    slug: 'mac',\n    objectID: '56744721958ef13879b94a22',\n  },\n  {\n    name: 'os',\n    slug: 'os',\n    objectID: '568f6e425e7a940b3d3e0a92',\n  },\n  {\n    name: 'integration',\n    slug: 'integration',\n    objectID: '57f58a9917809963610207dd',\n  },\n  {\n    name: 'logic',\n    slug: 'logic',\n    objectID: '57b23c4cab585a4d6c1529cd',\n  },\n  {\n    name: 'history',\n    slug: 'history',\n    objectID: '572706c827ca2053d6613898',\n  },\n  {\n    name: 'SOLID principles',\n    slug: 'solid-principles',\n    objectID: '5f4dd1ae6f2d7874d4060e9b',\n  },\n  {\n    name: 'Blogger',\n    slug: 'blogger-1',\n    objectID: '5f2a4ee0d7d55f162b5da120',\n  },\n  {\n    name: 'developer relations',\n    slug: 'developer-relations',\n    objectID: '56744723958ef13879b953b6',\n  },\n  {\n    name: 'Service Workers',\n    slug: 'service-workers',\n    objectID: '56a746ba6e715c3c7fc5b7ef',\n  },\n  {\n    name: 'iphone',\n    slug: 'iphone',\n    objectID: '56744722958ef13879b95166',\n  },\n  {\n    name: 'Parse',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1457160633/ybpgyd9fhrucyvgwda6a.png',\n    slug: 'parse',\n    objectID: '56744722958ef13879b94efb',\n  },\n  {\n    slug: 'sagar-jaybhay',\n    objectID: '5cd9909c45e85c572ab538f7',\n  },\n  {\n    name: 'design review',\n    slug: 'design-review',\n    objectID: '5fb179420f6d4f4d2f66a32a',\n  },\n  {\n    name: 'Career Coach',\n    slug: 'career-coach',\n    objectID: '5f0bc6ef3fe8405bdb8d80be',\n  },\n  {\n    name: 'hadoop',\n    slug: 'hadoop',\n    objectID: '56744720958ef13879b94799',\n  },\n  {\n    name: 'graph database',\n    slug: 'graph-database',\n    objectID: '58b96527be993da9e4853150',\n  },\n  {\n    name: 'continuous delivery',\n    slug: 'continuous-delivery',\n    objectID: '56744721958ef13879b949a3',\n  },\n  {\n    name: 'concurrency',\n    slug: 'concurrency',\n    objectID: '56744723958ef13879b95312',\n  },\n  {\n    name: 'compiler',\n    slug: 'compiler',\n    objectID: '58790ce83c79514bec51631b',\n  },\n  {\n    name: 'gsoc',\n    slug: 'gsoc',\n    objectID: '56744721958ef13879b94dea',\n  },\n  {\n    name: 'spa',\n    slug: 'spa',\n    objectID: '56744721958ef13879b94d40',\n  },\n  {\n    name: 'Collaboration',\n    slug: 'collaboration',\n    objectID: '57d0839fb64935c2e8fdba94',\n  },\n  {\n    name: 'Event Loop',\n    slug: 'event-loop',\n    objectID: '56f7b7c59cad82b1e979026a',\n  },\n  {\n    name: 'crud',\n    slug: 'crud',\n    objectID: '56f71ff1aa013a5f87413652',\n  },\n  {\n    name: 'Hoisting',\n    slug: 'hoisting',\n    objectID: '56db37c9e853431899d03773',\n  },\n  {\n    name: 'life-hack',\n    slug: 'life-hack',\n    objectID: '5f96548740346172a86c2be7',\n  },\n  {\n    name: 'mobile application design',\n    slug: 'mobile-application-design',\n    objectID: '56744723958ef13879b95516',\n  },\n  {\n    name: 'unix',\n    slug: 'unix',\n    objectID: '56744721958ef13879b94a53',\n  },\n  {\n    name: 'AdonisJS',\n    slug: 'adonisjs',\n    objectID: '5770f47198002dc2b990254a',\n  },\n  {\n    name: 'ecmascript6',\n    slug: 'ecmascript6',\n    objectID: '56744720958ef13879b947db',\n  },\n  {\n    name: 'stack',\n    slug: 'stack',\n    objectID: '56744723958ef13879b95368',\n  },\n  {\n    slug: 'cybersecurity',\n    objectID: '593a98f803de49038fb02fd4',\n  },\n  {\n    name: 'streaming',\n    slug: 'streaming',\n    objectID: '56744722958ef13879b9505d',\n  },\n  {\n    name: 'sysadmin',\n    slug: 'sysadmin',\n    objectID: '56744721958ef13879b94aa2',\n  },\n  {\n    name: 'build',\n    slug: 'build',\n    objectID: '56744723958ef13879b95552',\n  },\n  {\n    name: 'smart home',\n    slug: 'smart-home',\n    objectID: '590d86fe042257bf29db782c',\n  },\n  {\n    name: 'modules',\n    slug: 'modules',\n    objectID: '56744722958ef13879b95197',\n  },\n  {\n    name: 'CDN',\n    slug: 'cdn',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496755229/pmzin0lidq2ld88qeba5.png',\n    objectID: '56744720958ef13879b947ae',\n  },\n  {\n    name: '#the-technical-writing-bootcamp',\n    slug: 'the-technical-writing-bootcamp-1',\n    objectID: '5f732a92f955ec0a130f6290',\n  },\n  {\n    name: 'Sublime Text',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1497046439/xny5lu0xfjzpfybrrl9c.png',\n    slug: 'sublime-text',\n    objectID: '56744723958ef13879b95216',\n  },\n  {\n    name: 'Ember.js',\n    slug: 'emberjs',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1498115063/txor4lfourtkcofjipii.png',\n    objectID: '56744721958ef13879b94c17',\n  },\n  {\n    name: 'Vuex',\n    slug: 'vuex',\n    objectID: '580209af0c9f06220778a866',\n  },\n  {\n    name: 'wordpress plugins',\n    slug: 'wordpress-plugins',\n    objectID: '56744721958ef13879b94965',\n  },\n  {\n    name: 'zsh',\n    slug: 'zsh',\n    objectID: '56744723958ef13879b95202',\n  },\n  {\n    name: 'recruitment',\n    slug: 'recruitment',\n    objectID: '57b0b5a1fbdd622c03136428',\n  },\n  {\n    name: 'Server side rendering',\n    slug: 'server-side-rendering',\n    objectID: '5759222f462c2daddc9ac412',\n  },\n  {\n    name: 'Roadmap',\n    slug: 'roadmap',\n    objectID: '58cd6353557528fb61666e5d',\n  },\n  {\n    name: 'hashnodebootcamp2',\n    slug: 'hashnodebootcamp2-1',\n    objectID: '5faec06b7fcc8d387fc0d1a6',\n  },\n  {\n    name: 'Polymer',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450468312/zwtljjmofmpplvho1wfa.png',\n    slug: 'polymer',\n    objectID: '56744723958ef13879b954ab',\n  },\n  {\n    name: 'Expo',\n    slug: 'expo',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1515394575880/SkuMI5gEM.jpeg',\n    objectID: '58cb5f69ecb020d9744a6487',\n  },\n  {\n    name: 'xml',\n    slug: 'xml',\n    objectID: '56744721958ef13879b94b0b',\n  },\n  {\n    name: 'tooling',\n    slug: 'tooling',\n    objectID: '56744723958ef13879b95335',\n  },\n  {\n    name: 'canvas',\n    slug: 'canvas',\n    objectID: '56744722958ef13879b94f55',\n  },\n  {\n    name: 'Backup',\n    slug: 'backup',\n    objectID: '57df9e894a6aa43e72a98a15',\n  },\n  {\n    name: 'Explain like I am five',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516175943338/SJ1IzFnVf.jpeg',\n    slug: 'explain-like-i-am-five',\n    objectID: '5991e91f0bcf15061f140b7f',\n  },\n  {\n    name: 'embedded',\n    slug: 'embedded',\n    objectID: '571eb24785916079574f035e',\n  },\n  {\n    name: 'bots',\n    slug: 'bots',\n    objectID: '56f2726a35c92c494c5e3a73',\n  },\n  {\n    name: 'Homebrew',\n    slug: 'homebrew',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502355995/yusx5q732shmiaypoq3f.png',\n    objectID: '56744722958ef13879b951e9',\n  },\n  {\n    name: 'webdesign',\n    slug: 'webdesign',\n    objectID: '56744721958ef13879b949ec',\n  },\n  {\n    name: 'styling',\n    slug: 'styling',\n    objectID: '580515064c0f5aee780a3c9b',\n  },\n  {\n    name: 'Mozilla',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1477481389/i3tfvov4fuqfkg2yeddv.png',\n    slug: 'mozilla',\n    objectID: '56744721958ef13879b94c4f',\n  },\n  {\n    name: 'javascript books',\n    slug: 'javascript-books',\n    objectID: '56744723958ef13879b953fa',\n  },\n  {\n    name: 'Atom',\n    slug: 'atom',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1497040963/kh7an2akihm9tf1w5ab2.png',\n    objectID: '56744721958ef13879b94aa6',\n  },\n  {\n    name: 'dev',\n    slug: 'dev',\n    objectID: '56744721958ef13879b948bc',\n  },\n  {\n    name: 'Best of Hashnode',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1556009843016/9mcKMnTI3.png',\n    slug: 'best-of-hashnode',\n    objectID: '5c0c2ed6659f658d077550cf',\n  },\n  {\n    name: 'Stack Overflow',\n    slug: 'stackoverflow',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1499949492/jff8br3fbln1yccb1tpb.png',\n    objectID: '56744721958ef13879b949d7',\n  },\n  {\n    name: 'progressive web apps',\n    slug: 'progressive-web-apps',\n    objectID: '5702d00aabbcb496574bce11',\n  },\n  {\n    name: 'animations',\n    slug: 'animations',\n    objectID: '56744721958ef13879b948b4',\n  },\n  {\n    name: 'translation',\n    slug: 'translation',\n    objectID: '576ccb742d4c0ff55a8ae17a',\n  },\n  {\n    name: 'desktop',\n    slug: 'desktop',\n    objectID: '56744721958ef13879b948ce',\n  },\n  {\n    name: 'habits',\n    slug: 'habits',\n    objectID: '57e22bd48b1fca72b28833a4',\n  },\n  {\n    name: '#codingNewbies',\n    slug: 'codingnewbies',\n    objectID: '5f7f9e43b8638504a7c122ed',\n  },\n  {\n    name: 'google maps',\n    slug: 'google-maps',\n    objectID: '57496c3892b151fb90adc735',\n  },\n  {\n    name: 'back4app',\n    slug: 'back4app',\n    objectID: '578bf0674416601b9574cb3b',\n  },\n  {\n    name: 'Libraries',\n    slug: 'libraries',\n    objectID: '568ecddf91716a2d1dbadb19',\n  },\n  {\n    name: 'prototyping',\n    slug: 'prototyping',\n    objectID: '56744723958ef13879b95241',\n  },\n  {\n    name: 'Real Estate',\n    slug: 'real-estate',\n    objectID: '56ee695b5edec9d7189a0be5',\n  },\n  {\n    name: 'cache',\n    slug: 'cache',\n    objectID: '567bfb342b926c3063c307dc',\n  },\n  {\n    name: 'teaching',\n    slug: 'teaching',\n    objectID: '56744723958ef13879b955b7',\n  },\n  {\n    name: 'multithreading',\n    slug: 'multithreading',\n    objectID: '56744723958ef13879b95300',\n  },\n  {\n    name: 'opinion pieces',\n    slug: 'opinion-pieces',\n    objectID: '5f0ffe5eaa660c1c354c06fc',\n  },\n  {\n    name: '.net core',\n    slug: 'net-core',\n    objectID: '57d7d0d0f72dd3705c16014a',\n  },\n  {\n    name: 'freelance',\n    slug: 'freelance',\n    objectID: '56744722958ef13879b94e57',\n  },\n  {\n    name: 'deployment automation',\n    slug: 'deployment-automation',\n    objectID: '56744722958ef13879b95067',\n  },\n  {\n    name: 'icon',\n    slug: 'icon',\n    objectID: '56744723958ef13879b95289',\n  },\n  {\n    name: 'Hashing',\n    slug: 'hashing',\n    objectID: '591fd9bfe1cc498f829bf264',\n  },\n  {\n    name: 'boilerplate',\n    slug: 'boilerplate',\n    objectID: '56744723958ef13879b953b2',\n  },\n  {\n    name: 'navigation',\n    slug: 'navigation',\n    objectID: '574125dadf1e4d3563843066',\n  },\n  {\n    name: 'Geospatial',\n    slug: 'geospatial',\n    objectID: '5f25726a90ac4260edf35078',\n  },\n  {\n    name: 'angular material',\n    slug: 'angular-material',\n    objectID: '57c3ba45cb80370904fc5b48',\n  },\n  {\n    name: 'ios apps',\n    slug: 'ios-apps',\n    objectID: '56744721958ef13879b94ae2',\n  },\n  {\n    name: 'wordpress themes',\n    slug: 'wordpress-themes',\n    objectID: '56744721958ef13879b94af8',\n  },\n  {\n    name: 'k8s',\n    slug: 'k8s',\n    objectID: '58456f2afc2da7579e5f3ed0',\n  },\n  {\n    name: 'Hugo',\n    slug: 'hugo',\n    objectID: '57ce27e495368c463b09804f',\n  },\n  {\n    name: 'a11y',\n    slug: 'a11y',\n    objectID: '57aa00d170387a4ab0fe0cf8',\n  },\n  {\n    name: 'webapps',\n    slug: 'webapps',\n    objectID: '56744721958ef13879b94b6f',\n  },\n  {\n    name: 'features',\n    slug: 'features',\n    objectID: '56744722958ef13879b9515c',\n  },\n  {\n    name: 'Prettier',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496483511/fdbnmvy2bkecx03csbom.png',\n    slug: 'prettier',\n    objectID: '592d689fa6614cba3f738146',\n  },\n  {\n    name: 'WebRTC',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1465362672/yzdode4h9er49uccfvbu.png',\n    slug: 'webrtc',\n    objectID: '56744722958ef13879b94f0e',\n  },\n  {\n    name: 'web developers',\n    slug: 'web-developers',\n    objectID: '56744722958ef13879b94e6b',\n  },\n  {\n    name: 'Emails',\n    slug: 'emails',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496150225/u6kgjtvvqkkefncoyovl.png',\n    objectID: '57458b6c92b151fb90adc493',\n  },\n  {\n    name: 'bundling',\n    slug: 'bundling',\n    objectID: '5777dbd757675ec2fcfd09fb',\n  },\n  {\n    name: 'localstorage',\n    slug: 'localstorage',\n    objectID: '56744722958ef13879b95107',\n  },\n  {\n    name: 'Earth Engine',\n    slug: 'earth-engine',\n    objectID: '5f26246490ac4260edf3596e',\n  },\n  {\n    name: 'test driven development',\n    slug: 'test-driven-development',\n    objectID: '56744723958ef13879b95595',\n  },\n  {\n    name: 'S3',\n    slug: 's3',\n    objectID: '588f13c9ae0398620533ed80',\n  },\n  {\n    name: 'message queue',\n    slug: 'message-queue',\n    objectID: '5688d3a00716b983ccc79766',\n  },\n  {\n    name: 'mentor',\n    slug: 'mentor',\n    objectID: '56744721958ef13879b94dc8',\n  },\n  {\n    name: 'websites',\n    slug: 'websites',\n    objectID: '56744721958ef13879b94c58',\n  },\n  {\n    name: 'maven',\n    slug: 'maven',\n    objectID: '56744723958ef13879b95232',\n  },\n  {\n    name: 'turkish',\n    slug: 'turkish',\n    objectID: '5f61e4c5dc74720d9b85ed19',\n  },\n  {\n    name: 'MEAN Stack',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472484615/gnwpbhw8nqe9aj4frzzh.jpg',\n    slug: 'mean',\n    objectID: '56744721958ef13879b94bc0',\n  },\n  {\n    name: 'Emacs',\n    slug: 'emacs',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502978326/qzxw4oqebc9su0pzpvqt.png',\n    objectID: '56744721958ef13879b949cf',\n  },\n  {\n    name: 'Preact',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1459503980/i2tjk2olam4wqr7kyqet.jpg',\n    slug: 'preact',\n    objectID: '56fe1c265db965849f7b379f',\n  },\n  {\n    name: 'Future',\n    slug: 'future',\n    objectID: '5699066c72ca04ea5d79faa1',\n  },\n  {\n    name: 'es2015',\n    slug: 'es2015',\n    objectID: '5678d29ae0956f4764b3edfb',\n  },\n  {\n    name: 'sales',\n    slug: 'sales',\n    objectID: '58cd06ec68e963fa61d68d7f',\n  },\n  {\n    name: 'versioning',\n    slug: 'versioning',\n    objectID: '578b9582b1a4a0d81ffbb1fe',\n  },\n  {\n    name: 'computer',\n    slug: 'computer',\n    objectID: '57628dcd820dd45f3fbd8eb6',\n  },\n  {\n    name: 'cookies',\n    slug: 'cookies',\n    objectID: '56744721958ef13879b94a7d',\n  },\n  {\n    name: 'proxy',\n    slug: 'proxy',\n    objectID: '56744721958ef13879b94917',\n  },\n  {\n    name: 'Drupal',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1490298700/uqjjgtu4a1lpqxjcdshb.png',\n    slug: 'drupal',\n    objectID: '57444da29ade925885158cb0',\n  },\n  {\n    name: 'graphics',\n    slug: 'graphics',\n    objectID: '578378ebfcb4d586db19492c',\n  },\n  {\n    name: 'Scraping',\n    slug: 'scraping',\n    objectID: '5834805addfa96eb7c5d478b',\n  },\n  {\n    name: 'typography',\n    slug: 'typography',\n    objectID: '56744721958ef13879b94944',\n  },\n  {\n    name: 'marketplace',\n    slug: 'marketplace',\n    objectID: '586d0df986a586aec93327e1',\n  },\n  {\n    name: 'OOPS',\n    slug: 'oops',\n    objectID: '5713f234162bdaad9f92b0c1',\n  },\n  {\n    name: 'production',\n    slug: 'production',\n    objectID: '57067a5e115103c3b097818b',\n  },\n  {\n    name: 'process',\n    slug: 'process',\n    objectID: '5694af13c1c0117cef5aea67',\n  },\n  {\n    name: 'API basics ',\n    slug: 'api-basics',\n    objectID: '5f8dd8dffc30613d8cd9379a',\n  },\n  {\n    name: 'PaaS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1461694808/ip8ls4fz7nxi01uhvmch.jpg',\n    slug: 'paas',\n    objectID: '56744721958ef13879b94ddc',\n  },\n  {\n    name: 'Website design',\n    slug: 'website-design',\n    objectID: '5866a4c0b99398bc30c43daa',\n  },\n  {\n    name: 'SSR',\n    slug: 'ssr',\n    objectID: '5747fbbd9ade925885158f94',\n  },\n  {\n    name: 'i18n',\n    slug: 'i18n',\n    objectID: '568f1af6525da8063d08fb2d',\n  },\n  {\n    name: 'ci',\n    slug: 'ci',\n    objectID: '56744721958ef13879b94a16',\n  },\n  {\n    name: 'centos',\n    slug: 'centos',\n    objectID: '57a67d66e6998a66b06f40e6',\n  },\n  {\n    name: 'social',\n    slug: 'social',\n    objectID: '5709b8c3115103c3b0978327',\n  },\n  {\n    slug: 'go-cjidm6n1p00lpq9s29dy2bsiq',\n    objectID: '5b218969e0d20c016e052f69',\n  },\n  {\n    name: 'patterns',\n    slug: 'patterns',\n    objectID: '56744721958ef13879b94db8',\n  },\n  {\n    name: 'workathome',\n    slug: 'workathome',\n    objectID: '5f19d647cef915427a14ca2c',\n  },\n  {\n    name: 'selenium-webdriver',\n    slug: 'selenium-webdriver-1',\n    objectID: '5f0c3b23880268625262ba76',\n  },\n  {\n    name: 'macbook',\n    slug: 'macbook',\n    objectID: '56744721958ef13879b94dc2',\n  },\n  {\n    name: 'Voice',\n    slug: 'voice',\n    objectID: '590102fd9863a67f4cc93055',\n  },\n  {\n    name: 'orm',\n    slug: 'orm',\n    objectID: '56b632b3a0967efc587c7d24',\n  },\n  {\n    name: 'Bitbucket',\n    slug: 'bitbucket',\n    objectID: '580e08175fec191d85b14fc7',\n  },\n  {\n    name: 'dashboard',\n    slug: 'dashboard',\n    objectID: '56b45894500fd79e29bd7bf3',\n  },\n  {\n    name: 'composer',\n    slug: 'composer',\n    objectID: '56b234f2a71b2df12bea6e43',\n  },\n  {\n    name: 'Remote Sensing ',\n    slug: 'remote-sensing',\n    objectID: '5f25726a90ac4260edf35077',\n  },\n  {\n    name: 'ELM',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1491295764/mh4haipogztgffbnt4y4.png',\n    slug: 'elm',\n    objectID: '567bbdf52b926c3063c30713',\n  },\n  {\n    name: 'spark',\n    slug: 'spark',\n    objectID: '56744722958ef13879b95180',\n  },\n  {\n    name: 'ionic framework',\n    slug: 'ionic-framework',\n    objectID: '56744723958ef13879b95254',\n  },\n  {\n    name: 'robotics',\n    slug: 'robotics',\n    objectID: '56744723958ef13879b953a2',\n  },\n  {\n    name: 'twilio',\n    slug: 'twilio',\n    objectID: '57e57691ef99cf03582fe2b3',\n  },\n  {\n    name: 'mvp',\n    slug: 'mvp',\n    objectID: '56744723958ef13879b95572',\n  },\n  {\n    name: 'medium',\n    slug: 'medium',\n    objectID: '56744721958ef13879b94871',\n  },\n  {\n    slug: 'devjourney',\n    objectID: '5e43fc8b8c89a92316ccd6c2',\n  },\n  {\n    name: 'azure certified',\n    slug: 'azure-certified',\n    objectID: '5f28ea6e3e336e0de23093c0',\n  },\n  {\n    name: 'PostCSS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1459504796/nipxkl4fu2zf7sqfl5fj.jpg',\n    slug: 'postcss',\n    objectID: '56744721958ef13879b94e29',\n  },\n  {\n    name: 'AR',\n    slug: 'ar',\n    objectID: '586cd5ae615b9737b81b3ddb',\n  },\n  {\n    name: 'photoshop',\n    slug: 'photoshop',\n    objectID: '5674471d958ef13879b94796',\n  },\n  {\n    name: 'crm',\n    slug: 'crm',\n    objectID: '580df8332a45c6fdcb43fa14',\n  },\n  {\n    name: 'funny',\n    slug: 'funny',\n    objectID: '56744723958ef13879b9547b',\n  },\n  {\n    name: 'Frontend frameworks',\n    slug: 'frontend-frameworks',\n    objectID: '56a0676792921b8f79d360f5',\n  },\n  {\n    name: 'technology stack',\n    slug: 'technology-stack',\n    objectID: '56b99e6cacee1cee848702ec',\n  },\n  {\n    name: 'jekyll',\n    slug: 'jekyll',\n    objectID: '56744721958ef13879b948e8',\n  },\n  {\n    name: 'cloudinary',\n    slug: 'cloudinary',\n    objectID: '5678a007e0956f4764b3ed53',\n  },\n  {\n    name: 'queue',\n    slug: 'queue',\n    objectID: '56744723958ef13879b952c0',\n  },\n  {\n    name: 'sdk',\n    slug: 'sdk',\n    objectID: '56f972afea33a5b266f2fe04',\n  },\n  {\n    name: 'styleguide',\n    slug: 'styleguide',\n    objectID: '56744722958ef13879b951a4',\n  },\n  {\n    name: 'Meta',\n    slug: 'meta',\n    objectID: '58b6c12eb2566b537ac16cb7',\n  },\n  {\n    name: 'CORS',\n    slug: 'cors',\n    objectID: '5676154ae64b075af6ade54e',\n  },\n  {\n    name: 'props',\n    slug: 'props',\n    objectID: '5f2959166face9141b78fa82',\n  },\n  {\n    name: 'Aurelia',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1453819641/j5c2dwhqwvzh9apczioe.jpg',\n    slug: 'aurelia',\n    objectID: '56744722958ef13879b94f49',\n  },\n  {\n    name: 'YAML',\n    slug: 'yaml',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1499159858/ude93xlquvvbxbw5xkg4.png',\n    objectID: '56d9941a489cf60d99aa90c4',\n  },\n  {\n    name: 'EQCSS',\n    slug: 'eqcss',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1520491825399/HJF4pLCdz.png',\n    objectID: '5784baeefcb4d586db194a64',\n  },\n  {\n    name: 'layout',\n    slug: 'layout',\n    objectID: '56d2f72f1878dfef04178e6e',\n  },\n  {\n    name: 'flow',\n    slug: 'flow',\n    objectID: '56744721958ef13879b94a2e',\n  },\n  {\n    name: 'admin',\n    slug: 'admin',\n    objectID: '57778738f271844db9e1eb41',\n  },\n  {\n    name: 'tech',\n    slug: 'tech-cilba77mg0010ya53d05qtkuu',\n    objectID: '56d7498b6722ee828dbeafe3',\n  },\n  {\n    name: 'Cordova',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1520160966692/ByyCeLt_M.jpeg',\n    slug: 'cordova',\n    objectID: '56744721958ef13879b94a1b',\n  },\n  {\n    name: 'Build tool',\n    slug: 'build-tool',\n    objectID: '56744722958ef13879b950a3',\n  },\n  {\n    name: 'vps',\n    slug: 'vps',\n    objectID: '56744722958ef13879b951c4',\n  },\n  {\n    name: 'gradle',\n    slug: 'gradle',\n    objectID: '56744722958ef13879b95164',\n  },\n  {\n    name: 'ebook',\n    slug: 'ebook',\n    objectID: '56744721958ef13879b948f0',\n  },\n  {\n    slug: 'hooks',\n    objectID: '5c1778c2252f6d5b707ae169',\n  },\n  {\n    name: 'gmail',\n    slug: 'gmail',\n    objectID: '58596eaaeb509c3ba23d4c87',\n  },\n  {\n    name: 'inheritance',\n    slug: 'inheritance',\n    objectID: '573349a7181d813d33746639',\n  },\n  {\n    name: 'stripe',\n    slug: 'stripe',\n    objectID: '56744723958ef13879b9554c',\n  },\n  {\n    name: '#sucessful blogging',\n    slug: 'sucessful-blogging',\n    objectID: '5fb801781b7ab0041800c67c',\n  },\n  {\n    name: 'watercooler',\n    slug: 'watercooler',\n    objectID: '5f36e920877a013acb03cd10',\n  },\n  {\n    name: 'eloquent',\n    slug: 'eloquent',\n    objectID: '56ed7b765edec9d7189a0b73',\n  },\n  {\n    name: 'image',\n    slug: 'image',\n    objectID: '56744721958ef13879b948fc',\n  },\n  {\n    name: 'book',\n    slug: 'book',\n    objectID: '56744720958ef13879b947b2',\n  },\n  {\n    name: 'router',\n    slug: 'router',\n    objectID: '56744723958ef13879b95210',\n  },\n  {\n    name: '#ChooseToChallenge',\n    slug: 'choosetochallenge',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1614605641484/1qeXO9QXg.png',\n    objectID: '603cc4b61f91337d465bee68',\n  },\n  {\n    name: 'geemap',\n    slug: 'geemap',\n    objectID: '5f465bac9b597625e2dec06a',\n  },\n  {\n    name: 'ASP',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1451108441/qt4zgtcynwzy2rjvk0t6.png',\n    slug: 'asp',\n    objectID: '5674471d958ef13879b9477c',\n  },\n  {\n    name: 'front end',\n    slug: 'front-end',\n    objectID: '56744723958ef13879b95554',\n  },\n  {\n    name: 'SVG Animation',\n    slug: 'svg-animation',\n    objectID: '569cd00972ca04ea5d79fca2',\n  },\n  {\n    name: 'meteorjs',\n    slug: 'meteorjs',\n    objectID: '56744723958ef13879b9558f',\n  },\n  {\n    name: 'nest',\n    slug: 'nest',\n    objectID: '583ca6c6ddfa96eb7c5d896f',\n  },\n  {\n    name: 'podcasts',\n    slug: 'podcasts',\n    objectID: '56744722958ef13879b95194',\n  },\n  {\n    name: 'designing',\n    slug: 'designing',\n    objectID: '56744721958ef13879b94bd9',\n  },\n  {\n    name: 'Clerk.dev',\n    slug: 'clerkdev',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1625245518596/nW4Y4hYHH.png',\n    objectID: '60df384f03707d644a4feb38',\n  },\n  {\n    name: 'web servers',\n    slug: 'web-servers',\n    objectID: '56744721958ef13879b94a88',\n  },\n  {\n    name: 'function',\n    slug: 'function',\n    objectID: '56744720958ef13879b947ea',\n  },\n  {\n    name: 'DraftJS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1491387822/zwctxp8006exywfg17pd.jpg',\n    slug: 'draftjs',\n    objectID: '56f4d674990bca7c25e99318',\n  },\n  {\n    name: 'redux-saga',\n    slug: 'redux-saga',\n    objectID: '5776f09cf271844db9e1eb05',\n  },\n  {\n    name: 'responsive designs',\n    slug: 'responsive-designs',\n    objectID: '56744721958ef13879b94d5b',\n  },\n  {\n    name: 'Socket.io',\n    slug: 'socketio-cijy9e2c700c6vm5357q8xsf3',\n    objectID: '56aa0ea0960088c21db4d77a',\n  },\n  {\n    name: 'OSS',\n    slug: 'oss',\n    objectID: '581875942ca37f164781f4b1',\n  },\n  {\n    name: 'chartjs',\n    slug: 'chartjs',\n    objectID: '56744721958ef13879b94993',\n  },\n  {\n    slug: 'deno',\n    objectID: '5cca9dd21077bc6278d31cc7',\n  },\n  {\n    slug: 'cisco',\n    objectID: '5d9cc879f74b4d4660eede6b',\n  },\n  {\n    name: 'emoji',\n    slug: 'emoji',\n    objectID: '571751b03c2a84abc85a1e11',\n  },\n  {\n    name: 'await',\n    slug: 'await',\n    objectID: '56cbdb23b70682283f9edeb7',\n  },\n  {\n    name: 'hibernate',\n    slug: 'hibernate',\n    objectID: '56744723958ef13879b955ac',\n  },\n  {\n    name: 'Julia',\n    slug: 'julia',\n    objectID: '58749cfee6e8728a7f133535',\n  },\n  {\n    name: 'vagrant',\n    slug: 'vagrant',\n    objectID: '56744721958ef13879b94a24',\n  },\n  {\n    name: 'grid',\n    slug: 'grid',\n    objectID: '56744723958ef13879b952d3',\n  },\n  {\n    name: 'naming',\n    slug: 'naming',\n    objectID: '5747655e92b151fb90adc622',\n  },\n  {\n    name: 'error',\n    slug: 'error',\n    objectID: '56744721958ef13879b9496b',\n  },\n  {\n    name: 'templates',\n    slug: 'templates',\n    objectID: '56744721958ef13879b94853',\n  },\n  {\n    name: 'design and architecture',\n    slug: 'design-and-architecture',\n    objectID: '5f38bd060801bf3f76e5f9e5',\n  },\n  {\n    name: 'Haskell',\n    slug: 'haskell',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496182720/z8gaemi99htfdnclmicj.png',\n    objectID: '56744723958ef13879b9537a',\n  },\n  {\n    name: 'PayPal',\n    slug: 'paypal',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1504679615/ds7ftsav58hjetqeeqq3.jpg',\n    objectID: '56ee65cfcb06805ba9b7c66d',\n  },\n  {\n    name: 'native',\n    slug: 'native',\n    objectID: '56744723958ef13879b9530a',\n  },\n  {\n    name: 'maps',\n    slug: 'maps',\n    objectID: '574853c092b151fb90adc6b1',\n  },\n  {\n    name: 'class',\n    slug: 'class',\n    objectID: '573c6a7803e642f04bb03d47',\n  },\n  {\n    name: 'mobile application development',\n    slug: 'mobile-application-development',\n    objectID: '56744721958ef13879b949b7',\n  },\n  {\n    name: 'The Clerk Hackathon on Hashnode',\n    slug: 'clerkhackathon',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1625245553278/S6gbVfdNp.png',\n    objectID: '60df381403707d644a4feb2f',\n  },\n  {\n    name: 'web3',\n    slug: 'web3',\n    logo: null,\n    objectID: '59df443dfb1deef9745a4ef0',\n  },\n  {\n    slug: 'wsl',\n    objectID: '595ed5ae8f1dffe434c00000',\n  },\n  {\n    name: 'geolocation',\n    slug: 'geolocation',\n    objectID: '579f2a6bb5724a7273404206',\n  },\n  {\n    name: 'coroutines',\n    slug: 'coroutines',\n    objectID: '56facb5fbac95334fc2fa50b',\n  },\n  {\n    name: 'object',\n    slug: 'object',\n    objectID: '56744722958ef13879b9505b',\n  },\n  {\n    name: 'debug',\n    slug: 'debug',\n    objectID: '56744721958ef13879b94922',\n  },\n  {\n    name: 'freelancer',\n    slug: 'freelancer',\n    objectID: '56744723958ef13879b9550a',\n  },\n  {\n    name: 'Cosmic JS',\n    slug: 'cosmic-js',\n    objectID: '590743c50e14932382c2ad5a',\n  },\n  {\n    name: 'WhoIsHiring',\n    slug: 'whoishiring',\n    objectID: '5d946e4ec510092a323bc34a',\n  },\n  {\n    name: 'ide',\n    slug: 'ide',\n    objectID: '56744721958ef13879b94879',\n  },\n  {\n    name: 'pair programming',\n    slug: 'pair-programming',\n    objectID: '56744722958ef13879b95071',\n  },\n  {\n    slug: 'health-cjaeh844x02vvo3wtj5r2s75q',\n    objectID: '5a189c9fee67ea9312f02c18',\n  },\n  {\n    name: 'code smell',\n    slug: 'code-smell',\n    objectID: '57361d1cffaaff8febd12cee',\n  },\n  {\n    name: 'V8',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1450536374/hnlihf5tv3veoxx1igpa.jpg',\n    slug: 'v8',\n    objectID: '56744723958ef13879b954f0',\n  },\n  {\n    name: 'Erlang',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475321105/tp4co0lnolmi4x7ln7f6.jpg',\n    slug: 'erlang',\n    objectID: '56744722958ef13879b94e60',\n  },\n  {\n    name: 'Clojure',\n    slug: 'clojure',\n    objectID: '56b01bce0a7ca0c6f70c1ef8',\n  },\n  {\n    name: 'rabbitmq',\n    slug: 'rabbitmq',\n    objectID: '56a4fc8ec84f2c6913b8e9f9',\n  },\n  {\n    name: 'Sketch',\n    slug: 'sketch',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1522266937699/ByGS7dtcG.jpeg',\n    objectID: '56744722958ef13879b94e7d',\n  },\n  {\n    name: 'backend as a service',\n    slug: 'backend-as-a-service',\n    objectID: '577f08da16a33191db042f9e',\n  },\n  {\n    name: 'mocha',\n    slug: 'mocha',\n    objectID: '56744721958ef13879b94a3a',\n  },\n  {\n    name: 'stream',\n    slug: 'stream',\n    objectID: '56744723958ef13879b95580',\n  },\n  {\n    name: 'container',\n    slug: 'container',\n    objectID: '56744721958ef13879b94ad6',\n  },\n  {\n    name: 'woocommerce',\n    slug: 'woocommerce',\n    objectID: '56744720958ef13879b94808',\n  },\n  {\n    name: 'webperf',\n    slug: 'webperf-ciur6tor503mfpx53ic2rvrs2',\n    objectID: '5810e609a901f605c438b691',\n  },\n  {\n    name: 'form',\n    slug: 'form',\n    objectID: '56744722958ef13879b95138',\n  },\n  {\n    name: '#⛺the-technical-writing-bootcamp',\n    slug: 'the-technical-writing-bootcamp',\n    objectID: '5f6d12a1005ded5336f6f534',\n  },\n  {\n    name: 'HTML Emails',\n    slug: 'html-emails',\n    objectID: '56a1b72a72ca04ea5d7a003b',\n  },\n  {\n    name: 'PHPUnit',\n    slug: 'phpunit',\n    objectID: '57ea3f2397eba84632db561a',\n  },\n  {\n    name: 'http2',\n    slug: 'http2',\n    objectID: '56744721958ef13879b94a76',\n  },\n  {\n    name: 'kibana',\n    slug: 'kibana',\n    objectID: '56744721958ef13879b9486d',\n  },\n  {\n    name: 'osx',\n    slug: 'osx',\n    objectID: '56744723958ef13879b9523e',\n  },\n  {\n    name: 'ghost',\n    slug: 'ghost',\n    objectID: '56744722958ef13879b951c6',\n  },\n  {\n    name: 'hybrid apps',\n    slug: 'hybrid-apps',\n    objectID: '56744721958ef13879b94e08',\n  },\n  {\n    name: 'virtual dom',\n    slug: 'virtual-dom',\n    objectID: '56744720958ef13879b947fc',\n  },\n  {\n    name: 'editor',\n    slug: 'editor',\n    objectID: '5674471d958ef13879b94781',\n  },\n  {\n    name: 'Session',\n    slug: 'session',\n    objectID: '57c8241860189c8953a67f81',\n  },\n  {\n    name: 'parse server',\n    slug: 'parse-server',\n    objectID: '578ae4e4b1a4a0d81ffbb1bb',\n  },\n  {\n    slug: 'tailwind',\n    objectID: '5ddd484e94c050e177a6aa7e',\n  },\n  {\n    name: 'mongo',\n    slug: 'mongo',\n    objectID: '56744721958ef13879b94a93',\n  },\n  {\n    name: 'what successful blogging means to me',\n    slug: 'what-successful-blogging-means-to-me',\n    objectID: '5faff31939a1f54636490632',\n  },\n  {\n    name: 'windows server',\n    slug: 'windows-server',\n    objectID: '5f1dd296f4016901885ccbf8',\n  },\n  {\n    name: 'Objective C',\n    slug: 'objective-c',\n    objectID: '56744721958ef13879b94bfe',\n  },\n  {\n    name: 'vr',\n    slug: 'vr',\n    objectID: '5674d5807446b75bb60141f8',\n  },\n  {\n    name: 'microsoft edge',\n    slug: 'microsoft-edge',\n    objectID: '56744720958ef13879b9480c',\n  },\n  {\n    name: 'zurb',\n    slug: 'zurb',\n    objectID: '56744721958ef13879b94a36',\n  },\n  {\n    name: 'promise',\n    slug: 'promise',\n    objectID: '56744721958ef13879b9488b',\n  },\n  {\n    slug: 'growth',\n    objectID: '5a64fbe6e30c5b6655a6a4df',\n  },\n  {\n    name: 'Meetup',\n    slug: 'meetup',\n    objectID: '56d9b1b0e853431899d036ce',\n  },\n  {\n    name: 'modal',\n    slug: 'modal',\n    objectID: '56ace1e6cc975f0cc6878bc0',\n  },\n  {\n    name: 'Benchmark',\n    slug: 'benchmark',\n    objectID: '5680fde5aeae5c9e229cf8e1',\n  },\n  {\n    name: 'Lua',\n    slug: 'lua',\n    objectID: '5726e4fac1f71f91e880ad2b',\n  },\n  {\n    name: 'perl',\n    slug: 'perl',\n    objectID: '56744722958ef13879b9512e',\n  },\n  {\n    name: 'postgres',\n    slug: 'postgres',\n    objectID: '56744722958ef13879b94f0b',\n  },\n  {\n    name: 'Element Queries',\n    slug: 'element-queries',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1498763362/lvxxrbdpyjwm1c8pxjck.png',\n    objectID: '581a55d4c055bbfb46d880da',\n  },\n  {\n    name: 'logstash',\n    slug: 'logstash',\n    objectID: '56744723958ef13879b953c3',\n  },\n  {\n    name: 'FaaS',\n    slug: 'faas',\n    objectID: '58cbe70848830eae2c11fdf4',\n  },\n  {\n    name: 'laravel ',\n    slug: 'laravel-cikr40o0m01r27453d8eux03p',\n    objectID: '56c4ad109c7666b0da73f29d',\n  },\n  {\n    name: 'immutable',\n    slug: 'immutable',\n    objectID: '56744722958ef13879b9514a',\n  },\n  {\n    slug: 'pmlcourse',\n    objectID: '5e4a6b728c89a92316cd4a33',\n  },\n  {\n    name: 'alternative',\n    slug: 'alternative',\n    objectID: '58085c202a45c6fdcb43f3c3',\n  },\n  {\n    name: 'Smalltalk',\n    slug: 'smalltalk',\n    objectID: '57da642fd17cab545caba0d3',\n  },\n  {\n    name: 'cpu',\n    slug: 'cpu',\n    objectID: '57ae11c08dae0c2f1d4420cb',\n  },\n  {\n    name: 'survey',\n    slug: 'survey',\n    objectID: '56744721958ef13879b949c2',\n  },\n  {\n    name: 'Cassandra',\n    slug: 'cassandra',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516175375653/BJdMlF34z.jpeg',\n    objectID: '56744721958ef13879b9490e',\n  },\n  {\n    name: 'css3 animation',\n    slug: 'css3-animation',\n    objectID: '56744722958ef13879b94ef0',\n  },\n  {\n    name: 'Semantic UI',\n    slug: 'semantic-ui',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1496405644/rsuq8bqv2aoqqnq8ckzw.png',\n    objectID: '56744723958ef13879b95206',\n  },\n  {\n    name: 'restful',\n    slug: 'restful',\n    objectID: '56744723958ef13879b952c6',\n  },\n  {\n    name: 'Deploy ',\n    slug: 'deploy',\n    objectID: '57578b6282cbbab8dcd47842',\n  },\n  {\n    name: 'solid',\n    slug: 'solid',\n    objectID: '56e6d5598c0bb8288a559c97',\n  },\n  {\n    name: 'font awesome',\n    slug: 'font-awesome',\n    objectID: '56744721958ef13879b9492f',\n  },\n  {\n    slug: 'flutter-cjxern4nz000zx6s1d95hxw7x',\n    objectID: '5d14d342867d9aba094fd8f5',\n  },\n  {\n    slug: 'nestjs',\n    objectID: '59e46480ebcd60373ac04db3',\n  },\n  {\n    name: 'junit',\n    slug: 'junit',\n    objectID: '57935f8804cd973c9154652c',\n  },\n  {\n    name: 'TLS',\n    slug: 'tls',\n    objectID: '56a6742dc84f2c6913b8eac2',\n  },\n  {\n    name: 'NetworkAutomation',\n    slug: 'networkautomation',\n    objectID: '5f9da80a701b426a980950db',\n  },\n  {\n    name: 'Less',\n    slug: 'less',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1509610482/o0vybjlg9bncpy4tq0x0.png',\n    objectID: '56744721958ef13879b949ef',\n  },\n  {\n    name: 'bdd',\n    slug: 'bdd',\n    objectID: '56744721958ef13879b94aa0',\n  },\n  {\n    name: 'baas',\n    slug: 'baas',\n    objectID: '56744723958ef13879b953ad',\n  },\n  {\n    name: 'MVVM',\n    slug: 'mvvm',\n    objectID: '56a0ee5172ca04ea5d79ff9d',\n  },\n  {\n    name: 'responsive',\n    slug: 'responsive',\n    objectID: '56744723958ef13879b95520',\n  },\n  {\n    name: 'Error Tracking',\n    slug: 'error-tracking',\n    objectID: '58d2b7fa440c92dcfd4c5801',\n  },\n  {\n    name: 'media queries',\n    slug: 'media-queries',\n    objectID: '56744721958ef13879b949f2',\n  },\n  {\n    slug: '2articles1week-1',\n    objectID: '5f0b171bf80d68509e50d2c1',\n  },\n  {\n    name: 'RethinkDB',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1455115223/oluebzm7a23ayicyyr93.png',\n    slug: 'rethinkdb',\n    objectID: '5674471d958ef13879b94774',\n  },\n  {\n    name: '.NET',\n    slug: 'net-cikag7ck9004u4153550rzs6c',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1515074840143/Bkl7B3sXz.jpeg',\n    objectID: '56b54dae8dabdc6142c1ac87',\n  },\n  {\n    name: 'codeigniter',\n    slug: 'codeigniter',\n    objectID: '577d5a59f5d62870bc1e3436',\n  },\n  {\n    name: 'web dev',\n    slug: 'web-dev',\n    objectID: '56744722958ef13879b951f5',\n  },\n  {\n    name: 'Question',\n    slug: 'question',\n    objectID: '56b4ee44ed97cf2d3faa9e85',\n  },\n  {\n    name: 'passport',\n    slug: 'passport',\n    objectID: '56744723958ef13879b955b5',\n  },\n  {\n    slug: 'strapi',\n    objectID: '5a60b356acaaf63131a26558',\n  },\n  {\n    name: 'ECS',\n    slug: 'ecs',\n    objectID: '58456f2afc2da7579e5f3ece',\n  },\n  {\n    name: 'Motivation ',\n    slug: 'motivation-1',\n    objectID: '5f95c76540346172a86c28c1',\n  },\n  {\n    name: 'KoaJS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472485426/mypuzb6iv30nivcnj67f.jpg',\n    slug: 'koa',\n    objectID: '56744720958ef13879b947fb',\n  },\n  {\n    name: 'HapiJS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472485161/dtjd0iyqgwiqqksg3see.png',\n    slug: 'hapijs',\n    objectID: '56744721958ef13879b94dd2',\n  },\n  {\n    name: 'Java Framework',\n    slug: 'java-framework',\n    objectID: '5674471d958ef13879b9476f',\n  },\n  {\n    name: 'NativeScript',\n    slug: 'nativescript',\n    objectID: '578f329a5460288cdeb6f281',\n  },\n  {\n    name: 'realtime apps',\n    slug: 'realtime-apps',\n    objectID: '56744721958ef13879b94a1e',\n  },\n  {\n    name: 'DevRant',\n    slug: 'devrant',\n    objectID: '5d946e601971c92f3298b281',\n  },\n  {\n    name: 'amp',\n    slug: 'amp',\n    objectID: '56744723958ef13879b9556c',\n  },\n  {\n    name: 'grunt',\n    slug: 'grunt',\n    objectID: '56744723958ef13879b9547f',\n  },\n  {\n    name: 'es5',\n    slug: 'es5',\n    objectID: '56744722958ef13879b94e5a',\n  },\n  {\n    name: 'servers',\n    slug: 'servers',\n    objectID: '56744722958ef13879b94e49',\n  },\n  {\n    name: 'rss',\n    slug: 'rss',\n    objectID: '56744721958ef13879b949e6',\n  },\n  {\n    slug: 'flask-cje4g3tgk00wdm0wtaepqxd29',\n    objectID: '5a94378b2e2d22686d3319ec',\n  },\n  {\n    slug: 'vpn',\n    objectID: '5a66e6714c88fdb11626d866',\n  },\n  {\n    name: 'writing ',\n    slug: 'writing-1',\n    objectID: '5f541f8fd34e0b0a2135b7ac',\n  },\n  {\n    name: 'CouchDB',\n    slug: 'couchdb',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1516182897537/HJ9_TqnNG.jpeg',\n    objectID: '56744722958ef13879b94e52',\n  },\n  {\n    name: 'responsive design',\n    slug: 'responsive-design',\n    objectID: '568104b15d0b198322f23be3',\n  },\n  {\n    name: 'functional',\n    slug: 'functional',\n    objectID: '56744723958ef13879b9541e',\n  },\n  {\n    name: 'es7',\n    slug: 'es7',\n    objectID: '56744722958ef13879b9516e',\n  },\n  {\n    name: 'flowtype',\n    slug: 'flowtype',\n    objectID: '57a07b7703626115baea275d',\n  },\n  {\n    name: 'airbnb',\n    slug: 'airbnb',\n    objectID: '56744721958ef13879b9495f',\n  },\n  {\n    slug: 'swiftui',\n    objectID: '5d117acd15a6b27b36bb063b',\n  },\n  {\n    name: 'offline',\n    slug: 'offline',\n    objectID: '57ff8bed7a5d253b23bc40dd',\n  },\n  {\n    name: 'css preprocessors',\n    slug: 'css-preprocessors',\n    objectID: '56744723958ef13879b95314',\n  },\n  {\n    name: 'web app',\n    slug: 'web-app',\n    objectID: '56744722958ef13879b950de',\n  },\n  {\n    name: 'beta',\n    slug: 'beta',\n    objectID: '56c6bd7d46a50cb768ba7d04',\n  },\n  {\n    name: 'webdriver',\n    slug: 'webdriver',\n    objectID: '56a1bb2a92921b8f79d3620e',\n  },\n  {\n    name: 'Algolia',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1454497142/tmtr6swfz0tqfeiphd0q.png',\n    slug: 'algolia',\n    objectID: '56744723958ef13879b95404',\n  },\n  {\n    name: 'tech stacks',\n    slug: 'tech-stacks',\n    objectID: '56744721958ef13879b94aea',\n  },\n  {\n    name: 'relay',\n    slug: 'relay',\n    objectID: '56744720958ef13879b947a8',\n  },\n  {\n    name: 'Sequelize',\n    slug: 'sequelize',\n    objectID: '56bf8908f7a8a564cd3cf417',\n  },\n  {\n    name: 'CoffeeScript',\n    slug: 'coffeescript',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1524116531939/ry2EnsS2M.jpeg',\n    objectID: '56744722958ef13879b9519f',\n  },\n  {\n    name: 'browserify',\n    slug: 'browserify',\n    objectID: '56744721958ef13879b94c51',\n  },\n  {\n    slug: 'rtos',\n    objectID: '5e94317328f1a84f59c49fb9',\n  },\n  {\n    slug: 'spanish',\n    objectID: '5d24dd07963b3099469e31b1',\n  },\n  {\n    name: 'universal',\n    slug: 'universal',\n    objectID: '5691098591906f99ef523690',\n  },\n  {\n    name: 'software design',\n    slug: 'software-design',\n    objectID: '56744721958ef13879b94acd',\n  },\n  {\n    name: 'CSS Modules',\n    slug: 'css-modules',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1502977775/w0xxhrabmj1zhddsdiu1.png',\n    objectID: '56bf8908f7a8a564cd3cf415',\n  },\n  {\n    name: 'PhpStorm',\n    slug: 'phpstorm',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1497046152/nmpeb8i0lo2zofxg7xo5.png',\n    objectID: '56eae87928492b76a9948344',\n  },\n  {\n    name: 'scaling',\n    slug: 'scaling',\n    objectID: '56744721958ef13879b94aa9',\n  },\n  {\n    name: 'tool',\n    slug: 'tool',\n    objectID: '568bb9dbe99c5444f3233892',\n  },\n  {\n    name: 'charting library',\n    slug: 'charting-library',\n    objectID: '56744721958ef13879b94e41',\n  },\n  {\n    slug: 'devblog',\n    objectID: '5cdbcce2d7898f811504a6c9',\n  },\n  {\n    name: 'IWD2021',\n    slug: 'iwd2021',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1614605663765/dl9O9JyP9.png',\n    objectID: '603cecbcc8eb04017922ce83',\n  },\n  {\n    slug: 'cpp-ck4ra5k7300nlv2s1jbkdp2qh',\n    objectID: '5e08e075bcc8c0ce78e93263',\n  },\n  {\n    name: 'smtp',\n    slug: 'smtp',\n    objectID: '56744723958ef13879b953c9',\n  },\n  {\n    name: 'plugin',\n    slug: 'plugin',\n    objectID: '56744722958ef13879b94ff8',\n  },\n  {\n    name: 'cto',\n    slug: 'cto',\n    objectID: '56744720958ef13879b9480f',\n  },\n  {\n    name: '100DaysOfCloud',\n    slug: '100daysofcloud',\n    objectID: '5f216568938147308462a35b',\n  },\n  {\n    name: 'PhoneGap',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1475235526/igis5i1twypixaebdkun.jpg',\n    slug: 'phonegap',\n    objectID: '56744720958ef13879b947fa',\n  },\n  {\n    name: 'SailsJS',\n    logo: 'https://res.cloudinary.com/hashnode/image/upload/v1472484652/puabwwilk0dvwv9gsepb.png',\n    slug: 'sailsjs',\n    objectID: '56744723958ef13879b9527a',\n  },\n  {\n    name: 'socket',\n    slug: 'socket',\n    objectID: '576bd575956de5c931689074',\n  },\n  {\n    name: 'wasm',\n    slug: 'wasm',\n    objectID: '57612cfa7e4505f8314fb29a',\n  },\n  {\n    name: 'rxjava',\n    slug: 'rxjava',\n    objectID: '56d93d14696d94e491c06f47',\n  },\n  {\n    name: 'Testing Library',\n    slug: 'testing-library',\n    logo: 'https://cdn.hashnode.com/res/hashnode/image/upload/v1618896704282/9Z3cbqhmn.png',\n    objectID: '607e6751eb2bd30d2d22a556',\n  },\n  {\n    name: 'c#',\n    slug: 'c-cikbdqjwh0042l553122kmxlz',\n    objectID: '56b629b2e6740d0959b6f3d9',\n  },\n  {\n    name: 'Alexa',\n    slug: 'alexa',\n    objectID: '57bb2f081351c2290bba1d24',\n  },\n  {\n    name: 'mern-stack',\n    slug: 'mern-stack',\n    objectID: '56c752ab34d45a99221aa34f',\n  },\n  {\n    name: 'microservice',\n    slug: 'microservice',\n    objectID: '56744723958ef13879b95421',\n  },\n  {\n    name: 'lodash',\n    slug: 'lodash',\n    objectID: '56744722958ef13879b95162',\n  },\n  {\n    name: 'code splitting',\n    slug: 'code-splitting',\n    objectID: '56e17a0f5d4f204da59e0058',\n  },\n  {\n    name: 'GraphQL ',\n    slug: 'graphql-cintl8ori01p0y353nth5857g',\n    objectID: '572a9b9f109fb69b463406e9',\n  },\n  {\n    name: 'isomorphic apps',\n    slug: 'isomorphic-apps',\n    objectID: '56744723958ef13879b95505',\n  },\n  {\n    name: 'internet explorer',\n    slug: 'internet-explorer',\n    objectID: '56744721958ef13879b94c7b',\n  },\n  {\n    name: 'mobile app',\n    slug: 'mobile-app',\n    objectID: '576934c7a841f03b9338c6b3',\n  },\n];\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';\nimport { Integration } from '@prisma/client';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  \"Instagram should have at least one attachment, if it's a story, it can have only one picture\"\n)\nexport class InstagramProvider\n  extends SocialAbstract\n  implements SocialProvider\n{\n  identifier = 'instagram';\n  name = 'Instagram\\n(Facebook Business)';\n  isBetweenSteps = true;\n  toolTip = 'Instagram must be business and connected to a Facebook page';\n  scopes = [\n    'instagram_basic',\n    'pages_show_list',\n    'pages_read_engagement',\n    'business_management',\n    'instagram_content_publish',\n    'instagram_manage_comments',\n    'instagram_manage_insights',\n  ];\n  override maxConcurrentJob = 400;\n  editor = 'normal' as const;\n  dto = InstagramDto;\n  maxLength() {\n    return 2200;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  public override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body' | 'retry';\n        value: string;\n      }\n    | undefined {\n    if (body.indexOf('An unknown error occurred') > -1) {\n      return {\n        type: 'retry' as const,\n        value: 'An unknown error occurred, please try again later',\n      };\n    }\n    if (body.indexOf('2207081') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: \"This account doesn't support Trial Reels\",\n      };\n    }\n\n    if (body.indexOf('REVOKED_ACCESS_TOKEN') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value:\n          'Something is wrong with your connected user, please re-authenticate',\n      };\n    }\n\n    if (\n      body.toLowerCase().indexOf('the user is not an instagram business') > -1\n    ) {\n      return {\n        type: 'refresh-token' as const,\n        value:\n          'Your Instagram account is not a business account, please convert it to a business account',\n      };\n    }\n\n    if (body.toLowerCase().indexOf('session has been invalidated') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Please re-authenticate your Instagram account',\n      };\n    }\n\n    if (body.indexOf('2207050') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Instagram user is restricted',\n      };\n    }\n\n    // Media download/upload errors\n    if (body.indexOf('2207003') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Timeout downloading media, please try again',\n      };\n    }\n\n    if (body.indexOf('2207020') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Media expired, please upload again',\n      };\n    }\n\n    if (body.indexOf('2207032') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Failed to create media, please try again',\n      };\n    }\n\n    if (body.indexOf('2207053') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Unknown upload error, please try again',\n      };\n    }\n\n    if (body.indexOf('2207052') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Media fetch failed, please try again',\n      };\n    }\n\n    if (body.indexOf('2207057') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid thumbnail offset for video',\n      };\n    }\n\n    if (body.indexOf('2207026') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Unsupported video format',\n      };\n    }\n\n    if (body.indexOf('2207023') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Unknown media type',\n      };\n    }\n\n    if (body.indexOf('2207006') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Media not found, please upload again',\n      };\n    }\n\n    if (body.indexOf('2207008') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Media builder expired, please try again',\n      };\n    }\n\n    // Content validation errors\n    if (body.indexOf('2207028') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Carousel validation failed',\n      };\n    }\n\n    if (body.indexOf('2207010') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Caption is too long',\n      };\n    }\n\n    // Product tagging errors\n    if (body.indexOf('2207035') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Product tag positions not supported for videos',\n      };\n    }\n\n    if (body.indexOf('2207036') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Product tag positions required for photos',\n      };\n    }\n\n    if (body.indexOf('2207037') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Product tag validation failed',\n      };\n    }\n\n    if (body.indexOf('2207040') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Too many product tags',\n      };\n    }\n\n    // Image format/size errors\n    if (body.indexOf('2207004') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Image is too large',\n      };\n    }\n\n    if (body.indexOf('2207005') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Unsupported image format',\n      };\n    }\n\n    if (body.indexOf('2207009') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Aspect ratio not supported, must be between 4:5 to 1.91:1',\n      };\n    }\n\n    if (body.indexOf('Page request limit reached') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Page posting for today is limited, please try again tomorrow',\n      };\n    }\n\n    if (body.indexOf('2207042') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'You have reached the maximum of 25 posts per day, allowed for your account',\n      };\n    }\n\n    if (body.indexOf('Not enough permissions to post') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Not enough permissions to post',\n      };\n    }\n\n    if (body.indexOf('36003') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Aspect ratio not supported, must be between 4:5 to 1.91:1',\n      };\n    }\n\n    if (body.indexOf('190,') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'The account is missing some permissions to perform this action, please re-add the account and allow all permissions',\n      };\n    }\n\n    if (body.indexOf('36001') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid Instagram image resolution max: 1920x1080px',\n      };\n    }\n\n    if (body.indexOf('2207051') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Instagram blocked your request',\n      };\n    }\n\n    if (body.indexOf('2207001') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'Instagram detected that your post is spam, please try again with different content',\n      };\n    }\n\n    if (body.indexOf('2207027') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Unknown error, please try again later or contact support',\n      };\n    }\n\n    if (body.indexOf('param collaborators is not allowed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Collaborators are not allowed for carousel'\n      };\n    }\n\n    return undefined;\n  }\n\n  async reConnect(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {\n    const findPage = (await this.pages(accessToken)).find(\n      (p) => p.id === requiredId\n    );\n\n    const information = await this.fetchPageInformation(accessToken, {\n      id: requiredId,\n      pageId: findPage?.pageId!,\n    });\n\n    return {\n      id: information.id,\n      name: information.name,\n      accessToken: information.access_token,\n      picture: information.picture,\n      username: information.username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url:\n        'https://www.facebook.com/v20.0/dialog/oauth' +\n        `?client_id=${process.env.FACEBOOK_APP_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${process.env.FRONTEND_URL}/integrations/social/instagram`\n        )}` +\n        `&state=${state}` +\n        `&scope=${encodeURIComponent(this.scopes.join(','))}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh: string;\n  }) {\n    const getAccessToken = await (\n      await fetch(\n        'https://graph.facebook.com/v20.0/oauth/access_token' +\n          `?client_id=${process.env.FACEBOOK_APP_ID}` +\n          `&redirect_uri=${encodeURIComponent(\n            `${process.env.FRONTEND_URL}/integrations/social/instagram${\n              params.refresh ? `?refresh=${params.refresh}` : ''\n            }`\n          )}` +\n          `&client_secret=${process.env.FACEBOOK_APP_SECRET}` +\n          `&code=${params.code}`\n      )\n    ).json();\n\n    const { access_token, expires_in, ...all } = await (\n      await fetch(\n        'https://graph.facebook.com/v20.0/oauth/access_token' +\n          '?grant_type=fb_exchange_token' +\n          `&client_id=${process.env.FACEBOOK_APP_ID}` +\n          `&client_secret=${process.env.FACEBOOK_APP_SECRET}` +\n          `&fb_exchange_token=${getAccessToken.access_token}`\n      )\n    ).json();\n\n    const { data } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/me/permissions?access_token=${access_token}`\n      )\n    ).json();\n\n    const permissions = data\n      .filter((d: any) => d.status === 'granted')\n      .map((p: any) => p.permission);\n    this.checkScopes(this.scopes, permissions);\n\n    const { id, name, picture } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}`\n      )\n    ).json();\n\n    return {\n      id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(),\n      picture: picture?.data?.url || '',\n      username: '',\n    };\n  }\n\n  async pages(accessToken: string) {\n    const seenPageIds = new Set<string>();\n    const allFacebookPages: any[] = [];\n\n    const fetchPaginated = async (startUrl: string) => {\n      let nextUrl: string | undefined = startUrl;\n      while (nextUrl) {\n        const response = await (await fetch(nextUrl)).json();\n        if (response.data) {\n          for (const page of response.data) {\n            if (!seenPageIds.has(page.id)) {\n              seenPageIds.add(page.id);\n              allFacebookPages.push(page);\n            }\n          }\n        }\n        nextUrl = response.paging?.next;\n      }\n    };\n\n    // Fetch pages the user explicitly shared during the OAuth dialog\n    await fetchPaginated(\n      `https://graph.facebook.com/v20.0/me/accounts?fields=id,instagram_business_account,username,name,picture.type(large)&limit=100&access_token=${accessToken}`\n    );\n\n    // Also fetch pages via Business Manager API to discover pages\n    // not selected during the OAuth page selection step\n    try {\n      let bizUrl: string | undefined =\n        `https://graph.facebook.com/v20.0/me/businesses?access_token=${accessToken}`;\n\n      while (bizUrl) {\n        const bizResponse = await (await fetch(bizUrl)).json();\n        if (bizResponse.data) {\n          for (const business of bizResponse.data) {\n            try {\n              await fetchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/owned_pages?fields=id,instagram_business_account,username,name,picture.type(large)&limit=100&access_token=${accessToken}`\n              );\n            } catch {\n              // Continue with other businesses\n            }\n\n            try {\n              await fetchPaginated(\n                `https://graph.facebook.com/v20.0/${business.id}/client_pages?fields=id,instagram_business_account,username,name,picture.type(large)&limit=100&access_token=${accessToken}`\n              );\n            } catch {\n              // Continue with other businesses\n            }\n          }\n        }\n        bizUrl = bizResponse.paging?.next;\n      }\n    } catch {\n      // Business Manager API not available for all users\n    }\n\n    const onlyConnectedAccounts = await Promise.all(\n      allFacebookPages\n        .filter((f: any) => f.instagram_business_account)\n        .map(async (p: any) => {\n          return {\n            pageId: p.id,\n            ...(await (\n              await fetch(\n                `https://graph.facebook.com/v20.0/${p.instagram_business_account.id}?fields=name,profile_picture_url&access_token=${accessToken}`\n              )\n            ).json()),\n            id: p.instagram_business_account.id,\n          };\n        })\n    );\n\n    return onlyConnectedAccounts.map((p: any) => ({\n      pageId: p.pageId,\n      id: p.id,\n      name: p.name,\n      picture: { data: { url: p.profile_picture_url } },\n    }));\n  }\n\n  async fetchPageInformation(\n    accessToken: string,\n    data: { pageId: string; id: string }\n  ) {\n    const { access_token, ...all } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/${data.pageId}?fields=access_token,name,picture.type(large)&access_token=${accessToken}`\n      )\n    ).json();\n\n    const { id, name, profile_picture_url, username } = await (\n      await fetch(\n        `https://graph.facebook.com/v20.0/${data.id}?fields=username,name,profile_picture_url&access_token=${accessToken}`\n      )\n    ).json();\n\n    return {\n      id,\n      name,\n      picture: profile_picture_url,\n      access_token,\n      username,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<InstagramDto>[],\n    integration: Integration,\n    type = 'graph.facebook.com'\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    console.log('in progress', id);\n    const isStory = firstPost.settings.post_type === 'story';\n    const isTrialReel = !!firstPost.settings.is_trial_reel;\n    const medias = await Promise.all(\n      firstPost?.media?.map(async (m) => {\n        const caption =\n          firstPost.media?.length === 1\n            ? `&caption=${encodeURIComponent(firstPost.message)}`\n            : ``;\n        const isCarousel =\n          (firstPost?.media?.length || 0) > 1 && !isStory ? `&is_carousel_item=true` : ``;\n        const mediaType =\n          m.path.indexOf('.mp4') > -1\n            ? firstPost?.media?.length === 1\n              ? isStory\n                ? `video_url=${m.path}&media_type=STORIES`\n                : `video_url=${m.path}&media_type=REELS&thumb_offset=${\n                    m?.thumbnailTimestamp || 0\n                  }`\n              : isStory\n              ? `video_url=${m.path}&media_type=STORIES`\n              : `video_url=${m.path}&media_type=VIDEO&thumb_offset=${\n                  m?.thumbnailTimestamp || 0\n                }`\n            : isStory\n            ? `image_url=${m.path}&media_type=STORIES`\n            : `image_url=${m.path}`;\n\n        const trialParams = isTrialReel\n          ? `&trial_params=${encodeURIComponent(\n              JSON.stringify({\n                graduation_strategy:\n                  firstPost.settings.graduation_strategy || 'MANUAL',\n              })\n            )}`\n          : ``;\n\n        const collaborators =\n          firstPost?.settings?.collaborators?.length && !isStory\n            ? `&collaborators=${JSON.stringify(\n                firstPost?.settings?.collaborators.map((p) => p.label)\n              )}`\n            : ``;\n\n        const { id: photoId } = await (\n          await this.fetch(\n            `https://${type}/v20.0/${id}/media?${mediaType}${isCarousel}${collaborators}${trialParams}&access_token=${accessToken}${caption}`,\n            {\n              method: 'POST',\n            }\n          )\n        ).json();\n        console.log('in progress2', id);\n\n        let status = 'IN_PROGRESS';\n        while (status === 'IN_PROGRESS') {\n          const { status_code } = await (\n            await this.fetch(\n              `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code`,\n              undefined,\n              '',\n              0,\n              true\n            )\n          ).json();\n          await timer(30000);\n          status = status_code;\n        }\n        console.log('in progress3', id);\n\n        return photoId;\n      }) || []\n    );\n\n    if (isStory && medias.length > 1) {\n      // Stories don't support carousels - publish each media as a separate story\n      let lastMediaId = '';\n      let lastPermalink = '';\n      for (const mediaCreationId of medias) {\n        const { id: mediaId } = await (\n          await this.fetch(\n            `https://${type}/v20.0/${id}/media_publish?creation_id=${mediaCreationId}&access_token=${accessToken}&field=id`,\n            {\n              method: 'POST',\n            }\n          )\n        ).json();\n        lastMediaId = mediaId;\n\n        const { permalink } = await (\n          await this.fetch(\n            `https://${type}/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}`\n          )\n        ).json();\n        lastPermalink = permalink;\n      }\n\n      return [\n        {\n          id: firstPost.id,\n          postId: lastMediaId,\n          releaseURL: lastPermalink,\n          status: 'success',\n        },\n      ];\n    } else if (medias.length === 1) {\n      const { id: mediaId } = await (\n        await this.fetch(\n          `https://${type}/v20.0/${id}/media_publish?creation_id=${medias[0]}&access_token=${accessToken}&field=id`,\n          {\n            method: 'POST',\n          }\n        )\n      ).json();\n\n      const { permalink } = await (\n        await this.fetch(\n          `https://${type}/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}`\n        )\n      ).json();\n\n      return [\n        {\n          id: firstPost.id,\n          postId: mediaId,\n          releaseURL: permalink,\n          status: 'success',\n        },\n      ];\n    } else {\n      const { id: containerId, ...all3 } = await (\n        await this.fetch(\n          `https://${type}/v20.0/${id}/media?caption=${encodeURIComponent(\n            firstPost?.message\n          )}&media_type=CAROUSEL&children=${encodeURIComponent(\n            medias.join(',')\n          )}&access_token=${accessToken}`,\n          {\n            method: 'POST',\n          }\n        )\n      ).json();\n\n      let status = 'IN_PROGRESS';\n      while (status === 'IN_PROGRESS') {\n        const { status_code } = await (\n          await this.fetch(\n            `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}`,\n            undefined,\n            '',\n            0,\n            true\n          )\n        ).json();\n        await timer(30000);\n        status = status_code;\n      }\n\n      const { id: mediaId, ...all4 } = await (\n        await this.fetch(\n          `https://${type}/v20.0/${id}/media_publish?creation_id=${containerId}&access_token=${accessToken}&field=id`,\n          {\n            method: 'POST',\n          }\n        )\n      ).json();\n\n      const { permalink } = await (\n        await this.fetch(\n          `https://${type}/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}`\n        )\n      ).json();\n\n      return [\n        {\n          id: firstPost.id,\n          postId: mediaId,\n          releaseURL: permalink,\n          status: 'success',\n        },\n      ];\n    }\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<InstagramDto>[],\n    integration: Integration,\n    type = 'graph.facebook.com'\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n\n    const { id: commentId } = await (\n      await this.fetch(\n        `https://${type}/v20.0/${postId}/comments?message=${encodeURIComponent(\n          commentPost.message\n        )}&access_token=${accessToken}`,\n        {\n          method: 'POST',\n        }\n      )\n    ).json();\n\n    // Get the permalink from the parent post\n    const { permalink } = await (\n      await this.fetch(\n        `https://${type}/v20.0/${postId}?fields=permalink&access_token=${accessToken}`\n      )\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: commentId,\n        releaseURL: permalink,\n        status: 'success',\n      },\n    ];\n  }\n\n  private setTitle(name: string) {\n    switch (name) {\n      case 'likes': {\n        return 'Likes';\n      }\n\n      case 'followers': {\n        return 'Followers';\n      }\n\n      case 'reach': {\n        return 'Reach';\n      }\n\n      case 'follower_count': {\n        return 'Follower Count';\n      }\n\n      case 'views': {\n        return 'Views';\n      }\n\n      case 'comments': {\n        return 'Comments';\n      }\n\n      case 'shares': {\n        return 'Shares';\n      }\n\n      case 'saves': {\n        return 'Saves';\n      }\n\n      case 'replies': {\n        return 'Replies';\n      }\n    }\n\n    return '';\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number,\n    type = 'graph.facebook.com'\n  ): Promise<AnalyticsData[]> {\n    const until = dayjs().endOf('day').unix();\n    const since = dayjs().subtract(date, 'day').unix();\n\n    const { data, ...all } = await (\n      await fetch(\n        `https://${type}/v21.0/${id}/insights?metric=follower_count,reach&access_token=${accessToken}&period=day&since=${since}&until=${until}`\n      )\n    ).json();\n\n    const { data: data2, ...all2 } = await (\n      await fetch(\n        `https://${type}/v21.0/${id}/insights?metric_type=total_value&metric=likes,views,comments,shares,saves,replies&access_token=${accessToken}&period=day&since=${since}&until=${until}`\n      )\n    ).json();\n    const analytics = [];\n\n    analytics.push(\n      ...(data?.map((d: any) => ({\n        label: this.setTitle(d.name),\n        percentageChange: 5,\n        data: d.values.map((v: any) => ({\n          total: v.value,\n          date: dayjs(v.end_time).format('YYYY-MM-DD'),\n        })),\n      })) || [])\n    );\n\n    analytics.push(\n      ...data2.map((d: any) => ({\n        label: this.setTitle(d.name),\n        percentageChange: 5,\n        data: [\n          {\n            total: d.total_value.value,\n            date: dayjs().format('YYYY-MM-DD'),\n          },\n          {\n            total: d.total_value.value,\n            date: dayjs().add(1, 'day').format('YYYY-MM-DD'),\n          },\n        ],\n      }))\n    );\n\n    return analytics;\n  }\n\n  music(accessToken: string, data: { q: string }) {\n    return this.fetch(\n      `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(\n        data.q\n      )}&access_token=${accessToken}`\n    );\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number,\n    type = 'graph.facebook.com'\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    try {\n      // Fetch media insights from Instagram Graph API\n      const { data } = await (\n        await this.fetch(\n          `https://${type}/v21.0/${postId}/insights?metric=views,reach,saved,likes,comments,shares&access_token=${accessToken}`\n        )\n      ).json();\n\n      if (!data || data.length === 0) {\n        return [];\n      }\n\n      const result: AnalyticsData[] = [];\n\n      for (const metric of data) {\n        const value = metric.values?.[0]?.value;\n        if (value === undefined) continue;\n\n        let label = '';\n\n        switch (metric.name) {\n          case 'views':\n            label = 'Views';\n            break;\n          case 'reach':\n            label = 'Reach';\n            break;\n          case 'engagement':\n            label = 'Engagement';\n            break;\n          case 'saved':\n            label = 'Saves';\n            break;\n          case 'likes':\n            label = 'Likes';\n            break;\n          case 'comments':\n            label = 'Comments';\n            break;\n          case 'shares':\n            label = 'Shares';\n            break;\n        }\n\n        if (label) {\n          result.push({\n            label,\n            percentageChange: 0,\n            data: [{ total: String(value), date: today }],\n          });\n        }\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching Instagram post analytics:', err);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto';\nimport { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider';\nimport { Integration } from '@prisma/client';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\nconst instagramProvider = new InstagramProvider();\n\n@Rules(\n  \"Instagram should have at least one attachment, if it's a story, it can have only one picture\"\n)\nexport class InstagramStandaloneProvider\n  extends SocialAbstract\n  implements SocialProvider\n{\n  identifier = 'instagram-standalone';\n  name = 'Instagram\\n(Standalone)';\n  isBetweenSteps = false;\n  refreshCron = true;\n  scopes = [\n    'instagram_business_basic',\n    'instagram_business_content_publish',\n    'instagram_business_manage_comments',\n    'instagram_business_manage_insights',\n  ];\n    override maxConcurrentJob = 200; // Instagram standalone has stricter limits\n  dto = InstagramDto;\n\n  editor = 'normal' as const;\n  maxLength() {\n    return 2200;\n  }\n\n  public override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    return instagramProvider.handleErrors(body);\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    const { access_token } = await (\n      await fetch(\n        `https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${refresh_token}`\n      )\n    ).json();\n\n    const {\n      user_id,\n      name,\n      username,\n      profile_picture_url = '',\n    } = await (\n      await fetch(\n        `https://graph.instagram.com/v21.0/me?fields=user_id,username,name,profile_picture_url&access_token=${access_token}`\n      )\n    ).json();\n\n    return {\n      id: user_id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(58, 'days').unix() - dayjs().unix(),\n      picture: profile_picture_url || '',\n      username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url:\n        `https://www.instagram.com/oauth/authorize?enable_fb_login=0&client_id=${\n          process.env.INSTAGRAM_APP_ID\n        }&redirect_uri=${encodeURIComponent(\n          `${\n            process?.env.FRONTEND_URL?.indexOf('https') == -1\n              ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n              : `${process?.env.FRONTEND_URL}`\n          }/integrations/social/instagram-standalone`\n        )}&response_type=code&scope=${encodeURIComponent(\n          this.scopes.join(',')\n        )}` + `&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh: string;\n  }) {\n    const formData = new FormData();\n    formData.append('client_id', process.env.INSTAGRAM_APP_ID!);\n    formData.append('client_secret', process.env.INSTAGRAM_APP_SECRET!);\n    formData.append('grant_type', 'authorization_code');\n    formData.append(\n      'redirect_uri',\n      `${\n        process?.env.FRONTEND_URL?.indexOf('https') == -1\n          ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n          : `${process?.env.FRONTEND_URL}`\n      }/integrations/social/instagram-standalone`\n    );\n    formData.append('code', params.code);\n\n    const getAccessToken = await (\n      await fetch('https://api.instagram.com/oauth/access_token', {\n        method: 'POST',\n        body: formData,\n      })\n    ).json();\n\n    const { access_token, expires_in, ...all } = await (\n      await fetch(\n        'https://graph.instagram.com/access_token' +\n          '?grant_type=ig_exchange_token' +\n          `&client_id=${process.env.INSTAGRAM_APP_ID}` +\n          `&client_secret=${process.env.INSTAGRAM_APP_SECRET}` +\n          `&access_token=${getAccessToken.access_token}`\n      )\n    ).json();\n\n    this.checkScopes(this.scopes, getAccessToken.permissions);\n\n    const { user_id, name, username, profile_picture_url } = await (\n      await fetch(\n        `https://graph.instagram.com/v21.0/me?fields=user_id,username,name,profile_picture_url&access_token=${access_token}`\n      )\n    ).json();\n\n    return {\n      id: user_id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(58, 'days').unix() - dayjs().unix(),\n      picture: profile_picture_url,\n      username,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<InstagramDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return instagramProvider.post(\n      id,\n      accessToken,\n      postDetails,\n      integration,\n      'graph.instagram.com'\n    );\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<InstagramDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return instagramProvider.comment(\n      id,\n      postId,\n      lastCommentId,\n      accessToken,\n      postDetails,\n      integration,\n      'graph.instagram.com'\n    );\n  }\n\n  async analytics(id: string, accessToken: string, date: number) {\n    return instagramProvider.analytics(\n      id,\n      accessToken,\n      date,\n      'graph.instagram.com'\n    );\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ) {\n    return instagramProvider.postAnalytics(\n      integrationId,\n      accessToken,\n      postId,\n      date,\n      'graph.instagram.com'\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/kick.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { KickDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/kick.dto';\nimport { createHash, randomBytes } from 'crypto';\n\nexport class KickProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3;\n  identifier = 'kick';\n  name = 'Kick';\n  isBetweenSteps = false;\n  editor = 'normal' as const;\n  scopes = ['chat:write', 'user:read', 'channel:read'];\n  dto = KickDto;\n\n  maxLength() {\n    return 500; // Kick chat message max length\n  }\n\n  private generatePKCE() {\n    const codeVerifier = randomBytes(64).toString('base64url');\n    const challenge = Buffer.from(\n      createHash('sha256').update(codeVerifier).digest()\n    )\n      .toString('base64')\n      .replace(/=*$/g, '')\n      .replace(/\\+/g, '-')\n      .replace(/\\//g, '_');\n\n    return { codeVerifier, codeChallenge: challenge };\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const response = await this.fetch('https://id.kick.com/oauth/token', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      body: new URLSearchParams({\n        grant_type: 'refresh_token',\n        client_id: process.env.KICK_CLIENT_ID!,\n        client_secret: process.env.KICK_SECRET!,\n        refresh_token: refreshToken,\n      }),\n    });\n\n    const { access_token, refresh_token, expires_in } = await response.json();\n\n    // Get user info\n    const userInfo = await this.getUserInfo(access_token);\n\n    return {\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      accessToken: access_token,\n      id: userInfo.id,\n      name: userInfo.name,\n      picture: userInfo.picture || '',\n      username: userInfo.username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(32);\n    const { codeVerifier, codeChallenge } = this.generatePKCE();\n\n    const redirectUri = `${process.env.FRONTEND_URL}/integrations/social/kick`;\n\n    const url =\n      `https://id.kick.com/oauth/authorize` +\n      `?response_type=code` +\n      `&client_id=${process.env.KICK_CLIENT_ID}` +\n      `&redirect_uri=${encodeURIComponent(redirectUri)}` +\n      `&scope=${encodeURIComponent(this.scopes.join(' '))}` +\n      `&state=${state}` +\n      `&code_challenge=${codeChallenge}` +\n      `&code_challenge_method=S256`;\n\n    return {\n      url,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const redirectUri = `${process.env.FRONTEND_URL}/integrations/social/kick${\n      params.refresh ? `?refresh=${params.refresh}` : ''\n    }`;\n\n    const tokenResponse = await this.fetch('https://id.kick.com/oauth/token', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      body: new URLSearchParams({\n        grant_type: 'authorization_code',\n        client_id: process.env.KICK_CLIENT_ID!,\n        client_secret: process.env.KICK_SECRET!,\n        redirect_uri: redirectUri,\n        code: params.code,\n        code_verifier: params.codeVerifier,\n      }),\n    });\n\n    const { access_token, refresh_token, expires_in, scope } =\n      await tokenResponse.json();\n\n    // Get user info\n    const userInfo = await this.getUserInfo(access_token);\n\n    return {\n      id: userInfo.id,\n      name: userInfo.name,\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      picture: userInfo.picture || '',\n      username: userInfo.username,\n    };\n  }\n\n  private async getUserInfo(\n    accessToken: string\n  ): Promise<{ id: string; name: string; username: string; picture?: string }> {\n    // Use token introspect to get basic info, then fetch user details\n    // Try to get full user info from the API\n    const userResponse = await fetch('https://api.kick.com/public/v1/users', {\n      method: 'GET',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n      },\n    });\n\n    const userData = await userResponse.json();\n    const user = userData.data?.[0] || userData.data;\n    return {\n      id: String(user.user_id || user.id),\n      name: user.name,\n      username: user.name,\n      picture: user.profile_picture || '',\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n\n    // Post chat message to Kick\n    // Note: Kick chat doesn't support media attachments directly in messages\n    const response = await this.fetch('https://api.kick.com/public/v1/chat', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        type: 'user',\n        content: firstPost.message.substring(0, 500), // Ensure max length\n        broadcaster_user_id: parseInt(id, 10),\n      }),\n    });\n\n    const data = await response.json();\n\n    return [\n      {\n        id: firstPost.id,\n        postId: data.data?.message_id || data.message_id || makeId(10),\n        releaseURL: `https://kick.com/${integration.profile || 'channel'}`,\n        status: data.data?.is_sent || data.is_sent ? 'posted' : 'error',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n\n    // Kick supports reply_to_message_id for replies\n    const response = await this.fetch('https://api.kick.com/public/v1/chat', {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        type: 'user',\n        content: commentPost.message.substring(0, 500),\n        broadcaster_user_id: parseInt(id, 10),\n        reply_to_message_id: lastCommentId || postId,\n      }),\n    });\n\n    const data = await response.json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: data.data?.message_id || data.message_id || makeId(10),\n        releaseURL: `https://kick.com/${integration.profile || 'channel'}`,\n        status: data.data?.is_sent || data.is_sent ? 'posted' : 'error',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/lemmy.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class LemmyProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Lemmy instances typically have moderate limits\n  identifier = 'lemmy';\n  name = 'Lemmy';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'normal' as const;\n  maxLength() {\n    return 10000;\n  }\n  dto = LemmySettingsDto;\n\n  async customFields() {\n    return [\n      {\n        key: 'service',\n        label: 'Service',\n        defaultValue: 'https://lemmy.world',\n        validation: `/^https?:\\\\/\\\\/(www\\\\.)?[-a-zA-Z0-9@:%._\\\\+~#=]{1,256}\\\\.[a-zA-Z0-9()]{1,6}\\\\b([-a-zA-Z0-9()@:%_\\\\+.~#?&//=]*)$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'identifier',\n        label: 'Identifier',\n        validation: `/^.{3,}$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'password',\n        label: 'Password',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n\n    const load = await fetch(body.service + '/api/v3/user/login', {\n      body: JSON.stringify({\n        username_or_email: body.identifier,\n        password: body.password,\n      }),\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n\n    if (load.status === 401) {\n      return 'Invalid credentials';\n    }\n\n    const { jwt } = await load.json();\n\n    try {\n      const user = await (\n        await fetch(body.service + `/api/v3/user?username=${body.identifier}`, {\n          headers: {\n            Authorization: `Bearer ${jwt}`,\n          },\n        })\n      ).json();\n\n      return {\n        refreshToken: jwt!,\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: jwt!,\n        id: String(user.person_view.person.id),\n        name:\n          user.person_view.person.display_name ||\n          user.person_view.person.name ||\n          '',\n        picture: user?.person_view?.person?.avatar || '',\n        username: body.identifier || '',\n      };\n    } catch (e) {\n      console.log(e);\n      return 'Invalid credentials';\n    }\n  }\n\n  private async getJwtAndService(integration: Integration): Promise<{ jwt: string; service: string }> {\n    const body = JSON.parse(\n      AuthService.fixedDecryption(integration.customInstanceDetails!)\n    );\n\n    const { jwt } = await (\n      await fetch(body.service + '/api/v3/user/login', {\n        body: JSON.stringify({\n          username_or_email: body.identifier,\n          password: body.password,\n        }),\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n      })\n    ).json();\n\n    return { jwt, service: body.service };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<LemmySettingsDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const { jwt, service } = await this.getJwtAndService(integration);\n\n    const valueArray: PostResponse[] = [];\n\n    for (const lemmy of firstPost.settings.subreddit) {\n      console.log({\n        community_id: +lemmy.value.id,\n        name: lemmy.value.title,\n        body: firstPost.message,\n        ...(lemmy.value.url ? { url: lemmy.value.url } : {}),\n        ...(firstPost.media?.length\n          ? { custom_thumbnail: firstPost.media[0].path }\n          : {}),\n        nsfw: false,\n      });\n      const { post_view } = await (\n        await fetch(service + '/api/v3/post', {\n          body: JSON.stringify({\n            community_id: +lemmy.value.id,\n            name: lemmy.value.title,\n            body: firstPost.message,\n            ...(lemmy.value.url\n              ? {\n                  url:\n                    lemmy.value.url.indexOf('http') === -1\n                      ? `https://${lemmy.value.url}`\n                      : lemmy.value.url,\n                }\n              : {}),\n            ...(firstPost.media?.length\n              ? { custom_thumbnail: firstPost.media[0].path }\n              : {}),\n            nsfw: false,\n          }),\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${jwt}`,\n            'Content-Type': 'application/json',\n          },\n        })\n      ).json();\n\n      valueArray.push({\n        postId: post_view.post.id,\n        releaseURL: service + '/post/' + post_view.post.id,\n        id: firstPost.id,\n        status: 'published',\n      });\n    }\n\n    return [\n      {\n        id: firstPost.id,\n        postId: valueArray.map((p) => String(p.postId)).join(','),\n        releaseURL: valueArray.map((p) => p.releaseURL).join(','),\n        status: 'published',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<LemmySettingsDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const { jwt, service } = await this.getJwtAndService(integration);\n\n    // postId can be comma-separated if posted to multiple communities\n    const postIds = postId.split(',');\n    const valueArray: PostResponse[] = [];\n\n    for (const singlePostId of postIds) {\n      const { comment_view } = await (\n        await fetch(service + '/api/v3/comment', {\n          body: JSON.stringify({\n            post_id: +singlePostId,\n            content: commentPost.message,\n          }),\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${jwt}`,\n            'Content-Type': 'application/json',\n          },\n        })\n      ).json();\n\n      valueArray.push({\n        postId: String(comment_view.comment.id),\n        releaseURL: service + '/comment/' + comment_view.comment.id,\n        id: commentPost.id,\n        status: 'published',\n      });\n    }\n\n    return [\n      {\n        id: commentPost.id,\n        postId: valueArray.map((p) => p.postId).join(','),\n        releaseURL: valueArray.map((p) => p.releaseURL).join(','),\n        status: 'published',\n      },\n    ];\n  }\n\n  @Tool({\n    description: 'Search for Lemmy communities by keyword',\n    dataSchema: [\n      {\n        key: 'word',\n        type: 'string',\n        description: 'Keyword to search for',\n      },\n    ],\n  })\n  async subreddits(\n    accessToken: string,\n    data: any,\n    id: string,\n    integration: Integration\n  ) {\n    const { jwt, service } = await this.getJwtAndService(integration);\n\n    const { communities } = await (\n      await fetch(\n        service + `/api/v3/search?type_=Communities&sort=Active&q=${data.word}`,\n        {\n          headers: {\n            Authorization: `Bearer ${jwt}`,\n          },\n        }\n      )\n    ).json();\n\n    return communities.map((p: any) => ({\n      title: p.community.title,\n      name: p.community.title,\n      id: p.community.id,\n    }));\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/linkedin.page.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { LinkedinProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.provider';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { Plug } from '@gitroom/helpers/decorators/plug.decorator';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  'LinkedIn can have maximum one attachment when selecting video, when choosing a carousel on LinkedIn minimum amount of attachment must be two, and only pictures, if uploading a video, LinkedIn can have only one attachment'\n)\nexport class LinkedinPageProvider\n  extends LinkedinProvider\n  implements SocialProvider\n{\n  override identifier = 'linkedin-page';\n  override name = 'LinkedIn Page';\n  override isBetweenSteps = true;\n  override refreshWait = true;\n  override maxConcurrentJob = 2; // LinkedIn Page has professional posting limits\n  override scopes = [\n    'openid',\n    'profile',\n    'w_member_social',\n    'r_basicprofile',\n    'rw_organization_admin',\n    'w_organization_social',\n    'r_organization_social',\n  ];\n\n  override editor = 'normal' as const;\n\n  override async refreshToken(\n    refresh_token: string\n  ): Promise<AuthTokenDetails> {\n    const {\n      access_token: accessToken,\n      expires_in,\n      refresh_token: refreshToken,\n    } = await (\n      await fetch('https://www.linkedin.com/oauth/v2/accessToken', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token,\n          client_id: process.env.LINKEDIN_CLIENT_ID!,\n          client_secret: process.env.LINKEDIN_CLIENT_SECRET!,\n        }),\n      })\n    ).json();\n\n    const { vanityName } = await (\n      await fetch('https://api.linkedin.com/v2/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    const {\n      name,\n      sub: id,\n      picture,\n    } = await (\n      await fetch('https://api.linkedin.com/v2/userinfo', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id,\n      accessToken,\n      refreshToken,\n      expiresIn: expires_in,\n      name,\n      picture,\n      username: vanityName,\n    };\n  }\n\n  override async addComment(\n    integration: Integration,\n    originalIntegration: Integration,\n    postId: string,\n    information: any,\n  ) {\n    return super.addComment(\n      integration,\n      originalIntegration,\n      postId,\n      information,\n      false\n    );\n  }\n\n  override async repostPostUsers(\n    integration: Integration,\n    originalIntegration: Integration,\n    postId: string,\n    information: any\n  ) {\n    return super.repostPostUsers(\n      integration,\n      originalIntegration,\n      postId,\n      information,\n      false\n    );\n  }\n\n  override async generateAuthUrl() {\n    const state = makeId(6);\n    const codeVerifier = makeId(30);\n    const url = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&prompt=none&client_id=${\n      process.env.LINKEDIN_CLIENT_ID\n    }&redirect_uri=${encodeURIComponent(\n      `${process.env.FRONTEND_URL}/integrations/social/linkedin-page`\n    )}&state=${state}&scope=${encodeURIComponent(this.scopes.join(' '))}`;\n    return {\n      url,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async companies(accessToken: string) {\n    const { elements, ...all } = await (\n      await fetch(\n        'https://api.linkedin.com/v2/organizationalEntityAcls?q=roleAssignee&role=ADMINISTRATOR&projection=(elements*(organizationalTarget~(localizedName,vanityName,logoV2(original~:playableStreams))))',\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'X-Restli-Protocol-Version': '2.0.0',\n            'LinkedIn-Version': '202601',\n          },\n        }\n      )\n    ).json();\n\n    return (elements || []).map((e: any) => ({\n      id: e.organizationalTarget.split(':').pop(),\n      page: e.organizationalTarget.split(':').pop(),\n      username: e['organizationalTarget~'].vanityName,\n      name: e['organizationalTarget~'].localizedName,\n      picture:\n        e['organizationalTarget~'].logoV2?.['original~']?.elements?.[0]\n          ?.identifiers?.[0]?.identifier,\n    }));\n  }\n\n  async reConnect(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {\n    const information = await this.fetchPageInformation(accessToken, {\n      page: requiredId,\n    });\n\n    return {\n      id: information.id,\n      name: information.name,\n      accessToken: information.access_token,\n      picture: information.picture,\n      username: information.username,\n    };\n  }\n\n  async fetchPageInformation(accessToken: string, params: { page: string }) {\n    const pageId = params.page;\n    const data = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizations/${pageId}?projection=(id,localizedName,vanityName,logoV2(original~:playableStreams))`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      )\n    ).json();\n\n    return {\n      id: data.id,\n      name: data.localizedName,\n      access_token: accessToken,\n      picture:\n        data?.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0].identifier,\n      username: data.vanityName,\n    };\n  }\n\n  override async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = new URLSearchParams();\n    body.append('grant_type', 'authorization_code');\n    body.append('code', params.code);\n    body.append(\n      'redirect_uri',\n      `${process.env.FRONTEND_URL}/integrations/social/linkedin-page`\n    );\n    body.append('client_id', process.env.LINKEDIN_CLIENT_ID!);\n    body.append('client_secret', process.env.LINKEDIN_CLIENT_SECRET!);\n\n    const {\n      access_token: accessToken,\n      expires_in: expiresIn,\n      refresh_token: refreshToken,\n      scope,\n    } = await (\n      await fetch('https://www.linkedin.com/oauth/v2/accessToken', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body,\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const {\n      name,\n      sub: id,\n      picture,\n    } = await (\n      await fetch('https://api.linkedin.com/v2/userinfo', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    const { vanityName } = await (\n      await fetch('https://api.linkedin.com/v2/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: `p_${id}`,\n      accessToken,\n      refreshToken,\n      expiresIn,\n      name,\n      picture,\n      username: vanityName,\n    };\n  }\n\n  override async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return super.post(id, accessToken, postDetails, integration, 'company');\n  }\n\n  override async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return super.comment(\n      id,\n      postId,\n      lastCommentId,\n      accessToken,\n      postDetails,\n      integration,\n      'company'\n    );\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const endDate = dayjs().unix() * 1000;\n    const startDate = dayjs().subtract(date, 'days').unix() * 1000;\n\n    const { elements }: { elements: Root[]; paging: any } = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizationPageStatistics?q=organization&organization=${encodeURIComponent(\n          `urn:li:organization:${id}`\n        )}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Linkedin-Version': '202601',\n            'X-Restli-Protocol-Version': '2.0.0',\n          },\n        }\n      )\n    ).json();\n\n    const { elements: elements2 }: { elements: Root[]; paging: any } = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(\n          `urn:li:organization:${id}`\n        )}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Linkedin-Version': '202601',\n            'X-Restli-Protocol-Version': '2.0.0',\n          },\n        }\n      )\n    ).json();\n\n    const { elements: elements3 }: { elements: Root[]; paging: any } = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(\n          `urn:li:organization:${id}`\n        )}&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Linkedin-Version': '202601',\n            'X-Restli-Protocol-Version': '2.0.0',\n          },\n        }\n      )\n    ).json();\n\n    const analytics = [...elements2, ...elements, ...elements3].reduce(\n      (all, current) => {\n        if (\n          typeof current?.totalPageStatistics?.views?.allPageViews\n            ?.pageViews !== 'undefined'\n        ) {\n          all['Page Views'].push({\n            total: current.totalPageStatistics.views.allPageViews.pageViews,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n        }\n\n        if (\n          typeof current?.followerGains?.organicFollowerGain !== 'undefined'\n        ) {\n          all['Organic Followers'].push({\n            total: current?.followerGains?.organicFollowerGain,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n        }\n\n        if (typeof current?.followerGains?.paidFollowerGain !== 'undefined') {\n          all['Paid Followers'].push({\n            total: current?.followerGains?.paidFollowerGain,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n        }\n\n        if (typeof current?.totalShareStatistics !== 'undefined') {\n          all['Clicks'].push({\n            total: current?.totalShareStatistics.clickCount,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n\n          all['Shares'].push({\n            total: current?.totalShareStatistics.shareCount,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n\n          all['Engagement'].push({\n            total: current?.totalShareStatistics.engagement,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n\n          all['Comments'].push({\n            total: current?.totalShareStatistics.commentCount,\n            date: dayjs(current.timeRange.start).format('YYYY-MM-DD'),\n          });\n        }\n\n        return all;\n      },\n      {\n        'Page Views': [] as any[],\n        Clicks: [] as any[],\n        Shares: [] as any[],\n        Engagement: [] as any[],\n        Comments: [] as any[],\n        'Organic Followers': [] as any[],\n        'Paid Followers': [] as any[],\n      }\n    );\n\n    return Object.keys(analytics).map((key) => ({\n      label: key,\n      data: analytics[\n        key as 'Page Views' | 'Organic Followers' | 'Paid Followers'\n      ],\n      percentageChange: 5,\n    }));\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const endDate = dayjs().unix() * 1000;\n    const startDate = dayjs().subtract(date, 'days').unix() * 1000;\n\n    // Fetch share statistics for the specific post\n    const shareStatsUrl = `https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=${encodeURIComponent(\n      `urn:li:organization:${integrationId}`\n    )}&shares=List(${encodeURIComponent(postId)})&timeIntervals=(timeRange:(start:${startDate},end:${endDate}),timeGranularityType:DAY)`;\n\n    const { elements: shareElements }: { elements: PostShareStatElement[] } =\n      await (\n        await this.fetch(shareStatsUrl, {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'LinkedIn-Version': '202601',\n            'X-Restli-Protocol-Version': '2.0.0',\n          },\n        })\n      ).json();\n\n    // Also fetch social actions (likes, comments, shares) for the specific post\n    let socialActions: SocialActionsResponse | null = null;\n    try {\n      const socialActionsUrl = `https://api.linkedin.com/v2/socialActions/${encodeURIComponent(\n        postId\n      )}`;\n      socialActions = await (\n        await this.fetch(socialActionsUrl, {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'LinkedIn-Version': '202601',\n            'X-Restli-Protocol-Version': '2.0.0',\n          },\n        })\n      ).json();\n    } catch (e) {\n      // Social actions may not be available for all posts\n    }\n\n    // Process share statistics into time series data\n    const analytics = (shareElements || []).reduce(\n      (all, current) => {\n        if (typeof current?.totalShareStatistics !== 'undefined') {\n          const dateStr = dayjs(current.timeRange.start).format('YYYY-MM-DD');\n\n          all['Impressions'].push({\n            total: current.totalShareStatistics.impressionCount || 0,\n            date: dateStr,\n          });\n\n          all['Unique Impressions'].push({\n            total: current.totalShareStatistics.uniqueImpressionsCount || 0,\n            date: dateStr,\n          });\n\n          all['Clicks'].push({\n            total: current.totalShareStatistics.clickCount || 0,\n            date: dateStr,\n          });\n\n          all['Likes'].push({\n            total: current.totalShareStatistics.likeCount || 0,\n            date: dateStr,\n          });\n\n          all['Comments'].push({\n            total: current.totalShareStatistics.commentCount || 0,\n            date: dateStr,\n          });\n\n          all['Shares'].push({\n            total: current.totalShareStatistics.shareCount || 0,\n            date: dateStr,\n          });\n\n          all['Engagement'].push({\n            total: current.totalShareStatistics.engagement || 0,\n            date: dateStr,\n          });\n        }\n        return all;\n      },\n      {\n        Impressions: [] as { total: number; date: string }[],\n        'Unique Impressions': [] as { total: number; date: string }[],\n        Clicks: [] as { total: number; date: string }[],\n        Likes: [] as { total: number; date: string }[],\n        Comments: [] as { total: number; date: string }[],\n        Shares: [] as { total: number; date: string }[],\n        Engagement: [] as { total: number; date: string }[],\n      }\n    );\n\n    // If no time series data but we have social actions, create a single data point\n    if (\n      Object.values(analytics).every((arr) => arr.length === 0) &&\n      socialActions\n    ) {\n      const today = dayjs().format('YYYY-MM-DD');\n      analytics['Likes'].push({\n        total: socialActions.likesSummary?.totalLikes || 0,\n        date: today,\n      });\n      analytics['Comments'].push({\n        total: socialActions.commentsSummary?.totalFirstLevelComments || 0,\n        date: today,\n      });\n    }\n\n    // Filter out empty analytics\n    const result = Object.entries(analytics)\n      .filter(([_, data]) => data.length > 0)\n      .map(([label, data]) => ({\n        label,\n        data,\n        percentageChange: 0,\n      }));\n\n    return result as any;\n  }\n\n  @Plug({\n    identifier: 'linkedin-page-autoRepostPost',\n    title: 'Auto Repost Posts',\n    description:\n      'When a post reached a certain number of likes, repost it to increase engagement (1 week old posts)',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n    ],\n  })\n  async autoRepostPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string }\n  ) {\n    const {\n      likesSummary: { totalLikes },\n    } = await (\n      await this.fetch(\n        `https://api.linkedin.com/v2/socialActions/${encodeURIComponent(id)}`,\n        {\n          method: 'GET',\n          headers: {\n            'X-Restli-Protocol-Version': '2.0.0',\n            'Content-Type': 'application/json',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${integration.token}`,\n          },\n        }\n      )\n    ).json();\n\n    if (totalLikes >= +fields.likesAmount) {\n      await timer(2000);\n      await this.fetch(`https://api.linkedin.com/rest/posts`, {\n        body: JSON.stringify({\n          author: `urn:li:organization:${integration.internalId}`,\n          commentary: '',\n          visibility: 'PUBLIC',\n          distribution: {\n            feedDistribution: 'MAIN_FEED',\n            targetEntities: [],\n            thirdPartyDistributionChannels: [],\n          },\n          lifecycleState: 'PUBLISHED',\n          isReshareDisabledByAuthor: false,\n          reshareContext: {\n            parent: id,\n          },\n        }),\n        method: 'POST',\n        headers: {\n          'X-Restli-Protocol-Version': '2.0.0',\n          'Content-Type': 'application/json',\n          'LinkedIn-Version': '202601',\n          Authorization: `Bearer ${integration.token}`,\n        },\n      });\n      return true;\n    }\n\n    return false;\n  }\n\n  @Plug({\n    identifier: 'linkedin-page-autoPlugPost',\n    title: 'Auto plug post',\n    description:\n      'When a post reached a certain number of likes, add another post to it so you followers get a notification about your promotion',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n      {\n        name: 'post',\n        type: 'richtext',\n        placeholder: 'Post to plug',\n        description: 'Message content to plug',\n        validation: /^[\\s\\S]{3,}$/g,\n      },\n    ],\n  })\n  async autoPlugPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string; post: string }\n  ) {\n    const {\n      likesSummary: { totalLikes },\n    } = await (\n      await this.fetch(\n        `https://api.linkedin.com/v2/socialActions/${encodeURIComponent(id)}`,\n        {\n          method: 'GET',\n          headers: {\n            'X-Restli-Protocol-Version': '2.0.0',\n            'Content-Type': 'application/json',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${integration.token}`,\n          },\n        }\n      )\n    ).json();\n\n    if (totalLikes >= fields.likesAmount) {\n      await timer(2000);\n      await this.fetch(\n        `https://api.linkedin.com/v2/socialActions/${decodeURIComponent(\n          id\n        )}/comments`,\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${integration.token}`,\n          },\n          body: JSON.stringify({\n            actor: `urn:li:organization:${integration.internalId}`,\n            object: id,\n            message: {\n              text: this.fixText(fields.post),\n            },\n          }),\n        }\n      );\n      return true;\n    }\n\n    return false;\n  }\n}\n\nexport interface Root {\n  pageStatisticsByIndustryV2: any[];\n  pageStatisticsBySeniority: any[];\n  organization: string;\n  pageStatisticsByGeoCountry: any[];\n  pageStatisticsByTargetedContent: any[];\n  totalPageStatistics: TotalPageStatistics;\n  pageStatisticsByStaffCountRange: any[];\n  pageStatisticsByFunction: any[];\n  pageStatisticsByGeo: any[];\n  followerGains: { organicFollowerGain: number; paidFollowerGain: number };\n  timeRange: TimeRange;\n  totalShareStatistics: {\n    uniqueImpressionsCount: number;\n    shareCount: number;\n    engagement: number;\n    clickCount: number;\n    likeCount: number;\n    impressionCount: number;\n    commentCount: number;\n  };\n}\n\nexport interface TotalPageStatistics {\n  clicks: Clicks;\n  views: Views;\n}\n\nexport interface Clicks {\n  mobileCustomButtonClickCounts: any[];\n  desktopCustomButtonClickCounts: any[];\n}\n\nexport interface Views {\n  mobileProductsPageViews: MobileProductsPageViews;\n  allDesktopPageViews: AllDesktopPageViews;\n  insightsPageViews: InsightsPageViews;\n  mobileAboutPageViews: MobileAboutPageViews;\n  allMobilePageViews: AllMobilePageViews;\n  productsPageViews: ProductsPageViews;\n  desktopProductsPageViews: DesktopProductsPageViews;\n  jobsPageViews: JobsPageViews;\n  peoplePageViews: PeoplePageViews;\n  overviewPageViews: OverviewPageViews;\n  mobileOverviewPageViews: MobileOverviewPageViews;\n  lifeAtPageViews: LifeAtPageViews;\n  desktopOverviewPageViews: DesktopOverviewPageViews;\n  mobileCareersPageViews: MobileCareersPageViews;\n  allPageViews: AllPageViews;\n  careersPageViews: CareersPageViews;\n  mobileJobsPageViews: MobileJobsPageViews;\n  mobileLifeAtPageViews: MobileLifeAtPageViews;\n  desktopJobsPageViews: DesktopJobsPageViews;\n  desktopPeoplePageViews: DesktopPeoplePageViews;\n  aboutPageViews: AboutPageViews;\n  desktopAboutPageViews: DesktopAboutPageViews;\n  mobilePeoplePageViews: MobilePeoplePageViews;\n  desktopCareersPageViews: DesktopCareersPageViews;\n  desktopInsightsPageViews: DesktopInsightsPageViews;\n  desktopLifeAtPageViews: DesktopLifeAtPageViews;\n  mobileInsightsPageViews: MobileInsightsPageViews;\n}\n\nexport interface MobileProductsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface AllDesktopPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface InsightsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileAboutPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface AllMobilePageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface ProductsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopProductsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface JobsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface PeoplePageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface OverviewPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileOverviewPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface LifeAtPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopOverviewPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileCareersPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface AllPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface CareersPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileJobsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileLifeAtPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopJobsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopPeoplePageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface AboutPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopAboutPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobilePeoplePageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopCareersPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopInsightsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface DesktopLifeAtPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface MobileInsightsPageViews {\n  pageViews: number;\n  uniquePageViews: number;\n}\n\nexport interface TimeRange {\n  start: number;\n  end: number;\n}\n\n// Post analytics interfaces\nexport interface PostShareStatElement {\n  organizationalEntity: string;\n  share: string;\n  totalShareStatistics: {\n    uniqueImpressionsCount: number;\n    shareCount: number;\n    engagement: number;\n    clickCount: number;\n    likeCount: number;\n    impressionCount: number;\n    commentCount: number;\n  };\n  timeRange: TimeRange;\n}\n\nexport interface SocialActionsResponse {\n  likesSummary?: {\n    totalLikes: number;\n    likedByCurrentUser: boolean;\n  };\n  commentsSummary?: {\n    totalFirstLevelComments: number;\n    commentsState: string;\n  };\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport sharp from 'sharp';\nimport { lookup } from 'mime-types';\nimport { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { Integration } from '@prisma/client';\nimport { PostPlug } from '@gitroom/helpers/decorators/post.plug';\nimport { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto';\nimport imageToPDF from 'image-to-pdf';\nimport { Readable } from 'stream';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  'LinkedIn can have maximum one attachment when selecting video, when choosing a carousel on LinkedIn minimum amount of attachment must be two, and only pictures, if uploading a video, LinkedIn can have only one attachment'\n)\nexport class LinkedinProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'linkedin';\n  name = 'LinkedIn';\n  oneTimeToken = true;\n\n  isBetweenSteps = false;\n  scopes = [\n    'openid',\n    'profile',\n    'w_member_social',\n    'r_basicprofile',\n    'rw_organization_admin',\n    'w_organization_social',\n    'r_organization_social',\n  ];\n  override maxConcurrentJob = 2; // LinkedIn has professional posting limits\n  refreshWait = true;\n  editor = 'normal' as const;\n  maxLength() {\n    return 3000;\n  }\n\n  override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    if (body.indexOf('Unable to obtain activity') > -1) {\n      return {\n        type: 'retry',\n        value: 'Unable to obtain activity',\n      };\n    }\n\n    if (body.indexOf('resource is forbidden') > -1) {\n      return {\n        type: 'retry',\n        value: 'Resource is forbidden',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    const {\n      access_token: accessToken,\n      refresh_token: refreshToken,\n      expires_in,\n    } = await (\n      await fetch('https://www.linkedin.com/oauth/v2/accessToken', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token,\n          client_id: process.env.LINKEDIN_CLIENT_ID!,\n          client_secret: process.env.LINKEDIN_CLIENT_SECRET!,\n        }),\n      })\n    ).json();\n\n    const { vanityName } = await (\n      await fetch('https://api.linkedin.com/v2/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    const {\n      name,\n      sub: id,\n      picture,\n    } = await (\n      await fetch('https://api.linkedin.com/v2/userinfo', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id,\n      accessToken,\n      refreshToken,\n      expiresIn: expires_in,\n      name,\n      picture: picture || '',\n      username: vanityName,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    const codeVerifier = makeId(30);\n    const url = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${\n      process.env.LINKEDIN_CLIENT_ID\n    }&prompt=none&redirect_uri=${encodeURIComponent(\n      `${process.env.FRONTEND_URL}/integrations/social/linkedin`\n    )}&state=${state}&scope=${encodeURIComponent(this.scopes.join(' '))}`;\n    return {\n      url,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = new URLSearchParams();\n    body.append('grant_type', 'authorization_code');\n    body.append('code', params.code);\n    body.append(\n      'redirect_uri',\n      `${process.env.FRONTEND_URL}/integrations/social/linkedin${\n        params.refresh ? `?refresh=${params.refresh}` : ''\n      }`\n    );\n    body.append('client_id', process.env.LINKEDIN_CLIENT_ID!);\n    body.append('client_secret', process.env.LINKEDIN_CLIENT_SECRET!);\n\n    const {\n      access_token: accessToken,\n      expires_in: expiresIn,\n      refresh_token: refreshToken,\n      scope,\n    } = await (\n      await fetch('https://www.linkedin.com/oauth/v2/accessToken', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body,\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const {\n      name,\n      sub: id,\n      picture,\n    } = await (\n      await fetch('https://api.linkedin.com/v2/userinfo', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    const { vanityName } = await (\n      await fetch('https://api.linkedin.com/v2/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id,\n      accessToken,\n      refreshToken,\n      expiresIn,\n      name,\n      picture,\n      username: vanityName,\n    };\n  }\n\n  async company(token: string, data: { url: string }) {\n    const { url } = data;\n    const getCompanyVanity = url.match(\n      /^https?:\\/\\/(?:www\\.)?linkedin\\.com\\/company\\/([^/]+)\\/?$/\n    );\n    if (!getCompanyVanity || !getCompanyVanity?.length) {\n      throw new Error('Invalid LinkedIn company URL');\n    }\n\n    const { elements } = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizations?q=vanityName&vanityName=${getCompanyVanity[1]}`,\n        {\n          method: 'GET',\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Restli-Protocol-Version': '2.0.0',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${token}`,\n          },\n        }\n      )\n    ).json();\n\n    return {\n      options: elements.map((e: { localizedName: string; id: string }) => ({\n        label: e.localizedName,\n        value: `@[${e.localizedName}](urn:li:organization:${e.id})`,\n      }))?.[0],\n    };\n  }\n\n  protected async uploadPicture(\n    fileName: string,\n    accessToken: string,\n    personId: string,\n    picture: any,\n    type = 'personal' as 'company' | 'personal'\n  ) {\n    // Determine the appropriate endpoint based on file type\n    const isVideo = fileName.indexOf('mp4') > -1;\n    const isPdf = fileName.toLowerCase().indexOf('pdf') > -1;\n\n    let endpoint: string;\n    if (isVideo) {\n      endpoint = 'videos';\n    } else if (isPdf) {\n      endpoint = 'documents';\n    } else {\n      endpoint = 'images';\n    }\n\n    const {\n      value: { uploadUrl, image, video, document, uploadInstructions, ...all },\n    } = await (\n      await this.fetch(\n        `https://api.linkedin.com/rest/${endpoint}?action=initializeUpload`,\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Restli-Protocol-Version': '2.0.0',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: JSON.stringify({\n            initializeUploadRequest: {\n              owner:\n                type === 'personal'\n                  ? `urn:li:person:${personId}`\n                  : `urn:li:organization:${personId}`,\n              ...(isVideo\n                ? {\n                    fileSizeBytes: picture.length,\n                    uploadCaptions: false,\n                    uploadThumbnail: false,\n                  }\n                : {}),\n            },\n          }),\n        }\n      )\n    ).json();\n\n    const sendUrlRequest = uploadInstructions?.[0]?.uploadUrl || uploadUrl;\n    const finalOutput = video || image || document;\n\n    const etags = [];\n    for (let i = 0; i < picture.length; i += 1024 * 1024 * 2) {\n      const upload = await this.fetch(\n        sendUrlRequest,\n        {\n          method: 'PUT',\n          headers: {\n            'X-Restli-Protocol-Version': '2.0.0',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${accessToken}`,\n            ...(isVideo\n              ? { 'Content-Type': 'application/octet-stream' }\n              : isPdf\n              ? { 'Content-Type': 'application/pdf' }\n              : {}),\n          },\n          body: picture.slice(i, i + 1024 * 1024 * 2),\n        },\n        'linkedin',\n        0,\n        true\n      );\n\n      etags.push(upload.headers.get('etag'));\n    }\n\n    if (isVideo) {\n      const a = await this.fetch(\n        'https://api.linkedin.com/rest/videos?action=finalizeUpload',\n        {\n          method: 'POST',\n          body: JSON.stringify({\n            finalizeUploadRequest: {\n              video,\n              uploadToken: '',\n              uploadedPartIds: etags,\n            },\n          }),\n          headers: {\n            'X-Restli-Protocol-Version': '2.0.0',\n            'LinkedIn-Version': '202601',\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n    }\n\n    return finalOutput;\n  }\n\n  protected fixText(text: string) {\n    const pattern = /@\\[.+?]\\(urn:li:organization.+?\\)/g;\n    const matches = text.match(pattern) || [];\n    const splitAll = text.split(pattern);\n    const splitTextReformat = splitAll.map((p) => {\n      return p\n        .replace(/\\\\/g, '\\\\\\\\')\n        .replace(/</g, '\\\\<')\n        .replace(/>/g, '\\\\>')\n        .replace(/#/g, '\\\\#')\n        .replace(/~/g, '\\\\~')\n        .replace(/_/g, '\\\\_')\n        .replace(/\\|/g, '\\\\|')\n        .replace(/\\[/g, '\\\\[')\n        .replace(/]/g, '\\\\]')\n        .replace(/\\*/g, '\\\\*')\n        .replace(/\\(/g, '\\\\(')\n        .replace(/\\)/g, '\\\\)')\n        .replace(/\\{/g, '\\\\{')\n        .replace(/}/g, '\\\\}')\n        .replace(/@/g, '\\\\@');\n    });\n\n    const connectAll = splitTextReformat.reduce((all, current) => {\n      const match = matches.shift();\n      all.push(current);\n      if (match) {\n        all.push(match);\n      }\n      return all;\n    }, [] as string[]);\n\n    return connectAll.join('');\n  }\n\n  private async convertImagesToPdfCarousel(\n    postDetails: PostDetails<LinkedinDto>[],\n    firstPost: PostDetails<LinkedinDto>\n  ): Promise<PostDetails<LinkedinDto>[]> {\n    if (!firstPost.media?.length) {\n      return postDetails;\n    }\n\n    // Fetch all images and get their dimensions\n    const images = await Promise.all(\n      firstPost.media.map(async (media) => {\n        const raw = await readOrFetch(media.path);\n        const image = sharp(raw, { animated: false }).toFormat('jpeg');\n        const { width, height } = await image.metadata();\n        const buffer = await image.toBuffer();\n        return { buffer, width: width || 0, height: height || 0 };\n      })\n    );\n\n    // Find the largest image by area to use as the PDF page size\n    const largest = images.reduce((max, img) =>\n      img.width * img.height > max.width * max.height ? img : max\n    );\n\n    const imageBuffers = images.map((img) => img.buffer);\n\n    // Create a PDF sized to the largest image; it fills the page,\n    // smaller images are fitted and centered within the same dimensions\n    const pdfStream = imageToPDF(\n      imageBuffers,\n      [largest.width, largest.height]\n    ) as unknown as Readable;\n    const pdfBuffer = await this.streamToBuffer(pdfStream);\n\n    // Replace the first post's media with the single PDF\n    const [first, ...rest] = postDetails;\n    return [\n      {\n        ...first,\n        media: [\n          {\n            type: 'image' as const,\n            path: 'carousel.pdf',\n            buffer: pdfBuffer,\n          } as any,\n        ],\n      },\n      ...rest,\n    ];\n  }\n\n  private async streamToBuffer(stream: Readable): Promise<Buffer> {\n    return new Promise((resolve, reject) => {\n      const chunks: Buffer[] = [];\n      stream.on('data', (chunk) => chunks.push(chunk));\n      stream.on('end', () => resolve(Buffer.concat(chunks)));\n      stream.on('error', reject);\n    });\n  }\n\n  private async processMediaForPosts(\n    postDetails: PostDetails<LinkedinDto>[],\n    accessToken: string,\n    personId: string,\n    type: 'company' | 'personal'\n  ): Promise<Record<string, string[]>> {\n    const mediaUploads = await Promise.all(\n      postDetails.flatMap(\n        (post) =>\n          post.media?.map(async (media) => {\n            let mediaBuffer: Buffer;\n\n            // Check if media has a buffer (from PDF conversion)\n            if (\n              media &&\n              typeof media === 'object' &&\n              'buffer' in media &&\n              Buffer.isBuffer(media.buffer)\n            ) {\n              mediaBuffer = (media as any).buffer;\n            } else {\n              mediaBuffer = await this.prepareMediaBuffer(media.path);\n            }\n\n            const uploadedMediaId = await this.uploadPicture(\n              media.path,\n              accessToken,\n              personId,\n              mediaBuffer,\n              type\n            );\n\n            return {\n              id: uploadedMediaId,\n              postId: post.id,\n            };\n          }) || []\n      )\n    );\n\n    return mediaUploads.reduce((acc, upload) => {\n      if (!upload?.id) return acc;\n\n      acc[upload.postId] = acc[upload.postId] || [];\n      acc[upload.postId].push(upload.id);\n      return acc;\n    }, {} as Record<string, string[]>);\n  }\n\n  private async prepareMediaBuffer(mediaUrl: string): Promise<Buffer> {\n    const isVideo = mediaUrl.indexOf('mp4') > -1;\n\n    if (isVideo) {\n      return Buffer.from(await readOrFetch(mediaUrl));\n    }\n\n    return await sharp(await readOrFetch(mediaUrl), {\n      animated: lookup(mediaUrl) === 'image/gif',\n    })\n      .toFormat('jpeg')\n      .resize({ width: 1000 })\n      .toBuffer();\n  }\n\n  private buildPostContent(isPdf: boolean, mediaIds: string[], pdfTitle?: string) {\n    if (mediaIds.length === 0) {\n      return {};\n    }\n\n    if (mediaIds.length === 1) {\n      return {\n        content: {\n          media: {\n            ...(isPdf ? { title: pdfTitle || 'slides' } : {}),\n            id: mediaIds[0],\n          },\n        },\n      };\n    }\n\n    return {\n      content: {\n        multiImage: {\n          images: mediaIds.map((id) => ({ id })),\n        },\n      },\n    };\n  }\n\n  private createLinkedInPostPayload(\n    id: string,\n    type: 'company' | 'personal',\n    message: string,\n    mediaIds: string[],\n    isPdf: boolean,\n    pdfTitle?: string\n  ) {\n    const author =\n      type === 'personal' ? `urn:li:person:${id}` : `urn:li:organization:${id}`;\n\n    return {\n      author,\n      commentary: this.fixText(message),\n      visibility: 'PUBLIC',\n      distribution: {\n        feedDistribution: 'MAIN_FEED',\n        targetEntities: [] as string[],\n        thirdPartyDistributionChannels: [] as string[],\n      },\n      ...this.buildPostContent(isPdf, mediaIds, pdfTitle),\n      lifecycleState: 'PUBLISHED',\n      isReshareDisabledByAuthor: false,\n    };\n  }\n\n  private async createMainPost(\n    id: string,\n    accessToken: string,\n    firstPost: PostDetails<LinkedinDto>,\n    mediaIds: string[],\n    type: 'company' | 'personal',\n    isPdf: boolean\n  ): Promise<string> {\n    const pdfTitle = isPdf\n      ? firstPost.settings?.carousel_name || 'slides'\n      : undefined;\n\n    const postPayload = this.createLinkedInPostPayload(\n      id,\n      type,\n      firstPost.message,\n      mediaIds,\n      isPdf,\n      pdfTitle\n    );\n\n    const response = await this.fetch(`https://api.linkedin.com/rest/posts`, {\n      method: 'POST',\n      headers: {\n        'LinkedIn-Version': '202601',\n        'X-Restli-Protocol-Version': '2.0.0',\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${accessToken}`,\n      },\n      body: JSON.stringify(postPayload),\n    });\n\n    if (response.status !== 201 && response.status !== 200) {\n      throw new Error('Error posting to LinkedIn');\n    }\n\n    return response.headers.get('x-restli-id')!;\n  }\n\n  private async createCommentPost(\n    id: string,\n    accessToken: string,\n    post: PostDetails,\n    parentPostId: string,\n    type: 'company' | 'personal'\n  ): Promise<string> {\n    const actor =\n      type === 'personal' ? `urn:li:person:${id}` : `urn:li:organization:${id}`;\n\n    const response = await this.fetch(\n      `https://api.linkedin.com/v2/socialActions/${encodeURIComponent(\n        parentPostId\n      )}/comments`,\n      {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: `Bearer ${accessToken}`,\n        },\n        body: JSON.stringify({\n          actor,\n          object: parentPostId,\n          message: {\n            text: this.fixText(post.message),\n          },\n        }),\n      }\n    );\n\n    const { object } = await response.json();\n    return object;\n  }\n\n  private createPostResponse(\n    postId: string,\n    originalPostId: string,\n    isMainPost: boolean = false\n  ): PostResponse {\n    const baseUrl = isMainPost\n      ? 'https://www.linkedin.com/feed/update/'\n      : 'https://www.linkedin.com/embed/feed/update/';\n\n    return {\n      status: 'posted',\n      postId,\n      id: originalPostId,\n      releaseURL: `${baseUrl}${postId}`,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<LinkedinDto>[],\n    integration: Integration,\n    type = 'personal' as 'company' | 'personal'\n  ): Promise<PostResponse[]> {\n    let processedPostDetails = postDetails;\n    const [firstPost] = postDetails;\n\n    // Check if we should convert images to PDF carousel\n    if (firstPost.settings?.post_as_images_carousel) {\n      processedPostDetails = await this.convertImagesToPdfCarousel(\n        postDetails,\n        firstPost\n      );\n    }\n\n    const [processedFirstPost] = processedPostDetails;\n\n    // Process and upload media for the first post only\n    const uploadedMedia = await this.processMediaForPosts(\n      [processedFirstPost],\n      accessToken,\n      id,\n      type\n    );\n\n    // Get media IDs for the main post\n    const mainPostMediaIds = (\n      uploadedMedia[processedFirstPost.id] || []\n    ).filter(Boolean);\n\n    // Create the main LinkedIn post\n    const mainPostId = await this.createMainPost(\n      id,\n      accessToken,\n      processedFirstPost,\n      mainPostMediaIds,\n      type,\n      !!firstPost.settings?.post_as_images_carousel\n    );\n\n    // Return response for main post only\n    return [this.createPostResponse(mainPostId, processedFirstPost.id, true)];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<LinkedinDto>[],\n    integration: Integration,\n    type = 'personal' as 'company' | 'personal'\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n\n    const commentPostId = await this.createCommentPost(\n      id,\n      accessToken,\n      commentPost,\n      postId,\n      type\n    );\n\n    return [this.createPostResponse(commentPostId, commentPost.id, false)];\n  }\n\n  @PostPlug({\n    identifier: 'linkedin-add-comment',\n    title: 'Add comments by a different account',\n    description: 'Add accounts to comment on your post',\n    pickIntegration: ['linkedin', 'linkedin-page'],\n    fields: [\n      {\n        name: 'comment',\n        description: 'The comment to add to the post',\n        type: 'textarea',\n        placeholder: 'Enter your comment here',\n      },\n    ],\n  })\n  async addComment(\n    integration: Integration,\n    originalIntegration: Integration,\n    postId: string,\n    information: any,\n    isPersonal = true\n  ) {\n    return this.comment(\n      integration.internalId,\n      postId,\n      undefined,\n      integration.token,\n      [\n        {\n          id: makeId(10),\n          message: information.comment,\n          media: [],\n          settings: {\n            post_as_images_carousel: false,\n          },\n        },\n      ],\n      integration,\n      isPersonal ? 'personal' : 'company'\n    );\n  }\n\n  @PostPlug({\n    identifier: 'linkedin-repost-post-users',\n    title: 'Add Re-posters',\n    description: 'Add accounts to repost your post',\n    pickIntegration: ['linkedin', 'linkedin-page'],\n    fields: [],\n  })\n  async repostPostUsers(\n    integration: Integration,\n    originalIntegration: Integration,\n    postId: string,\n    information: any,\n    isPersonal = true\n  ) {\n    await this.fetch(`https://api.linkedin.com/rest/posts`, {\n      body: JSON.stringify({\n        author:\n          (isPersonal ? 'urn:li:person:' : `urn:li:organization:`) +\n          `${integration.internalId}`,\n        commentary: '',\n        visibility: 'PUBLIC',\n        distribution: {\n          feedDistribution: 'MAIN_FEED',\n          targetEntities: [],\n          thirdPartyDistributionChannels: [],\n        },\n        lifecycleState: 'PUBLISHED',\n        isReshareDisabledByAuthor: false,\n        reshareContext: {\n          parent: postId,\n        },\n      }),\n      method: 'POST',\n      headers: {\n        'X-Restli-Protocol-Version': '2.0.0',\n        'Content-Type': 'application/json',\n        'LinkedIn-Version': '202601',\n        Authorization: `Bearer ${integration.token}`,\n      },\n    });\n  }\n\n  override async mention(token: string, data: { query: string }) {\n    const { elements } = await (\n      await fetch(\n        `https://api.linkedin.com/v2/organizations?q=vanityName&vanityName=${encodeURIComponent(\n          data.query\n        )}&projection=(elements*(id,localizedName,logoV2(original~:playableStreams)))`,\n        {\n          headers: {\n            'X-Restli-Protocol-Version': '2.0.0',\n            'Content-Type': 'application/json',\n            'LinkedIn-Version': '202601',\n            Authorization: `Bearer ${token}`,\n          },\n        }\n      )\n    ).json();\n\n    return elements.map((p: any) => ({\n      id: String(p.id),\n      label: p.localizedName,\n      image:\n        p.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0]?.identifier ||\n        '',\n    }));\n  }\n\n  mentionFormat(idOrHandle: string, name: string) {\n    return `@[${name.replace('@', '')}](urn:li:organization:${idOrHandle})`;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/listmonk.provider.ts",
    "content": "import { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '../social.abstract';\nimport {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from './social.integrations.interface';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { ListmonkDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/listmonk.dto';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport slugify from 'slugify';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class ListmonkProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 100; // Bluesky has moderate rate limits\n  identifier = 'listmonk';\n  name = 'ListMonk';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'html' as const;\n  dto = ListmonkDto;\n\n  maxLength() {\n    return 100000000;\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'url',\n        label: 'URL',\n        defaultValue: '',\n        validation: `/^(https?:\\\\/\\\\/)(?:\\\\S+(?::\\\\S*)?@)?(?:(?:localhost)|(?:\\\\d{1,3}(?:\\\\.\\\\d{1,3}){3})|(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\\\.)+[a-z]{2,63})(?::\\\\d{2,5})?(?:\\\\/[^\\\\s?#]*)?(?:\\\\?[^\\\\s#]*)?(?:#[^\\\\s]*)?$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'username',\n        label: 'Username',\n        validation: `/^.+$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'password',\n        label: 'Password',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body: { url: string; username: string; password: string } =\n      JSON.parse(Buffer.from(params.code, 'base64').toString());\n\n    console.log(body);\n    try {\n      const basic = Buffer.from(body.username + ':' + body.password).toString(\n        'base64'\n      );\n\n      const { data } = await (\n        await this.fetch(body.url + '/api/settings', {\n          headers: {\n            'Content-Type': 'application/json',\n            Accept: 'application/json',\n            Authorization: 'Basic ' + basic,\n          },\n        })\n      ).json();\n\n      return {\n        refreshToken: basic,\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: basic,\n        id: Buffer.from(body.url).toString('base64'),\n        name: data['app.site_name'],\n        picture: data['app.logo_url'] || '',\n        username: data['app.site_name'],\n      };\n    } catch (e) {\n      console.log(e);\n      return 'Invalid credentials';\n    }\n  }\n\n  @Tool({ description: 'List of available lists', dataSchema: [] })\n  async list(\n    token: string,\n    data: any,\n    internalId: string,\n    integration: Integration\n  ) {\n    const body: { url: string; username: string; password: string } =\n      JSON.parse(\n        AuthService.fixedDecryption(integration.customInstanceDetails!)\n      );\n\n    const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n      'base64'\n    );\n\n    const postTypes = await (\n      await this.fetch(`${body.url}/api/lists`, {\n        headers: {\n          Authorization: `Basic ${auth}`,\n        },\n      })\n    ).json();\n\n    return postTypes.data.results.map((p: any) => ({ id: p.id, name: p.name }));\n  }\n\n  @Tool({ description: 'List of available templates', dataSchema: [] })\n  async templates(\n    token: string,\n    data: any,\n    internalId: string,\n    integration: Integration\n  ) {\n    const body: { url: string; username: string; password: string } =\n      JSON.parse(\n        AuthService.fixedDecryption(integration.customInstanceDetails!)\n      );\n\n    const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n      'base64'\n    );\n\n    const postTypes = await (\n      await this.fetch(`${body.url}/api/templates`, {\n        headers: {\n          Authorization: `Basic ${auth}`,\n        },\n      })\n    ).json();\n\n    return [\n      { id: 0, name: 'Default' },\n      ...postTypes.data.map((p: any) => ({ id: p.id, name: p.name })),\n    ];\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<ListmonkDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const body: { url: string; username: string; password: string } =\n      JSON.parse(\n        AuthService.fixedDecryption(integration.customInstanceDetails!)\n      );\n\n    const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n      'base64'\n    );\n\n    const sendBody = `\n<style>\n.content {\n  padding: 20px;\n  font-size: 15px;\n  line-height: 1.6;\n}\n</style>\n<div class=\"hidden-preheader\"\n       style=\"display:none !important; visibility:hidden; opacity:0; overflow:hidden;\n              max-height:0; max-width:0; line-height:1px; font-size:1px; color:transparent;\n              mso-hide:all;\">\n    <!-- A short visible decoy (optional): shows as \".\" or short text in preview -->\n    ${postDetails?.[0]?.settings?.preview || ''}\n    <!-- Then invisible padding to eat up preview characters -->\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    <!-- Repeat the trio (zero-width space, zero-width non-joiner, nbsp, BOM) a bunch of times -->\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n    &#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;&#8203;&zwnj;&nbsp;&#65279;\n  </div>\n  \n  <div class=\"content\">\n    ${postDetails[0].message}\n  </div>\n`;\n\n    const {\n      data: { uuid: postId, id: campaignId },\n    } = await (\n      await this.fetch(body.url + '/api/campaigns', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Accept: 'application/json',\n          Authorization: `Basic ${auth}`,\n        },\n        body: JSON.stringify({\n          name: slugify(postDetails[0].settings.subject, {\n            lower: true,\n            strict: true,\n            trim: true,\n          }),\n          type: 'regular',\n          content_type: 'html',\n          subject: postDetails[0].settings.subject,\n          lists: [+postDetails[0].settings.list],\n          body: sendBody,\n          ...(+postDetails?.[0]?.settings?.template\n            ? { template_id: +postDetails[0].settings.template }\n            : {}),\n        }),\n      })\n    ).json();\n\n    await this.fetch(body.url + `/api/campaigns/${campaignId}/status`, {\n      method: 'PUT',\n      headers: {\n        'Content-Type': 'application/json',\n        Accept: 'application/json',\n        Authorization: `Basic ${auth}`,\n      },\n      body: JSON.stringify({\n        status: 'running',\n      }),\n    });\n\n    return [\n      {\n        id: postDetails[0].id,\n        status: 'completed',\n        releaseURL: `${body.url}/api/campaigns/${campaignId}/preview`,\n        postId,\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/mastodon.custom.provider.ts",
    "content": "import {\n  ClientInformation,\n  PostDetails,\n  PostResponse,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { Integration } from '@prisma/client';\n\nexport class MastodonCustomProvider extends MastodonProvider {\n  override identifier = 'mastodon-custom';\n  override name = 'M. Instance';\n  override maxConcurrentJob = 5; // Custom Mastodon instances typically have generous limits\n  editor = 'normal' as const;\n\n  async externalUrl(url: string) {\n    const form = new FormData();\n    form.append('client_name', 'Postiz');\n    form.append(\n      'redirect_uris',\n      `${process.env.FRONTEND_URL}/integrations/social/mastodon`\n    );\n    form.append('scopes', this.scopes.join(' '));\n    form.append('website', process.env.FRONTEND_URL!);\n    const { client_id, client_secret, ...all } = await (\n      await fetch(url + '/api/v1/apps', {\n        method: 'POST',\n        body: form,\n      })\n    ).json();\n\n    return {\n      client_id,\n      client_secret,\n    };\n  }\n  override async generateAuthUrl(\n    refresh?: string,\n    external?: ClientInformation\n  ) {\n    const state = makeId(6);\n    const url = this.generateUrlDynamic(\n      external?.instanceUrl!,\n      state,\n      external?.client_id!,\n      process.env.FRONTEND_URL!,\n      refresh\n    );\n\n    return {\n      url,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  override async authenticate(\n    params: {\n      code: string;\n      codeVerifier: string;\n      refresh?: string;\n    },\n    clientInformation?: ClientInformation\n  ) {\n    return this.dynamicAuthenticate(\n      clientInformation?.client_id!,\n      clientInformation?.client_secret!,\n      clientInformation?.instanceUrl!,\n      params.code\n    );\n  }\n\n  override async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    return this.dynamicPost(\n      id,\n      accessToken,\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      postDetails\n    );\n  }\n\n  override async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return this.dynamicComment(\n      id,\n      postId,\n      lastCommentId,\n      accessToken,\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      postDetails\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\n\nexport class MastodonProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 5; // Mastodon instances typically have generous limits\n  identifier = 'mastodon';\n  name = 'Mastodon';\n  isBetweenSteps = false;\n  scopes = ['write:statuses', 'profile', 'write:media'];\n  editor = 'normal' as const;\n  maxLength() {\n    return 500;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n  protected generateUrlDynamic(\n    customUrl: string,\n    state: string,\n    clientId: string,\n    url: string\n  ) {\n    return `${customUrl}/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${encodeURIComponent(\n      `${url}/integrations/social/mastodon`\n    )}&scope=${this.scopes.join('+')}&state=${state}`;\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    const url = this.generateUrlDynamic(\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      state,\n      process.env.MASTODON_CLIENT_ID!,\n      process.env.FRONTEND_URL!\n    );\n    return {\n      url,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  protected async dynamicAuthenticate(\n    clientId: string,\n    clientSecret: string,\n    url: string,\n    code: string\n  ) {\n    const form = new FormData();\n    form.append('client_id', clientId);\n    form.append('client_secret', clientSecret);\n    form.append('code', code);\n    form.append('grant_type', 'authorization_code');\n    form.append(\n      'redirect_uri',\n      `${process.env.FRONTEND_URL}/integrations/social/mastodon`\n    );\n    form.append('scope', this.scopes.join(' '));\n\n    const tokenInformation = await (\n      await this.fetch(`${url}/oauth/token`, {\n        method: 'POST',\n        body: form,\n      })\n    ).json();\n\n    const personalInformation = await (\n      await this.fetch(`${url}/api/v1/accounts/verify_credentials`, {\n        headers: {\n          Authorization: `Bearer ${tokenInformation.access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: personalInformation.id,\n      name: personalInformation.display_name || personalInformation.acct,\n      accessToken: tokenInformation.access_token,\n      refreshToken: 'null',\n      expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n      picture: personalInformation?.avatar || '',\n      username: personalInformation.username,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    return this.dynamicAuthenticate(\n      process.env.MASTODON_CLIENT_ID!,\n      process.env.MASTODON_CLIENT_SECRET!,\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      params.code\n    );\n  }\n\n  async uploadFile(instanceUrl: string, fileUrl: string, accessToken: string) {\n    const form = new FormData();\n    form.append('file', await fetch(fileUrl).then((r) => r.blob()));\n    const media = await (\n      await this.fetch(`${instanceUrl}/api/v1/media`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n        body: form,\n      })\n    ).json();\n    return media.id;\n  }\n\n  async dynamicPost(\n    id: string,\n    accessToken: string,\n    url: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n\n    const uploadFiles = await Promise.all(\n      firstPost?.media?.map((media) =>\n        this.uploadFile(url, media.path, accessToken)\n      ) || []\n    );\n\n    const form = new FormData();\n    form.append('status', firstPost.message);\n    form.append('visibility', 'public');\n    if (uploadFiles.length) {\n      for (const file of uploadFiles) {\n        form.append('media_ids[]', file);\n      }\n    }\n\n    const post = await (\n      await this.fetch(`${url}/api/v1/statuses`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n        body: form,\n      })\n    ).json();\n\n    return [\n      {\n        id: firstPost.id,\n        postId: post.id,\n        releaseURL: `${url}/statuses/${post.id}`,\n        status: 'completed',\n      },\n    ];\n  }\n\n  async dynamicComment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    url: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const replyToId = lastCommentId || postId;\n\n    const uploadFiles = await Promise.all(\n      commentPost?.media?.map((media) =>\n        this.uploadFile(url, media.path, accessToken)\n      ) || []\n    );\n\n    const form = new FormData();\n    form.append('status', commentPost.message);\n    form.append('visibility', 'public');\n    form.append('in_reply_to_id', replyToId);\n    if (uploadFiles.length) {\n      for (const file of uploadFiles) {\n        form.append('media_ids[]', file);\n      }\n    }\n\n    const post = await (\n      await this.fetch(`${url}/api/v1/statuses`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n        body: form,\n      })\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: post.id,\n        releaseURL: `${url}/statuses/${post.id}`,\n        status: 'completed',\n      },\n    ];\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    return this.dynamicPost(\n      id,\n      accessToken,\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      postDetails\n    );\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    return this.dynamicComment(\n      id,\n      postId,\n      lastCommentId,\n      accessToken,\n      process.env.MASTODON_URL || 'https://mastodon.social',\n      postDetails\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/medium.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class MediumProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Medium has lenient publishing limits\n  identifier = 'medium';\n  name = 'Medium';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'markdown' as const;\n  dto = MediumSettingsDto;\n  maxLength() {\n    return 100000;\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'apiKey',\n        label: 'API key',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n    try {\n      const {\n        data: { name, id, imageUrl, username },\n      } = await (\n        await fetch('https://api.medium.com/v1/me', {\n          headers: {\n            Authorization: `Bearer ${body.apiKey}`,\n          },\n        })\n      ).json();\n\n      return {\n        refreshToken: '',\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: body.apiKey,\n        id,\n        name,\n        picture: imageUrl || '',\n        username,\n      };\n    } catch (err) {\n      return 'Invalid credentials';\n    }\n  }\n\n  @Tool({ description: 'List of publications', dataSchema: [] })\n  async publications(accessToken: string, _: any, id: string) {\n    const { data } = await (\n      await fetch(`https://api.medium.com/v1/users/${id}/publications`, {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return data;\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { settings } = postDetails?.[0] || { settings: {} };\n    const { data } = await (\n      await fetch(\n        settings?.publication\n          ? `https://api.medium.com/v1/publications/${settings?.publication}/posts`\n          : `https://api.medium.com/v1/users/${id}/posts`,\n        {\n          method: 'POST',\n          body: JSON.stringify({\n            title: settings.title,\n            contentFormat: 'markdown',\n            content: postDetails?.[0].message,\n            ...(settings.canonical ? { canonicalUrl: settings.canonical } : {}),\n            ...(settings?.tags?.length\n              ? { tags: settings?.tags?.map((p: any) => p.value) }\n              : {}),\n            publishStatus: settings?.publication ? 'draft' : 'public',\n          }),\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: postDetails?.[0].id,\n        status: 'completed',\n        postId: data.id,\n        releaseURL: data.url,\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/mewe.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { MeweDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/mewe.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class MeweProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'mewe';\n  name = 'MeWe';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'normal' as const;\n  dto = MeweDto;\n\n  private get meweHost() {\n    return process.env.MEWE_HOST || 'https://mewe.com';\n  }\n\n  private authHeaders(apiToken: string) {\n    return {\n      'X-App-Id': process.env.MEWE_APP_ID!,\n      'X-Api-Key': process.env.MEWE_API_KEY!,\n      Authorization: `Bearer ${apiToken}`,\n      'Content-Type': 'application/json',\n    };\n  }\n\n  maxLength() {\n    return 63206;\n  }\n\n  override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    if (body.indexOf('Unauthorized') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Access token expired, please re-authenticate',\n      };\n    }\n\n    if (body.indexOf('Enhance Your Calm') > -1 || body.indexOf('420') > -1) {\n      return {\n        type: 'retry' as const,\n        value: 'Rate limited, retrying...',\n      };\n    }\n\n    if (body.indexOf('Forbidden') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Insufficient permissions for this action',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url:\n        `${this.meweHost}/login` +\n        `?client_id=${process.env.MEWE_APP_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${process.env.FRONTEND_URL}/integrations/social/mewe`\n        )}` +\n        `&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const loginRequestToken = params.code;\n\n    if (!loginRequestToken) {\n      return 'No login request token received. Please try again.';\n    }\n\n    try {\n      // Exchange loginRequestToken for apiToken\n      const tokenResponse = await fetch(\n        `${this.meweHost}/api/dev/token?loginRequestToken=${loginRequestToken}`,\n        {\n          method: 'GET',\n          headers: {\n            'X-App-Id': process.env.MEWE_APP_ID!,\n            'X-Api-Key': process.env.MEWE_API_KEY!,\n          },\n        }\n      );\n\n      if (!tokenResponse.ok) {\n        return 'Failed to exchange token. Please try again.';\n      }\n\n      const tokenData = await tokenResponse.json();\n\n      if (tokenData.pending) {\n        return 'Login request is still pending. Please approve on MeWe and try again.';\n      }\n\n      if (!tokenData.apiToken) {\n        return 'No API token received. Please try again.';\n      }\n\n      const apiToken = tokenData.apiToken;\n      const expiresAt = tokenData.expiresAt;\n\n      // Fetch user profile\n      const profileResponse = await fetch(`${this.meweHost}/api/dev/me`, {\n        method: 'GET',\n        headers: this.authHeaders(apiToken),\n      });\n\n      if (!profileResponse.ok) {\n        return 'Failed to fetch MeWe profile.';\n      }\n\n      const profile = await profileResponse.json();\n\n      const expiresIn = expiresAt\n        ? dayjs(expiresAt).unix() - dayjs().unix()\n        : dayjs().add(30, 'days').unix() - dayjs().unix();\n\n      return {\n        id: profile.userId,\n        name:\n          profile.name ||\n          `${profile.firstName || ''} ${profile.lastName || ''}`.trim(),\n        accessToken: apiToken,\n        refreshToken: '',\n        expiresIn,\n        picture: '',\n        username: profile.handle || '',\n      };\n    } catch (e) {\n      console.log(e);\n      return 'MeWe authentication failed. Please try again.';\n    }\n  }\n\n  @Tool({ description: 'Groups', dataSchema: [] })\n  async groups(\n    accessToken: string,\n    params: any,\n    id: string,\n    integration: Integration\n  ) {\n    try {\n      const allGroups: any[] = [];\n      let nextUrl: string | null = `${this.meweHost}/api/dev/groups`;\n\n      while (nextUrl) {\n        const response = await fetch(nextUrl, {\n          method: 'GET',\n          headers: this.authHeaders(accessToken),\n        });\n\n        if (!response.ok) break;\n\n        const data = await response.json();\n        allGroups.push(...(data.groups || []));\n        nextUrl = data.nextPage ? `${this.meweHost}${data.nextPage}` : null;\n      }\n\n      return allGroups.map((group: any) => ({\n        id: String(group.groupId),\n        name: group.name,\n      }));\n    } catch (err) {\n      return [];\n    }\n  }\n\n  private async uploadPhoto(\n    accessToken: string,\n    mediaPath: string\n  ): Promise<string> {\n    const mediaResponse = await fetch(mediaPath);\n    const blob = await mediaResponse.blob();\n    const fileName = mediaPath.split('/').pop() || 'photo.jpg';\n\n    const form = new FormData();\n    form.append('file', blob, fileName);\n\n    const uploadResponse = await fetch(\n      `${this.meweHost}/api/dev/photo/upload`,\n      {\n        method: 'POST',\n        headers: {\n          'X-App-Id': process.env.MEWE_APP_ID!,\n          'X-Api-Key': process.env.MEWE_API_KEY!,\n          Authorization: `Bearer ${accessToken}`,\n        },\n        body: form,\n      }\n    );\n\n    if (!uploadResponse.ok) {\n      const errorText = await uploadResponse.text();\n      throw new Error(`Photo upload failed: ${errorText}`);\n    }\n\n    const uploadData = await uploadResponse.json();\n    return uploadData.id;\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<MeweDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const postType = firstPost.settings.postType || 'group';\n    const groupId = firstPost.settings.group;\n\n    // Upload photos if present (exclude videos)\n    const imageMedia =\n      firstPost.media?.filter((m) => !m.path || m.path.indexOf('mp4') === -1) ||\n      [];\n\n    const uploadedPhotoIds: string[] = [];\n    for (const media of imageMedia) {\n      const photoId = await this.uploadPhoto(accessToken, media.path);\n      uploadedPhotoIds.push(photoId);\n    }\n\n    const postBody: Record<string, any> = { text: firstPost.message };\n    if (uploadedPhotoIds.length > 0) {\n      postBody.uploadedPhotoIds = uploadedPhotoIds;\n    }\n\n    const postUrl =\n      postType === 'timeline'\n        ? `${this.meweHost}/api/dev/me/post`\n        : `${this.meweHost}/api/dev/group/${groupId}/post`;\n\n    // MeWe post endpoint may return 204 (no content), so use raw fetch\n    const postResponse = await fetch(postUrl, {\n      method: 'POST',\n      headers: this.authHeaders(accessToken),\n      body: JSON.stringify(postBody),\n    });\n\n    if (!postResponse.ok) {\n      const errorText = await postResponse.text();\n      const handleError = this.handleErrors(errorText);\n      if (handleError) {\n        throw new Error(handleError.value);\n      }\n      throw new Error('Failed to create MeWe post');\n    }\n\n    const postId = makeId(12);\n\n    const releaseURL = postType === 'timeline' ? `https://mewe.com/${integration.profile}/posts` : `https://mewe.com/group/${firstPost.settings.group}`;\n\n    return [\n      {\n        id: firstPost.id,\n        postId,\n        releaseURL,\n        status: 'success',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/moltbook.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport axios from 'axios';\n\nconst MOLTBOOK_API_BASE = 'https://www.moltbook.com/api/v1';\n\nexport class MoltbookProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 100; // Moltbook: 100 requests/minute\n  identifier = 'moltbook';\n  name = 'Moltbook';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  isWeb3 = true;\n  editor = 'normal' as const;\n\n  maxLength() {\n    return 300;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async registerAgent(name: string, description: string) {\n    const response = await axios.post(\n      `${MOLTBOOK_API_BASE}/agents/register`,\n      { name, description },\n      { headers: { 'Content-Type': 'application/json' } }\n    );\n\n    if (!response.data.success) {\n      throw new Error(response.data.error || 'Registration failed');\n    }\n\n    return response.data.agent;\n  }\n\n  async checkAgentStatus(apiKey: string) {\n    const response = await axios.get(`${MOLTBOOK_API_BASE}/agents/status`, {\n      headers: { Authorization: `Bearer ${apiKey}` },\n    });\n\n    return response.data;\n  }\n\n  async getAgentProfile(apiKey: string) {\n    const response = await axios.get(`${MOLTBOOK_API_BASE}/agents/me`, {\n      headers: { Authorization: `Bearer ${apiKey}` },\n    });\n\n    if (!response.data.success) {\n      throw new Error(response.data.error || 'Failed to get profile');\n    }\n\n    return response.data.agent;\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const apiKey = params.code;\n\n    const profile = await this.getAgentProfile(apiKey);\n\n    return {\n      id: profile.name || profile.id,\n      name: profile.display_name || profile.name,\n      accessToken: apiKey,\n      refreshToken: '',\n      expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(),\n      picture: '',\n      username: profile.name,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const results: PostResponse[] = [];\n\n    for (const post of postDetails) {\n      const postData: {\n        submolt: string;\n        title: string;\n        content?: string;\n        url?: string;\n      } = {\n        submolt: post.settings?.submolt || 'general',\n        title: post.message.slice(0, 100),\n        content: post.message,\n      };\n\n      const response = await axios.post(\n        `${MOLTBOOK_API_BASE}/posts`,\n        postData,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      if (!response.data.success) {\n        throw new Error(response.data.error || 'Failed to create post');\n      }\n\n      const postId = response.data.post.id;\n      results.push({\n        id: post.id,\n        postId: String(postId),\n        releaseURL: `https://www.moltbook.com/post/${postId}`,\n        status: 'completed',\n      });\n    }\n\n    return results;\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const results: PostResponse[] = [];\n\n    for (const post of postDetails) {\n      const commentData: { content: string; parent_id?: string } = {\n        content: post.message,\n      };\n\n      if (lastCommentId) {\n        commentData.parent_id = lastCommentId;\n      }\n\n      const response = await axios.post(\n        `${MOLTBOOK_API_BASE}/posts/${postId}/comments`,\n        commentData,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      if (!response.data.success) {\n        throw new Error(response.data.error || 'Failed to create comment');\n      }\n\n      const commentId = response.data.comment.id;\n      results.push({\n        id: post.id,\n        postId: String(commentId),\n        releaseURL: `https://www.moltbook.com/post/${postId}`,\n        status: 'completed',\n      });\n    }\n\n    return results;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { getPublicKey, Relay, finalizeEvent, SimplePool } from 'nostr-tools';\n\nimport WebSocket from 'ws';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { Integration } from '@prisma/client';\n\n// @ts-ignore\nglobal.WebSocket = WebSocket;\n\nconst list = [\n  'wss://nos.lol',\n  'wss://relay.damus.io',\n  'wss://relay.snort.social',\n  'wss://temp.iris.to',\n  'wss://vault.iris.to',\n];\n\nconst pool = new SimplePool();\n\nexport class NostrProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 5;\n  identifier = 'nostr';\n  name = 'Nostr';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  editor = 'normal' as const;\n  toolTip = 'Make sure you private a HEX key of your Nostr private key, you can get it from websites like iris.to'\n\n  maxLength() {\n    return 100000;\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'password',\n        label: 'Nostr private key',\n        validation: `/^.{3,}$/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(17);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  private async findRelayInformation(pubkey: string) {\n    // This queries ALL relays in parallel and resolves with\n    // the first matching event from ANY relay.\n    const evt = await pool.get(list, {\n      kinds: [0],\n      authors: [pubkey],\n      limit: 1,\n    });\n\n    if (!evt) return {};\n\n    let content: any = {};\n    try {\n      content = JSON.parse(evt.content || '{}');\n    } catch {\n      return {};\n    }\n\n    if (content.name || content.displayName || content.display_name) {\n      return content;\n    }\n\n    return {};\n  }\n\n  private async publish(pubkey: string, event: any) {\n    let id = '';\n    for (const relay of list) {\n      try {\n        const relayInstance = await Relay.connect(relay);\n        const value = new Promise<any>((resolve) => {\n          relayInstance.subscribe([{ kinds: [1], authors: [pubkey] }], {\n            eoseTimeout: 6000,\n            onevent: (event) => {\n              resolve(event);\n            },\n            oneose: () => {\n              resolve({});\n            },\n            onclose: () => {\n              resolve({});\n            },\n          });\n        });\n\n        await relayInstance.publish(event);\n        const all = await value;\n        relayInstance.close();\n        // relayInstance.close();\n        id = id || all?.id;\n      } catch (err) {\n        /**empty**/\n      }\n    }\n\n    return id;\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    try {\n      const body = JSON.parse(Buffer.from(params.code, 'base64').toString());\n\n      const pubkey = getPublicKey(\n        Uint8Array.from(\n          body.password.match(/.{1,2}/g).map((byte: any) => parseInt(byte, 16))\n        )\n      );\n\n      const user = await this.findRelayInformation(pubkey);\n\n      return {\n        id: pubkey,\n        name: user.display_name || user.displayName || user.name || 'No Name',\n        accessToken: AuthService.signJWT({ password: body.password }),\n        refreshToken: '',\n        expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(),\n        picture: user?.picture || '',\n        username: user.name || 'nousername',\n      };\n    } catch (e) {\n      console.log(e);\n      return 'Invalid credentials';\n    }\n  }\n\n  private buildContent(post: PostDetails): string {\n    const mediaContent = post.media?.map((m) => m.path).join('\\n\\n') || '';\n    return mediaContent\n      ? `${post.message}\\n\\n${mediaContent}`\n      : post.message;\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const { password } = AuthService.verifyJWT(accessToken) as any;\n    const [firstPost] = postDetails;\n\n    const textEvent = finalizeEvent(\n      {\n        kind: 1, // Text note\n        content: this.buildContent(firstPost),\n        tags: [],\n        created_at: Math.floor(Date.now() / 1000),\n      },\n      password\n    );\n\n    const eventId = await this.publish(id, textEvent);\n\n    return [\n      {\n        id: firstPost.id,\n        postId: String(eventId),\n        releaseURL: `https://primal.net/e/${eventId}`,\n        status: 'completed',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { password } = AuthService.verifyJWT(accessToken) as any;\n    const [commentPost] = postDetails;\n    const replyToId = lastCommentId || postId;\n\n    const textEvent = finalizeEvent(\n      {\n        kind: 1, // Text note\n        content: this.buildContent(commentPost),\n        tags: [\n          ['e', replyToId, '', 'reply'],\n          ['p', id],\n        ],\n        created_at: Math.floor(Date.now() / 1000),\n      },\n      password\n    );\n\n    const eventId = await this.publish(id, textEvent);\n\n    return [\n      {\n        id: commentPost.id,\n        postId: String(eventId),\n        releaseURL: `https://primal.net/e/${eventId}`,\n        status: 'completed',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';\nimport axios from 'axios';\nimport FormData from 'form-data';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  'Pinterest requires at least one media, if posting a video, you must have two attachment, one for video, one for the cover picture, When posting a video, there can be only one'\n)\nexport class PinterestProvider\n  extends SocialAbstract\n  implements SocialProvider\n{\n  identifier = 'pinterest';\n  name = 'Pinterest';\n  isBetweenSteps = false;\n  scopes = [\n    'boards:read',\n    'boards:write',\n    'pins:read',\n    'pins:write',\n    'user_accounts:read',\n  ];\n  override maxConcurrentJob = 3; // Pinterest has more lenient rate limits\n  maxLength() {\n    return 500;\n  }\n\n  dto = PinterestSettingsDto;\n\n  editor = 'normal' as const;\n\n  public override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    if (body.indexOf('cover_image_url or cover_image_content_type') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'When uploading a video, you must add also an image to be used as a cover image.',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const { access_token, expires_in } = await (\n      await fetch('https://api.pinterest.com/v5/oauth/token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            `${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`\n          ).toString('base64')}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n          scope: this.scopes.join(','),\n          redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,\n        }),\n      })\n    ).json();\n\n    const { id, profile_image, username } = await (\n      await fetch('https://api.pinterest.com/v5/user_account', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: id,\n      name: username,\n      accessToken: access_token,\n      refreshToken: refreshToken,\n      expiresIn: expires_in,\n      picture: profile_image || '',\n      username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: `https://www.pinterest.com/oauth/?client_id=${\n        process.env.PINTEREST_CLIENT_ID\n      }&redirect_uri=${encodeURIComponent(\n        `${process.env.FRONTEND_URL}/integrations/social/pinterest`\n      )}&response_type=code&scope=${encodeURIComponent(\n        'boards:read,boards:write,pins:read,pins:write,user_accounts:read'\n      )}&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh: string;\n  }) {\n    const { access_token, refresh_token, expires_in, scope } = await (\n      await fetch('https://api.pinterest.com/v5/oauth/token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            `${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`\n          ).toString('base64')}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'authorization_code',\n          code: params.code,\n          redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,\n        }),\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const { id, profile_image, username } = await (\n      await fetch('https://api.pinterest.com/v5/user_account', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: id,\n      name: username,\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      picture: profile_image,\n      username,\n    };\n  }\n\n  @Tool({ description: 'List of boards', dataSchema: [] })\n  async boards(accessToken: string) {\n    const { items } = await (\n      await fetch('https://api.pinterest.com/v5/boards', {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return (\n      items?.map((item: any) => ({\n        name: item.name,\n        id: item.id,\n      })) || []\n    );\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<PinterestSettingsDto>[]\n  ): Promise<PostResponse[]> {\n    let mediaId = '';\n    const findMp4 = postDetails?.[0]?.media?.find(\n      (p) => (p.path?.indexOf('mp4') || -1) > -1\n    );\n    const picture = postDetails?.[0]?.media?.find(\n      (p) => (p.path?.indexOf('mp4') || -1) === -1\n    );\n\n    if (findMp4) {\n      const { upload_url, media_id, upload_parameters } = await (\n        await this.fetch('https://api.pinterest.com/v5/media', {\n          method: 'POST',\n          body: JSON.stringify({\n            media_type: 'video',\n          }),\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${accessToken}`,\n          },\n        })\n      ).json();\n\n      const { data, status } = await axios.get(\n        postDetails?.[0]?.media?.[0]?.path!,\n        {\n          responseType: 'stream',\n        }\n      );\n\n      const formData = Object.keys(upload_parameters)\n        .filter((f) => f)\n        .reduce((acc, key) => {\n          acc.append(key, upload_parameters[key]);\n          return acc;\n        }, new FormData());\n\n      formData.append('file', data);\n      await axios.post(upload_url, formData);\n\n      let statusCode = '';\n      while (statusCode !== 'succeeded') {\n        const mediafile = await (\n          await this.fetch(\n            'https://api.pinterest.com/v5/media/' + media_id,\n            {\n              method: 'GET',\n              headers: {\n                Authorization: `Bearer ${accessToken}`,\n              },\n            },\n            '',\n            0,\n            true\n          )\n        ).json();\n\n        await timer(30000);\n        statusCode = mediafile.status;\n      }\n\n      mediaId = media_id;\n    }\n\n    const mapImages = postDetails?.[0]?.media?.map((m) => ({\n      path: m.path,\n    }));\n\n    const { id: pId } = await (\n      await this.fetch('https://api.pinterest.com/v5/pins', {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          ...(postDetails?.[0]?.settings.link\n            ? { link: postDetails?.[0]?.settings.link }\n            : {}),\n          ...(postDetails?.[0]?.settings.title\n            ? { title: postDetails?.[0]?.settings.title }\n            : {}),\n          description: postDetails?.[0]?.message,\n          ...(postDetails?.[0]?.settings.dominant_color\n            ? { dominant_color: postDetails?.[0]?.settings.dominant_color }\n            : {}),\n          board_id: postDetails?.[0]?.settings.board,\n          media_source: mediaId\n            ? {\n                source_type: 'video_id',\n                media_id: mediaId,\n                cover_image_url: picture?.path,\n              }\n            : mapImages?.length === 1\n            ? {\n                source_type: 'image_url',\n                url: mapImages?.[0]?.path,\n              }\n            : {\n                source_type: 'multiple_image_urls',\n                items: mapImages,\n              },\n        }),\n      })\n    ).json();\n\n    return [\n      {\n        id: postDetails?.[0]?.id,\n        postId: pId,\n        releaseURL: `https://www.pinterest.com/pin/${pId}`,\n        status: 'success',\n      },\n    ];\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const until = dayjs().format('YYYY-MM-DD');\n    const since = dayjs().subtract(date, 'day').format('YYYY-MM-DD');\n\n    const {\n      all: { daily_metrics },\n    } = await (\n      await fetch(\n        `https://api.pinterest.com/v5/user_account/analytics?start_date=${since}&end_date=${until}`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      )\n    ).json();\n\n    return daily_metrics.reduce(\n      (acc: any, item: any) => {\n        if (typeof item.metrics.PIN_CLICK_RATE !== 'undefined') {\n          acc[0].data.push({\n            date: item.date,\n            total: item.metrics.PIN_CLICK_RATE,\n          });\n\n          acc[1].data.push({\n            date: item.date,\n            total: item.metrics.IMPRESSION,\n          });\n\n          acc[2].data.push({\n            date: item.date,\n            total: item.metrics.PIN_CLICK,\n          });\n\n          acc[3].data.push({\n            date: item.date,\n            total: item.metrics.ENGAGEMENT,\n          });\n\n          acc[4].data.push({\n            date: item.date,\n            total: item.metrics.SAVE,\n          });\n        }\n\n        return acc;\n      },\n      [\n        { label: 'Pin click rate', data: [] as any[] },\n        { label: 'Impressions', data: [] as any[] },\n        { label: 'Pin Clicks', data: [] as any[] },\n        { label: 'Engagement', data: [] as any[] },\n        { label: 'Saves', data: [] as any[] },\n      ]\n    );\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n    // Use a very long date range (2 years) to capture lifetime metrics for older posts\n    const since = dayjs().subtract(2, 'year').format('YYYY-MM-DD');\n\n    try {\n      // Fetch pin analytics from Pinterest API\n      const response = await this.fetch(\n        `https://api.pinterest.com/v5/pins/${postId}/analytics?start_date=${since}&end_date=${today}&metric_types=IMPRESSION,PIN_CLICK,OUTBOUND_CLICK,SAVE`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const data = await response.json();\n\n      if (!data || !data.all) {\n        return [];\n      }\n\n      const result: AnalyticsData[] = [];\n      const metrics = data.all;\n\n      if (metrics.lifetime_metrics) {\n        const lifetimeMetrics = metrics.lifetime_metrics;\n\n        if (lifetimeMetrics.IMPRESSION !== undefined) {\n          result.push({\n            label: 'Impressions',\n            percentageChange: 0,\n            data: [{ total: String(lifetimeMetrics.IMPRESSION), date: today }],\n          });\n        }\n\n        if (lifetimeMetrics.PIN_CLICK !== undefined) {\n          result.push({\n            label: 'Pin Clicks',\n            percentageChange: 0,\n            data: [{ total: String(lifetimeMetrics.PIN_CLICK), date: today }],\n          });\n        }\n\n        if (lifetimeMetrics.OUTBOUND_CLICK !== undefined) {\n          result.push({\n            label: 'Outbound Clicks',\n            percentageChange: 0,\n            data: [{ total: String(lifetimeMetrics.OUTBOUND_CLICK), date: today }],\n          });\n        }\n\n        if (lifetimeMetrics.SAVE !== undefined) {\n          result.push({\n            label: 'Saves',\n            percentageChange: 0,\n            data: [{ total: String(lifetimeMetrics.SAVE), date: today }],\n          });\n        }\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching Pinterest post analytics:', err);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { groupBy } from 'lodash';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { lookup } from 'mime-types';\nimport axios from 'axios';\nimport WebSocket from 'ws';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\nimport { Integration } from '@prisma/client';\n\n// @ts-ignore\nglobal.WebSocket = WebSocket;\n\nexport class RedditProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 1; // Reddit has strict rate limits (1 request per second)\n  identifier = 'reddit';\n  name = 'Reddit';\n  isBetweenSteps = false;\n  scopes = ['read', 'identity', 'submit', 'flair'];\n  editor = 'normal' as const;\n  dto = RedditSettingsDto;\n\n  maxLength() {\n    return 10000;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const { access_token: accessToken, expires_in: expiresIn } = await (\n      await this.fetch('https://www.reddit.com/api/v1/access_token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            `${process.env.REDDIT_CLIENT_ID}:${process.env.REDDIT_CLIENT_SECRET}`\n          ).toString('base64')}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n        }),\n      })\n    ).json();\n\n    const { name, id, icon_img } = await (\n      await this.fetch('https://oauth.reddit.com/api/v1/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id,\n      name,\n      accessToken,\n      refreshToken: refreshToken,\n      expiresIn,\n      picture: icon_img?.split?.('?')?.[0] || '',\n      username: name,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    const codeVerifier = makeId(30);\n    const url = `https://www.reddit.com/api/v1/authorize?client_id=${\n      process.env.REDDIT_CLIENT_ID\n    }&response_type=code&state=${state}&redirect_uri=${encodeURIComponent(\n      `${process.env.FRONTEND_URL}/integrations/social/reddit`\n    )}&duration=permanent&scope=${encodeURIComponent(this.scopes.join(' '))}`;\n    return {\n      url,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async authenticate(params: { code: string; codeVerifier: string }) {\n    const {\n      access_token: accessToken,\n      refresh_token: refreshToken,\n      expires_in: expiresIn,\n      scope,\n    } = await (\n      await this.fetch('https://www.reddit.com/api/v1/access_token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n          Authorization: `Basic ${Buffer.from(\n            `${process.env.REDDIT_CLIENT_ID}:${process.env.REDDIT_CLIENT_SECRET}`\n          ).toString('base64')}`,\n        },\n        body: new URLSearchParams({\n          grant_type: 'authorization_code',\n          code: params.code,\n          redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/reddit`,\n        }),\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const { name, id, icon_img } = await (\n      await this.fetch('https://oauth.reddit.com/api/v1/me', {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n        },\n      })\n    ).json();\n\n    return {\n      id,\n      name,\n      accessToken,\n      refreshToken,\n      expiresIn,\n      picture: icon_img?.split?.('?')?.[0] || '',\n      username: name,\n    };\n  }\n\n  private async uploadFileToReddit(accessToken: string, path: string) {\n    const mimeType = lookup(path);\n    const formData = new FormData();\n    formData.append('filepath', path.split('/').pop());\n    formData.append('mimetype', mimeType || 'application/octet-stream');\n\n    const {\n      args: { action, fields },\n    } = await (\n      await this.fetch(\n        'https://oauth.reddit.com/api/media/asset',\n        {\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: formData,\n        },\n        'reddit',\n        0,\n        true\n      )\n    ).json();\n\n    const { data } = await axios.get(path, {\n      responseType: 'arraybuffer',\n    });\n\n    const upload = (fields as { name: string; value: string }[]).reduce(\n      (acc, value) => {\n        acc.append(value.name, value.value);\n        return acc;\n      },\n      new FormData()\n    );\n\n    upload.append(\n      'file',\n      new Blob([Buffer.from(data)], { type: mimeType as string })\n    );\n\n    const d = await fetch('https:' + action, {\n      method: 'POST',\n      body: upload,\n    });\n\n    return [...(await d.text()).matchAll(/<Location>(.*?)<\\/Location>/g)][0][1];\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<RedditSettingsDto>[]\n  ): Promise<PostResponse[]> {\n    const [post] = postDetails;\n\n    const valueArray: PostResponse[] = [];\n    for (const firstPostSettings of post.settings.subreddit) {\n      const postData = {\n        api_type: 'json',\n        title: firstPostSettings.value.title || '',\n        kind:\n          firstPostSettings.value.type === 'media'\n            ? post.media[0].path.indexOf('mp4') > -1\n              ? 'video'\n              : 'image'\n            : firstPostSettings.value.type,\n        ...(firstPostSettings.value.flair\n          ? { flair_id: firstPostSettings.value.flair.id }\n          : {}),\n        ...(firstPostSettings.value.type === 'link'\n          ? {\n              url: firstPostSettings.value.url,\n            }\n          : {}),\n        ...(firstPostSettings.value.type === 'media'\n          ? {\n              url: await this.uploadFileToReddit(\n                accessToken,\n                post.media[0].path\n              ),\n              ...(post.media[0].path.indexOf('mp4') > -1\n                ? {\n                    video_poster_url: await this.uploadFileToReddit(\n                      accessToken,\n                      post.media[0].thumbnail\n                    ),\n                  }\n                : {}),\n            }\n          : {}),\n        text: post.message,\n        sr:\n          firstPostSettings.value.subreddit.indexOf('/r/') > -1\n            ? firstPostSettings.value.subreddit\n            : `/r/${firstPostSettings.value.subreddit}`,\n      };\n\n      const all = await (\n        await this.fetch('https://oauth.reddit.com/api/submit', {\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n          body: new URLSearchParams(postData),\n        })\n      ).json();\n\n      const {\n        id: redditId,\n        name,\n        url,\n      } = await new Promise<{\n        id: string;\n        name: string;\n        url: string;\n      }>((res) => {\n        if (all?.json?.data?.id) {\n          res(all.json.data);\n        }\n\n        const ws = new WebSocket(all.json.data.websocket_url);\n        ws.on('message', (data: any) => {\n          setTimeout(() => {\n            res({ id: '', name: '', url: '' });\n            ws.close();\n          }, 30_000);\n          try {\n            const parsedData = JSON.parse(data.toString());\n            if (parsedData?.payload?.redirect) {\n              const onlyId = parsedData?.payload?.redirect.replace(\n                /https:\\/\\/www\\.reddit\\.com\\/r\\/.*?\\/comments\\/(.*?)\\/.*/g,\n                '$1'\n              );\n              res({\n                id: onlyId,\n                name: `t3_${onlyId}`,\n                url: parsedData?.payload?.redirect,\n              });\n            }\n          } catch (err) {}\n        });\n      });\n\n      valueArray.push({\n        postId: redditId,\n        releaseURL: url,\n        id: post.id,\n        status: 'published',\n      });\n\n      if (post.settings.subreddit.length > 1) {\n        await timer(5000);\n      }\n    }\n\n    return Object.values(groupBy(valueArray, (p) => p.id)).map((p) => ({\n      id: p[0].id,\n      postId: p.map((p) => p.postId).join(','),\n      releaseURL: p.map((p) => p.releaseURL).join(','),\n      status: 'published',\n    }));\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<RedditSettingsDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n\n    // Reddit uses thing_id format like t3_xxx for posts\n    const thingId = postId.startsWith('t3_') ? postId : `t3_${postId}`;\n\n    const {\n      json: {\n        data: {\n          things: [\n            {\n              data: { id: commentId, permalink },\n            },\n          ],\n        },\n      },\n    } = await (\n      await this.fetch('https://oauth.reddit.com/api/comment', {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body: new URLSearchParams({\n          text: commentPost.message,\n          thing_id: thingId,\n          api_type: 'json',\n        }),\n      })\n    ).json();\n\n    return [\n      {\n        postId: commentId,\n        releaseURL: 'https://www.reddit.com' + permalink,\n        id: commentPost.id,\n        status: 'published',\n      },\n    ];\n  }\n\n  @Tool({\n    description: 'Get list of subreddits with information',\n    dataSchema: [\n      {\n        key: 'word',\n        type: 'string',\n        description: 'Search subreddit by string',\n      },\n    ],\n  })\n  async subreddits(accessToken: string, data: any) {\n    const {\n      data: { children },\n    } = await (\n      await this.fetch(\n        `https://oauth.reddit.com/subreddits/search?show=public&q=${data.word}&sort=activity&show_users=false&limit=10`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n        },\n        'reddit',\n        0,\n        false\n      )\n    ).json();\n\n    return children\n      .filter(\n        ({ data }: { data: any }) =>\n          data.subreddit_type === 'public' && data.submission_type !== 'image'\n      )\n      .map(({ data: { title, url, id } }: any) => ({\n        title,\n        name: url,\n        id,\n      }));\n  }\n\n  private getPermissions(submissionType: string, allow_images: string) {\n    const permissions = [];\n    if (['any', 'self'].indexOf(submissionType) > -1) {\n      permissions.push('self');\n    }\n\n    if (['any', 'link'].indexOf(submissionType) > -1) {\n      permissions.push('link');\n    }\n\n    if (allow_images) {\n      permissions.push('media');\n    }\n\n    return permissions;\n  }\n\n  @Tool({\n    description: 'Get list of flairs and restrictions for a subreddit',\n    dataSchema: [\n      {\n        key: 'subreddit',\n        type: 'string',\n        description:\n          'Search flairs and restrictions by subreddit key should be \"/r/[name]\"',\n      },\n    ],\n  })\n  async restrictions(accessToken: string, data: { subreddit: string }) {\n    const {\n      data: { submission_type, allow_images, ...all2 },\n    } = await (\n      await this.fetch(\n        `https://oauth.reddit.com/${data.subreddit}/about`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n        },\n        'reddit',\n        0,\n        false\n      )\n    ).json();\n\n    const { is_flair_required, ...all } = await (\n      await this.fetch(\n        `https://oauth.reddit.com/api/v1/${\n          data.subreddit.split('/r/')[1]\n        }/post_requirements`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n        },\n        'reddit',\n        0,\n        false\n      )\n    ).json();\n\n    // eslint-disable-next-line no-async-promise-executor\n    const newData = await new Promise<{ id: string; name: string }[]>(\n      async (res) => {\n        try {\n          const flair = await (\n            await this.fetch(\n              `https://oauth.reddit.com/${data.subreddit}/api/link_flair_v2`,\n              {\n                method: 'GET',\n                headers: {\n                  Authorization: `Bearer ${accessToken}`,\n                  'Content-Type': 'application/x-www-form-urlencoded',\n                },\n              },\n              'reddit',\n              0,\n              false\n            )\n          ).json();\n\n          res(flair);\n        } catch (err) {\n          return res([]);\n        }\n      }\n    );\n\n    return {\n      subreddit: data.subreddit,\n      allow: this.getPermissions(submission_type, allow_images),\n      is_flair_required: is_flair_required && newData.length > 0,\n      flairs:\n        newData?.map?.((p: any) => ({\n          id: p.id,\n          name: p.text,\n        })) || [],\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/skool.provider.ts",
    "content": "import { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '../social.abstract';\nimport {\n  AuthTokenDetails,\n  MediaContent,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from './social.integrations.interface';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\nimport { SkoolDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/skool.dto';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\n\nexport class SkoolProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'skool';\n  name = 'Skool';\n  isBetweenSteps = false;\n  isChromeExtension = true;\n  scopes = [] as string[];\n  editor = 'normal' as const;\n  dto = SkoolDto;\n\n  extensionCookies = [\n    { name: 'client_id', domain: '.skool.com' },\n    { name: 'auth_token', domain: '.skool.com' },\n  ];\n\n  private getCookies(integration: Integration): {\n    client_id: string;\n    auth_token: string;\n  } {\n    return AuthService.verifyJWT(integration.customInstanceDetails!) as {\n      client_id: string;\n      auth_token: string;\n    };\n  }\n\n  override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    if (body.includes('must be admin or level')) {\n      return { type: 'bad-body', value: 'You can\\'t post to this channel' };\n    }\n    if (body.includes('cannot post to this label')) {\n      return { type: 'bad-body', value: 'Cannot post to this label' };\n    }\n    return undefined;\n  }\n\n  maxLength() {\n    return 5000;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    try {\n      const cookies: Record<string, string> = JSON.parse(\n        Buffer.from(params.code, 'base64').toString()\n      );\n\n      const missing = this.extensionCookies\n        .map((c) => c.name)\n        .filter((name) => !cookies[name]);\n\n      if (missing.length > 0) {\n        return `Missing required cookies: ${missing.join(', ')}`;\n      }\n\n      const data = await (\n        await fetch('https://api2.skool.com/self', {\n          method: 'GET',\n          headers: {\n            Cookie: `auth_token=${cookies.auth_token}; client_id=${cookies.client_id}`,\n          },\n        })\n      ).json();\n\n      return {\n        refreshToken: '',\n        expiresIn: dayjs().add(100, 'year').unix() - dayjs().unix(),\n        accessToken: AuthService.signJWT(cookies),\n        id: data.id,\n        name: data.first_name + ' ' + data.last_name,\n        picture: data.metadata.picture_profile || '',\n        username: data.name,\n      };\n    } catch (e) {\n      return 'Invalid cookie data';\n    }\n  }\n\n  @Tool({ description: 'Groups', dataSchema: [] })\n  async groups(accessToken: string, params: any, id: string, integration: Integration) {\n    try {\n      const { client_id, auth_token } = this.getCookies(integration);\n      const { groups } = await (\n        await fetch(\n          `https://api2.skool.com/users/${id}/groups?offset=0&limit=30`,\n          {\n            headers: {\n              Cookie: `auth_token=${auth_token}; client_id=${client_id}`,\n            },\n          }\n        )\n      ).json();\n\n      return groups.map((p: any) => ({\n        id: String(p.id),\n        name: p.metadata.display_name,\n      }));\n    } catch (err) {\n      return [];\n    }\n  }\n\n  @Tool({ description: 'Label', dataSchema: [] })\n  async label(accessToken: string, params: any, id: string, integration: Integration) {\n    try {\n      const { client_id, auth_token } = this.getCookies(integration);\n      const { metadata } = await (\n        await this.fetch(`https://api2.skool.com/groups/${params.id}`, {\n          headers: {\n            Cookie: `auth_token=${auth_token}; client_id=${client_id}`,\n          },\n        })\n      ).json();\n\n      if (!metadata.labels || metadata.labels.length === 0) {\n        return [{ id: 'none', name: 'Default Label' }];\n      }\n\n      const labels = metadata.labels.split(',');\n\n      if (labels.length === 0) {\n        return [{ id: 'none', name: 'Default Label' }];\n      }\n\n      const labelInformation = await Promise.all(\n        labels.map(async (labelId: string) => {\n          return (\n            await this.fetch(`https://api2.skool.com/labels/${labelId}`, {\n              headers: {\n                Cookie: `auth_token=${auth_token}; client_id=${client_id}`,\n              },\n            })\n          ).json();\n        })\n      );\n\n      return labelInformation.map((p: any) => ({\n        id: String(p.id),\n        name: p.metadata.display_name,\n      }));\n    } catch (err) {\n      return [];\n    }\n  }\n\n  private async uploadMediaToSkool(\n    media: MediaContent[],\n    userId: string,\n    cookies: { client_id: string; auth_token: string }\n  ): Promise<string> {\n    if (!media || media.length === 0) return '';\n\n    const fileIds: string[] = [];\n\n    for (const item of media) {\n      const fileResponse = await fetch(item.path);\n      const fileBuffer = await fileResponse.arrayBuffer();\n      const contentType =\n        fileResponse.headers.get('content-type') || 'application/octet-stream';\n      const fileName = item.path.split('/').pop() || 'file';\n\n      const createFileResponse = await (\n        await this.fetch('https://api2.skool.com/files', {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Cookie: `auth_token=${cookies.auth_token}; client_id=${cookies.client_id}`,\n          },\n          body: JSON.stringify({\n            file_name: fileName,\n            content_type: contentType,\n            content_length: fileBuffer.byteLength,\n            content_disposition: '',\n            ref: '',\n            owner_id: userId,\n            large_thumbnail: false,\n          }),\n        }, 'create file record')\n      ).json();\n\n      await fetch(createFileResponse.write_url, {\n        method: 'PUT',\n        headers: {\n          'Content-Type': createFileResponse.content_type,\n          'x-amz-acl': createFileResponse.acl,\n        },\n        body: fileBuffer,\n      });\n\n      fileIds.push(createFileResponse.file.id);\n    }\n\n    return fileIds.join(',');\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { client_id, auth_token } = this.getCookies(integration);\n    const [post] = postDetails;\n\n    const attachments = await this.uploadMediaToSkool(\n      post.media || [],\n      id,\n      { client_id, auth_token }\n    );\n\n    const { id: postId, name } = await (\n      await this.fetch('https://api2.skool.com/posts?follow=true', {\n        method: 'POST',\n        headers: {\n          Cookie: `auth_token=${auth_token}; client_id=${client_id}`,\n        },\n        body: JSON.stringify({\n          post_type: 'generic',\n          group_id: post.settings.group,\n          metadata: {\n            title: post.settings.title,\n            content: post.message,\n            attachments,\n            ...(post.settings.label && post.settings.label !== 'none'\n              ? { labels: post.settings.label }\n              : {}),\n            action: 0,\n            video_ids: '',\n          },\n        }),\n      })\n    ).json();\n\n    return [\n      {\n        id: String(postId),\n        postId,\n        releaseURL: `https://www.skool.com/${post.settings.group}/${name}`,\n        status: 'success',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const { client_id, auth_token } = this.getCookies(integration);\n    const [post] = postDetails;\n\n    const attachments = await this.uploadMediaToSkool(\n      post.media || [],\n      id,\n      { client_id, auth_token }\n    );\n\n    const { id: postIdFinal, name } = await (\n      await this.fetch('https://api2.skool.com/posts?follow=true', {\n        method: 'POST',\n        headers: {\n          Cookie: `auth_token=${auth_token}; client_id=${client_id}`,\n        },\n        body: JSON.stringify({\n          post_type: 'comment',\n          group_id: post.settings.group,\n          root_id: postId,\n          parent_id: lastCommentId || postId,\n          metadata: {\n            title: '',\n            content: post.message,\n            attachments,\n            action: 0,\n            video_ids: '',\n          },\n        }),\n      })\n    ).json();\n\n    return [\n      {\n        id: String(id),\n        postId: postIdFinal,\n        releaseURL: `https://www.skool.com/${post.settings.group}/${name}`,\n        status: 'success',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/slack.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class SlackProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Slack has moderate API limits\n  identifier = 'slack';\n  name = 'Slack';\n  isBetweenSteps = false;\n  editor = 'normal' as const;\n  scopes = [\n    'channels:read',\n    'chat:write',\n    'users:read',\n    'groups:read',\n    'channels:join',\n    'chat:write.customize',\n  ];\n  dto = SlackDto;\n\n  maxLength() {\n    return 400000;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 1000000,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n  async generateAuthUrl() {\n    const state = makeId(6);\n\n    return {\n      url: `https://slack.com/oauth/v2/authorize?client_id=${\n        process.env.SLACK_ID\n      }&redirect_uri=${encodeURIComponent(\n        `${\n          process?.env?.FRONTEND_URL?.indexOf('https') === -1\n            ? 'https://redirectmeto.com/'\n            : ''\n        }${process?.env?.FRONTEND_URL}/integrations/social/slack`\n      )}&scope=channels:read,chat:write,users:read,groups:read,channels:join,chat:write.customize&state=${state}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const { access_token, team, bot_user_id, scope } = await (\n      await this.fetch(`https://slack.com/api/oauth.v2.access`, {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        body: new URLSearchParams({\n          client_id: process.env.SLACK_ID!,\n          client_secret: process.env.SLACK_SECRET!,\n          code: params.code,\n          redirect_uri: `${\n            process?.env?.FRONTEND_URL?.indexOf('https') === -1\n              ? 'https://redirectmeto.com/'\n              : ''\n          }${process?.env?.FRONTEND_URL}/integrations/social/slack${\n            params.refresh ? `?refresh=${params.refresh}` : ''\n          }`,\n        }),\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope.split(','));\n\n    const { user } = await (\n      await fetch(`https://slack.com/api/users.info?user=${bot_user_id}`, {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${access_token}`,\n        },\n      })\n    ).json();\n\n    return {\n      id: team.id,\n      name: user.real_name,\n      accessToken: access_token,\n      refreshToken: 'null',\n      expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n      picture: user?.profile?.image_original || '',\n      username: user.name,\n    };\n  }\n\n  @Tool({\n    description: 'Get list of channels',\n    dataSchema: [],\n  })\n  async channels(accessToken: string, params: any, id: string) {\n    const list = await (\n      await fetch(\n        `https://slack.com/api/conversations.list?types=public_channel,private_channel`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      )\n    ).json();\n\n    return list.channels.map((p: any) => ({\n      id: p.id,\n      name: p.name,\n    }));\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const channel = firstPost.settings.channel;\n\n    // Join the channel first\n    await fetch(`https://slack.com/api/conversations.join`, {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        channel,\n      }),\n    });\n\n    // Post the main message\n    const { ts, channel: responseChannel } = await (\n      await fetch(`https://slack.com/api/chat.postMessage`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          channel,\n          username: integration.name,\n          icon_url: integration.picture,\n          blocks: [\n            {\n              type: 'section',\n              text: {\n                type: 'mrkdwn',\n                text: firstPost.message,\n              },\n            },\n            ...(firstPost.media?.length\n              ? firstPost.media.map((m) => ({\n                  type: 'image',\n                  image_url: m.path,\n                  alt_text: '',\n                }))\n              : []),\n          ],\n        }),\n      })\n    ).json();\n\n    // Get permalink for the message\n    const { permalink } = await (\n      await fetch(\n        `https://slack.com/api/chat.getPermalink?channel=${responseChannel}&message_ts=${ts}`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: firstPost.id,\n        postId: ts,\n        releaseURL: permalink || '',\n        status: 'posted',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const channel = commentPost.settings.channel;\n    const threadTs = lastCommentId || postId;\n\n    // Post the threaded reply\n    const { ts, channel: responseChannel } = await (\n      await fetch(`https://slack.com/api/chat.postMessage`, {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          channel,\n          username: integration.name,\n          icon_url: integration.picture,\n          thread_ts: threadTs,\n          blocks: [\n            {\n              type: 'section',\n              text: {\n                type: 'mrkdwn',\n                text: commentPost.message,\n              },\n            },\n            ...(commentPost.media?.length\n              ? commentPost.media.map((m) => ({\n                  type: 'image',\n                  image_url: m.path,\n                  alt_text: '',\n                }))\n              : []),\n          ],\n        }),\n      })\n    ).json();\n\n    // Get permalink for the comment\n    const { permalink } = await (\n      await fetch(\n        `https://slack.com/api/chat.getPermalink?channel=${responseChannel}&message_ts=${ts}`,\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: ts,\n        releaseURL: permalink || '',\n        status: 'posted',\n      },\n    ];\n  }\n\n  async changeProfilePicture(id: string, accessToken: string, url: string) {\n    return {\n      url,\n    };\n  }\n\n  async changeNickname(id: string, accessToken: string, name: string) {\n    return {\n      name,\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts",
    "content": "import { Integration } from '@prisma/client';\n\nexport interface ClientInformation {\n  client_id: string;\n  client_secret: string;\n  instanceUrl: string;\n}\nexport interface IAuthenticator {\n  authenticate(\n    params: {\n      code: string;\n      codeVerifier: string;\n      refresh?: string;\n    },\n    clientInformation?: ClientInformation\n  ): Promise<AuthTokenDetails | string>;\n  refreshToken(refreshToken: string): Promise<AuthTokenDetails>;\n  reConnect?(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>>;\n  generateAuthUrl(\n    clientInformation?: ClientInformation\n  ): Promise<GenerateAuthUrlResponse>;\n  analytics?(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]>;\n  postAnalytics?(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    fromDate: number,\n  ): Promise<AnalyticsData[]>;\n  changeNickname?(\n    id: string,\n    accessToken: string,\n    name: string\n  ): Promise<{ name: string }>;\n  changeProfilePicture?(\n    id: string,\n    accessToken: string,\n    url: string\n  ): Promise<{ url: string }>;\n  missing?(\n    id: string,\n    accessToken: string\n  ): Promise<{ id: string; url: string }[]>;\n}\n\nexport interface AnalyticsData {\n  label: string;\n  data: Array<{ total: string; date: string }>;\n  percentageChange: number;\n}\n\n\nexport type GenerateAuthUrlResponse = {\n  url: string;\n  codeVerifier: string;\n  state: string;\n};\n\nexport type AuthTokenDetails = {\n  id: string;\n  name: string;\n  error?: string;\n  accessToken: string; // The obtained access token\n  refreshToken?: string; // The refresh token, if applicable\n  expiresIn?: number; // The duration in seconds for which the access token is valid\n  picture?: string;\n  username: string;\n  additionalSettings?: {\n    title: string;\n    description: string;\n    type: 'checkbox' | 'text' | 'textarea';\n    value: any;\n    regex?: string;\n  }[];\n};\n\nexport interface ISocialMediaIntegration {\n  post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]>; // Schedules a new post\n\n  comment?(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]>; // Schedules a new post\n}\n\nexport type PostResponse = {\n  id: string; // The db internal id of the post\n  postId: string; // The ID of the scheduled post returned by the platform\n  releaseURL: string; // The URL of the post on the platform\n  status: string; // Status of the operation or initial post status\n};\n\nexport type PostDetails<T = any> = {\n  id: string;\n  message: string;\n  settings: T;\n  media?: MediaContent[];\n  poll?: PollDetails;\n};\n\nexport type PollDetails = {\n  options: string[]; // Array of poll options\n  duration: number; // Duration in hours for which the poll will be active\n};\n\nexport type MediaContent = {\n  type: 'image' | 'video'; // Type of the media content\n  path: string;\n  alt?: string;\n  thumbnail?: string;\n  thumbnailTimestamp?: number;\n};\n\nexport type FetchPageInformationResult = {\n  id: string;\n  name: string;\n  access_token: string;\n  picture: string;\n  username: string;\n};\n\nexport interface SocialProvider\n  extends IAuthenticator,\n    ISocialMediaIntegration {\n  identifier: string;\n  refreshWait?: boolean;\n  convertToJPEG?: boolean;\n  refreshCron?: boolean;\n  dto?: any;\n  maxLength: (additionalSettings?: any) => number;\n  isWeb3?: boolean;\n  isChromeExtension?: boolean;\n  extensionCookies?: { name: string; domain: string }[];\n  editor: 'none' | 'normal' | 'markdown' | 'html';\n  customFields?: () => Promise<\n    {\n      key: string;\n      label: string;\n      defaultValue?: string;\n      validation: string;\n      type: 'text' | 'password';\n    }[]\n  >;\n  name: string;\n  toolTip?: string;\n  oneTimeToken?: boolean;\n  isBetweenSteps: boolean;\n  scopes: string[];\n  externalUrl?: (\n    url: string\n  ) => Promise<{ client_id: string; client_secret: string }>;\n  mention?: (\n    token: string,\n    data: { query: string },\n    id: string,\n    integration: Integration\n  ) => Promise<\n    | { id: string; label: string; image: string; doNotCache?: boolean }[]\n    | { none: true }\n  >;\n  mentionFormat?(idOrHandle: string, name: string): string;\n  fetchPageInformation?(\n    accessToken: string,\n    data: any\n  ): Promise<FetchPageInformationResult>;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\n//@ts-ignore\nimport mime from 'mime';\nimport TelegramBot from 'node-telegram-bot-api';\nimport { Integration } from '@prisma/client';\nimport striptags from 'striptags';\n\nconst telegramBot = new TelegramBot(process.env.TELEGRAM_TOKEN!);\n// Added to support local storage posting\nconst frontendURL = process.env.FRONTEND_URL || 'http://localhost:5000';\nconst mediaStorage = process.env.STORAGE_PROVIDER || 'local';\n\nexport class TelegramProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 3; // Telegram has moderate bot API limits\n  identifier = 'telegram';\n  name = 'Telegram';\n  isBetweenSteps = false;\n  isWeb3 = true;\n  scopes = [] as string[];\n  editor = 'html' as const;\n  maxLength() {\n    return 4096;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(17);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const chat = await telegramBot.getChat(params.code);\n\n    console.log(JSON.stringify(chat));\n    if (!chat?.id) {\n      return 'No chat found';\n    }\n\n    const photo = !chat?.photo?.big_file_id\n      ? ''\n      : await telegramBot.getFileLink(chat.photo.big_file_id);\n\n    // Modified id to work with chat.username (public groups/channels) or chat.id (private groups/channels) when chat.username is not available\n    return {\n      id: String(chat.username ? chat.username : chat.id),\n      name: chat.title!,\n      accessToken: String(chat.id),\n      refreshToken: '',\n      expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(),\n      picture: photo || '',\n      username: chat.username!,\n    };\n  }\n\n  async getBotId(query: { id?: number; word: string }) {\n    // Added allowed_updates Ensure only necessary updates are fetched\n    const res = await telegramBot.getUpdates({\n      ...(query.id ? { offset: query.id } : {}),\n      allowed_updates: ['message', 'channel_post'],\n    });\n    //message.text is for groups, channel_post.text is for channels\n    const match = res.find(\n      (p) =>\n        (p?.message?.text === `/connect ${query.word}` &&\n          p?.message?.chat?.id) ||\n        (p?.channel_post?.text === `/connect ${query.word}` &&\n          p?.channel_post?.chat?.id)\n    );\n    // get correct chatId based on the channel type\n    const chatId = match?.message?.chat?.id || match?.channel_post?.chat?.id;\n\n    // prevents the code from running while chatId is still undefined to avoid the error 'ETELEGRAM: 400 Bad Request: chat_id is empty'. the code would still work eventually but console spam is not pretty\n    if (chatId) {\n      //get the numberic ID of the bot\n      const botId = (await telegramBot.getMe()).id;\n      // check if the bot is an admin in the chat\n      const isAdmin = await this.botIsAdmin(chatId, botId);\n      // get the messageId of the message that triggered the connection\n      const connectMessageId =\n        match?.message?.message_id || match?.channel_post?.message_id;\n\n      if (!isAdmin) {\n        // alternatively you can replace this with a console.log if you do not want to inform the user of the bot's admin status\n        telegramBot.sendMessage(\n          chatId,\n          \"Connection Successful. I don't have admin privileges to delete these messages, please go ahead and remove them yourself.\"\n        );\n      } else {\n        // Delete the message that triggered the connection\n        await telegramBot.deleteMessage(chatId, connectMessageId);\n        // Send success message to the chat\n        const successMessage = await telegramBot.sendMessage(\n          chatId,\n          'Connection Successful. Message will be deleted in 10 seconds.'\n        );\n        // Delete the success message after 10 seconds\n        setTimeout(async () => {\n          await telegramBot.deleteMessage(chatId, successMessage.message_id);\n          console.log('Success message deleted.');\n        }, 10000);\n      }\n    }\n\n    // modified lastChatId to work with any type of channel (private/public groups/channels)\n    return chatId\n      ? { chatId }\n      : res.length > 0\n      ? {\n          lastChatId: res[res.length - 1].update_id + 1,\n        }\n      : {};\n  }\n\n  private processMedia(mediaFiles: PostDetails['media']) {\n    return (mediaFiles || []).map((media) => {\n      let mediaUrl = media.path;\n      if (mediaStorage === 'local' && mediaUrl.startsWith(frontendURL)) {\n        mediaUrl = mediaUrl.replace(frontendURL, '');\n      }\n      //get mime type to pass contentType to telegram api.\n      //some photos and videos might not pass telegram api restrictions, so they are sent as documents instead of returning errors\n      const mimeType = mime.getType(mediaUrl); // Detect MIME type\n      let mediaType: 'photo' | 'video' | 'document';\n\n      if (mimeType?.startsWith('image/')) {\n        mediaType = 'photo';\n      } else if (mimeType?.startsWith('video/')) {\n        mediaType = 'video';\n      } else {\n        mediaType = 'document';\n      }\n\n      return {\n        type: mediaType,\n        media: mediaUrl,\n        fileOptions: {\n          filename: media.path.split('/').pop(),\n          contentType: mimeType || 'application/octet-stream',\n        },\n      };\n    });\n  }\n\n  private async sendMessage(\n    accessToken: string,\n    message: PostDetails,\n    replyToMessageId?: number\n  ): Promise<number | null> {\n    let messageId: number | null = null;\n    const mediaFiles = message.media || [];\n    const text = striptags(message.message || '', ['u', 'strong', 'p'])\n      .replace(/<strong>/g, '<b>')\n      .replace(/<\\/strong>/g, '</b>')\n      .replace(/<p>(.*?)<\\/p>/g, '$1\\n');\n\n    console.log(text);\n    const processedMedia = this.processMedia(mediaFiles);\n\n    // if there's no media, bot sends a text message only\n    if (processedMedia.length === 0) {\n      const response = await telegramBot.sendMessage(accessToken, text, {\n        parse_mode: 'HTML',\n        ...(replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}),\n      });\n      messageId = response.message_id;\n    }\n    // if there's only one media, bot sends the media with the text message as caption\n    else if (processedMedia.length === 1) {\n      const media = processedMedia[0];\n      const options = {\n        caption: text,\n        parse_mode: 'HTML' as const,\n        ...(replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}),\n      };\n      const response =\n        media.type === 'video'\n          ? await telegramBot.sendVideo(\n              accessToken,\n              media.media,\n              options,\n              media.fileOptions\n            )\n          : media.type === 'photo'\n          ? await telegramBot.sendPhoto(\n              accessToken,\n              media.media,\n              options,\n              media.fileOptions\n            )\n          : await telegramBot.sendDocument(\n              accessToken,\n              media.media,\n              options,\n              media.fileOptions\n            );\n      messageId = response.message_id;\n    }\n    // if there are multiple media, bot sends them as a media group - max 10 media per group - with the text as a caption (if there are more than 1 group, the caption will only be sent with the first group)\n    else {\n      const mediaGroups = this.chunkMedia(processedMedia, 10);\n      for (let i = 0; i < mediaGroups.length; i++) {\n        const mediaGroup = mediaGroups[i].map((m, index) => ({\n          type: m.type === 'document' ? 'document' : m.type, // Documents are not allowed in media groups\n          media: m.media,\n          caption: i === 0 && index === 0 ? text : undefined,\n          parse_mode: 'HTML',\n        }));\n\n        const response = await telegramBot.sendMediaGroup(\n          accessToken,\n          mediaGroup as any[],\n          {\n            ...(replyToMessageId && i === 0\n              ? { reply_to_message_id: replyToMessageId }\n              : {}),\n          }\n        );\n        if (i === 0) {\n          messageId = response[0].message_id;\n        }\n      }\n    }\n\n    return messageId;\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n\n    const messageId = await this.sendMessage(accessToken, firstPost);\n\n    // for private groups/channels message.id is undefined so the link generated by Postiz will be unusable \"https://t.me/c/undefined/16\"\n    // to avoid that, we use accessToken instead of message.id and we generate the link manually removing the -100 from the start.\n    if (messageId) {\n      return [\n        {\n          id: firstPost.id,\n          postId: String(messageId),\n          releaseURL: `https://t.me/${\n            id !== 'undefined' ? id : `c/${accessToken.replace('-100', '')}`\n          }/${messageId}`,\n          status: 'completed',\n        },\n      ];\n    }\n\n    return [];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n    const replyToId = Number(lastCommentId || postId);\n\n    const messageId = await this.sendMessage(accessToken, commentPost, replyToId);\n\n    if (messageId) {\n      return [\n        {\n          id: commentPost.id,\n          postId: String(messageId),\n          releaseURL: `https://t.me/${\n            id !== 'undefined' ? id : `c/${accessToken.replace('-100', '')}`\n          }/${messageId}`,\n          status: 'completed',\n        },\n      ];\n    }\n\n    return [];\n  }\n  // chunkMedia is used to split media into groups of \"size\". 10 is used here because telegram api allows a maximum of 10 media per group\n  private chunkMedia(media: { type: string; media: string }[], size: number) {\n    const result = [];\n    for (let i = 0; i < media.length; i += size) {\n      result.push(media.slice(i, i + size));\n    }\n    return result;\n  }\n\n  async botIsAdmin(chatId: number, botId: number): Promise<boolean> {\n    try {\n      const chatMember = await telegramBot.getChatMember(chatId, botId);\n\n      if (\n        chatMember.status === 'administrator' ||\n        chatMember.status === 'creator'\n      ) {\n        const permissions = chatMember.can_delete_messages;\n        return !!permissions; // Return true if bot can delete messages\n      }\n\n      return false;\n    } catch (error) {\n      console.error('Error checking bot privileges:', error);\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/threads.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { capitalize, chunk } from 'lodash';\nimport { Plug } from '@gitroom/helpers/decorators/plug.decorator';\nimport { Integration } from '@prisma/client';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\n\nexport class ThreadsProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'threads';\n  name = 'Threads';\n  isBetweenSteps = false;\n  scopes = [\n    'threads_basic',\n    'threads_content_publish',\n    'threads_manage_replies',\n    'threads_manage_insights',\n    // 'threads_profile_discovery',\n  ];\n  override maxConcurrentJob = 2; // Threads has moderate rate limits\n  refreshCron = true;\n\n  editor = 'normal' as const;\n  maxLength() {\n    return 500;\n  }\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    console.log(body);\n    if (body.includes('Error validating access token')) {\n      return { type: 'refresh-token', value: 'Threads access token expired' };\n    }\n\n    if (body.includes('text must be at most 500 characters')) {\n      return {\n        type: 'bad-body',\n        value: 'Post text exceeds 500 characters limit',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    const { access_token } = await (\n      await this.fetch(\n        `https://graph.threads.net/refresh_access_token?grant_type=th_refresh_token&access_token=${refresh_token}`\n      )\n    ).json();\n\n    const { id, name, username, picture } = await this.fetchUserInfo(\n      access_token\n    );\n\n    return {\n      id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(58, 'days').unix() - dayjs().unix(),\n      picture: picture || '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url:\n        'https://www.threads.net/oauth/authorize' +\n        `?client_id=${process.env.THREADS_APP_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${\n            process?.env.FRONTEND_URL?.indexOf('https') == -1\n              ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n              : `${process?.env.FRONTEND_URL}`\n          }/integrations/social/threads`\n        )}` +\n        `&state=${state}` +\n        `&scope=${encodeURIComponent(this.scopes.join(','))}`,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const getAccessToken = await (\n      await this.fetch(\n        'https://graph.threads.net/oauth/access_token' +\n          `?client_id=${process.env.THREADS_APP_ID}` +\n          `&redirect_uri=${encodeURIComponent(\n            `${\n              process?.env.FRONTEND_URL?.indexOf('https') == -1\n                ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n                : `${process?.env.FRONTEND_URL}`\n            }/integrations/social/threads`\n          )}` +\n          `&grant_type=authorization_code` +\n          `&client_secret=${process.env.THREADS_APP_SECRET}` +\n          `&code=${params.code}`\n      )\n    ).json();\n\n    const { access_token } = await (\n      await this.fetch(\n        'https://graph.threads.net/access_token' +\n          '?grant_type=th_exchange_token' +\n          `&client_secret=${process.env.THREADS_APP_SECRET}` +\n          `&access_token=${getAccessToken.access_token}`\n      )\n    ).json();\n\n    const { id, name, username, picture } = await this.fetchUserInfo(\n      access_token\n    );\n\n    return {\n      id,\n      name,\n      accessToken: access_token,\n      refreshToken: access_token,\n      expiresIn: dayjs().add(58, 'days').unix() - dayjs().unix(),\n      picture: picture || '',\n      username: username,\n    };\n  }\n\n  private async checkLoaded(\n    mediaContainerId: string,\n    accessToken: string\n  ): Promise<boolean> {\n    const { status, id, error_message } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${mediaContainerId}?fields=status,error_message&access_token=${accessToken}`\n      )\n    ).json();\n\n    if (status === 'ERROR') {\n      throw new Error(id);\n    }\n\n    if (status === 'FINISHED') {\n      await timer(2000);\n      return true;\n    }\n\n    await timer(2200);\n    return this.checkLoaded(mediaContainerId, accessToken);\n  }\n\n  private async fetchUserInfo(accessToken: string) {\n    const { id, username, threads_profile_picture_url } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/me?fields=id,username,threads_profile_picture_url&access_token=${accessToken}`\n      )\n    ).json();\n\n    return {\n      id,\n      name: username,\n      picture: threads_profile_picture_url || '',\n      username,\n    };\n  }\n\n  private async createSingleMediaContent(\n    userId: string,\n    accessToken: string,\n    media: { path: string },\n    message: string,\n    isCarouselItem = false,\n    replyToId?: string\n  ): Promise<string> {\n    const mediaType =\n      media.path.indexOf('.mp4') > -1 ? 'video_url' : 'image_url';\n    const mediaParams = new URLSearchParams({\n      ...(mediaType === 'video_url' ? { video_url: media.path } : {}),\n      ...(mediaType === 'image_url' ? { image_url: media.path } : {}),\n      ...(isCarouselItem ? { is_carousel_item: 'true' } : {}),\n      ...(replyToId ? { reply_to_id: replyToId } : {}),\n      media_type: mediaType === 'video_url' ? 'VIDEO' : 'IMAGE',\n      text: message,\n      access_token: accessToken,\n    });\n\n    const { id: mediaId } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${userId}/threads?${mediaParams.toString()}`,\n        {\n          method: 'POST',\n        }\n      )\n    ).json();\n\n    return mediaId;\n  }\n\n  private async createCarouselContent(\n    userId: string,\n    accessToken: string,\n    media: { path: string }[],\n    message: string,\n    replyToId?: string\n  ): Promise<string> {\n    // Create each media item\n    const mediaIds = [];\n    for (const mediaItem of media) {\n      const mediaId = await this.createSingleMediaContent(\n        userId,\n        accessToken,\n        mediaItem,\n        message,\n        true\n      );\n      mediaIds.push(mediaId);\n    }\n\n    // Wait for all media to be loaded\n    await Promise.all(\n      mediaIds.map((id: string) => this.checkLoaded(id, accessToken))\n    );\n\n    // Create carousel container\n    const params = new URLSearchParams({\n      text: message,\n      media_type: 'CAROUSEL',\n      children: mediaIds.join(','),\n      ...(replyToId ? { reply_to_id: replyToId } : {}),\n      access_token: accessToken,\n    });\n\n    const { id: containerId } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${userId}/threads?${params.toString()}`,\n        {\n          method: 'POST',\n        }\n      )\n    ).json();\n\n    return containerId;\n  }\n\n  private async createTextContent(\n    userId: string,\n    accessToken: string,\n    message: string,\n    replyToId?: string,\n    quoteId?: string\n  ): Promise<string> {\n    const form = new FormData();\n    form.append('media_type', 'TEXT');\n    form.append('text', message);\n    form.append('access_token', accessToken);\n\n    if (replyToId) {\n      form.append('reply_to_id', replyToId);\n    }\n\n    if (quoteId) {\n      form.append('quote_post_id', quoteId);\n    }\n\n    const { id: contentId, ...all } = await (\n      await this.fetch(`https://graph.threads.net/v1.0/${userId}/threads`, {\n        method: 'POST',\n        body: form,\n      })\n    ).json();\n\n    return contentId;\n  }\n\n  private async publishThread(\n    userId: string,\n    accessToken: string,\n    creationId: string\n  ): Promise<{ threadId: string; permalink: string }> {\n    await this.checkLoaded(creationId, accessToken);\n\n    const { id: threadId } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${userId}/threads_publish?creation_id=${creationId}&access_token=${accessToken}`,\n        {\n          method: 'POST',\n        }\n      )\n    ).json();\n\n    const { permalink } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${threadId}?fields=id,permalink&access_token=${accessToken}`\n      )\n    ).json();\n\n    return { threadId, permalink };\n  }\n\n  private async createThreadContent(\n    userId: string,\n    accessToken: string,\n    postDetails: PostDetails,\n    replyToId?: string,\n    quoteId?: string\n  ): Promise<string> {\n    // Handle content creation based on media type\n    if (!postDetails.media || postDetails.media.length === 0) {\n      // Text-only content\n      return await this.createTextContent(\n        userId,\n        accessToken,\n        postDetails.message,\n        replyToId,\n        quoteId\n      );\n    } else if (postDetails.media.length === 1) {\n      // Single media content\n      return await this.createSingleMediaContent(\n        userId,\n        accessToken,\n        postDetails.media[0],\n        postDetails.message,\n        false,\n        replyToId\n      );\n    } else {\n      // Carousel content\n      return await this.createCarouselContent(\n        userId,\n        accessToken,\n        postDetails.media,\n        postDetails.message,\n        replyToId\n      );\n    }\n  }\n\n  async post(\n    userId: string,\n    accessToken: string,\n    postDetails: PostDetails<{\n      active_thread_finisher: boolean;\n      thread_finisher: string;\n    }>[]\n  ): Promise<PostResponse[]> {\n    if (!postDetails.length) {\n      return [];\n    }\n\n    const [firstPost] = postDetails;\n\n    // Create the initial thread\n    const initialContentId = await this.createThreadContent(\n      userId,\n      accessToken,\n      firstPost\n    );\n\n    // Publish the thread\n    const { threadId, permalink } = await this.publishThread(\n      userId,\n      accessToken,\n      initialContentId\n    );\n\n    // Return the main post response\n    return [\n      {\n        id: firstPost.id,\n        postId: threadId,\n        status: 'success',\n        releaseURL: permalink,\n      },\n    ];\n  }\n\n  async comment(\n    userId: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<{\n      active_thread_finisher: boolean;\n      thread_finisher: string;\n    }>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    if (!postDetails.length) {\n      return [];\n    }\n\n    const [commentPost] = postDetails;\n    const replyToId = lastCommentId || postId;\n\n    // Create reply content\n    const replyContentId = await this.createThreadContent(\n      userId,\n      accessToken,\n      commentPost,\n      replyToId\n    );\n\n    // Publish the reply\n    const { threadId: replyThreadId, permalink } = await this.publishThread(\n      userId,\n      accessToken,\n      replyContentId\n    );\n\n    return [\n      {\n        id: commentPost.id,\n        postId: replyThreadId,\n        status: 'success',\n        releaseURL: permalink,\n      },\n    ];\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const until = dayjs().endOf('day').unix();\n    const since = dayjs().subtract(date, 'day').unix();\n\n    const { data, ...all } = await (\n      await fetch(\n        `https://graph.threads.net/v1.0/${id}/threads_insights?metric=views,likes,replies,reposts,quotes&access_token=${accessToken}&period=day&since=${since}&until=${until}`\n      )\n    ).json();\n\n    return (\n      data?.map((d: any) => ({\n        label: capitalize(d.name),\n        percentageChange: 5,\n        data: d.total_value\n          ? [{ total: d.total_value.value, date: dayjs().format('YYYY-MM-DD') }]\n          : d.values.map((v: any) => ({\n              total: v.value,\n              date: dayjs(v.end_time).format('YYYY-MM-DD'),\n            })),\n      })) || []\n    );\n  }\n\n  @Plug({\n    identifier: 'threads-autoPlugPost',\n    title: 'Auto plug post',\n    description:\n      'When a post reached a certain number of likes, add another post to it so you followers get a notification about your promotion',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n      {\n        name: 'post',\n        type: 'richtext',\n        placeholder: 'Post to plug',\n        description: 'Message content to plug',\n        validation: /^[\\s\\S]{3,}$/g,\n      },\n    ],\n  })\n  async autoPlugPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string; post: string }\n  ) {\n    const { data } = await (\n      await fetch(\n        `https://graph.threads.net/v1.0/${id}/insights?metric=likes&access_token=${integration.token}`\n      )\n    ).json();\n\n    const {\n      values: [value],\n    } = data.find((p: any) => p.name === 'likes');\n\n    if (value.value >= fields.likesAmount) {\n      await timer(2000);\n\n      const form = new FormData();\n      form.append('media_type', 'TEXT');\n      form.append('text', stripHtmlValidation('normal', fields.post, true));\n      form.append('reply_to_id', id);\n      form.append('access_token', integration.token);\n\n      const { id: replyId } = await (\n        await this.fetch('https://graph.threads.net/v1.0/me/threads', {\n          method: 'POST',\n          body: form,\n        })\n      ).json();\n\n      await (\n        await this.fetch(\n          `https://graph.threads.net/v1.0/${integration.internalId}/threads_publish?creation_id=${replyId}&access_token=${integration.token}`,\n          {\n            method: 'POST',\n          }\n        )\n      ).json();\n      return true;\n    }\n\n    return false;\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    try {\n      // Fetch thread insights from Threads API\n      const { data } = await (\n        await this.fetch(\n          `https://graph.threads.net/v1.0/${postId}/insights?metric=views,likes,replies,reposts,quotes&access_token=${accessToken}`\n        )\n      ).json();\n\n      if (!data || data.length === 0) {\n        return [];\n      }\n\n      const result: AnalyticsData[] = [];\n\n      for (const metric of data) {\n        const value = metric.values?.[0]?.value ?? metric.total_value?.value;\n        if (value === undefined) continue;\n\n        let label = '';\n\n        switch (metric.name) {\n          case 'views':\n            label = 'Views';\n            break;\n          case 'likes':\n            label = 'Likes';\n            break;\n          case 'replies':\n            label = 'Replies';\n            break;\n          case 'reposts':\n            label = 'Reposts';\n            break;\n          case 'quotes':\n            label = 'Quotes';\n            break;\n        }\n\n        if (label) {\n          result.push({\n            label,\n            percentageChange: 0,\n            data: [{ total: String(value), date: today }],\n          });\n        }\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching Threads post analytics:', err);\n      return [];\n    }\n  }\n\n  // override async mention(\n  //   token: string,\n  //   data: { query: string },\n  //   id: string,\n  //   integration: Integration\n  // ) {\n  //   const p = await (\n  //     await fetch(\n  //       `https://graph.threads.net/v1.0/profile_lookup?username=${data.query}&access_token=${integration.token}`\n  //     )\n  //   ).json();\n  //\n  //   return [\n  //     {\n  //       id: String(p.id),\n  //       label: p.name,\n  //       image: p.profile_picture_url,\n  //     },\n  //   ];\n  // }\n  //\n  // mentionFormat(idOrHandle: string, name: string) {\n  //   return `@${idOrHandle}`;\n  // }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport dayjs from 'dayjs';\nimport {\n  BadBody,\n  SocialAbstract,\n} from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { Integration } from '@prisma/client';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  'TikTok can have one video or one picture or multiple pictures, it cannot be without an attachment'\n)\nexport class TiktokProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'tiktok';\n  name = 'Tiktok';\n  isBetweenSteps = false;\n  convertToJPEG = true;\n  scopes = [\n    'video.list',\n    'user.info.basic',\n    'video.publish',\n    'video.upload',\n    'user.info.profile',\n    'user.info.stats',\n  ];\n  override maxConcurrentJob = 300;\n  dto = TikTokDto;\n  editor = 'normal' as const;\n  maxLength() {\n    return 2000;\n  }\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    // Authentication/Authorization errors - require re-authentication\n    if (body.indexOf('access_token_invalid') > -1) {\n      return {\n        type: 'refresh-token' as const,\n        value:\n          'Access token invalid, please re-authenticate your TikTok account',\n      };\n    }\n\n    if (body.indexOf('scope_not_authorized') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'Missing required permissions, please re-authenticate with all scopes',\n      };\n    }\n\n    if (body.indexOf('scope_permission_missed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Additional permissions required, please re-authenticate',\n      };\n    }\n\n    // Rate limiting errors\n    if (body.indexOf('rate_limit_exceeded') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'TikTok API rate limit exceeded, please try again later',\n      };\n    }\n\n    if (body.indexOf('file_format_check_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'File format is invalid, please check video specifications',\n      };\n    }\n\n    if (body.indexOf('app_version_check_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'In order to use the TikTok upload feature, you have to update your app to the latest version',\n      };\n    }\n\n    if (body.indexOf('duration_check_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Video duration is invalid, please check video specifications',\n      };\n    }\n\n    if (body.indexOf('frame_rate_check_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Video frame rate is invalid, please check video specifications',\n      };\n    }\n\n    if (body.indexOf('video_pull_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Failed to pull video from URL, please check the URL',\n      };\n    }\n\n    if (body.indexOf('photo_pull_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Failed to pull photo from URL, please check the URL',\n      };\n    }\n\n    if (body.indexOf('spam_risk_user_banned_from_posting') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'Account banned from posting, please check TikTok account status',\n      };\n    }\n\n    if (body.indexOf('spam_risk_text') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'TikTok detected potential spam in the post text',\n      };\n    }\n\n    if (body.indexOf('spam_risk_too_many_posts') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'TikTok says your daily post limit reached, please try again tomorrow',\n      };\n    }\n\n    if (body.indexOf('spam_risk_too_many_pending_share') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'TikTok limit the maximum of pending posts to 5, please check your TikTok inbox at your TikTok mobile app',\n      };\n    }\n\n    if (body.indexOf('spam_risk_user_banned_from_posting') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value:\n          'Account banned from posting, please check TikTok account status',\n      };\n    }\n\n    if (body.indexOf('spam_risk') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'TikTok detected potential spam',\n      };\n    }\n\n    if (body.indexOf('reached_active_user_cap') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Daily active user quota reached, please try again later',\n      };\n    }\n\n    if (\n      body.indexOf('unaudited_client_can_only_post_to_private_accounts') > -1\n    ) {\n      return {\n        type: 'bad-body' as const,\n        value: 'App not approved for public posting, contact support',\n      };\n    }\n\n    if (body.indexOf('url_ownership_unverified') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'URL ownership not verified, please verify domain ownership',\n      };\n    }\n\n    if (body.indexOf('privacy_level_option_mismatch') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Privacy level mismatch, please check privacy settings',\n      };\n    }\n\n    // Content/Format validation errors\n    if (body.indexOf('invalid_file_upload') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid file format or specifications not met',\n      };\n    }\n\n    if (body.indexOf('invalid_params') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid request parameters, please check content format',\n      };\n    }\n\n    // Server errors\n    if (body.indexOf('internal') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'There is a problem with TikTok servers, please try again later',\n      };\n    }\n\n    // Generic TikTok API errors\n    if (body.indexOf('picture_size_check_failed') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Video must be at least 720p, Picture must no exceed 1080p',\n      };\n    }\n\n    if (body.indexOf('TikTok API error') > -1) {\n      return {\n        type: 'bad-body' as const,\n        value: 'TikTok API error, please try again',\n      };\n    }\n\n    // Fall back to parent class error handling\n    return undefined;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const value = {\n      client_key: process.env.TIKTOK_CLIENT_ID!,\n      client_secret: process.env.TIKTOK_CLIENT_SECRET!,\n      grant_type: 'refresh_token',\n      refresh_token: refreshToken,\n    };\n\n    const { access_token, refresh_token, ...all } = await (\n      await fetch('https://open.tiktokapis.com/v2/oauth/token/', {\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        method: 'POST',\n        body: new URLSearchParams(value).toString(),\n      })\n    ).json();\n\n    const {\n      data: {\n        user: { avatar_url, display_name, open_id, username },\n      },\n    } = await (\n      await fetch(\n        'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,union_id,username',\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${access_token}`,\n          },\n        }\n      )\n    ).json();\n\n    return {\n      refreshToken: refresh_token,\n      expiresIn: dayjs().add(23, 'hours').unix() - dayjs().unix(),\n      accessToken: access_token,\n      id: open_id.replace(/-/g, ''),\n      name: display_name,\n      picture: avatar_url || '',\n      username: username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = Math.random().toString(36).substring(2);\n\n    return {\n      url:\n        'https://www.tiktok.com/v2/auth/authorize/' +\n        `?client_key=${process.env.TIKTOK_CLIENT_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${\n            process?.env?.FRONTEND_URL?.indexOf('https') === -1\n              ? 'https://redirectmeto.com/'\n              : ''\n          }${process?.env?.FRONTEND_URL}/integrations/social/tiktok`\n        )}` +\n        `&state=${state}` +\n        `&response_type=code` +\n        `&scope=${encodeURIComponent(this.scopes.join(','))}`,\n      codeVerifier: state,\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const value = {\n      client_key: process.env.TIKTOK_CLIENT_ID!,\n      client_secret: process.env.TIKTOK_CLIENT_SECRET!,\n      code: params.code,\n      grant_type: 'authorization_code',\n      code_verifier: params.codeVerifier,\n      redirect_uri: `${\n        process?.env?.FRONTEND_URL?.indexOf('https') === -1\n          ? 'https://redirectmeto.com/'\n          : ''\n      }${process?.env?.FRONTEND_URL}/integrations/social/tiktok`,\n    };\n\n    const { access_token, refresh_token, scope } = await (\n      await fetch('https://open.tiktokapis.com/v2/oauth/token/', {\n        headers: {\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n        method: 'POST',\n        body: new URLSearchParams(value).toString(),\n      })\n    ).json();\n\n    this.checkScopes(this.scopes, scope);\n\n    const {\n      data: {\n        user: { avatar_url, display_name, open_id, username },\n      },\n    } = await (\n      await fetch(\n        'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,union_id,username',\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${access_token}`,\n          },\n        }\n      )\n    ).json();\n\n    return {\n      id: open_id.replace(/-/g, ''),\n      name: display_name,\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresIn: dayjs().add(23, 'hours').unix() - dayjs().unix(),\n      picture: avatar_url,\n      username: username,\n    };\n  }\n\n  async maxVideoLength(accessToken: string) {\n    const {\n      data: { max_video_post_duration_sec },\n    } = await (\n      await fetch(\n        'https://open.tiktokapis.com/v2/post/publish/creator_info/query/',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json; charset=UTF-8',\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      )\n    ).json();\n\n    return {\n      maxDurationSeconds: max_video_post_duration_sec,\n    };\n  }\n\n  private async uploadedVideoSuccess(\n    id: string,\n    publishId: string,\n    accessToken: string\n  ): Promise<{ url: string; id: string }> {\n    // eslint-disable-next-line no-constant-condition\n    while (true) {\n      const post = await (\n        await this.fetch(\n          'https://open.tiktokapis.com/v2/post/publish/status/fetch/',\n          {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json; charset=UTF-8',\n              Authorization: `Bearer ${accessToken}`,\n            },\n            body: JSON.stringify({\n              publish_id: publishId,\n            }),\n          },\n          '',\n          0,\n          true\n        )\n      ).json();\n\n      const { status, publicaly_available_post_id } = post.data;\n\n      if (status === 'SEND_TO_USER_INBOX') {\n        return {\n          url: 'https://www.tiktok.com/messages?lang=en',\n          id: 'missing',\n        };\n      }\n\n      if (status === 'PUBLISH_COMPLETE') {\n        return {\n          url: !publicaly_available_post_id\n            ? `https://www.tiktok.com/@${id}`\n            : `https://www.tiktok.com/@${id}/video/` +\n              publicaly_available_post_id,\n          id: !publicaly_available_post_id\n            ? publishId\n            : publicaly_available_post_id?.[0],\n        };\n      }\n\n      if (status === 'FAILED') {\n        const handleError = this.handleErrors(JSON.stringify(post));\n        throw new BadBody(\n          'titok-error-upload',\n          JSON.stringify(post),\n          Buffer.from(JSON.stringify(post)),\n          handleError?.value || ''\n        );\n      }\n\n      await timer(10000);\n    }\n  }\n\n  private postingMethod(\n    method: TikTokDto['content_posting_method'],\n    isPhoto: boolean\n  ): string {\n    switch (method) {\n      case 'UPLOAD':\n        return isPhoto ? '/content/init/' : '/inbox/video/init/';\n      case 'DIRECT_POST':\n      default:\n        return isPhoto ? '/content/init/' : '/video/init/';\n    }\n  }\n\n  private buildTikokPostInfoBody(firstPost: PostDetails<TikTokDto>) {\n    const isPhoto = (firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1;\n    const method = firstPost?.settings?.content_posting_method;\n\n    if (method === 'DIRECT_POST') {\n      return {\n        post_info: {\n          ...(isPhoto && firstPost.settings.title\n            ? { title: firstPost.settings.title.slice(0, 90) }\n            : {}),\n          ...(!isPhoto && firstPost.message\n            ? { title: firstPost.message }\n            : {}),\n          ...(isPhoto ? { description: firstPost.message } : {}),\n          privacy_level:\n            firstPost.settings.privacy_level || 'PUBLIC_TO_EVERYONE',\n          ...(isPhoto\n            ? {}\n            : { disable_duet: !firstPost.settings.duet || false }),\n          disable_comment: !firstPost.settings.comment || false,\n          ...(isPhoto\n            ? {}\n            : { disable_stitch: !firstPost.settings.stitch || false }),\n          ...(isPhoto\n            ? {}\n            : { is_aigc: firstPost.settings.video_made_with_ai || false }),\n          brand_content_toggle:\n            firstPost.settings.brand_content_toggle || false,\n          brand_organic_toggle:\n            firstPost.settings.brand_organic_toggle || false,\n          ...(isPhoto\n            ? {\n                auto_add_music: firstPost.settings.autoAddMusic === 'yes',\n              }\n            : {}),\n        },\n      };\n    }\n\n    return {\n      post_info: {\n        ...(isPhoto && firstPost.settings.title\n          ? { title: firstPost.settings.title }\n          : {}),\n        ...(!isPhoto && firstPost.message ? { title: firstPost.message } : {}),\n        ...(isPhoto ? { description: firstPost.message } : {}),\n      },\n    };\n  }\n\n  private buildTikokSourceInfoBody(firstPost: PostDetails<TikTokDto>) {\n    const isPhoto = (firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1;\n\n    if (isPhoto) {\n      return {\n        post_mode:\n          firstPost?.settings?.content_posting_method === 'DIRECT_POST'\n            ? 'DIRECT_POST'\n            : 'MEDIA_UPLOAD',\n        media_type: 'PHOTO',\n        source_info: {\n          source: 'PULL_FROM_URL',\n          photo_cover_index: 0,\n          photo_images: firstPost.media?.map((p) => p.path),\n        },\n      };\n    }\n\n    return {\n      source_info: {\n        source: 'PULL_FROM_URL',\n        video_url: firstPost?.media?.[0]?.path!,\n        ...(firstPost?.media?.[0]?.thumbnailTimestamp!\n          ? {\n              video_cover_timestamp_ms:\n                firstPost?.media?.[0]?.thumbnailTimestamp!,\n            }\n          : {}),\n      },\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<TikTokDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n    const isPhoto = (firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1;\n\n    console.log({\n      ...this.buildTikokPostInfoBody(firstPost),\n      ...this.buildTikokSourceInfoBody(firstPost),\n    });\n    const {\n      data: { publish_id },\n    } = await (\n      await this.fetch(\n        `https://open.tiktokapis.com/v2/post/publish${this.postingMethod(\n          firstPost.settings.content_posting_method,\n          (firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1\n        )}`,\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json; charset=UTF-8',\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: JSON.stringify({\n            ...this.buildTikokPostInfoBody(firstPost),\n            ...this.buildTikokSourceInfoBody(firstPost),\n          }),\n        }\n      )\n    ).json();\n\n    const { url, id: videoId } = await this.uploadedVideoSuccess(\n      integration.profile!,\n      publish_id,\n      accessToken\n    );\n\n    return [\n      {\n        id: firstPost.id,\n        releaseURL: url,\n        postId: String(videoId),\n        status: 'success',\n      },\n    ];\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    try {\n      // Get user stats (follower_count, following_count, likes_count, video_count)\n      const userStatsResponse = await this.fetch(\n        'https://open.tiktokapis.com/v2/user/info/?fields=follower_count,following_count,likes_count,video_count',\n        {\n          method: 'GET',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n\n      const userStatsData = await userStatsResponse.json();\n      const userStats = userStatsData?.data?.user;\n\n      const result: AnalyticsData[] = [];\n\n      if (userStats) {\n        if (userStats.follower_count !== undefined) {\n          result.push({\n            label: 'Followers',\n            percentageChange: 0,\n            data: [{ total: String(userStats.follower_count), date: today }],\n          });\n        }\n\n        if (userStats.following_count !== undefined) {\n          result.push({\n            label: 'Following',\n            percentageChange: 0,\n            data: [{ total: String(userStats.following_count), date: today }],\n          });\n        }\n\n        if (userStats.likes_count !== undefined) {\n          result.push({\n            label: 'Total Likes',\n            percentageChange: 0,\n            data: [{ total: String(userStats.likes_count), date: today }],\n          });\n        }\n\n        if (userStats.video_count !== undefined) {\n          result.push({\n            label: 'Videos',\n            percentageChange: 0,\n            data: [{ total: String(userStats.video_count), date: today }],\n          });\n        }\n      }\n\n      // Get recent videos and aggregate their stats\n      const videoListResponse = await this.fetch(\n        'https://open.tiktokapis.com/v2/video/list/?fields=id',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: JSON.stringify({ max_count: 20 }),\n        }\n      );\n\n      const videoListData = await videoListResponse.json();\n      const videos = videoListData?.data?.videos;\n\n      if (videos && videos.length > 0) {\n        const videoIds = videos.map((v: { id: string }) => v.id);\n\n        // Query video details to get engagement metrics\n        const videoQueryResponse = await this.fetch(\n          'https://open.tiktokapis.com/v2/video/query/?fields=id,like_count,comment_count,share_count,view_count',\n          {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: `Bearer ${accessToken}`,\n            },\n            body: JSON.stringify({\n              filters: { video_ids: videoIds },\n            }),\n          }\n        );\n\n        const videoQueryData = await videoQueryResponse.json();\n        const videoDetails = videoQueryData?.data?.videos;\n\n        if (videoDetails && videoDetails.length > 0) {\n          let totalViews = 0;\n          let totalLikes = 0;\n          let totalComments = 0;\n          let totalShares = 0;\n\n          for (const video of videoDetails) {\n            totalViews += video.view_count || 0;\n            totalLikes += video.like_count || 0;\n            totalComments += video.comment_count || 0;\n            totalShares += video.share_count || 0;\n          }\n\n          result.push({\n            label: 'Views',\n            percentageChange: 0,\n            data: [{ total: String(totalViews), date: today }],\n          });\n\n          result.push({\n            label: 'Recent Likes',\n            percentageChange: 0,\n            data: [{ total: String(totalLikes), date: today }],\n          });\n\n          result.push({\n            label: 'Recent Comments',\n            percentageChange: 0,\n            data: [{ total: String(totalComments), date: today }],\n          });\n\n          result.push({\n            label: 'Recent Shares',\n            percentageChange: 0,\n            data: [{ total: String(totalShares), date: today }],\n          });\n        }\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching TikTok analytics:', err);\n      return [];\n    }\n  }\n\n  async missing(\n    id: string,\n    accessToken: string\n  ): Promise<{ id: string; url: string }[]> {\n    try {\n      const videoListResponse = await this.fetch(\n        'https://open.tiktokapis.com/v2/video/list/?fields=id,cover_image_url,title',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: JSON.stringify({ max_count: 20 }),\n        }\n      );\n\n      const videoListData = await videoListResponse.json();\n      const videos = videoListData?.data?.videos;\n\n      if (!videos || videos.length === 0) {\n        return [];\n      }\n\n      return videos.map((v: { id: string; cover_image_url: string }) => ({\n        id: String(v.id),\n        url: v.cover_image_url,\n      }));\n    } catch (err) {\n      console.error('Error fetching TikTok missing content:', err);\n      return [];\n    }\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    fromDate: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    if (postId.indexOf('v_pub_url') > -1) {\n      const post = await (\n        await this.fetch(\n          'https://open.tiktokapis.com/v2/post/publish/status/fetch/',\n          {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json; charset=UTF-8',\n              Authorization: `Bearer ${accessToken}`,\n            },\n            body: JSON.stringify({\n              publish_id: postId,\n            }),\n          },\n          '',\n          0,\n          true\n        )\n      ).json();\n\n      if (!post?.data?.publicaly_available_post_id?.[0]) {\n        return [];\n      }\n\n      postId = post.data.publicaly_available_post_id[0];\n    }\n\n    try {\n      // Query video details using the video ID\n      const response = await this.fetch(\n        'https://open.tiktokapis.com/v2/video/query/?fields=id,like_count,comment_count,share_count,view_count',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: `Bearer ${accessToken}`,\n          },\n          body: JSON.stringify({\n            filters: {\n              video_ids: [postId],\n            },\n          }),\n        }\n      );\n\n      const data = await response.json();\n      const video = data?.data?.videos?.[0];\n\n      if (!video) {\n        return [];\n      }\n\n      const result: AnalyticsData[] = [];\n\n      if (video.view_count !== undefined) {\n        result.push({\n          label: 'Views',\n          percentageChange: 0,\n          data: [{ total: String(video.view_count), date: today }],\n        });\n      }\n\n      if (video.like_count !== undefined) {\n        result.push({\n          label: 'Likes',\n          percentageChange: 0,\n          data: [{ total: String(video.like_count), date: today }],\n        });\n      }\n\n      if (video.comment_count !== undefined) {\n        result.push({\n          label: 'Comments',\n          percentageChange: 0,\n          data: [{ total: String(video.comment_count), date: today }],\n        });\n      }\n\n      if (video.share_count !== undefined) {\n        result.push({\n          label: 'Shares',\n          percentageChange: 0,\n          data: [{ total: String(video.share_count), date: today }],\n        });\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching TikTok post analytics:', err);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/twitch.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { Integration } from '@prisma/client';\nimport { TwitchDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/twitch.dto';\nimport { timer } from '@gitroom/helpers/utils/timer';\n\nexport class TwitchProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 1;\n  identifier = 'twitch';\n  name = 'Twitch';\n  isBetweenSteps = false;\n  editor = 'normal' as const;\n  scopes = ['user:write:chat', 'user:read:chat', 'moderator:manage:announcements'];\n  dto = TwitchDto;\n\n  maxLength() {\n    return 500; // Twitch chat message max length\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const response = await this.fetch('https://id.twitch.tv/oauth2/token', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      body: new URLSearchParams({\n        grant_type: 'refresh_token',\n        client_id: process.env.TWITCH_CLIENT_ID!,\n        client_secret: process.env.TWITCH_CLIENT_SECRET!,\n        refresh_token: refreshToken,\n      }),\n    });\n\n    const { access_token, refresh_token, expires_in } = await response.json();\n\n    // Get user info\n    const userInfo = await this.getUserInfo(access_token);\n\n    return {\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      accessToken: access_token,\n      id: userInfo.id,\n      name: userInfo.name,\n      picture: userInfo.picture || '',\n      username: userInfo.username,\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(32);\n\n    const redirectUri = `${process.env.FRONTEND_URL}/integrations/social/twitch`;\n\n    const url =\n      `https://id.twitch.tv/oauth2/authorize` +\n      `?response_type=code` +\n      `&client_id=${process.env.TWITCH_CLIENT_ID}` +\n      `&redirect_uri=${encodeURIComponent(redirectUri)}` +\n      `&scope=${encodeURIComponent(this.scopes.join(' '))}` +\n      `&state=${state}`;\n\n    return {\n      url,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const redirectUri = `${process.env.FRONTEND_URL}/integrations/social/twitch${\n      params.refresh ? `?refresh=${params.refresh}` : ''\n    }`;\n\n    const tokenResponse = await this.fetch('https://id.twitch.tv/oauth2/token', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      body: new URLSearchParams({\n        grant_type: 'authorization_code',\n        client_id: process.env.TWITCH_CLIENT_ID!,\n        client_secret: process.env.TWITCH_CLIENT_SECRET!,\n        redirect_uri: redirectUri,\n        code: params.code,\n      }),\n    });\n\n    const { access_token, refresh_token, expires_in } =\n      await tokenResponse.json();\n\n    // Get user info\n    const userInfo = await this.getUserInfo(access_token);\n\n    return {\n      id: userInfo.id,\n      name: userInfo.name,\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresIn: expires_in,\n      picture: userInfo.picture || '',\n      username: userInfo.username,\n    };\n  }\n\n  private async getUserInfo(\n    accessToken: string\n  ): Promise<{ id: string; name: string; username: string; picture?: string }> {\n    const userResponse = await fetch('https://api.twitch.tv/helix/users', {\n      method: 'GET',\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        'Client-Id': process.env.TWITCH_CLIENT_ID!,\n      },\n    });\n\n    const userData = await userResponse.json();\n    const user = userData.data?.[0];\n\n    return {\n      id: String(user.id),\n      name: user.display_name,\n      username: user.login,\n      picture: user.profile_image_url || '',\n    };\n  }\n\n  private async sendAnnouncement(\n    broadcasterId: string,\n    accessToken: string,\n    message: string,\n    color: string = 'primary'\n  ): Promise<{ success: boolean }> {\n    await fetch(\n      `https://api.twitch.tv/helix/chat/announcements?broadcaster_id=${broadcasterId}&moderator_id=${broadcasterId}`,\n      {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Client-Id': process.env.TWITCH_CLIENT_ID!,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          message: message.substring(0, 500),\n          color,\n        }),\n      }\n    );\n\n    // Announcements return 204 No Content on success\n    return { success: true };\n  }\n\n  private async sendChatMessage(\n    broadcasterId: string,\n    accessToken: string,\n    message: string,\n    replyToMessageId?: string\n  ): Promise<{ messageId: string; isSent: boolean }> {\n    const body: Record<string, string> = {\n      broadcaster_id: broadcasterId,\n      sender_id: broadcasterId,\n      message: message.substring(0, 500),\n    };\n\n    if (replyToMessageId) {\n      body.reply_parent_message_id = replyToMessageId;\n    }\n\n    const response = await this.fetch(\n      'https://api.twitch.tv/helix/chat/messages',\n      {\n        method: 'POST',\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Client-Id': process.env.TWITCH_CLIENT_ID!,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(body),\n      }\n    );\n\n    const data = await response.json();\n\n    return {\n      messageId: data.data?.[0]?.message_id || makeId(10),\n      isSent: data.data?.[0]?.is_sent ?? false,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    await timer(2000);\n    const [firstPost] = postDetails;\n    const messageType = firstPost.settings?.messageType || 'message';\n    const announcementColor = firstPost.settings?.announcementColor || 'primary';\n\n    if (messageType === 'announcement') {\n      const result = await this.sendAnnouncement(\n        id,\n        accessToken,\n        firstPost.message,\n        announcementColor\n      );\n\n      return [\n        {\n          id: firstPost.id,\n          postId: makeId(10), // Announcements don't return a message ID\n          releaseURL: `https://twitch.tv/${integration.profile || integration.providerIdentifier}`,\n          status: result.success ? 'posted' : 'error',\n        },\n      ];\n    }\n\n    // Regular chat message\n    const result = await this.sendChatMessage(id, accessToken, firstPost.message);\n\n    return [\n      {\n        id: firstPost.id,\n        postId: result.messageId,\n        releaseURL: `https://twitch.tv/${integration.profile || integration.providerIdentifier}`,\n        status: result.isSent ? 'posted' : 'error',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    await timer(2000);\n    const [commentPost] = postDetails;\n    const messageType = commentPost.settings?.messageType || 'message';\n    const announcementColor = commentPost.settings?.announcementColor || 'primary';\n\n    if (messageType === 'announcement') {\n      const result = await this.sendAnnouncement(\n        id,\n        accessToken,\n        commentPost.message,\n        announcementColor\n      );\n\n      return [\n        {\n          id: commentPost.id,\n          postId: makeId(10),\n          releaseURL: `https://twitch.tv/${integration.profile || integration.providerIdentifier}`,\n          status: result.success ? 'posted' : 'error',\n        },\n      ];\n    }\n\n    // Regular chat message with reply\n    const result = await this.sendChatMessage(\n      id,\n      accessToken,\n      commentPost.message,\n      lastCommentId || postId\n    );\n\n    return [\n      {\n        id: commentPost.id,\n        postId: result.messageId,\n        releaseURL: `https://twitch.tv/${integration.profile || integration.providerIdentifier}`,\n        status: result.isSent ? 'posted' : 'error',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/vk.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport dayjs from 'dayjs';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { createHash, randomBytes } from 'crypto';\nimport axios from 'axios';\nimport FormDataNew from 'form-data';\nimport mime from 'mime-types';\nimport { Integration } from '@prisma/client';\n\nexport class VkProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 2; // VK has moderate API limits\n  identifier = 'vk';\n  name = 'VK';\n  isBetweenSteps = false;\n  scopes = [\n    'vkid.personal_info',\n    'email',\n    'wall',\n    'status',\n    'docs',\n    'photos',\n    'video',\n  ];\n\n  editor = 'normal' as const;\n  maxLength() {\n    return 2048;\n  }\n\n  async refreshToken(refresh: string): Promise<AuthTokenDetails> {\n    const [oldRefreshToken, device_id] = refresh.split('&&&&');\n    const formData = new FormData();\n    formData.append('grant_type', 'refresh_token');\n    formData.append('refresh_token', oldRefreshToken);\n    formData.append('client_id', process.env.VK_ID!);\n    formData.append('device_id', device_id);\n    formData.append('state', makeId(32));\n    formData.append('scope', this.scopes.join(' '));\n\n    const { access_token, refresh_token, expires_in } = await (\n      await this.fetch('https://id.vk.com/oauth2/auth', {\n        method: 'POST',\n        body: formData,\n      })\n    ).json();\n\n    const newFormData = new FormData();\n    newFormData.append('client_id', process.env.VK_ID!);\n    newFormData.append('access_token', access_token);\n\n    const {\n      user: { user_id, first_name, last_name, avatar },\n    } = await (\n      await this.fetch('https://id.vk.com/oauth2/user_info', {\n        method: 'POST',\n        body: newFormData,\n      })\n    ).json();\n\n    return {\n      id: user_id,\n      name: first_name + ' ' + last_name,\n      accessToken: access_token,\n      refreshToken: refresh_token + '&&&&' + device_id,\n      expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(),\n      picture: avatar || '',\n      username: first_name.toLowerCase(),\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(32);\n    const codeVerifier = randomBytes(64).toString('base64url');\n    const challenge = Buffer.from(\n      createHash('sha256').update(codeVerifier).digest()\n    )\n      .toString('base64')\n      .replace(/=*$/g, '')\n      .replace(/\\+/g, '-')\n      .replace(/\\//g, '_');\n\n    return {\n      url:\n        'https://id.vk.com/authorize' +\n        `?response_type=code` +\n        `&client_id=${process.env.VK_ID}` +\n        `&code_challenge_method=S256` +\n        `&code_challenge=${challenge}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${\n            process?.env.FRONTEND_URL?.indexOf('https') == -1\n              ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n              : `${process?.env.FRONTEND_URL}`\n          }/integrations/social/vk`\n        )}` +\n        `&state=${state}` +\n        `&scope=${encodeURIComponent(this.scopes.join(' '))}`,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const [code, device_id] = params.code.split('&&&&');\n\n    const formData = new FormData();\n    formData.append('client_id', process.env.VK_ID!);\n    formData.append('grant_type', 'authorization_code');\n    formData.append('code_verifier', params.codeVerifier);\n    formData.append('device_id', device_id);\n    formData.append('code', code);\n    formData.append(\n      'redirect_uri',\n      `${\n        process?.env.FRONTEND_URL?.indexOf('https') == -1\n          ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}`\n          : `${process?.env.FRONTEND_URL}`\n      }/integrations/social/vk`\n    );\n\n    const { access_token, scope, refresh_token, expires_in } = await (\n      await this.fetch('https://id.vk.com/oauth2/auth', {\n        method: 'POST',\n        body: formData,\n      })\n    ).json();\n\n    const newFormData = new FormData();\n    newFormData.append('client_id', process.env.VK_ID!);\n    newFormData.append('access_token', access_token);\n\n    const {\n      user: { user_id, first_name, last_name, avatar },\n    } = await (\n      await this.fetch('https://id.vk.com/oauth2/user_info', {\n        method: 'POST',\n        body: newFormData,\n      })\n    ).json();\n\n    return {\n      id: user_id,\n      name: first_name + ' ' + last_name,\n      accessToken: access_token,\n      refreshToken: refresh_token + '&&&&' + device_id,\n      expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(),\n      picture: avatar || '',\n      username: first_name.toLowerCase(),\n    };\n  }\n\n  private async uploadMedia(\n    userId: string,\n    accessToken: string,\n    post: PostDetails\n  ): Promise<{ id: string; type: string }[]> {\n    return await Promise.all(\n      (post?.media || []).map(async (media) => {\n        const all = await (\n          await this.fetch(\n            media.path.indexOf('mp4') > -1\n              ? `https://api.vk.com/method/video.save?access_token=${accessToken}&v=5.251`\n              : `https://api.vk.com/method/photos.getWallUploadServer?owner_id=${userId}&access_token=${accessToken}&v=5.251`\n          )\n        ).json();\n\n        const { data } = await axios.get(media.path!, {\n          responseType: 'stream',\n        });\n\n        const slash = media.path.split('/').at(-1);\n\n        const formData = new FormDataNew();\n        formData.append('photo', data, {\n          filename: slash,\n          contentType: mime.lookup(slash!) || '',\n        });\n        const value = (\n          await axios.post(all.response.upload_url, formData, {\n            headers: {\n              ...formData.getHeaders(),\n            },\n          })\n        ).data;\n\n        if (media.path.indexOf('mp4') > -1) {\n          return {\n            id: all.response.video_id,\n            type: 'video',\n          };\n        }\n\n        const formSend = new FormData();\n        formSend.append('photo', value.photo);\n        formSend.append('server', value.server);\n        formSend.append('hash', value.hash);\n\n        const { id } = (\n          await (\n            await fetch(\n              `https://api.vk.com/method/photos.saveWallPhoto?access_token=${accessToken}&v=5.251`,\n              {\n                method: 'POST',\n                body: formSend,\n              }\n            )\n          ).json()\n        ).response[0];\n\n        return {\n          id,\n          type: 'photo',\n        };\n      })\n    );\n  }\n\n  async post(\n    userId: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [firstPost] = postDetails;\n\n    // Upload media for the first post\n    const mediaList = await this.uploadMedia(userId, accessToken, firstPost);\n\n    const body = new FormData();\n    body.append('message', firstPost.message);\n\n    if (mediaList.length) {\n      body.append(\n        'attachments',\n        mediaList.map((p) => `${p.type}${userId}_${p.id}`).join(',')\n      );\n    }\n\n    const { response } = await (\n      await this.fetch(\n        `https://api.vk.com/method/wall.post?v=5.251&access_token=${accessToken}&client_id=${process.env.VK_ID}`,\n        {\n          method: 'POST',\n          body,\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: firstPost.id,\n        postId: String(response?.post_id),\n        releaseURL: `https://vk.com/feed?w=wall${userId}_${response?.post_id}`,\n        status: 'completed',\n      },\n    ];\n  }\n\n  async comment(\n    userId: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [commentPost] = postDetails;\n\n    // Upload media for the comment\n    const mediaList = await this.uploadMedia(userId, accessToken, commentPost);\n\n    const body = new FormData();\n    body.append('message', commentPost.message);\n    body.append('post_id', postId);\n\n    if (mediaList.length) {\n      body.append(\n        'attachments',\n        mediaList.map((p) => `${p.type}${userId}_${p.id}`).join(',')\n      );\n    }\n\n    const { response } = await (\n      await this.fetch(\n        `https://api.vk.com/method/wall.createComment?v=5.251&access_token=${accessToken}&client_id=${process.env.VK_ID}`,\n        {\n          method: 'POST',\n          body,\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: commentPost.id,\n        postId: String(response?.comment_id),\n        releaseURL: `https://vk.com/feed?w=wall${userId}_${postId}`,\n        status: 'completed',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/whop.provider.ts",
    "content": "import { createHash, randomBytes } from 'crypto';\nimport {\n  AuthTokenDetails,\n  MediaContent,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { WhopDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/whop.dto';\nimport { Integration } from '@prisma/client';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\n\nexport class WhopProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'whop';\n  name = 'Whop';\n  isBetweenSteps = false;\n  scopes = ['openid', 'profile', 'email', 'forum:post:create', 'forum:read', 'company:basic:read'];\n  refreshCron = false;\n  editor = 'markdown' as const;\n  dto = WhopDto;\n  toolTip = 'Schedule posts to forums';\n\n  maxLength() {\n    return 50000;\n  }\n\n  private generateCodeChallenge(codeVerifier: string): string {\n    return createHash('sha256').update(codeVerifier).digest('base64url');\n  }\n\n  override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body'; value: string }\n    | undefined {\n    if (body.includes('invalid_grant')) {\n      return {\n        type: 'refresh-token' as const,\n        value: 'Invalid token, please re-authenticate',\n      };\n    }\n\n    if (body.includes('insufficient_scope')) {\n      return {\n        type: 'refresh-token' as const,\n        value:\n          'Insufficient permissions, please re-authenticate with required scopes',\n      };\n    }\n\n    if (body.includes('invalid_request')) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Invalid request parameters',\n      };\n    }\n\n    if (body.includes('not_found')) {\n      return {\n        type: 'bad-body' as const,\n        value: 'Forum or experience not found',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    const response = await (\n      await fetch('https://api.whop.com/oauth/token', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          grant_type: 'refresh_token',\n          refresh_token: refreshToken,\n          client_id: process.env.WHOP_CLIENT_ID,\n        }),\n      })\n    ).json();\n\n    const userInfo = await (\n      await fetch('https://api.whop.com/oauth/userinfo', {\n        headers: { Authorization: `Bearer ${response.access_token}` },\n      })\n    ).json();\n\n    return {\n      id: userInfo.sub,\n      name: userInfo.name || userInfo.preferred_username || '',\n      accessToken: response.access_token,\n      refreshToken: response.refresh_token,\n      expiresIn: response.expires_in || 3600,\n      picture: userInfo.picture || '',\n      username: userInfo.preferred_username || '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    const codeVerifier = randomBytes(32).toString('base64url');\n    const codeChallenge = this.generateCodeChallenge(codeVerifier);\n    const nonce = makeId(16);\n\n    return {\n      url:\n        'https://api.whop.com/oauth/authorize' +\n        `?response_type=code` +\n        `&client_id=${process.env.WHOP_CLIENT_ID}` +\n        `&redirect_uri=${encodeURIComponent(\n          `${process.env.FRONTEND_URL}/integrations/social/whop`\n        )}` +\n        `&scope=${encodeURIComponent(this.scopes.join(' '))}` +\n        `&state=${state}` +\n        `&nonce=${nonce}` +\n        `&code_challenge=${codeChallenge}` +\n        `&code_challenge_method=S256`,\n      codeVerifier,\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const redirectUri = `${process.env.FRONTEND_URL}/integrations/social/whop${\n      params.refresh ? `?refresh=${params.refresh}` : ''\n    }`;\n\n    const tokenResponse = await (\n      await fetch('https://api.whop.com/oauth/token', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          grant_type: 'authorization_code',\n          code: params.code,\n          redirect_uri: redirectUri,\n          client_id: process.env.WHOP_CLIENT_ID,\n          code_verifier: params.codeVerifier,\n        }),\n      })\n    ).json();\n\n    if (tokenResponse.error) {\n      return `Authentication failed: ${\n        tokenResponse.error_description || tokenResponse.error\n      }`;\n    }\n\n    const userInfo = await (\n      await fetch('https://api.whop.com/oauth/userinfo', {\n        headers: { Authorization: `Bearer ${tokenResponse.access_token}` },\n      })\n    ).json();\n\n    return {\n      id: userInfo.sub,\n      name: userInfo.name || userInfo.preferred_username || '',\n      accessToken: tokenResponse.access_token,\n      refreshToken: tokenResponse.refresh_token,\n      expiresIn: tokenResponse.expires_in || 3600,\n      picture: userInfo.picture || '',\n      username: userInfo.preferred_username || '',\n    };\n  }\n\n  @Tool({ description: 'Companies', dataSchema: [] })\n  async companies(accessToken: string, params: any, id: string) {\n    try {\n      const response = await fetch(\n        'https://api.whop.com/api/v1/companies?first=50',\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n\n      const { data } = await response.json();\n\n      return (data || []).map((company: any) => ({\n        id: company.id,\n        name: company.title,\n      }));\n    } catch {\n      return [];\n    }\n  }\n\n  @Tool({ description: 'Experiences', dataSchema: [] })\n  async experiences(accessToken: string, params: any, id: string) {\n    try {\n      if (!params?.id) return [];\n\n      const response = await fetch(\n        `https://api.whop.com/api/v1/forums?company_id=${params.id}&first=50`,\n        {\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n          },\n        }\n      );\n\n      const { data } = await response.json();\n\n      return (data || []).map((forum: any) => ({\n        id: forum.experience?.id || forum.id,\n        name: forum.experience?.name || forum.id,\n      }));\n    } catch {\n      return [];\n    }\n  }\n\n  private async uploadMediaToWhop(\n    media: MediaContent[],\n    accessToken: string\n  ): Promise<{ id: string }[]> {\n    if (!media || media.length === 0) return [];\n\n    const attachments: { id: string }[] = [];\n\n    for (const item of media) {\n      const fileResponse = await fetch(item.path);\n      const fileBuffer = await fileResponse.arrayBuffer();\n      const fileName = item.path.split('/').pop() || 'file';\n\n      const createFileResponse = await (\n        await this.fetch(\n          'https://api.whop.com/api/v1/files',\n          {\n            method: 'POST',\n            headers: {\n              Authorization: `Bearer ${accessToken}`,\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({\n              filename: fileName,\n            }),\n          },\n          'create file record'\n        )\n      ).json();\n\n      if (createFileResponse.upload_url) {\n        await fetch(createFileResponse.upload_url, {\n          method: 'PUT',\n          headers: createFileResponse.upload_headers || {},\n          body: fileBuffer,\n        });\n\n        let uploadStatus = 'pending';\n        while (uploadStatus !== 'ready') {\n          const fileStatus = await (\n            await this.fetch(\n              `https://api.whop.com/api/v1/files/${createFileResponse.id}`,\n              {\n                headers: {\n                  Authorization: `Bearer ${accessToken}`,\n                },\n              },\n              'check file status',\n              0,\n              true\n            )\n          ).json();\n          uploadStatus = fileStatus.upload_status;\n          if (uploadStatus === 'failed') {\n            throw new Error('File upload failed');\n          }\n          if (uploadStatus !== 'ready') {\n            await timer(5000);\n          }\n        }\n      }\n\n      attachments.push({ id: createFileResponse.id });\n    }\n\n    return attachments;\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<WhopDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [post] = postDetails;\n\n    const attachments = await this.uploadMediaToWhop(\n      post.media || [],\n      accessToken\n    );\n\n    const data = await (\n      await this.fetch(\n        'https://api.whop.com/api/v1/forum_posts',\n        {\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            experience_id: post.settings.experience,\n            content: post.message,\n            ...(post.settings.title ? { title: post.settings.title } : {}),\n            ...(attachments.length ? { attachments } : {}),\n          }),\n        },\n        'create forum post'\n      )\n    ).json();\n\n    return [\n      {\n        id: post.id,\n        postId: data.id,\n        releaseURL: `https://whop.com/experiences/${post.settings.experience}/${data.id}`,\n        status: 'success',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<WhopDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const [post] = postDetails;\n    const replyToId = lastCommentId || postId;\n\n    const attachments = await this.uploadMediaToWhop(\n      post.media || [],\n      accessToken\n    );\n\n    const data = await (\n      await this.fetch(\n        'https://api.whop.com/api/v1/forum_posts',\n        {\n          method: 'POST',\n          headers: {\n            Authorization: `Bearer ${accessToken}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            experience_id: post.settings.experience,\n            content: post.message,\n            parent_id: replyToId,\n            ...(attachments.length ? { attachments } : {}),\n          }),\n        },\n        'create comment'\n      )\n    ).json();\n\n    return [\n      {\n        id: post.id,\n        postId: data.id,\n        releaseURL: `https://whop.com/experiences/${post.settings.experience}/${postId}`,\n        status: 'success',\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/wordpress.provider.ts",
    "content": "import {\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport dayjs from 'dayjs';\nimport { Integration } from '@prisma/client';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';\nimport slugify from 'slugify';\n// import FormData from 'form-data';\nimport axios from 'axios';\nimport { Tool } from '@gitroom/nestjs-libraries/integrations/tool.decorator';\nimport { string } from 'yup';\n\nexport class WordpressProvider\n  extends SocialAbstract\n  implements SocialProvider\n{\n  identifier = 'wordpress';\n  name = 'WordPress';\n  isBetweenSteps = false;\n  editor = 'html' as const;\n  scopes = [] as string[];\n  override maxConcurrentJob = 5; // WordPress self-hosted typically has generous limits\n  dto = WordpressDto;\n  maxLength() {\n    return 100000;\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(6);\n    return {\n      url: state,\n      codeVerifier: makeId(10),\n      state,\n    };\n  }\n\n  async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {\n    return {\n      refreshToken: '',\n      expiresIn: 0,\n      accessToken: '',\n      id: '',\n      name: '',\n      picture: '',\n      username: '',\n    };\n  }\n  override handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    if (body.indexOf('rest_cannot_create') > -1) {\n      return {\n        type: 'bad-body',\n        value: 'The connect user has insufficient permissions to create posts',\n      };\n    }\n    return undefined;\n  }\n\n  async customFields() {\n    return [\n      {\n        key: 'domain',\n        label: 'Domain URL',\n        validation: `/^https?:\\\\/\\\\/(?:www\\\\.)?[\\\\w\\\\-]+(\\\\.[\\\\w\\\\-]+)+([\\\\/?#][^\\\\s]*)?$/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'username',\n        label: 'Username',\n        validation: `/.+/`,\n        type: 'text' as const,\n      },\n      {\n        key: 'password',\n        label: 'Password',\n        validation: `/.+/`,\n        type: 'password' as const,\n      },\n    ];\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const body = JSON.parse(Buffer.from(params.code, 'base64').toString()) as {\n      domain: string;\n      username: string;\n      password: string;\n    };\n    try {\n      const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n        'base64'\n      );\n      const { id, name, avatar_urls, code } = await (\n        await fetch(`${body.domain}/wp-json/wp/v2/users/me`, {\n          headers: {\n            Authorization: `Basic ${auth}`,\n          },\n        })\n      ).json();\n\n      if (code) {\n        throw \"Invalid credentials\";\n      }\n\n      const biggestImage = Object.entries(avatar_urls || {}).reduce(\n        (all, current) => {\n          if (all > Number(current[0])) {\n            return all;\n          }\n          return Number(current[0]);\n        },\n        0\n      );\n\n      return {\n        refreshToken: '',\n        expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),\n        accessToken: params.code,\n        id: body.domain + '_' + id,\n        name,\n        picture: avatar_urls?.[String(biggestImage)] || '',\n        username: body.username,\n      };\n    } catch (err) {\n      console.log(err);\n      return 'Invalid credentials';\n    }\n  }\n\n  @Tool({\n    description: 'Get list of post types',\n    dataSchema: [],\n  })\n  async postTypes(token: string) {\n    const body = JSON.parse(Buffer.from(token, 'base64').toString()) as {\n      domain: string;\n      username: string;\n      password: string;\n    };\n\n    const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n      'base64'\n    );\n\n    const postTypes = await (\n      await this.fetch(`${body.domain}/wp-json/wp/v2/types`, {\n        headers: {\n          Authorization: `Basic ${auth}`,\n        },\n      })\n    ).json();\n\n    return Object.entries<any>(postTypes).reduce((all, [key, value]) => {\n      if (\n        key.indexOf('wp_') > -1 ||\n        key.indexOf('nav_') > -1 ||\n        key === 'attachment'\n      ) {\n        return all;\n      }\n\n      all.push({\n        id: value.rest_base,\n        name: value.name,\n      });\n\n      return all;\n    }, []);\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<WordpressDto>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const body = JSON.parse(Buffer.from(accessToken, 'base64').toString()) as {\n      domain: string;\n      username: string;\n      password: string;\n    };\n\n    const auth = Buffer.from(`${body.username}:${body.password}`).toString(\n      'base64'\n    );\n\n    let mediaId = '';\n    if (postDetails?.[0]?.settings?.main_image?.path) {\n      console.log(\n        'Uploading image to WordPress',\n        postDetails[0].settings.main_image.path\n      );\n\n      const blob = await this.fetch(\n        postDetails[0].settings.main_image.path\n      ).then((r) => r.blob());\n\n      const mediaResponse = await (\n        await this.fetch(`${body.domain}/wp-json/wp/v2/media`, {\n          method: 'POST',\n          headers: {\n            Authorization: `Basic ${auth}`,\n            'Content-Disposition': `attachment; filename=\"${postDetails[0].settings.main_image.path\n              .split('/')\n              .pop()}\"`,\n            'Content-Type': blob.type,\n          },\n          body: blob,\n        })\n      ).json();\n\n      mediaId = mediaResponse.id;\n    }\n\n    const submit = await (\n      await this.fetch(\n        `${body.domain}/wp-json/wp/v2/${postDetails?.[0]?.settings?.type}`,\n        {\n          headers: {\n            Authorization: `Basic ${auth}`,\n            'Content-Type': 'application/json',\n          },\n          method: 'POST',\n          body: JSON.stringify({\n            title: postDetails?.[0]?.settings?.title,\n            content: postDetails?.[0]?.message,\n            slug: slugify(postDetails?.[0]?.settings?.title, {\n              lower: true,\n              strict: true,\n              trim: true,\n            }),\n            status: 'publish',\n            ...(mediaId ? { featured_media: mediaId } : {}),\n          }),\n        }\n      )\n    ).json();\n\n    return [\n      {\n        id: postDetails?.[0].id,\n        status: 'completed',\n        postId: String(submit.id),\n        releaseURL: submit.link,\n      },\n    ];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/x.provider.ts",
    "content": "import { TweetV2, TwitterApi } from 'twitter-api-v2';\nimport {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { lookup } from 'mime-types';\nimport sharp from 'sharp';\nimport { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';\nimport { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport { Plug } from '@gitroom/helpers/decorators/plug.decorator';\nimport { Integration } from '@prisma/client';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { PostPlug } from '@gitroom/helpers/decorators/post.plug';\nimport dayjs from 'dayjs';\nimport { uniqBy } from 'lodash';\nimport { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';\nimport { XDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/x.dto';\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\n@Rules(\n  'X can have maximum 4 pictures, or maximum one video, it can also be without attachments'\n)\nexport class XProvider extends SocialAbstract implements SocialProvider {\n  identifier = 'x';\n  name = 'X';\n  isBetweenSteps = false;\n  scopes = [] as string[];\n  override maxConcurrentJob = 1; // X has strict rate limits (300 posts per 3 hours)\n  toolTip =\n    'You will be logged in into your current account, if you would like a different account, change it first on X';\n\n  editor = 'normal' as const;\n  dto = XDto;\n\n  maxLength(isTwitterPremium: boolean) {\n    return isTwitterPremium ? 4000 : 200;\n  }\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    if (body.includes('Unsupported Authentication')) {\n      return {\n        type: 'refresh-token',\n        value: 'X authentication has expired, please reconnect your account',\n      };\n    }\n\n    if (body.includes('usage-capped')) {\n      return {\n        type: 'bad-body',\n        value: 'Posting failed - capped reached. Please try again later',\n      };\n    }\n    if (body.includes('duplicate-rules')) {\n      return {\n        type: 'bad-body',\n        value:\n          'You have already posted this post, please wait before posting again',\n      };\n    }\n    if (body.includes('The Tweet contains an invalid URL.')) {\n      return {\n        type: 'bad-body',\n        value: 'The Tweet contains a URL that is not allowed on X',\n      };\n    }\n    if (\n      body.includes(\n        'This user is not allowed to post a video longer than 2 minutes'\n      )\n    ) {\n      return {\n        type: 'bad-body',\n        value:\n          'The video you are trying to post is longer than 2 minutes, which is not allowed for this account',\n      };\n    }\n    return undefined;\n  }\n\n  @Plug({\n    identifier: 'x-autoRepostPost',\n    title: 'Auto Repost Posts',\n    disabled: !!process.env.DISABLE_X_ANALYTICS,\n    description:\n      'When a post reached a certain number of likes, repost it to increase engagement (1 week old posts)',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n    ],\n  })\n  async autoRepostPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string }\n  ) {\n    // @ts-ignore\n    // eslint-disable-next-line prefer-rest-params\n    const [accessTokenSplit, accessSecretSplit] = integration.token.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    if (\n      (await client.v2.tweetLikedBy(id)).meta.result_count >=\n      +fields.likesAmount\n    ) {\n      await timer(2000);\n      await client.v2.retweet(integration.internalId, id);\n      return true;\n    }\n\n    return false;\n  }\n\n  @PostPlug({\n    identifier: 'x-repost-post-users',\n    title: 'Add Re-posters',\n    description: 'Add accounts to repost your post',\n    pickIntegration: ['x'],\n    fields: [],\n  })\n  async repostPostUsers(\n    integration: Integration,\n    originalIntegration: Integration,\n    postId: string,\n    information: any\n  ) {\n    const [accessTokenSplit, accessSecretSplit] = integration.token.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    const {\n      data: { id },\n    } = await client.v2.me();\n\n    try {\n      await client.v2.retweet(id, postId);\n    } catch (err) {\n      /** nothing **/\n    }\n  }\n\n  @Plug({\n    identifier: 'x-autoPlugPost',\n    title: 'Auto plug post',\n    disabled: !!process.env.DISABLE_X_ANALYTICS,\n    description:\n      'When a post reached a certain number of likes, add another post to it so you followers get a notification about your promotion',\n    runEveryMilliseconds: 21600000,\n    totalRuns: 3,\n    fields: [\n      {\n        name: 'likesAmount',\n        type: 'number',\n        placeholder: 'Amount of likes',\n        description: 'The amount of likes to trigger the repost',\n        validation: /^\\d+$/,\n      },\n      {\n        name: 'post',\n        type: 'richtext',\n        placeholder: 'Post to plug',\n        description: 'Message content to plug',\n        validation: /^[\\s\\S]{3,}$/g,\n      },\n    ],\n  })\n  async autoPlugPost(\n    integration: Integration,\n    id: string,\n    fields: { likesAmount: string; post: string }\n  ) {\n    // @ts-ignore\n    // eslint-disable-next-line prefer-rest-params\n    const [accessTokenSplit, accessSecretSplit] = integration.token.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    if (\n      (await client.v2.tweetLikedBy(id)).meta.result_count >=\n      +fields.likesAmount\n    ) {\n      await timer(2000);\n\n      await client.v2.tweet({\n        text: stripHtmlValidation('normal', fields.post, true),\n        reply: { in_reply_to_tweet_id: id },\n      });\n      return true;\n    }\n\n    return false;\n  }\n\n  async refreshToken(): Promise<AuthTokenDetails> {\n    return {\n      id: '',\n      name: '',\n      accessToken: '',\n      refreshToken: '',\n      expiresIn: 0,\n      picture: '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n    });\n    const { url, oauth_token, oauth_token_secret } =\n      await client.generateAuthLink(\n        (process.env.X_URL || process.env.FRONTEND_URL) +\n          `/integrations/social/x`,\n        {\n          authAccessType: 'write',\n          linkMode: 'authenticate',\n          forceLogin: false,\n        }\n      );\n    return {\n      url,\n      codeVerifier: oauth_token + ':' + oauth_token_secret,\n      state: oauth_token,\n    };\n  }\n\n  async authenticate(params: { code: string; codeVerifier: string }) {\n    const { code, codeVerifier } = params;\n    const [oauth_token, oauth_token_secret] = codeVerifier.split(':');\n\n    const startingClient = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: oauth_token,\n      accessSecret: oauth_token_secret,\n    });\n\n    const { accessToken, client, accessSecret } = await startingClient.login(\n      code\n    );\n\n    const {\n      data: { username, verified, profile_image_url, name, id },\n    } = await client.v2.me({\n      'user.fields': [\n        'username',\n        'verified',\n        'verified_type',\n        'profile_image_url',\n        'name',\n      ],\n    });\n\n    return {\n      id: String(id),\n      accessToken: accessToken + ':' + accessSecret,\n      name,\n      refreshToken: '',\n      expiresIn: 999999999,\n      picture: profile_image_url || '',\n      username,\n      additionalSettings: [\n        {\n          title: 'Verified',\n          description: 'Is this a verified user? (Premium)',\n          type: 'checkbox' as const,\n          value: verified,\n        },\n      ],\n    };\n  }\n\n  private async getClient(accessToken: string) {\n    const [accessTokenSplit, accessSecretSplit] = accessToken.split(':');\n    return new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n  }\n\n  private async uploadMedia(\n    client: TwitterApi,\n    postDetails: PostDetails<any>[]\n  ) {\n    return (\n      await Promise.all(\n        postDetails.flatMap((p) =>\n          p?.media?.flatMap(async (m) => {\n            return {\n              id: await this.runInConcurrent(\n                async () =>\n                  client.v2.uploadMedia(\n                    m.path.indexOf('mp4') > -1\n                      ? Buffer.from(await readOrFetch(m.path))\n                      : await sharp(await readOrFetch(m.path), {\n                          animated: lookup(m.path) === 'image/gif',\n                        })\n                          .resize({\n                            width: 1000,\n                          })\n                          .gif()\n                          .toBuffer(),\n                    {\n                      media_type: (lookup(m.path) || '') as any,\n                    }\n                  ),\n                true\n              ),\n              postId: p.id,\n            };\n          })\n        )\n      )\n    ).reduce((acc, val) => {\n      if (!val?.id) {\n        return acc;\n      }\n\n      acc[val.postId] = acc[val.postId] || [];\n      acc[val.postId].push(val.id);\n\n      return acc;\n    }, {} as Record<string, string[]>);\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails<{\n      active_thread_finisher: boolean;\n      thread_finisher: string;\n      community?: string;\n      who_can_reply_post:\n        | 'everyone'\n        | 'following'\n        | 'mentionedUsers'\n        | 'subscribers'\n        | 'verified';\n    }>[]\n  ): Promise<PostResponse[]> {\n    const client = await this.getClient(accessToken);\n    const {\n      data: { username },\n    } = await this.runInConcurrent(async () =>\n      client.v2.me({\n        'user.fields': 'username',\n      })\n    );\n\n    const [firstPost] = postDetails;\n\n    // upload media for the first post\n    const uploadAll = await this.uploadMedia(client, [firstPost]);\n\n    const media_ids = (uploadAll[firstPost.id] || []).filter((f) => f);\n\n    // @ts-ignore\n    const { data }: { data: { id: string } } = await this.runInConcurrent(\n      async () =>\n        // @ts-ignore\n        client.v2.tweet({\n          ...(!firstPost?.settings?.who_can_reply_post ||\n          firstPost?.settings?.who_can_reply_post === 'everyone'\n            ? {}\n            : {\n                reply_settings: firstPost?.settings?.who_can_reply_post,\n              }),\n          ...(firstPost?.settings?.community\n            ? {\n                share_with_followers: true,\n                community_id:\n                  firstPost?.settings?.community?.split('/').pop() || '',\n              }\n            : {}),\n          text: firstPost.message,\n          ...(media_ids.length ? { media: { media_ids } } : {}),\n        })\n    );\n\n    return [\n      {\n        postId: data.id,\n        id: firstPost.id,\n        releaseURL: `https://twitter.com/${username}/status/${data.id}`,\n        status: 'posted',\n      },\n    ];\n  }\n\n  async comment(\n    id: string,\n    postId: string,\n    lastCommentId: string | undefined,\n    accessToken: string,\n    postDetails: PostDetails<{\n      active_thread_finisher: boolean;\n      thread_finisher: string;\n    }>[],\n    integration: Integration\n  ): Promise<PostResponse[]> {\n    const client = await this.getClient(accessToken);\n    const {\n      data: { username },\n    } = await this.runInConcurrent(async () =>\n      client.v2.me({\n        'user.fields': 'username',\n      })\n    );\n\n    const [commentPost] = postDetails;\n\n    // upload media for the comment\n    const uploadAll = await this.uploadMedia(client, [commentPost]);\n\n    const media_ids = (uploadAll[commentPost.id] || []).filter((f) => f);\n\n    const replyToId = lastCommentId || postId;\n\n    // @ts-ignore\n    const { data }: { data: { id: string } } = await this.runInConcurrent(\n      async () =>\n        // @ts-ignore\n        client.v2.tweet({\n          text: commentPost.message,\n          ...(media_ids.length ? { media: { media_ids } } : {}),\n          reply: { in_reply_to_tweet_id: replyToId },\n        })\n    );\n\n    return [\n      {\n        postId: data.id,\n        id: commentPost.id,\n        releaseURL: `https://twitter.com/${username}/status/${data.id}`,\n        status: 'posted',\n      },\n    ];\n  }\n\n  private loadAllTweets = async (\n    client: TwitterApi,\n    id: string,\n    until: string,\n    since: string,\n    token = ''\n  ): Promise<TweetV2[]> => {\n    const tweets = await client.v2.userTimeline(id, {\n      'tweet.fields': ['id'],\n      'user.fields': [],\n      'poll.fields': [],\n      'place.fields': [],\n      'media.fields': [],\n      exclude: ['replies', 'retweets'],\n      start_time: since,\n      end_time: until,\n      max_results: 100,\n      ...(token ? { pagination_token: token } : {}),\n    });\n\n    return [\n      ...tweets.data.data,\n      ...(tweets.data.data.length === 100\n        ? await this.loadAllTweets(\n            client,\n            id,\n            until,\n            since,\n            tweets.meta.next_token\n          )\n        : []),\n    ];\n  };\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    if (process.env.DISABLE_X_ANALYTICS) {\n      return [];\n    }\n\n    const until = dayjs().endOf('day');\n    const since = dayjs().subtract(date > 100 ? 100 : date, 'day');\n\n    const [accessTokenSplit, accessSecretSplit] = accessToken.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    try {\n      const tweets = uniqBy(\n        await this.loadAllTweets(\n          client,\n          id,\n          until.format('YYYY-MM-DDTHH:mm:ssZ'),\n          since.format('YYYY-MM-DDTHH:mm:ssZ')\n        ),\n        (p) => p.id\n      );\n\n      if (tweets.length === 0) {\n        return [];\n      }\n\n      const data = await client.v2.tweets(\n        tweets.map((p) => p.id),\n        {\n          'tweet.fields': ['public_metrics'],\n        }\n      );\n\n      const metrics = data.data.reduce(\n        (all, current) => {\n          all.impression_count =\n            (all.impression_count || 0) +\n            +current.public_metrics.impression_count;\n          all.bookmark_count =\n            (all.bookmark_count || 0) + +current.public_metrics.bookmark_count;\n          all.like_count =\n            (all.like_count || 0) + +current.public_metrics.like_count;\n          all.quote_count =\n            (all.quote_count || 0) + +current.public_metrics.quote_count;\n          all.reply_count =\n            (all.reply_count || 0) + +current.public_metrics.reply_count;\n          all.retweet_count =\n            (all.retweet_count || 0) + +current.public_metrics.retweet_count;\n\n          return all;\n        },\n        {\n          impression_count: 0,\n          bookmark_count: 0,\n          like_count: 0,\n          quote_count: 0,\n          reply_count: 0,\n          retweet_count: 0,\n        }\n      );\n\n      return Object.entries(metrics).map(([key, value]) => ({\n        label: key.replace('_count', '').replace('_', ' ').toUpperCase(),\n        percentageChange: 5,\n        data: [\n          {\n            total: String(0),\n            date: since.format('YYYY-MM-DD'),\n          },\n          {\n            total: String(value),\n            date: until.format('YYYY-MM-DD'),\n          },\n        ],\n      }));\n    } catch (err) {\n      console.log(err);\n    }\n    return [];\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    if (process.env.DISABLE_X_ANALYTICS) {\n      return [];\n    }\n\n    const today = dayjs().format('YYYY-MM-DD');\n\n    const [accessTokenSplit, accessSecretSplit] = accessToken.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    try {\n      // Fetch the specific tweet with public metrics\n      const tweet = await client.v2.singleTweet(postId, {\n        'tweet.fields': ['public_metrics', 'created_at'],\n      });\n\n      if (!tweet?.data?.public_metrics) {\n        return [];\n      }\n\n      const metrics = tweet.data.public_metrics;\n\n      const result: AnalyticsData[] = [];\n\n      if (metrics.impression_count !== undefined) {\n        result.push({\n          label: 'Impressions',\n          percentageChange: 0,\n          data: [{ total: String(metrics.impression_count), date: today }],\n        });\n      }\n\n      if (metrics.like_count !== undefined) {\n        result.push({\n          label: 'Likes',\n          percentageChange: 0,\n          data: [{ total: String(metrics.like_count), date: today }],\n        });\n      }\n\n      if (metrics.retweet_count !== undefined) {\n        result.push({\n          label: 'Retweets',\n          percentageChange: 0,\n          data: [{ total: String(metrics.retweet_count), date: today }],\n        });\n      }\n\n      if (metrics.reply_count !== undefined) {\n        result.push({\n          label: 'Replies',\n          percentageChange: 0,\n          data: [{ total: String(metrics.reply_count), date: today }],\n        });\n      }\n\n      if (metrics.quote_count !== undefined) {\n        result.push({\n          label: 'Quotes',\n          percentageChange: 0,\n          data: [{ total: String(metrics.quote_count), date: today }],\n        });\n      }\n\n      if (metrics.bookmark_count !== undefined) {\n        result.push({\n          label: 'Bookmarks',\n          percentageChange: 0,\n          data: [{ total: String(metrics.bookmark_count), date: today }],\n        });\n      }\n\n      return result;\n    } catch (err) {\n      console.log('Error fetching X post analytics:', err);\n    }\n\n    return [];\n  }\n\n  override async mention(token: string, d: { query: string }) {\n    const [accessTokenSplit, accessSecretSplit] = token.split(':');\n    const client = new TwitterApi({\n      appKey: process.env.X_API_KEY!,\n      appSecret: process.env.X_API_SECRET!,\n      accessToken: accessTokenSplit,\n      accessSecret: accessSecretSplit,\n    });\n\n    try {\n      const data = await client.v2.userByUsername(d.query, {\n        'user.fields': ['username', 'name', 'profile_image_url'],\n      });\n\n      if (!data?.data?.username) {\n        return [];\n      }\n\n      return [\n        {\n          id: data.data.username,\n          image: data.data.profile_image_url,\n          label: data.data.name,\n        },\n      ];\n    } catch (err) {\n      console.log(err);\n    }\n    return [];\n  }\n\n  mentionFormat(idOrHandle: string, name: string) {\n    return `@${idOrHandle}`;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts",
    "content": "import {\n  AnalyticsData,\n  AuthTokenDetails,\n  PostDetails,\n  PostResponse,\n  SocialProvider,\n} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { google, youtube_v3 } from 'googleapis';\nimport { OAuth2Client } from 'google-auth-library/build/src/auth/oauth2client';\nimport axios from 'axios';\nimport { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';\nimport {\n  BadBody,\n  SocialAbstract,\n} from '@gitroom/nestjs-libraries/integrations/social.abstract';\nimport * as process from 'node:process';\nimport dayjs from 'dayjs';\nimport { GaxiosResponse } from 'gaxios/build/src/common';\nimport Schema$Video = youtube_v3.Schema$Video;\nimport { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator';\n\nconst clientAndYoutube = () => {\n  const client = new google.auth.OAuth2({\n    clientId: process.env.YOUTUBE_CLIENT_ID,\n    clientSecret: process.env.YOUTUBE_CLIENT_SECRET,\n    redirectUri: `${process.env.FRONTEND_URL}/integrations/social/youtube`,\n  });\n\n  const youtube = (newClient: OAuth2Client) =>\n    google.youtube({\n      version: 'v3',\n      auth: newClient,\n    });\n\n  const youtubeAnalytics = (newClient: OAuth2Client) =>\n    google.youtubeAnalytics({\n      version: 'v2',\n      auth: newClient,\n    });\n\n  const oauth2 = (newClient: OAuth2Client) =>\n    google.oauth2({\n      version: 'v2',\n      auth: newClient,\n    });\n\n  return { client, youtube, oauth2, youtubeAnalytics };\n};\n\n@Rules('YouTube must have on video attachment, it cannot be empty')\nexport class YoutubeProvider extends SocialAbstract implements SocialProvider {\n  override maxConcurrentJob = 200; // YouTube has strict upload quotas\n  identifier = 'youtube';\n  name = 'YouTube';\n  isBetweenSteps = true;\n  dto = YoutubeSettingsDto;\n  scopes = [\n    'https://www.googleapis.com/auth/userinfo.profile',\n    'https://www.googleapis.com/auth/userinfo.email',\n    'https://www.googleapis.com/auth/youtube',\n    'https://www.googleapis.com/auth/youtube.force-ssl',\n    'https://www.googleapis.com/auth/youtube.readonly',\n    'https://www.googleapis.com/auth/youtube.upload',\n    'https://www.googleapis.com/auth/youtubepartner',\n    'https://www.googleapis.com/auth/yt-analytics.readonly',\n  ];\n\n  editor = 'normal' as const;\n  maxLength() {\n    return 5000;\n  }\n\n  override handleErrors(body: string):\n    | {\n        type: 'refresh-token' | 'bad-body';\n        value: string;\n      }\n    | undefined {\n    if (body.includes('invalidTitle')) {\n      return {\n        type: 'bad-body',\n        value:\n          'We have uploaded your video but we could not set the title. Title is too long.',\n      };\n    }\n\n    if (body.includes('failedPrecondition')) {\n      return {\n        type: 'bad-body',\n        value:\n          'We have uploaded your video but we could not set the thumbnail. Thumbnail size is too large.',\n      };\n    }\n\n    if (body.includes('uploadLimitExceeded')) {\n      return {\n        type: 'bad-body',\n        value:\n          'You have reached your daily upload limit, please try again tomorrow.',\n      };\n    }\n\n    if (body.includes('youtubeSignupRequired')) {\n      return {\n        type: 'bad-body',\n        value:\n          'You have to link your youtube account to your google account first.',\n      };\n    }\n\n    if (body.includes('youtube.thumbnail')) {\n      return {\n        type: 'bad-body',\n        value:\n          'Your account is not verified, we have uploaded your video but we could not set the thumbnail. Please verify your account and try again.',\n      };\n    }\n\n    if (body.includes('Unauthorized')) {\n      return {\n        type: 'refresh-token',\n        value:\n          'Token expired or invalid, please reconnect your YouTube account.',\n      };\n    }\n\n    if (body.includes('UNAUTHENTICATED') || body.includes('invalid_grant')) {\n      return {\n        type: 'refresh-token',\n        value: 'Please re-authenticate your YouTube account',\n      };\n    }\n\n    return undefined;\n  }\n\n  async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {\n    const { client, oauth2 } = clientAndYoutube();\n    client.setCredentials({ refresh_token });\n    const { credentials } = await client.refreshAccessToken();\n    const user = oauth2(client);\n    const expiryDate = new Date(credentials.expiry_date!);\n    const unixTimestamp =\n      Math.floor(expiryDate.getTime() / 1000) -\n      Math.floor(new Date().getTime() / 1000);\n\n    const { data } = await user.userinfo.get();\n\n    return {\n      accessToken: credentials.access_token!,\n      expiresIn: unixTimestamp!,\n      refreshToken: credentials.refresh_token!,\n      id: data.id!,\n      name: data.name!,\n      picture: data?.picture || '',\n      username: '',\n    };\n  }\n\n  async generateAuthUrl() {\n    const state = makeId(7);\n    const { client } = clientAndYoutube();\n    return {\n      url: client.generateAuthUrl({\n        access_type: 'offline',\n        prompt: 'consent',\n        state,\n        redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/youtube`,\n        scope: this.scopes.slice(0),\n      }),\n      codeVerifier: makeId(11),\n      state,\n    };\n  }\n\n  async authenticate(params: {\n    code: string;\n    codeVerifier: string;\n    refresh?: string;\n  }) {\n    const { client, oauth2 } = clientAndYoutube();\n    const { tokens } = await client.getToken(params.code);\n    client.setCredentials(tokens);\n    const { scopes } = await client.getTokenInfo(tokens.access_token!);\n    this.checkScopes(this.scopes, scopes);\n\n    const user = oauth2(client);\n    const { data } = await user.userinfo.get();\n\n    const expiryDate = new Date(tokens.expiry_date!);\n    const unixTimestamp =\n      Math.floor(expiryDate.getTime() / 1000) -\n      Math.floor(new Date().getTime() / 1000);\n\n    return {\n      accessToken: tokens.access_token!,\n      expiresIn: unixTimestamp,\n      refreshToken: tokens.refresh_token!,\n      id: data.id!,\n      name: data.name!,\n      picture: data?.picture || '',\n      username: '',\n    };\n  }\n\n  async pages(accessToken: string) {\n    const { client, youtube } = clientAndYoutube();\n    client.setCredentials({ access_token: accessToken });\n    const youtubeClient = youtube(client);\n\n    try {\n      // Get all channels the user has access to\n      const response = await youtubeClient.channels.list({\n        part: ['snippet', 'contentDetails', 'statistics'],\n        mine: true,\n      });\n\n      const channels = response.data.items || [];\n\n      return channels.map((channel) => ({\n        id: channel.id!,\n        name: channel.snippet?.title || 'Unnamed Channel',\n        picture: {\n          data: {\n            url: channel.snippet?.thumbnails?.default?.url || '',\n          },\n        },\n        username: channel.snippet?.customUrl || '',\n        subscriberCount: channel.statistics?.subscriberCount || '0',\n      }));\n    } catch (error) {\n      console.error('Failed to fetch YouTube channels:', error);\n      return [];\n    }\n  }\n\n  async fetchPageInformation(accessToken: string, data: { id: string }) {\n    const { client, youtube } = clientAndYoutube();\n    client.setCredentials({ access_token: accessToken });\n    const youtubeClient = youtube(client);\n\n    try {\n      const response = await youtubeClient.channels.list({\n        part: ['snippet', 'contentDetails', 'statistics'],\n        id: [data.id],\n      });\n\n      const channel = response.data.items?.[0];\n\n      if (!channel) {\n        throw new Error('Channel not found');\n      }\n\n      return {\n        id: channel.id!,\n        name: channel.snippet?.title || 'Unnamed Channel',\n        access_token: accessToken,\n        picture: channel.snippet?.thumbnails?.default?.url || '',\n        username: channel.snippet?.customUrl || '',\n      };\n    } catch (error) {\n      console.error('Failed to fetch YouTube channel information:', error);\n      throw error;\n    }\n  }\n\n  async reConnect(\n    id: string,\n    requiredId: string,\n    accessToken: string\n  ): Promise<Omit<AuthTokenDetails, 'refreshToken' | 'expiresIn'>> {\n    const pages = await this.pages(accessToken);\n    const findPage = pages.find((p) => p.id === requiredId);\n\n    if (!findPage) {\n      throw new Error('Channel not found');\n    }\n\n    const information = await this.fetchPageInformation(accessToken, {\n      id: requiredId,\n    });\n\n    return {\n      id: information.id,\n      name: information.name,\n      accessToken: information.access_token,\n      picture: information.picture,\n      username: information.username,\n    };\n  }\n\n  async post(\n    id: string,\n    accessToken: string,\n    postDetails: PostDetails[]\n  ): Promise<PostResponse[]> {\n    const [firstPost, ...comments] = postDetails;\n\n    const { client, youtube } = clientAndYoutube();\n    client.setCredentials({ access_token: accessToken });\n    const youtubeClient = youtube(client);\n\n    const { settings }: { settings: YoutubeSettingsDto } = firstPost;\n\n    const response = await axios({\n      url: firstPost?.media?.[0]?.path,\n      method: 'GET',\n      responseType: 'stream',\n    });\n\n    const all: GaxiosResponse<Schema$Video> = await this.runInConcurrent(\n      async () =>\n        youtubeClient.videos.insert({\n          part: ['id', 'snippet', 'status'],\n          notifySubscribers: true,\n          requestBody: {\n            snippet: {\n              title: settings.title,\n              description: firstPost?.message,\n              ...(settings?.tags?.length\n                ? { tags: settings.tags.map((p) => p.label) }\n                : {}),\n            },\n            status: {\n              privacyStatus: settings.type,\n              selfDeclaredMadeForKids:\n                settings.selfDeclaredMadeForKids === 'yes',\n            },\n          },\n          media: {\n            body: response.data,\n          },\n        }),\n      true\n    );\n\n    if (settings?.thumbnail?.path) {\n      await this.runInConcurrent(async () =>\n        youtubeClient.thumbnails.set({\n          videoId: all?.data?.id!,\n          media: {\n            body: (\n              await axios({\n                url: settings?.thumbnail?.path,\n                method: 'GET',\n                responseType: 'stream',\n              })\n            ).data,\n          },\n        })\n      );\n    }\n\n    return [\n      {\n        id: firstPost.id,\n        releaseURL: `https://www.youtube.com/watch?v=${all?.data?.id}`,\n        postId: all?.data?.id!,\n        status: 'success',\n      },\n    ];\n  }\n\n  async analytics(\n    id: string,\n    accessToken: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    try {\n      const endDate = dayjs().format('YYYY-MM-DD');\n      const startDate = dayjs().subtract(date, 'day').format('YYYY-MM-DD');\n\n      const { client, youtubeAnalytics } = clientAndYoutube();\n      client.setCredentials({ access_token: accessToken });\n\n      const youtubeClient = youtubeAnalytics(client);\n      const { data } = await youtubeClient.reports.query({\n        ids: 'channel==MINE',\n        startDate,\n        endDate,\n        metrics:\n          'views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage,subscribersGained,likes,subscribersLost',\n        dimensions: 'day',\n        sort: 'day',\n      });\n\n      const columns = data?.columnHeaders?.map((p) => p.name)!;\n      const mappedData = data?.rows?.map((p) => {\n        return columns.reduce((acc, curr, index) => {\n          acc[curr!] = p[index];\n          return acc;\n        }, {} as any);\n      });\n\n      const acc = [] as any[];\n      acc.push({\n        label: 'Estimated Minutes Watched',\n        data: mappedData?.map((p: any) => ({\n          total: p.estimatedMinutesWatched,\n          date: p.day,\n        })),\n      });\n\n      acc.push({\n        label: 'Average View Duration',\n        average: true,\n        data: mappedData?.map((p: any) => ({\n          total: p.averageViewDuration,\n          date: p.day,\n        })),\n      });\n\n      acc.push({\n        label: 'Average View Percentage',\n        average: true,\n        data: mappedData?.map((p: any) => ({\n          total: p.averageViewPercentage,\n          date: p.day,\n        })),\n      });\n\n      acc.push({\n        label: 'Subscribers Gained',\n        data: mappedData?.map((p: any) => ({\n          total: p.subscribersGained,\n          date: p.day,\n        })),\n      });\n\n      acc.push({\n        label: 'Subscribers Lost',\n        data: mappedData?.map((p: any) => ({\n          total: p.subscribersLost,\n          date: p.day,\n        })),\n      });\n\n      acc.push({\n        label: 'Likes',\n        data: mappedData?.map((p: any) => ({\n          total: p.likes,\n          date: p.day,\n        })),\n      });\n\n      return acc;\n    } catch (err) {\n      return [];\n    }\n  }\n\n  async postAnalytics(\n    integrationId: string,\n    accessToken: string,\n    postId: string,\n    date: number\n  ): Promise<AnalyticsData[]> {\n    const today = dayjs().format('YYYY-MM-DD');\n\n    try {\n      const { client, youtube } = clientAndYoutube();\n      client.setCredentials({ access_token: accessToken });\n      const youtubeClient = youtube(client);\n\n      // Fetch video statistics\n      const response = await youtubeClient.videos.list({\n        part: ['statistics', 'snippet'],\n        id: [postId],\n      });\n\n      const video = response.data.items?.[0];\n\n      if (!video || !video.statistics) {\n        return [];\n      }\n\n      const stats = video.statistics;\n      const result: AnalyticsData[] = [];\n\n      if (stats.viewCount !== undefined) {\n        result.push({\n          label: 'Views',\n          percentageChange: 0,\n          data: [{ total: String(stats.viewCount), date: today }],\n        });\n      }\n\n      if (stats.likeCount !== undefined) {\n        result.push({\n          label: 'Likes',\n          percentageChange: 0,\n          data: [{ total: String(stats.likeCount), date: today }],\n        });\n      }\n\n      if (stats.commentCount !== undefined) {\n        result.push({\n          label: 'Comments',\n          percentageChange: 0,\n          data: [{ total: String(stats.commentCount), date: today }],\n        });\n      }\n\n      if (stats.favoriteCount !== undefined) {\n        result.push({\n          label: 'Favorites',\n          percentageChange: 0,\n          data: [{ total: String(stats.favoriteCount), date: today }],\n        });\n      }\n\n      return result;\n    } catch (err) {\n      console.error('Error fetching YouTube post analytics:', err);\n      return [];\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/social.abstract.ts",
    "content": "import { timer } from '@gitroom/helpers/utils/timer';\nimport { Integration } from '@prisma/client';\nimport { ApplicationFailure } from '@temporalio/activity';\n\nexport class RefreshToken extends ApplicationFailure {\n  constructor(identifier: string, json: string, body: BodyInit, message = '') {\n    super(message, 'refresh_token', true, [\n      {\n        identifier,\n        json,\n        body,\n      },\n    ]);\n  }\n}\n\nexport class BadBody extends ApplicationFailure {\n  constructor(identifier: string, json: string, body: BodyInit, message = '') {\n    super(message, 'bad_body', true, [\n      {\n        identifier,\n        json,\n        body,\n      },\n    ]);\n  }\n}\n\nexport class NotEnoughScopes {\n  constructor(\n    public message = 'Not enough scopes, when choosing a provider, please add all the scopes'\n  ) {}\n}\n\nfunction safeStringify(obj: any) {\n  const seen = new WeakSet();\n\n  return JSON.stringify(obj, (key, value) => {\n    if (typeof value === 'object' && value !== null) {\n      if (seen.has(value)) {\n        return '[Circular]';\n      }\n      seen.add(value);\n    }\n    return value;\n  });\n}\n\nexport abstract class SocialAbstract {\n  abstract identifier: string;\n  maxConcurrentJob = 1;\n\n  public handleErrors(\n    body: string\n  ):\n    | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string }\n    | undefined {\n    return undefined;\n  }\n\n  public async mention(\n    token: string,\n    d: { query: string },\n    id: string,\n    integration: Integration\n  ): Promise<\n    | { id: string; label: string; image: string; doNotCache?: boolean }[]\n    | { none: true }\n  > {\n    return { none: true };\n  }\n\n  async runInConcurrent<T>(\n    func: (...args: any[]) => Promise<T>,\n    ignoreConcurrency?: boolean\n  ) {\n    let value: any;\n    try {\n      value = await func();\n    } catch (err) {\n      const handle = this.handleErrors(safeStringify(err));\n      value = { err: true, value: 'Unknown Error', ...(handle || {}) };\n    }\n\n    if (value && value?.err && value?.value) {\n      if (value.type === 'refresh-token') {\n        throw new RefreshToken(\n          '',\n          safeStringify({}),\n          {} as any,\n          value.value || ''\n        );\n      }\n      throw new BadBody('', safeStringify({}), {} as any, value.value || '');\n    }\n\n    return value;\n  }\n\n  async fetch(\n    url: string,\n    options: RequestInit = {},\n    identifier = '',\n    totalRetries = 0,\n    ignoreConcurrency = false\n  ): Promise<Response> {\n    const request = await fetch(url, options);\n\n    if (request.status === 200 || request.status === 201) {\n      return request;\n    }\n\n    if (totalRetries > 2) {\n      throw new BadBody(identifier, '{}', options.body || '{}');\n    }\n\n    let json = '{}';\n    try {\n      json = await request.text();\n    } catch (err) {\n      json = '{}';\n    }\n\n    const handleError = this.handleErrors(json || '{}');\n\n    if (\n      request.status === 429 ||\n      (request.status === 500 && !handleError) ||\n      json.includes('rate_limit_exceeded') ||\n      json.includes('Rate limit')\n    ) {\n      await timer(5000);\n      return this.fetch(\n        url,\n        options,\n        identifier,\n        totalRetries + 1,\n        ignoreConcurrency\n      );\n    }\n\n    if (handleError?.type === 'retry') {\n      await timer(5000);\n      return this.fetch(\n        url,\n        options,\n        identifier,\n        totalRetries + 1,\n        ignoreConcurrency\n      );\n    }\n\n    if (\n      (request.status === 401 &&\n        (handleError?.type === 'refresh-token' || !handleError)) ||\n      handleError?.type === 'refresh-token'\n    ) {\n      throw new RefreshToken(\n        identifier,\n        json,\n        options.body!,\n        handleError?.value\n      );\n    }\n\n    throw new BadBody(\n      identifier,\n      json,\n      options.body!,\n      handleError?.value || ''\n    );\n  }\n\n  checkScopes(required: string[], got: string | string[]) {\n    if (Array.isArray(got)) {\n      if (!required.every((scope) => got.includes(scope))) {\n        throw new NotEnoughScopes();\n      }\n\n      return true;\n    }\n\n    const newGot = decodeURIComponent(got);\n\n    const splitType = newGot.indexOf(',') > -1 ? ',' : ' ';\n    const gotArray = newGot.split(splitType);\n    if (!required.every((scope) => gotArray.includes(scope))) {\n      throw new NotEnoughScopes();\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/integrations/tool.decorator.ts",
    "content": "import 'reflect-metadata';\n\nexport function Tool(params: {\n  description: string;\n  dataSchema: Array<{ key: string; type: string; description: string }>;\n}) {\n  return function (target: any, propertyKey: string | symbol) {\n    // Retrieve existing metadata or initialize an empty array\n    const existingMetadata = Reflect.getMetadata('custom:tool', target) || [];\n\n    // Add the metadata information for this method\n    existingMetadata.push({ methodName: propertyKey, ...params });\n\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata('custom:tool', existingMetadata, target);\n  };\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/newsletter.interface.ts",
    "content": "export interface NewsletterInterface {\n  name: string;\n  register(email: string): Promise<void>;\n}"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/newsletter.service.ts",
    "content": "import { newsletterProviders } from '@gitroom/nestjs-libraries/newsletter/providers';\n\nexport class NewsletterService {\n  static getProvider() {\n    if (process.env.BEEHIIVE_API_KEY) {\n      return newsletterProviders.find((p) => p.name === 'beehiiv')!;\n    }\n    if (process.env.LISTMONK_API_KEY) {\n      return newsletterProviders.find((p) => p.name === 'listmonk')!;\n    }\n\n    return newsletterProviders.find((p) => p.name === 'empty')!;\n  }\n  static async register(email: string) {\n    if (email.indexOf('@') === -1) {\n      return;\n    }\n    return NewsletterService.getProvider().register(email);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/providers/beehiiv.provider.ts",
    "content": "import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';\n\nexport class BeehiivProvider implements NewsletterInterface {\n  name = 'beehiiv';\n  async register(email: string) {\n    const body = {\n      email,\n      reactivate_existing: false,\n      send_welcome_email: true,\n      utm_source: 'gitroom_platform',\n    };\n\n    await fetch(\n      `https://api.beehiiv.com/v2/publications/${process.env.BEEHIIVE_PUBLICATION_ID}/subscriptions`,\n      {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          Accept: 'application/json',\n          Authorization: `Bearer ${process.env.BEEHIIVE_API_KEY}`,\n        },\n        body: JSON.stringify(body),\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/providers/email-empty.provider.ts",
    "content": "import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';\n\nexport class EmailEmptyProvider implements NewsletterInterface {\n  name = 'empty';\n  async register(email: string) {\n    console.log('Could have registered to newsletter:', email);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/providers/listmonk.provider.ts",
    "content": "import { NewsletterInterface } from '@gitroom/nestjs-libraries/newsletter/newsletter.interface';\n\nexport class ListmonkProvider implements NewsletterInterface {\n  name = 'listmonk';\n  async register(email: string) {\n    const body = {\n      email,\n      status: 'enabled',\n      lists: [+process.env.LISTMONK_LIST_ID].filter((f) => f),\n    };\n\n    const authString = `${process.env.LISTMONK_USER}:${process.env.LISTMONK_API_KEY}`;\n    const headers = new Headers();\n    headers.set('Content-Type', 'application/json');\n    headers.set('Accept', 'application/json');\n    headers.set(\n      'Authorization',\n      'Basic ' + Buffer.from(authString).toString('base64')\n    );\n\n    try {\n      const {\n        data: { id },\n      } = await (\n        await fetch(`${process.env.LISTMONK_DOMAIN}/api/subscribers`, {\n          method: 'POST',\n          headers,\n          body: JSON.stringify(body),\n        })\n      ).json();\n\n      const welcomeEmail = {\n        subscriber_id: id,\n        template_id: +process.env.LISTMONK_WELCOME_TEMPLATE_ID,\n        subject: 'Welcome to Postiz 🚀',\n      };\n\n      await fetch(`${process.env.LISTMONK_DOMAIN}/api/tx`, {\n        method: 'POST',\n        headers,\n        body: JSON.stringify(welcomeEmail),\n      });\n    } catch (err) {}\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/newsletter/providers.ts",
    "content": "import { BeehiivProvider } from '@gitroom/nestjs-libraries/newsletter/providers/beehiiv.provider';\nimport { EmailEmptyProvider } from '@gitroom/nestjs-libraries/newsletter/providers/email-empty.provider';\nimport { ListmonkProvider } from '@gitroom/nestjs-libraries/newsletter/providers/listmonk.provider';\n\nexport const newsletterProviders = [\n  new BeehiivProvider(),\n  new ListmonkProvider(),\n  new EmailEmptyProvider(),\n];\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/openai/extract.content.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { JSDOM } from 'jsdom';\n\nfunction findDepth(element: Element) {\n  let depth = 0;\n  let elementer = element;\n  while (elementer.parentNode) {\n    depth++;\n    // @ts-ignore\n    elementer = elementer.parentNode;\n  }\n  return depth;\n}\n\n@Injectable()\nexport class ExtractContentService {\n  async extractContent(url: string) {\n    const load = await (await fetch(url)).text();\n    const dom = new JSDOM(load);\n\n    // only element that has a title\n    const allTitles = Array.from(dom.window.document.querySelectorAll('*'))\n      .filter((f) => {\n        return (\n          f.querySelector('h1') ||\n          f.querySelector('h2') ||\n          f.querySelector('h3') ||\n          f.querySelector('h4') ||\n          f.querySelector('h5') ||\n          f.querySelector('h6')\n        );\n      })\n      .reverse();\n\n    const findTheOneWithMostTitles = allTitles.reduce(\n      (all, current) => {\n        const depth = findDepth(current);\n        const calculate = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].reduce(\n          (total, tag) => {\n            if (current.querySelector(tag)) {\n              return total + 1;\n            }\n            return total;\n          },\n          0\n        );\n\n        if (calculate > all.total) {\n          return { total: calculate, depth, element: current };\n        }\n\n        if (depth > all.depth) {\n          return { total: calculate, depth, element: current };\n        }\n\n        return all;\n      },\n      { total: 0, depth: 0, element: null as Element | null }\n    );\n\n    return findTheOneWithMostTitles?.element?.textContent\n      ?.replace(/\\n/g, ' ')\n      .replace(/ {2,}/g, ' ');\n    //\n    // const allElements = Array.from(\n    //   dom.window.document.querySelectorAll('*')\n    // ).filter((f) => f.tagName !== 'SCRIPT');\n    // const findIndex = allElements.findIndex((element) => {\n    //   return (\n    //     ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(\n    //       element.tagName.toLowerCase()\n    //     ) > -1\n    //   );\n    // });\n    //\n    // if (!findIndex) {\n    //   return false;\n    // }\n    //\n    // return allElements\n    //   .slice(findIndex)\n    //   .map((element) => element.textContent)\n    //   .filter((f) => {\n    //     const trim = f?.trim();\n    //     return (trim?.length || 0) > 0 && trim !== '\\n';\n    //   })\n    //   .map((f) => f?.trim())\n    //   .join('')\n    //   .replace(/\\n/g, ' ')\n    //   .replace(/ {2,}/g, ' ');\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/openai/fal.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\n\nimport pLimit from 'p-limit';\nconst limit = pLimit(10);\n\n@Injectable()\nexport class FalService {\n  async generateImageFromText(\n    model: string,\n    text: string,\n    isVertical: boolean = false\n  ): Promise<string> {\n    const { images, video, ...all } = await (\n      await limit(() =>\n        fetch(`https://fal.run/fal-ai/${model}`, {\n          method: 'POST',\n          headers: {\n            Authorization: `Key ${process.env.FAL_KEY}`,\n            'Content-Type': 'application/json',\n          },\n          body: JSON.stringify({\n            prompt: text,\n            aspect_ratio: isVertical ? '9:16' : '16:9',\n            resolution: '720p',\n            num_images: 1,\n            output_format: 'jpeg',\n            expand_prompt: true,\n          }),\n        })\n      )\n    ).json();\n\n    console.log(all, video, images);\n\n    if (video) {\n      return video.url;\n    }\n\n    return images[0].url as string;\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/openai/openai.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport OpenAI from 'openai';\nimport { shuffle } from 'lodash';\nimport { zodResponseFormat } from 'openai/helpers/zod';\nimport { z } from 'zod';\n\nconst openai = new OpenAI({\n  apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',\n});\n\nconst PicturePrompt = z.object({\n  prompt: z.string(),\n});\n\nconst VoicePrompt = z.object({\n  voice: z.string(),\n});\n\n@Injectable()\nexport class OpenaiService {\n  async generateImage(prompt: string, isUrl: boolean, isVertical = false) {\n    const generate = (\n      await openai.images.generate({\n        prompt,\n        response_format: isUrl ? 'url' : 'b64_json',\n        model: 'dall-e-3',\n        ...(isVertical ? { size: '1024x1792' } : {}),\n      })\n    ).data[0];\n\n    return isUrl ? generate.url : generate.b64_json;\n  }\n\n  async generatePromptForPicture(prompt: string) {\n    return (\n      (\n        await openai.chat.completions.parse({\n          model: 'gpt-4.1',\n          messages: [\n            {\n              role: 'system',\n              content: `You are an assistant that take a description and style and generate a prompt that will be used later to generate images, make it a very long and descriptive explanation, and write a lot of things for the renderer like, if it${\"'\"}s realistic describe the camera`,\n            },\n            {\n              role: 'user',\n              content: `prompt: ${prompt}`,\n            },\n          ],\n          response_format: zodResponseFormat(PicturePrompt, 'picturePrompt'),\n        })\n      ).choices[0].message.parsed?.prompt || ''\n    );\n  }\n\n  async generateVoiceFromText(prompt: string) {\n    return (\n      (\n        await openai.chat.completions.parse({\n          model: 'gpt-4.1',\n          messages: [\n            {\n              role: 'system',\n              content: `You are an assistant that takes a social media post and convert it to a normal human voice, to be later added to a character, when a person talk they don\\'t use \"-\", and sometimes they add pause with \"...\" to make it sounds more natural, make sure you use a lot of pauses and make it sound like a real person`,\n            },\n            {\n              role: 'user',\n              content: `prompt: ${prompt}`,\n            },\n          ],\n          response_format: zodResponseFormat(VoicePrompt, 'voice'),\n        })\n      ).choices[0].message.parsed?.voice || ''\n    );\n  }\n\n  async generatePosts(content: string) {\n    const posts = (\n      await Promise.all([\n        openai.chat.completions.create({\n          messages: [\n            {\n              role: 'assistant',\n              content:\n                'Generate a Twitter post from the content without emojis in the following JSON format: { \"post\": string } put it in an array with one element',\n            },\n            {\n              role: 'user',\n              content: content!,\n            },\n          ],\n          n: 5,\n          temperature: 1,\n          model: 'gpt-4.1',\n        }),\n        openai.chat.completions.create({\n          messages: [\n            {\n              role: 'assistant',\n              content:\n                'Generate a thread for social media in the following JSON format: Array<{ \"post\": string }> without emojis',\n            },\n            {\n              role: 'user',\n              content: content!,\n            },\n          ],\n          n: 5,\n          temperature: 1,\n          model: 'gpt-4.1',\n        }),\n      ])\n    ).flatMap((p) => p.choices);\n\n    return shuffle(\n      posts.map((choice) => {\n        const { content } = choice.message;\n        const start = content?.indexOf('[')!;\n        const end = content?.lastIndexOf(']')!;\n        try {\n          return JSON.parse(\n            '[' +\n              content\n                ?.slice(start + 1, end)\n                .replace(/\\n/g, ' ')\n                .replace(/ {2,}/g, ' ') +\n              ']'\n          );\n        } catch (e) {\n          return [];\n        }\n      })\n    );\n  }\n  async extractWebsiteText(content: string) {\n    const websiteContent = await openai.chat.completions.create({\n      messages: [\n        {\n          role: 'assistant',\n          content:\n            'You take a full website text, and extract only the article content',\n        },\n        {\n          role: 'user',\n          content,\n        },\n      ],\n      model: 'gpt-4.1',\n    });\n\n    const { content: articleContent } = websiteContent.choices[0].message;\n\n    return this.generatePosts(articleContent!);\n  }\n\n  async separatePosts(content: string, len: number) {\n    const SeparatePostsPrompt = z.object({\n      posts: z.array(z.string()),\n    });\n\n    const SeparatePostPrompt = z.object({\n      post: z.string().max(len),\n    });\n\n    const posts =\n      (\n        await openai.chat.completions.parse({\n          model: 'gpt-4.1',\n          messages: [\n            {\n              role: 'system',\n              content: `You are an assistant that take a social media post and break it to a thread, each post must be minimum ${\n                len - 10\n              } and maximum ${len} characters, keeping the exact wording and break lines, however make sure you split posts based on context`,\n            },\n            {\n              role: 'user',\n              content: content,\n            },\n          ],\n          response_format: zodResponseFormat(\n            SeparatePostsPrompt,\n            'separatePosts'\n          ),\n        })\n      ).choices[0].message.parsed?.posts || [];\n\n    return {\n      posts: await Promise.all(\n        posts.map(async (post: any) => {\n          if (post.length <= len) {\n            return post;\n          }\n\n          let retries = 4;\n          while (retries) {\n            try {\n              return (\n                (\n                  await openai.chat.completions.parse({\n                    model: 'gpt-4.1',\n                    messages: [\n                      {\n                        role: 'system',\n                        content: `You are an assistant that take a social media post and shrink it to be maximum ${len} characters, keeping the exact wording and break lines`,\n                      },\n                      {\n                        role: 'user',\n                        content: post,\n                      },\n                    ],\n                    response_format: zodResponseFormat(\n                      SeparatePostPrompt,\n                      'separatePost'\n                    ),\n                  })\n                ).choices[0].message.parsed?.post || ''\n              );\n            } catch (e) {\n              retries--;\n            }\n          }\n\n          return post;\n        })\n      ),\n    };\n  }\n\n  async generateSlidesFromText(text: string) {\n    for (let i = 0; i < 3; i++) {\n      try {\n        const message = `You are an assistant that takes a text and break it into slides, each slide should have an image prompt and voice text to be later used to generate a video and voice, image prompt should capture the essence of the slide and also have a back dark gradient on top, image prompt should not contain text in the picture, generate between 3-5 slides maximum`;\n        const parse =\n          (\n            await openai.chat.completions.parse({\n              model: 'gpt-4.1',\n              messages: [\n                {\n                  role: 'system',\n                  content: message,\n                },\n                {\n                  role: 'user',\n                  content: text,\n                },\n              ],\n              response_format: zodResponseFormat(\n                z.object({\n                  slides: z\n                    .array(\n                      z.object({\n                        imagePrompt: z.string(),\n                        voiceText: z.string(),\n                      })\n                    )\n                    .describe('an array of slides'),\n                }),\n                'slides'\n              ),\n            })\n          ).choices[0].message.parsed?.slides || [];\n\n        return parse;\n      } catch (err) {\n        console.log(err);\n      }\n    }\n\n    return [];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/redis/redis.service.ts",
    "content": "import { Redis } from 'ioredis';\n\n// Create a mock Redis implementation for testing environments\nclass MockRedis {\n  private data: Map<string, any> = new Map();\n\n  async get(key: string) {\n    return this.data.get(key);\n  }\n\n  async set(key: string, value: any) {\n    this.data.set(key, value);\n    return 'OK';\n  }\n\n  async del(key: string) {\n    this.data.delete(key);\n    return 1;\n  }\n\n  // Add other Redis methods as needed for your tests\n}\n\n// Use real Redis if REDIS_URL is defined, otherwise use MockRedis\nexport const ioRedis = process.env.REDIS_URL\n  ? new Redis(process.env.REDIS_URL, {\n      maxRetriesPerRequest: null,\n      connectTimeout: 10000,\n    })\n  : (new MockRedis() as unknown as Redis); // Type cast to Redis to maintain interface compatibility\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/sentry/initialize.sentry.ts",
    "content": "import * as Sentry from '@sentry/nestjs';\nimport { nodeProfilingIntegration } from '@sentry/profiling-node';\nimport { capitalize } from 'lodash';\n\nexport const initializeSentry = (appName: string, allowLogs = false) => {\n  if (!process.env.NEXT_PUBLIC_SENTRY_DSN) {\n    return null;\n  }\n\n  try {\n    Sentry.init({\n      initialScope: {\n        tags: {\n          service: appName,\n          component: 'nestjs',\n        },\n        contexts: {\n          app: {\n            name: `Postiz ${capitalize(appName)}`,\n          },\n        },\n      },\n      environment: process.env.NODE_ENV || 'development',\n      dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n      spotlight: process.env.SENTRY_SPOTLIGHT === '1',\n      integrations: [\n        // Add our Profiling integration\n        nodeProfilingIntegration(),\n        Sentry.consoleLoggingIntegration({ levels: ['log', 'info', 'warn', 'error', 'debug', 'assert', 'trace'] }),\n        Sentry.openAIIntegration({\n          recordInputs: true,\n          recordOutputs: true,\n        }),\n      ],\n      tracesSampleRate: 1.0,\n      enableLogs: true,\n\n      // Profiling\n      profileSessionSampleRate: process.env.NODE_ENV === 'development' ? 1.0 : 0.45,\n      profileLifecycle: 'trace',\n    });\n  } catch (err) {\n    console.log(err);\n  }\n  return true;\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/sentry/sentry.exception.ts",
    "content": "import { APP_FILTER } from \"@nestjs/core\";\nimport { SentryGlobalFilter } from \"@sentry/nestjs/setup\";\n\nexport const FILTER = {\n  provide: APP_FILTER,\n  useClass: SentryGlobalFilter,\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/codes.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\n\n@Injectable()\nexport class CodesService {\n  generateCodes(providerToken: string) {\n    try {\n      const decrypt = AuthService.fixedDecryption(providerToken);\n      return [...new Array(10000)]\n        .map((_, index) => {\n          return AuthService.fixedEncryption(`${decrypt}:${index}`);\n        })\n        .join('\\n');\n    } catch (error) {\n      return '';\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/email.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { EmailInterface } from '@gitroom/nestjs-libraries/emails/email.interface';\nimport { ResendProvider } from '@gitroom/nestjs-libraries/emails/resend.provider';\nimport { EmptyProvider } from '@gitroom/nestjs-libraries/emails/empty.provider';\nimport { NodeMailerProvider } from '@gitroom/nestjs-libraries/emails/node.mailer.provider';\nimport { TemporalService } from 'nestjs-temporal-core';\nimport { timer } from '@gitroom/helpers/utils/timer';\n\n@Injectable()\nexport class EmailService {\n  emailService: EmailInterface;\n  constructor(private _temporalService: TemporalService) {\n    this.emailService = this.selectProvider(process.env.EMAIL_PROVIDER!);\n    console.log('Email service provider:', this.emailService.name);\n    for (const key of this.emailService.validateEnvKeys) {\n      if (!process.env[key]) {\n        console.error(`Missing environment variable: ${key}`);\n      }\n    }\n  }\n\n  hasProvider() {\n    return !(this.emailService instanceof EmptyProvider);\n  }\n\n  selectProvider(provider: string) {\n    switch (provider) {\n      case 'resend':\n        return new ResendProvider();\n      case 'nodemailer':\n        return new NodeMailerProvider();\n      default:\n        return new EmptyProvider();\n    }\n  }\n\n  async sendEmail(\n    to: string,\n    subject: string,\n    html: string,\n    addTo: 'top' | 'bottom',\n    replyTo?: string\n  ) {\n    return this._temporalService.client\n      .getRawClient()\n      ?.workflow.signalWithStart('sendEmailWorkflow', {\n        taskQueue: 'main',\n        workflowId: 'send_email',\n        signal: 'sendEmail',\n        args: [{ queue: [] }],\n        signalArgs: [{ to, subject, html, replyTo, addTo }],\n        workflowIdConflictPolicy: 'USE_EXISTING',\n      });\n  }\n\n  async sendEmailSync(\n    to: string,\n    subject: string,\n    html: string,\n    replyTo?: string\n  ) {\n    if (to.indexOf('@') === -1) {\n      return;\n    }\n\n    if (!process.env.EMAIL_FROM_ADDRESS || !process.env.EMAIL_FROM_NAME) {\n      console.log(\n        'Email sender information not found in environment variables'\n      );\n      return;\n    }\n\n    const modifiedHtml = `\n    <div style=\"\n        background: linear-gradient(to bottom right, #e6f2ff, #f0e6ff);\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        padding: 2rem;\n    \">\n        <div style=\"\n            background-color: rgba(255, 255, 255, 0.9);\n            backdrop-filter: blur(4px);\n            border-radius: 0.5rem;\n            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n            max-width: 48rem;\n            width: 100%;\n            padding: 2rem;\n        \">\n            <h1 style=\"\n                font-size: 1.875rem;\n                font-weight: bold;\n                margin-bottom: 1.5rem;\n                text-align: left;\n                color: #1f2937;\n            \">${subject}</h1>\n            \n            <div style=\"\n                margin-bottom: 2rem;\n                color: #374151;\n            \">\n                ${html}\n            </div>\n            \n            <div style=\"\n                display: flex;\n                align-items: center;\n                border-top: 1px solid #e5e7eb;\n                padding-top: 1.5rem;\n            \">\n                <div>\n                    <h2 style=\"\n                        font-size: 1.25rem;\n                        font-weight: 600;\n                        color: #1f2937;\n                        margin: 0;\n                    \">${process.env.EMAIL_FROM_NAME}</h2>\n                    <div style=\"font-size: 12px\">\n                      You can change your notification preferences in your <a href=\"${process.env.FRONTEND_URL}/settings\">account settings.</a>\n                     </div>\n                </div>\n            </div>\n        </div>\n    </div>\n    `;\n\n    let lastErr: unknown;\n    for (let attempt = 0; attempt < 3; attempt++) {\n      try {\n        const sends = await this.emailService.sendEmail(\n          to,\n          subject,\n          modifiedHtml,\n          process.env.EMAIL_FROM_NAME,\n          process.env.EMAIL_FROM_ADDRESS,\n          replyTo\n        );\n        console.log(sends);\n        return;\n      } catch (err) {\n        lastErr = err;\n        console.log(`Email attempt ${attempt + 1}/3 failed:`, err);\n        if (attempt < 2) {\n          await timer(700);\n        }\n      }\n    }\n    console.log(`Email to ${to} failed after 3 attempts:`, lastErr);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/exception.filter.ts",
    "content": "import {\n  ExceptionFilter,\n  Catch,\n  ArgumentsHost,\n  HttpException,\n} from '@nestjs/common';\nimport { Response } from 'express';\nimport { removeAuth } from '@gitroom/backend/services/auth/auth.middleware';\n\nexport class HttpForbiddenException extends HttpException {\n  constructor() {\n    super('Forbidden', 403);\n  }\n}\n\n@Catch(HttpForbiddenException)\nexport class HttpExceptionFilter implements ExceptionFilter {\n  catch(exception: HttpForbiddenException, host: ArgumentsHost) {\n    const ctx = host.switchToHttp();\n    const response = ctx.getResponse<Response>();\n    removeAuth(response);\n\n    return response.status(401).send();\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/make.is.ts",
    "content": "export const makeId = (length: number) => {\n  let text = '';\n  const possible =\n    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n\n  for (let i = 0; i < length; i += 1) {\n    text += possible.charAt(Math.floor(Math.random() * possible.length));\n  }\n  return text;\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/stripe.country.list.ts",
    "content": "export const countries = [\n  { value: 'AL', label: 'Albania' },\n  { value: 'AG', label: 'Antigua & Barbuda' },\n  { value: 'AR', label: 'Argentina' },\n  { value: 'AM', label: 'Armenia' },\n  { value: 'AU', label: 'Australia' },\n  { value: 'AT', label: 'Austria' },\n  { value: 'BS', label: 'Bahamas' },\n  { value: 'BH', label: 'Bahrain' },\n  { value: 'BE', label: 'Belgium' },\n  { value: 'BJ', label: 'Benin' },\n  { value: 'BO', label: 'Bolivia' },\n  { value: 'BA', label: 'Bosnia & Herzegovina' },\n  { value: 'BW', label: 'Botswana' },\n  { value: 'BN', label: 'Brunei' },\n  { value: 'BG', label: 'Bulgaria' },\n  { value: 'KH', label: 'Cambodia' },\n  { value: 'CA', label: 'Canada' },\n  { value: 'CL', label: 'Chile' },\n  { value: 'CO', label: 'Colombia' },\n  { value: 'CR', label: 'Costa Rica' },\n  { value: 'HR', label: 'Croatia' },\n  { value: 'CY', label: 'Cyprus' },\n  { value: 'CZ', label: 'Czech Republic' },\n  { value: 'CI', label: 'Côte d’Ivoire' },\n  { value: 'DK', label: 'Denmark' },\n  { value: 'DO', label: 'Dominican Republic' },\n  { value: 'EC', label: 'Ecuador' },\n  { value: 'EG', label: 'Egypt' },\n  { value: 'SV', label: 'El Salvador' },\n  { value: 'EE', label: 'Estonia' },\n  { value: 'ET', label: 'Ethiopia' },\n  { value: 'FI', label: 'Finland' },\n  { value: 'FR', label: 'France' },\n  { value: 'GM', label: 'Gambia' },\n  { value: 'DE', label: 'Germany' },\n  { value: 'GH', label: 'Ghana' },\n  { value: 'GI', label: 'Gibraltar' },\n  { value: 'GR', label: 'Greece' },\n  { value: 'GT', label: 'Guatemala' },\n  { value: 'GY', label: 'Guyana' },\n  { value: 'HK', label: 'Hong Kong SAR China' },\n  { value: 'HU', label: 'Hungary' },\n  { value: 'IS', label: 'Iceland' },\n  { value: 'IN', label: 'India' },\n  { value: 'ID', label: 'Indonesia' },\n  { value: 'IE', label: 'Ireland' },\n  { value: 'IL', label: 'Israel' },\n  { value: 'IT', label: 'Italy' },\n  { value: 'JM', label: 'Jamaica' },\n  { value: 'JP', label: 'Japan' },\n  { value: 'JO', label: 'Jordan' },\n  { value: 'KE', label: 'Kenya' },\n  { value: 'KW', label: 'Kuwait' },\n  { value: 'LV', label: 'Latvia' },\n  { value: 'LI', label: 'Liechtenstein' },\n  { value: 'LT', label: 'Lithuania' },\n  { value: 'LU', label: 'Luxembourg' },\n  { value: 'MO', label: 'Macao SAR China' },\n  { value: 'MG', label: 'Madagascar' },\n  { value: 'MY', label: 'Malaysia' },\n  { value: 'MT', label: 'Malta' },\n  { value: 'MU', label: 'Mauritius' },\n  { value: 'MX', label: 'Mexico' },\n  { value: 'MD', label: 'Moldova' },\n  { value: 'MC', label: 'Monaco' },\n  { value: 'MN', label: 'Mongolia' },\n  { value: 'MA', label: 'Morocco' },\n  { value: 'NA', label: 'Namibia' },\n  { value: 'NL', label: 'Netherlands' },\n  { value: 'NZ', label: 'New Zealand' },\n  { value: 'NG', label: 'Nigeria' },\n  { value: 'MK', label: 'North Macedonia' },\n  { value: 'NO', label: 'Norway' },\n  { value: 'OM', label: 'Oman' },\n  { value: 'PK', label: 'Pakistan' },\n  { value: 'PA', label: 'Panama' },\n  { value: 'PY', label: 'Paraguay' },\n  { value: 'PE', label: 'Peru' },\n  { value: 'PH', label: 'Philippines' },\n  { value: 'PL', label: 'Poland' },\n  { value: 'PT', label: 'Portugal' },\n  { value: 'QA', label: 'Qatar' },\n  { value: 'RO', label: 'Romania' },\n  { value: 'RW', label: 'Rwanda' },\n  { value: 'SA', label: 'Saudi Arabia' },\n  { value: 'SN', label: 'Senegal' },\n  { value: 'RS', label: 'Serbia' },\n  { value: 'SG', label: 'Singapore' },\n  { value: 'SK', label: 'Slovakia' },\n  { value: 'SI', label: 'Slovenia' },\n  { value: 'ZA', label: 'South Africa' },\n  { value: 'KR', label: 'South Korea' },\n  { value: 'ES', label: 'Spain' },\n  { value: 'LK', label: 'Sri Lanka' },\n  { value: 'LC', label: 'St. Lucia' },\n  { value: 'SE', label: 'Sweden' },\n  { value: 'CH', label: 'Switzerland' },\n  { value: 'TW', label: 'Taiwan' },\n  { value: 'TZ', label: 'Tanzania' },\n  { value: 'TH', label: 'Thailand' },\n  { value: 'TT', label: 'Trinidad & Tobago' },\n  { value: 'TN', label: 'Tunisia' },\n  { value: 'TR', label: 'Turkey' },\n  { value: 'AE', label: 'United Arab Emirates' },\n  { value: 'GB', label: 'United Kingdom' },\n  { value: 'US', label: 'United States' },\n  { value: 'UY', label: 'Uruguay' },\n  { value: 'UZ', label: 'Uzbekistan' },\n  { value: 'VN', label: 'Vietnam' },\n];\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/services/stripe.service.ts",
    "content": "import Stripe from 'stripe';\nimport { Injectable } from '@nestjs/common';\nimport { Organization, User } from '@prisma/client';\nimport { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';\nimport { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport { BillingSubscribeDto } from '@gitroom/nestjs-libraries/dtos/billing/billing.subscribe.dto';\nimport { groupBy } from 'lodash';\nimport { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';\nimport { AuthService } from '@gitroom/helpers/auth/auth.service';\nimport { TrackService } from '@gitroom/nestjs-libraries/track/track.service';\nimport { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service';\nimport { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'sk_nothing');\n\n@Injectable()\nexport class StripeService {\n  constructor(\n    private _subscriptionService: SubscriptionService,\n    private _organizationService: OrganizationService,\n    private _userService: UsersService,\n    private _trackService: TrackService\n  ) {}\n  validateRequest(rawBody: Buffer, signature: string, endpointSecret: string) {\n    return stripe.webhooks.constructEvent(rawBody, signature, endpointSecret);\n  }\n\n  async checkValidCard(\n    event:\n      | Stripe.CustomerSubscriptionCreatedEvent\n      | Stripe.CustomerSubscriptionUpdatedEvent\n  ) {\n    if (event.data.object.status === 'incomplete') {\n      return false;\n    }\n\n    const getOrgFromCustomer =\n      await this._organizationService.getOrgByCustomerId(\n        event.data.object.customer as string\n      );\n\n    if (!getOrgFromCustomer?.allowTrial) {\n      return true;\n    }\n\n    console.log('Checking card');\n\n    const paymentMethods = await stripe.paymentMethods.list({\n      customer: event.data.object.customer as string,\n    });\n\n    // find the last one created\n    const latestMethod = paymentMethods.data.reduce(\n      (prev, current) => {\n        if (prev.created < current.created) {\n          return current;\n        }\n        return prev;\n      },\n      { created: -100 } as Stripe.PaymentMethod\n    );\n\n    if (!latestMethod.id) {\n      return false;\n    }\n\n    try {\n      const paymentIntent = await stripe.paymentIntents.create({\n        amount: 100,\n        currency: 'usd',\n        payment_method: latestMethod.id,\n        customer: event.data.object.customer as string,\n        automatic_payment_methods: {\n          allow_redirects: 'never',\n          enabled: true,\n        },\n        capture_method: 'manual', // Authorize without capturing\n        confirm: true, // Confirm the PaymentIntent\n      });\n\n      if (paymentIntent.status !== 'requires_capture') {\n        console.error('Cant charge');\n        await stripe.paymentMethods.detach(paymentMethods.data[0].id);\n        await stripe.subscriptions.cancel(event.data.object.id as string);\n        return false;\n      }\n\n      await stripe.paymentIntents.cancel(paymentIntent.id as string);\n      return true;\n    } catch (err) {\n      try {\n        await stripe.paymentMethods.detach(paymentMethods.data[0].id);\n        await stripe.subscriptions.cancel(event.data.object.id as string);\n      } catch (err) {\n        /*dont do anything*/\n      }\n      return false;\n    }\n  }\n\n  async createSubscription(event: Stripe.CustomerSubscriptionCreatedEvent) {\n    const {\n      uniqueId,\n      billing,\n      period,\n    } = event.data.object.metadata as {\n      billing: 'STANDARD' | 'PRO';\n      period: 'MONTHLY' | 'YEARLY';\n      uniqueId: string;\n    };\n\n    try {\n      const check = await this.checkValidCard(event);\n      if (!check) {\n        return { ok: false };\n      }\n    } catch (err) {\n      return { ok: false };\n    }\n\n    return this._subscriptionService.createOrUpdateSubscription(\n      event.data.object.status !== 'active',\n      uniqueId,\n      event.data.object.customer as string,\n      pricing[billing].channel!,\n      billing,\n      period,\n      event.data.object.cancel_at\n    );\n  }\n  async updateSubscription(event: Stripe.CustomerSubscriptionUpdatedEvent) {\n    const {\n      uniqueId,\n      billing,\n      period,\n    } = event.data.object.metadata as {\n      billing: 'STANDARD' | 'PRO';\n      period: 'MONTHLY' | 'YEARLY';\n      uniqueId: string;\n    };\n\n    const check = await this.checkValidCard(event);\n    if (!check) {\n      return { ok: false };\n    }\n\n    return this._subscriptionService.createOrUpdateSubscription(\n      event.data.object.status !== 'active',\n      uniqueId,\n      event.data.object.customer as string,\n      pricing[billing].channel!,\n      billing,\n      period,\n      event.data.object.cancel_at\n    );\n  }\n\n  async deleteSubscription(event: Stripe.CustomerSubscriptionDeletedEvent) {\n    await this._subscriptionService.deleteSubscription(\n      event.data.object.customer as string\n    );\n  }\n\n  async createOrGetCustomer(organization: Organization) {\n    if (organization.paymentId) {\n      return organization.paymentId;\n    }\n\n    const users = await this._organizationService.getTeam(organization.id);\n    const customer = await stripe.customers.create({\n      email: users.users[0].user.email,\n      name: organization.name,\n    });\n    await this._subscriptionService.updateCustomerId(\n      organization.id,\n      customer.id\n    );\n    return customer.id;\n  }\n\n  async getPackages() {\n    const products = await stripe.prices.list({\n      active: true,\n      expand: ['data.tiers', 'data.product'],\n      lookup_keys: [\n        'standard_monthly',\n        'standard_yearly',\n        'pro_monthly',\n        'pro_yearly',\n      ],\n    });\n\n    const productsList = groupBy(\n      products.data.map((p) => ({\n        name: (p.product as Stripe.Product)?.name,\n        recurring: p?.recurring?.interval!,\n        price: p?.tiers?.[0]?.unit_amount! / 100,\n      })),\n      'recurring'\n    );\n\n    return { ...productsList };\n  }\n\n  async prorate(organizationId: string, body: BillingSubscribeDto) {\n    const org = await this._organizationService.getOrgById(organizationId);\n    const customer = await this.createOrGetCustomer(org!);\n    const priceData = pricing[body.billing];\n    const allProducts = await stripe.products.list({\n      active: true,\n      expand: ['data.prices'],\n    });\n\n    const findProduct =\n      allProducts.data.find(\n        (product) => product.name.toUpperCase() === body.billing.toUpperCase()\n      ) ||\n      (await stripe.products.create({\n        active: true,\n        name: body.billing,\n      }));\n\n    const pricesList = await stripe.prices.list({\n      active: true,\n      product: findProduct!.id,\n    });\n\n    const findPrice =\n      pricesList.data.find(\n        (p) =>\n          p?.recurring?.interval?.toLowerCase() ===\n            (body.period === 'MONTHLY' ? 'month' : 'year') &&\n          p?.nickname === body.billing + ' ' + body.period &&\n          p?.unit_amount ===\n            (body.period === 'MONTHLY'\n              ? priceData.month_price\n              : priceData.year_price) *\n              100\n      ) ||\n      (await stripe.prices.create({\n        active: true,\n        product: findProduct!.id,\n        currency: 'usd',\n        nickname: body.billing + ' ' + body.period,\n        unit_amount:\n          (body.period === 'MONTHLY'\n            ? priceData.month_price\n            : priceData.year_price) * 100,\n        recurring: {\n          interval: body.period === 'MONTHLY' ? 'month' : 'year',\n        },\n      }));\n\n    const proration_date = Math.floor(Date.now() / 1000);\n\n    const currentUserSubscription = {\n      data: (\n        await stripe.subscriptions.list({\n          customer,\n          status: 'all',\n        })\n      ).data.filter((f) => f.status === 'active' || f.status === 'trialing'),\n    };\n\n    try {\n      const price = await stripe.invoices.createPreview({\n        customer,\n        subscription: currentUserSubscription?.data?.[0]?.id,\n        subscription_details: {\n          proration_behavior: 'create_prorations',\n          billing_cycle_anchor: 'now',\n          items: [\n            {\n              id: currentUserSubscription?.data?.[0]?.items?.data?.[0]?.id,\n              price: findPrice?.id!,\n              quantity: 1,\n            },\n          ],\n          proration_date: proration_date,\n        },\n      });\n\n      return {\n        price: price?.amount_remaining ? price?.amount_remaining / 100 : 0,\n      };\n    } catch (err) {\n      return { price: 0 };\n    }\n  }\n\n  async getCustomerSubscriptions(organizationId: string) {\n    const org = (await this._organizationService.getOrgById(organizationId))!;\n    const customer = org.paymentId;\n    return stripe.subscriptions.list({\n      customer: customer!,\n      status: 'all',\n    });\n  }\n\n  async setToCancel(organizationId: string) {\n    const id = makeId(10);\n    const org = await this._organizationService.getOrgById(organizationId);\n    const customer = await this.createOrGetCustomer(org!);\n    const currentUserSubscription = {\n      data: (\n        await stripe.subscriptions.list({\n          customer,\n          status: 'all',\n          expand: ['data.latest_invoice'],\n        })\n      ).data.filter((f) => f.status !== 'canceled'),\n    };\n\n    const sub = currentUserSubscription.data[0];\n\n    // If the user is toggling back (un-cancelling), just remove the cancel\n    if (sub.cancel_at_period_end) {\n      const { cancel_at } = await stripe.subscriptions.update(sub.id, {\n        cancel_at_period_end: false,\n        metadata: { service: 'gitroom', id },\n      });\n\n      return {\n        id,\n        cancel_at: cancel_at ? new Date(cancel_at * 1000) : undefined,\n      };\n    }\n\n    // Check if the latest invoice has a failed payment\n    const latestInvoice = sub.latest_invoice as Stripe.Invoice | null;\n    const hasFailedPayment =\n      sub.status === 'past_due' ||\n      latestInvoice?.status === 'open' ||\n      latestInvoice?.status === 'uncollectible';\n\n    if (hasFailedPayment) {\n      // Payment already failed — cancel immediately and delete subscription\n      await stripe.subscriptions.cancel(sub.id);\n      await this._subscriptionService.deleteSubscription(customer);\n\n      return {\n        id,\n        cancel_at: new Date(),\n      };\n    }\n\n    // Payment succeeded — cancel at end of billing period\n    const { cancel_at } = await stripe.subscriptions.update(sub.id, {\n      cancel_at_period_end: true,\n      metadata: { service: 'gitroom', id },\n    });\n\n    return {\n      id,\n      cancel_at: cancel_at ? new Date(cancel_at * 1000) : undefined,\n    };\n  }\n\n  async getCustomerByOrganizationId(organizationId: string) {\n    const org = (await this._organizationService.getOrgById(organizationId))!;\n    return org.paymentId;\n  }\n\n  async createBillingPortalLink(customer: string) {\n    return stripe.billingPortal.sessions.create({\n      customer,\n      return_url: process.env['FRONTEND_URL'] + '/billing',\n    });\n  }\n\n  /**\n   * Find an active promotion code with autoapply: true metadata\n   * Only returns codes that are active and not expired\n   * Returns the promotion code string (not the ID) for frontend auto-apply\n   */\n  private async findAutoApplyPromotionCode(): Promise<string | null> {\n    try {\n      const promotionCodes = await stripe.promotionCodes.list({\n        active: true,\n        limit: 100,\n      });\n\n      const now = Math.floor(Date.now() / 1000);\n\n      for (const promoCode of promotionCodes.data) {\n        const coupon =\n          typeof promoCode.promotion.coupon === 'string'\n            ? null\n            : promoCode.promotion.coupon;\n\n        // Check if it has autoapply metadata set to true (check both promo and coupon metadata)\n        const autoApply = Object.assign(\n          {},\n          promoCode.metadata,\n          coupon?.metadata\n        )?.autoapply;\n        if (autoApply !== 'true') continue;\n\n        // Check if the promotion code has expired\n        if (promoCode.expires_at && promoCode.expires_at < now) continue;\n\n        // Check if the coupon has expired (redeem_by)\n        if (coupon?.redeem_by && coupon.redeem_by < now) continue;\n\n        // Check if max redemptions reached\n        if (\n          promoCode.max_redemptions &&\n          promoCode.times_redeemed >= promoCode.max_redemptions\n        )\n          continue;\n\n        // Found a valid auto-apply promotion code - return the code string for frontend\n        return promoCode.code;\n      }\n\n      return null;\n    } catch (err) {\n      console.error('Error finding auto-apply promotion code:', err);\n      return null;\n    }\n  }\n\n  private async createEmbeddedCheckout(\n    ud: string,\n    uniqueId: string,\n    customer: string,\n    body: BillingSubscribeDto,\n    price: string,\n    userId: string,\n    allowTrial: boolean\n  ) {\n    const user = await this._userService.getUserById(userId);\n\n    try {\n      await stripe.customers.update(customer, {\n        email: user.email,\n        ...(body.dub\n          ? {\n              metadata: {\n                dubCustomerExternalId: userId,\n                dubClickId: body.dub,\n              },\n            }\n          : {}),\n      });\n    } catch (err) {}\n\n    // Check for auto-apply promotion code (only for monthly plans)\n    let autoApplyPromoCode: string | null = null;\n    if (body.period === 'MONTHLY') {\n      autoApplyPromoCode = await this.findAutoApplyPromotionCode();\n    }\n\n    const isUtm = body.utm ? `&utm_source=${body.utm}` : '';\n    const { client_secret } = await stripe.checkout.sessions.create({\n      ui_mode: 'custom',\n      customer,\n      return_url:\n        process.env['FRONTEND_URL'] +\n        `/launches?onboarding=true&check=${uniqueId}${isUtm}`,\n      mode: 'subscription',\n      subscription_data: {\n        ...(allowTrial ? { trial_period_days: 7 } : {}),\n        metadata: {\n          service: 'gitroom',\n          ...body,\n          userId,\n          uniqueId,\n          ud,\n        },\n      },\n      ...(body.datafast_session_id && body.datafast_visitor_id\n        ? {\n            metadata: {\n              datafast_visitor_id: body.datafast_visitor_id,\n              datafast_session_id: body.datafast_session_id,\n            },\n          }\n        : {}),\n      allow_promotion_codes: body.period === 'MONTHLY',\n      line_items: [\n        {\n          price,\n          quantity: 1,\n        },\n      ],\n    });\n\n    // Return auto-apply promo code for frontend to apply\n    return {\n      client_secret,\n      ...(autoApplyPromoCode ? { auto_apply_coupon: autoApplyPromoCode } : {}),\n    };\n  }\n\n  private async createCheckoutSession(\n    ud: string,\n    uniqueId: string,\n    customer: string,\n    body: BillingSubscribeDto,\n    price: string,\n    userId: string,\n    allowTrial: boolean\n  ) {\n    const isUtm = body.utm ? `&utm_source=${body.utm}` : '';\n\n    if (body.dub) {\n      await stripe.customers.update(customer, {\n        metadata: {\n          dubCustomerExternalId: userId,\n          dubClickId: body.dub,\n        },\n      });\n    }\n\n    const { url } = await stripe.checkout.sessions.create({\n      customer,\n      cancel_url: process.env['FRONTEND_URL'] + `/billing?cancel=true${isUtm}`,\n      success_url:\n        process.env['FRONTEND_URL'] +\n        `/launches?onboarding=true&check=${uniqueId}${isUtm}`,\n      mode: 'subscription',\n      subscription_data: {\n        ...(allowTrial ? { trial_period_days: 7 } : {}),\n        metadata: {\n          service: 'gitroom',\n          ...body,\n          userId,\n          uniqueId,\n          ud,\n        },\n      },\n      allow_promotion_codes: body.period === 'MONTHLY',\n      line_items: [\n        {\n          price,\n          quantity: 1,\n        },\n      ],\n    });\n\n    return { url };\n  }\n\n  async finishTrial(paymentId: string) {\n    const list = (\n      await stripe.subscriptions.list({\n        customer: paymentId,\n      })\n    ).data.filter((f) => f.status === 'trialing');\n\n    return stripe.subscriptions.update(list[0].id, {\n      trial_end: 'now',\n    });\n  }\n\n  async checkDiscount(customer: string) {\n    if (!process.env.STRIPE_DISCOUNT_ID) {\n      return false;\n    }\n\n    const list = await stripe.charges.list({\n      customer,\n      limit: 1,\n    });\n\n    if (!list.data.filter((f) => f.amount > 1000).length) {\n      return false;\n    }\n\n    const currentUserSubscription = {\n      data: (\n        await stripe.subscriptions.list({\n          customer,\n          status: 'all',\n          expand: ['data.discounts'],\n        })\n      ).data.find((f) => f.status === 'active' || f.status === 'trialing'),\n    };\n\n    if (!currentUserSubscription) {\n      return false;\n    }\n\n    if (\n      currentUserSubscription.data?.items.data[0]?.price.recurring?.interval ===\n        'year' ||\n      currentUserSubscription.data?.discounts.length\n    ) {\n      return false;\n    }\n\n    return true;\n  }\n\n  async applyDiscount(customer: string) {\n    const check = this.checkDiscount(customer);\n    if (!check) {\n      return false;\n    }\n\n    const currentUserSubscription = {\n      data: (\n        await stripe.subscriptions.list({\n          customer,\n          status: 'all',\n          expand: ['data.discounts'],\n        })\n      ).data.find((f) => f.status === 'active' || f.status === 'trialing'),\n    };\n\n    await stripe.subscriptions.update(currentUserSubscription.data.id, {\n      discounts: [\n        {\n          coupon: process.env.STRIPE_DISCOUNT_ID!,\n        },\n      ],\n    });\n\n    return true;\n  }\n\n  async checkSubscription(organizationId: string, subscriptionId: string) {\n    const orgValue = await this._subscriptionService.checkSubscription(\n      organizationId,\n      subscriptionId\n    );\n\n    if (orgValue) {\n      return 2;\n    }\n\n    const getCustomerSubscriptions = await this.getCustomerSubscriptions(\n      organizationId\n    );\n    if (getCustomerSubscriptions.data.length === 0) {\n      return 0;\n    }\n\n    if (\n      getCustomerSubscriptions.data.find(\n        (p) => p.metadata.uniqueId === subscriptionId\n      )?.canceled_at\n    ) {\n      return 1;\n    }\n\n    return 0;\n  }\n\n  async embedded(\n    uniqueId: string,\n    organizationId: string,\n    userId: string,\n    body: BillingSubscribeDto,\n    allowTrial: boolean\n  ) {\n    const id = makeId(10);\n    const priceData = pricing[body.billing];\n    const org = await this._organizationService.getOrgById(organizationId);\n    const customer = await this.createOrGetCustomer(org!);\n    const allProducts = await stripe.products.list({\n      active: true,\n      expand: ['data.prices'],\n    });\n\n    const findProduct =\n      allProducts.data.find(\n        (product) => product.name.toUpperCase() === body.billing.toUpperCase()\n      ) ||\n      (await stripe.products.create({\n        active: true,\n        name: body.billing,\n      }));\n\n    const pricesList = await stripe.prices.list({\n      active: true,\n      product: findProduct!.id,\n    });\n\n    const findPrice =\n      pricesList.data.find(\n        (p) =>\n          p?.recurring?.interval?.toLowerCase() ===\n            (body.period === 'MONTHLY' ? 'month' : 'year') &&\n          p?.unit_amount ===\n            (body.period === 'MONTHLY'\n              ? priceData.month_price\n              : priceData.year_price) *\n              100\n      ) ||\n      (await stripe.prices.create({\n        active: true,\n        product: findProduct!.id,\n        currency: 'usd',\n        nickname: body.billing + ' ' + body.period,\n        unit_amount:\n          (body.period === 'MONTHLY'\n            ? priceData.month_price\n            : priceData.year_price) * 100,\n        recurring: {\n          interval: body.period === 'MONTHLY' ? 'month' : 'year',\n        },\n      }));\n\n    return this.createEmbeddedCheckout(\n      uniqueId,\n      id,\n      customer,\n      body,\n      findPrice!.id,\n      userId,\n      allowTrial\n    );\n  }\n\n  async subscribe(\n    uniqueId: string,\n    organizationId: string,\n    userId: string,\n    body: BillingSubscribeDto,\n    allowTrial: boolean\n  ) {\n    const id = makeId(10);\n    const priceData = pricing[body.billing];\n    const org = await this._organizationService.getOrgById(organizationId);\n    const customer = await this.createOrGetCustomer(org!);\n    const allProducts = await stripe.products.list({\n      active: true,\n      expand: ['data.prices'],\n    });\n\n    const findProduct =\n      allProducts.data.find(\n        (product) => product.name.toUpperCase() === body.billing.toUpperCase()\n      ) ||\n      (await stripe.products.create({\n        active: true,\n        name: body.billing,\n      }));\n\n    const pricesList = await stripe.prices.list({\n      active: true,\n      product: findProduct!.id,\n    });\n\n    const findPrice =\n      pricesList.data.find(\n        (p) =>\n          p?.recurring?.interval?.toLowerCase() ===\n            (body.period === 'MONTHLY' ? 'month' : 'year') &&\n          p?.unit_amount ===\n            (body.period === 'MONTHLY'\n              ? priceData.month_price\n              : priceData.year_price) *\n              100\n      ) ||\n      (await stripe.prices.create({\n        active: true,\n        product: findProduct!.id,\n        currency: 'usd',\n        nickname: body.billing + ' ' + body.period,\n        unit_amount:\n          (body.period === 'MONTHLY'\n            ? priceData.month_price\n            : priceData.year_price) * 100,\n        recurring: {\n          interval: body.period === 'MONTHLY' ? 'month' : 'year',\n        },\n      }));\n\n    const getCurrentSubscriptions =\n      await this._subscriptionService.getSubscription(organizationId);\n\n    if (!getCurrentSubscriptions) {\n      return this.createCheckoutSession(\n        uniqueId,\n        id,\n        customer,\n        body,\n        findPrice!.id,\n        userId,\n        allowTrial\n      );\n    }\n\n    const currentUserSubscription = {\n      data: (\n        await stripe.subscriptions.list({\n          customer,\n          status: 'all',\n        })\n      ).data.filter((f) => f.status === 'active' || f.status === 'trialing'),\n    };\n\n    try {\n      await stripe.subscriptions.update(currentUserSubscription.data[0].id, {\n        cancel_at_period_end: false,\n        metadata: {\n          service: 'gitroom',\n          ...body,\n          userId,\n          id,\n          ud: uniqueId,\n        },\n        proration_behavior: 'always_invoice',\n        items: [\n          {\n            id: currentUserSubscription.data[0].items.data[0].id,\n            price: findPrice!.id,\n            quantity: 1,\n          },\n        ],\n      });\n\n      return { id };\n    } catch (err) {\n      const { url } = await this.createBillingPortalLink(customer);\n      return {\n        portal: url,\n      };\n    }\n  }\n\n  async paymentSucceeded(event: Stripe.InvoicePaymentSucceededEvent) {\n    // get subscription from payment\n    const subscriptionId =\n      event.data.object.parent?.subscription_details?.subscription;\n    if (!subscriptionId) {\n      return { ok: true };\n    }\n    const subscription = await stripe.subscriptions.retrieve(\n      typeof subscriptionId === 'string' ? subscriptionId : subscriptionId.id\n    );\n\n    const { userId, ud } = subscription.metadata;\n    const user = await this._userService.getUserById(userId);\n    if (user && user.ip && user.agent) {\n      this._trackService.track(ud, user.ip, user.agent, TrackEnum.Purchase, {\n        value: event.data.object.amount_paid / 100,\n      });\n    }\n\n    return { ok: true };\n  }\n\n  async getCharges(organizationId: string) {\n    const org = await this._organizationService.getOrgById(organizationId);\n    if (!org?.paymentId) {\n      return [];\n    }\n\n    const charges = await stripe.charges.list({\n      customer: org.paymentId,\n      limit: 100,\n    });\n\n    return charges.data\n      .filter((f) => f.status === 'succeeded')\n      .map((charge) => ({\n        id: charge.id,\n        amount: charge.amount,\n        currency: charge.currency,\n        created: charge.created,\n        status: charge.status,\n        refunded: charge.refunded,\n        amount_refunded: charge.amount_refunded,\n        description: charge.description,\n      }));\n  }\n\n  async refundCharges(organizationId: string, chargeIds: string[]) {\n    const org = await this._organizationService.getOrgById(organizationId);\n    if (!org?.paymentId) {\n      throw new Error('No payment customer found for this organization');\n    }\n\n    const refunded: string[] = [];\n    const failed: string[] = [];\n\n    for (const chargeId of chargeIds) {\n      try {\n        await stripe.refunds.create({ charge: chargeId });\n        refunded.push(chargeId);\n      } catch (err) {\n        failed.push(chargeId);\n      }\n    }\n\n    return { refunded, failed };\n  }\n\n  async cancelSubscription(organizationId: string) {\n    const org = await this._organizationService.getOrgById(organizationId);\n    if (!org?.paymentId) {\n      throw new Error('No payment customer found for this organization');\n    }\n\n    const customer = org.paymentId;\n\n    const subscriptions = (\n      await stripe.subscriptions.list({\n        customer,\n        status: 'all',\n      })\n    ).data.filter((f) => f.status !== 'canceled');\n\n    if (!subscriptions.length) {\n      throw new Error('No active subscription found');\n    }\n\n    await stripe.subscriptions.cancel(subscriptions[0].id);\n    await this._subscriptionService.deleteSubscription(customer);\n\n    return { cancelled: true };\n  }\n\n  async lifetimeDeal(organizationId: string, code: string) {\n    const getCurrentSubscription =\n      await this._subscriptionService.getSubscriptionByOrganizationId(\n        organizationId\n      );\n    if (getCurrentSubscription && !getCurrentSubscription?.isLifetime) {\n      throw new Error('You already have a non lifetime subscription');\n    }\n\n    try {\n      const testCode = AuthService.fixedDecryption(code);\n      const findCode = await this._subscriptionService.getCode(testCode);\n      if (findCode) {\n        return {\n          success: false,\n        };\n      }\n\n      const nextPackage = !getCurrentSubscription ? 'STANDARD' : 'PRO';\n      const findPricing = pricing[nextPackage];\n\n      await this._subscriptionService.createOrUpdateSubscription(\n        false,\n        makeId(10),\n        organizationId,\n        getCurrentSubscription?.subscriptionTier === 'PRO'\n          ? getCurrentSubscription.totalChannels + 5\n          : findPricing.channel!,\n        nextPackage,\n        'MONTHLY',\n        null,\n        testCode,\n        organizationId\n      );\n      return {\n        success: true,\n      };\n    } catch (err) {\n      console.log(err);\n      return {\n        success: false,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/providers/dub.ts",
    "content": "import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\n\nconst DUB_API_ENDPOINT = process.env.DUB_API_ENDPOINT || 'https://api.dub.co';\nconst DUB_SHORT_LINK_DOMAIN = process.env.DUB_SHORT_LINK_DOMAIN || 'dub.sh';\n\nconst getOptions = () => ({\n  headers: {\n    Authorization: `Bearer ${process.env.DUB_TOKEN}`,\n    'Content-Type': 'application/json',\n  },\n});\n\nexport class Dub implements ShortLinking {\n  shortLinkDomain = DUB_SHORT_LINK_DOMAIN;\n\n  async linksStatistics(links: string[]) {\n    return Promise.all(\n      links.map(async (link) => {\n        const response = await (\n          await fetch(\n            `${DUB_API_ENDPOINT}/links/info?domain=${\n              this.shortLinkDomain\n            }&key=${link.split('/').pop()}`,\n            getOptions()\n          )\n        ).json();\n\n        return {\n          short: link,\n          original: response.url,\n          clicks: response.clicks,\n        };\n      })\n    );\n  }\n\n  async convertLinkToShortLink(id: string, link: string) {\n    return (\n      await (\n        await fetch(`${DUB_API_ENDPOINT}/links`, {\n          ...getOptions(),\n          method: 'POST',\n          body: JSON.stringify({\n            url: link,\n            tenantId: id,\n            domain: this.shortLinkDomain,\n          }),\n        })\n      ).json()\n    ).shortLink;\n  }\n\n  async convertShortLinkToLink(shortLink: string) {\n    return await (\n      await (\n        await fetch(\n          `${DUB_API_ENDPOINT}/links/info?domain=${shortLink}`,\n          getOptions()\n        )\n      ).json()\n    ).url;\n  }\n\n  // recursive functions that gets maximum 100 links per request if there are less than 100 links stop the recursion\n  async getAllLinksStatistics(\n    id: string,\n    page = 1\n  ): Promise<{ short: string; original: string; clicks: string }[]> {\n    const response = await (\n      await fetch(\n        `${DUB_API_ENDPOINT}/links?tenantId=${id}&page=${page}&pageSize=100`,\n        getOptions()\n      )\n    ).json();\n\n    const mapLinks = response.links.map((link: any) => ({\n      short: link,\n      original: response.url,\n      clicks: response.clicks,\n    }));\n\n    if (mapLinks.length < 100) {\n      return mapLinks;\n    }\n\n    return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/providers/empty.ts",
    "content": "import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\n\nexport class Empty implements ShortLinking {\n  shortLinkDomain = 'empty';\n\n  async linksStatistics(links: string[]) {\n    return [];\n  }\n\n  async convertLinkToShortLink(link: string) {\n    return '';\n  }\n\n  async convertShortLinkToLink(shortLink: string) {\n    return '';\n  }\n\n  getAllLinksStatistics(\n    id: string,\n    page: number\n  ): Promise<{ short: string; original: string; clicks: string }[]> {\n    return Promise.resolve([]);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/providers/kutt.ts",
    "content": "import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\n\nconst KUTT_API_ENDPOINT = process.env.KUTT_API_ENDPOINT || 'https://kutt.it/api/v2';\nconst KUTT_SHORT_LINK_DOMAIN = process.env.KUTT_SHORT_LINK_DOMAIN || 'kutt.it';\n\nconst getOptions = () => ({\n  headers: {\n    'X-API-Key': process.env.KUTT_API_KEY,\n    'Content-Type': 'application/json',\n  },\n});\n\nexport class Kutt implements ShortLinking {\n  shortLinkDomain = KUTT_SHORT_LINK_DOMAIN;\n\n  async linksStatistics(links: string[]) {\n    return Promise.all(\n      links.map(async (link) => {\n        const linkId = link.split('/').pop();\n        \n        try {\n          const response = await fetch(\n            `${KUTT_API_ENDPOINT}/links/${linkId}/stats`,\n            getOptions()\n          );\n          \n          if (!response.ok) {\n            throw new Error(`HTTP error! status: ${response.status}`);\n          }\n          \n          const data = await response.json();\n          \n          return {\n            short: link,\n            original: data.address || '',\n            clicks: data.lastDay?.stats?.reduce((total: number, stat: any) => total + stat, 0)?.toString() || '0',\n          };\n        } catch (error) {\n          return {\n            short: link,\n            original: '',\n            clicks: '0',\n          };\n        }\n      })\n    );\n  }\n\n  async convertLinkToShortLink(id: string, link: string) {\n    try {\n      const response = await fetch(`${KUTT_API_ENDPOINT}/links`, {\n        ...getOptions(),\n        method: 'POST',\n        body: JSON.stringify({\n          target: link,\n          domain: this.shortLinkDomain,\n          reuse: false,\n        }),\n      });\n\n      if (!response.ok) {\n        throw new Error(`HTTP error! status: ${response.status}`);\n      }\n\n      const data = await response.json();\n      return data.link;\n    } catch (error) {\n      throw new Error(`Failed to create short link: ${error}`);\n    }\n  }\n\n  async convertShortLinkToLink(shortLink: string) {\n    const linkId = shortLink.split('/').pop();\n    \n    try {\n      const response = await fetch(\n        `${KUTT_API_ENDPOINT}/links/${linkId}/stats`,\n        getOptions()\n      );\n\n      if (!response.ok) {\n        throw new Error(`HTTP error! status: ${response.status}`);\n      }\n\n      const data = await response.json();\n      return data.address || '';\n    } catch (error) {\n      throw new Error(`Failed to get original link: ${error}`);\n    }\n  }\n\n  async getAllLinksStatistics(\n    id: string,\n    page = 1\n  ): Promise<{ short: string; original: string; clicks: string }[]> {\n    try {\n      const response = await fetch(\n        `${KUTT_API_ENDPOINT}/links?limit=100&skip=${(page - 1) * 100}`,\n        getOptions()\n      );\n\n      if (!response.ok) {\n        throw new Error(`HTTP error! status: ${response.status}`);\n      }\n\n      const data = await response.json();\n      \n      const mapLinks = data.data?.map((link: any) => ({\n        short: link.link,\n        original: link.address,\n        clicks: link.visit_count?.toString() || '0',\n      })) || [];\n\n      if (mapLinks.length < 100) {\n        return mapLinks;\n      }\n\n      return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];\n    } catch (error) {\n      return [];\n    }\n  }\n}"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/providers/linkdrip.ts",
    "content": "import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\n\nconst LINK_DRIP_API_ENDPOINT =\n  process.env.LINK_DRIP_API_ENDPOINT || 'https://api.linkdrip.com/v1/';\nconst LINK_DRIP_SHORT_LINK_DOMAIN =\n  process.env.LINK_DRIP_SHORT_LINK_DOMAIN || 'dripl.ink';\n\nconst getOptions = () => ({\n  headers: {\n    Authorization: `Bearer ${process.env.LINK_DRIP_API_KEY}`,\n    'Content-Type': 'application/json',\n  },\n});\n\nexport class LinkDrip implements ShortLinking {\n  shortLinkDomain = LINK_DRIP_SHORT_LINK_DOMAIN;\n\n  async linksStatistics(links: string[]) {\n    return Promise.resolve([]);\n  }\n\n  async convertLinkToShortLink(id: string, link: string) {\n    try {\n      const response = await fetch(`${LINK_DRIP_API_ENDPOINT}/create`, {\n        ...getOptions(),\n        method: 'POST',\n        body: JSON.stringify({\n          target_url: link,\n          custom_domain: this.shortLinkDomain,\n        }),\n      });\n\n      if (!response.ok) {\n        throw new Error(\n          `Failed to create LinkDrip API short link with status: ${response.status}`\n        );\n      }\n\n      const data = await response.json();\n      return data.link;\n    } catch (error) {\n      throw new Error(`Failed to create LinkDrip short link: ${error}`);\n    }\n  }\n\n  async convertShortLinkToLink(shortLink: string) {\n    return '';\n  }\n\n  getAllLinksStatistics(\n    id: string,\n    page: number\n  ): Promise<{ short: string; original: string; clicks: string }[]> {\n    return Promise.resolve([]);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/providers/short.io.ts",
    "content": "import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\n\nconst options = {\n  headers: {\n    Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`,\n    'Content-Type': 'application/json',\n  },\n};\n\nexport class ShortIo implements ShortLinking {\n  shortLinkDomain = 'short.io';\n\n  async linksStatistics(links: string[]) {\n    return Promise.all(\n      links.map(async (link) => {\n        const url = `https://api.short.io/links/expand?domain=${\n          this.shortLinkDomain\n        }&path=${link.split('/').pop()}`;\n        const response = await fetch(url, options).then((res) => res.json());\n\n        const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;\n\n        const statResponse = await fetch(linkStatisticsUrl, options).then(\n          (res) => res.json()\n        );\n\n        return {\n          short: response.shortURL,\n          original: response.originalURL,\n          clicks: statResponse.totalClicks,\n        };\n      })\n    );\n  }\n\n  async convertLinkToShortLink(id: string, link: string) {\n    const response = await fetch(`https://api.short.io/links`, {\n      ...options,\n      method: 'POST',\n      body: JSON.stringify({\n        url: link,\n        tenantId: id,\n        domain: this.shortLinkDomain,\n        originalURL: link,\n      }),\n    }).then((res) => res.json());\n\n    return response.shortURL;\n  }\n\n  async convertShortLinkToLink(shortLink: string) {\n    return await (\n      await (\n        await fetch(\n          `https://api.short.io/links/expand?domain=${\n            this.shortLinkDomain\n          }&path=${shortLink.split('/').pop()}`,\n          options\n        )\n      ).json()\n    ).originalURL;\n  }\n\n  // recursive functions that gets maximum 100 links per request if there are less than 100 links stop the recursion\n  async getAllLinksStatistics(\n    id: string,\n    page = 1\n  ): Promise<{ short: string; original: string; clicks: string }[]> {\n    const response = await (\n      await fetch(\n        `https://api.short.io/api/links?domain_id=${id}&limit=150`,\n        options\n      )\n    ).json();\n\n    const mapLinks = response.links.map(async (link: any) => {\n      const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`;\n\n      const statResponse = await fetch(linkStatisticsUrl, options).then((res) =>\n        res.json()\n      );\n\n      return {\n        short: link,\n        original: response.url,\n        clicks: statResponse.totalClicks,\n      };\n    });\n\n    if (mapLinks.length < 100) {\n      return mapLinks;\n    }\n\n    return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/short-linking.interface.ts",
    "content": "export interface ShortLinking {\n  shortLinkDomain: string;\n  linksStatistics(\n    links: string[]\n  ): Promise<{ short: string; original: string; clicks: string }[]>;\n  convertLinkToShortLink(id: string, link: string): Promise<string>;\n  convertShortLinkToLink(shortLink: string): Promise<string>;\n  getAllLinksStatistics(\n    id: string,\n    page: number\n  ): Promise<{ short: string; original: string; clicks: string }[]>;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/short-linking/short.link.service.ts",
    "content": "import { Dub } from '@gitroom/nestjs-libraries/short-linking/providers/dub';\nimport { Empty } from '@gitroom/nestjs-libraries/short-linking/providers/empty';\nimport { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface';\nimport { Injectable } from '@nestjs/common';\nimport { ShortIo } from './providers/short.io';\nimport { Kutt } from './providers/kutt';\nimport { LinkDrip } from './providers/linkdrip';\nimport { uniq } from 'lodash';\nimport striptags from 'striptags';\n\nconst getProvider = (): ShortLinking => {\n  if (process.env.DUB_TOKEN) {\n    return new Dub();\n  }\n\n  if (process.env.SHORT_IO_SECRET_KEY) {\n    return new ShortIo();\n  }\n\n  if (process.env.KUTT_API_KEY) {\n    return new Kutt();\n  }\n\n  if (process.env.LINK_DRIP_API_KEY) {\n    return new LinkDrip();\n  }\n\n  return new Empty();\n};\n\n@Injectable()\nexport class ShortLinkService {\n  static provider = getProvider();\n\n  askShortLinkedin(messages: string[]): boolean {\n    if (ShortLinkService.provider.shortLinkDomain === 'empty') {\n      return false;\n    }\n\n    const mergeMessages = messages.join(' ');\n    const urlRegex =\n      /(https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*))/gm;\n    const urls = mergeMessages.match(urlRegex);\n    if (!urls) {\n      // No URLs found, return the original text\n      return false;\n    }\n\n    return urls.some(\n      (url) => url.indexOf(ShortLinkService.provider.shortLinkDomain) === -1\n    );\n  }\n\n  async convertTextToShortLinks(id: string, messagesList: string[]) {\n    if (ShortLinkService.provider.shortLinkDomain === 'empty') {\n      return messagesList;\n    }\n\n    const messages = messagesList.map((text) => {\n      return text\n        .replace(/&amp;/g, '&')\n        .replace(/&quest;/g, '?')\n        .replace(/&num;/g, '#');\n    });\n\n    const urlRegex =\n      /(https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*))/gm;\n    return Promise.all(\n      messages.map(async (text) => {\n        const urls = uniq(text.match(urlRegex));\n        if (!urls) {\n          // No URLs found, return the original text\n          return text;\n        }\n\n        const replacementMap: Record<string, string> = {};\n\n        // Process each URL asynchronously\n        await Promise.all(\n          urls.map(async (url) => {\n            if (url.indexOf(ShortLinkService.provider.shortLinkDomain) === -1) {\n              replacementMap[url] =\n                await ShortLinkService.provider.convertLinkToShortLink(id, url);\n            } else {\n              replacementMap[url] = url; // Keep the original URL if it matches the prefix\n            }\n          })\n        );\n\n        // Replace the URLs in the text with their replacements\n        return text.replace(urlRegex, (url) => replacementMap[url]);\n      })\n    );\n  }\n\n  async convertShortLinksToLinks(messages: string[]) {\n    if (ShortLinkService.provider.shortLinkDomain === 'empty') {\n      return messages;\n    }\n\n    const urlRegex = /https?:\\/\\/[^\\s/$.?#].[^\\s]*/g;\n    return Promise.all(\n      messages.map(async (text) => {\n        const urls = text.match(urlRegex);\n        if (!urls) {\n          // No URLs found, return the original text\n          return text;\n        }\n\n        const replacementMap: Record<string, string> = {};\n\n        // Process each URL asynchronously\n        await Promise.all(\n          urls.map(async (url) => {\n            if (url.indexOf(ShortLinkService.provider.shortLinkDomain) > -1) {\n              replacementMap[url] =\n                await ShortLinkService.provider.convertShortLinkToLink(url);\n            } else {\n              replacementMap[url] = url; // Keep the original URL if it matches the prefix\n            }\n          })\n        );\n\n        // Replace the URLs in the text with their replacements\n        return text.replace(urlRegex, (url) => replacementMap[url]);\n      })\n    );\n  }\n\n  async getStatistics(messages: string[]) {\n    if (ShortLinkService.provider.shortLinkDomain === 'empty') {\n      return [];\n    }\n\n    const mergeMessages = messages.join(' ');\n    const regex = new RegExp(\n      `https?://${ShortLinkService.provider.shortLinkDomain.replace(\n        '.',\n        '\\\\.'\n      )}/[^\\\\s]*`,\n      'g'\n    );\n    const urls = striptags(mergeMessages).match(regex);\n    if (!urls) {\n      // No URLs found, return the original text\n      return [];\n    }\n\n    return ShortLinkService.provider.linksStatistics(urls);\n  }\n\n  async getAllLinks(id: string) {\n    if (ShortLinkService.provider.shortLinkDomain === 'empty') {\n      return [];\n    }\n\n    return ShortLinkService.provider.getAllLinksStatistics(id, 1);\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/temporal/infinite.workflow.register.ts",
    "content": "import { Global, Injectable, Module, OnModuleInit } from '@nestjs/common';\nimport { TemporalService } from 'nestjs-temporal-core';\n\n@Injectable()\nexport class InfiniteWorkflowRegister implements OnModuleInit {\n  constructor(private _temporalService: TemporalService) {}\n\n  async onModuleInit(): Promise<void> {\n    if (!!process.env.RUN_CRON) {\n      try {\n        await this._temporalService.client\n          ?.getRawClient()\n          ?.workflow?.start('missingPostWorkflow', {\n            workflowId: 'missing-post-workflow',\n            taskQueue: 'main',\n          });\n      } catch (err) {}\n    }\n  }\n}\n\n@Global()\n@Module({\n  imports: [],\n  controllers: [],\n  providers: [InfiniteWorkflowRegister],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class InfiniteWorkflowRegisterModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/temporal/temporal.module.ts",
    "content": "import { TemporalModule } from 'nestjs-temporal-core';\nimport { socialIntegrationList } from '@gitroom/nestjs-libraries/integrations/integration.manager';\n\nexport const getTemporalModule = (\n  isWorkers: boolean,\n  path?: string,\n  activityClasses?: any[]\n) => {\n  return TemporalModule.register({\n    isGlobal: true,\n    connection: {\n      address: process.env.TEMPORAL_ADDRESS || 'localhost:7233',\n      ...process.env.TEMPORAL_TLS === 'true' ? {tls: true} : {},\n      ...process.env.TEMPORAL_API_KEY ? {apiKey: process.env.TEMPORAL_API_KEY} : {},\n      namespace: process.env.TEMPORAL_NAMESPACE || 'default',\n    },\n    taskQueue: 'main',\n    logLevel: 'error',\n    ...(isWorkers\n      ? {\n          workers: [\n            { identifier: 'main', maxConcurrentJob: undefined },\n            ...socialIntegrationList,\n          ]\n            .filter((f) => f.identifier.indexOf('-') === -1)\n            .map((integration) => ({\n              taskQueue: integration.identifier.split('-')[0],\n              workflowsPath: path!,\n              activityClasses: activityClasses!,\n              autoStart: true,\n              ...(integration.maxConcurrentJob\n                ? {\n                    workerOptions: {\n                      maxConcurrentActivityTaskExecutions:\n                        integration.maxConcurrentJob,\n                    },\n                  }\n                : {}),\n            })),\n        }\n      : {}),\n  });\n};\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/temporal/temporal.register.ts",
    "content": "import { Global, Injectable, Module, OnModuleInit } from '@nestjs/common';\nimport { TemporalService } from 'nestjs-temporal-core';\nimport { Connection } from '@temporalio/client';\n\n@Injectable()\nexport class TemporalRegister implements OnModuleInit {\n  constructor(private _client: TemporalService) {}\n\n  async onModuleInit(): Promise<void> {\n    if (process.env.TEMPORAL_TLS === 'true') {\n      return;\n    }\n    const connection = this._client?.client?.getRawClient()\n      ?.connection as Connection;\n\n    const { customAttributes } =\n      await connection.operatorService.listSearchAttributes({\n        namespace: process.env.TEMPORAL_NAMESPACE || 'default',\n      });\n\n    const neededAttribute = ['organizationId', 'postId'];\n    const missingAttributes = neededAttribute.filter(\n      (attr) => !customAttributes[attr]\n    );\n\n    if (missingAttributes.length > 0) {\n      await connection.operatorService.addSearchAttributes({\n        namespace: process.env.TEMPORAL_NAMESPACE || 'default',\n        searchAttributes: missingAttributes.reduce((all, current) => {\n          // @ts-ignore\n          all[current] = 1;\n          return all;\n        }, {}),\n      });\n    }\n  }\n}\n\n@Global()\n@Module({\n  imports: [],\n  controllers: [],\n  providers: [TemporalRegister],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class TemporalRegisterMissingSearchAttributesModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/temporal/temporal.search.attribute.ts",
    "content": "import {\n  defineSearchAttributeKey,\n  SearchAttributeType,\n} from '@temporalio/common';\n\nexport const organizationId = defineSearchAttributeKey(\n  'organizationId',\n  SearchAttributeType.TEXT\n);\n\nexport const postId = defineSearchAttributeKey(\n  'postId',\n  SearchAttributeType.TEXT\n);\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/throttler/throttler.provider.ts",
    "content": "import { ThrottlerGuard } from '@nestjs/throttler';\nimport { ExecutionContext, Injectable } from '@nestjs/common';\nimport { Request } from 'express';\n\n@Injectable()\nexport class ThrottlerBehindProxyGuard extends ThrottlerGuard {\n  public override async canActivate(\n    context: ExecutionContext\n  ): Promise<boolean> {\n    const { url, method } = context.switchToHttp().getRequest<Request>();\n    if (method === 'POST' && url.includes('/public/v1/posts')) {\n      return super.canActivate(context);\n    }\n\n    return true;\n  }\n\n  protected override async getTracker(\n    req: Record<string, any>\n  ): Promise<string> {\n    return (\n      req.org.id + '_' + (req.url.indexOf('/posts') > -1 ? 'posts' : 'other')\n    );\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/track/track.service.ts",
    "content": "import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { User } from '@prisma/client';\nimport { Injectable } from '@nestjs/common';\nimport {\n  ServerEvent,\n  EventRequest,\n  UserData,\n  CustomData,\n  FacebookAdsApi,\n} from 'facebook-nodejs-business-sdk';\nimport { createHash } from 'crypto';\n\nconst access_token = process.env.FACEBOOK_PIXEL_ACCESS_TOKEN!;\nconst pixel_id = process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!;\n\nif (access_token && pixel_id) {\n  FacebookAdsApi.init(access_token || '');\n}\n\n@Injectable()\nexport class TrackService {\n  private hashValue(value: string) {\n    return createHash('sha256').update(value).digest('hex');\n  }\n  track(\n    uniqueId: string,\n    ip: string,\n    agent: string,\n    tt: TrackEnum,\n    additional: Record<string, any>,\n    fbclid?: string,\n    user?: User\n  ) {\n    if (!access_token || !pixel_id) {\n      return;\n    }\n    // @ts-ignore\n    const current_timestamp = Math.floor(new Date() / 1000);\n\n    const userData = new UserData();\n    if (ip || user?.ip) {\n      userData.setClientIpAddress(ip || user?.ip || '');\n    }\n\n    if (agent || user?.agent) {\n      userData.setClientUserAgent(agent || user?.agent || '');\n    }\n    if (fbclid) {\n      userData.setFbc(fbclid);\n    }\n\n    if (user && user.email) {\n      userData.setEmail(this.hashValue(user.email));\n    }\n\n    let customData = null;\n    if (additional?.value) {\n      customData = new CustomData();\n      customData.setValue(additional.value).setCurrency('USD');\n    }\n\n    const serverEvent = new ServerEvent()\n      .setEventName(TrackEnum[tt])\n      .setEventTime(current_timestamp)\n      .setActionSource('website');\n\n    if (user && user.id) {\n      serverEvent.setEventId(uniqueId || user.id);\n    }\n\n    if (userData) {\n      serverEvent.setUserData(userData);\n    }\n    if (customData) {\n      serverEvent.setCustomData(customData);\n    }\n\n    const eventsData = [serverEvent];\n    const eventRequest = new EventRequest(access_token, pixel_id).setEvents(\n      eventsData\n    );\n\n    return eventRequest.execute();\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/cloudflare.storage.ts",
    "content": "import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport 'multer';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\nimport mime from 'mime-types';\n// @ts-ignore\nimport { getExtension } from 'mime';\nimport { IUploadProvider } from './upload.interface';\nimport axios from 'axios';\n\nclass CloudflareStorage implements IUploadProvider {\n  private _client: S3Client;\n\n  constructor(\n    accountID: string,\n    accessKey: string,\n    secretKey: string,\n    private region: string,\n    private _bucketName: string,\n    private _uploadUrl: string\n  ) {\n    this._client = new S3Client({\n      endpoint: `https://${accountID}.r2.cloudflarestorage.com`,\n      region,\n      credentials: {\n        accessKeyId: accessKey,\n        secretAccessKey: secretKey,\n      },\n      requestChecksumCalculation: 'WHEN_REQUIRED',\n    });\n\n    this._client.middlewareStack.add(\n      (next) =>\n        async (args): Promise<any> => {\n          const request = args.request as RequestInit;\n\n          // Remove checksum headers\n          const headers = request.headers as Record<string, string>;\n          delete headers['x-amz-checksum-crc32'];\n          delete headers['x-amz-checksum-crc32c'];\n          delete headers['x-amz-checksum-sha1'];\n          delete headers['x-amz-checksum-sha256'];\n          request.headers = headers;\n\n          Object.entries(request.headers).forEach(\n            // @ts-ignore\n            ([key, value]: [string, string]): void => {\n              if (!request.headers) {\n                request.headers = {};\n              }\n              (request.headers as Record<string, string>)[key] = value;\n            }\n          );\n\n          return next(args);\n        },\n      { step: 'build', name: 'customHeaders' }\n    );\n  }\n\n  async uploadSimple(path: string) {\n    const loadImage = await fetch(path);\n    const contentType =\n      loadImage?.headers?.get('content-type') ||\n      loadImage?.headers?.get('Content-Type');\n    const extension = getExtension(contentType)!;\n    const id = makeId(10);\n\n    const params = {\n      Bucket: this._bucketName,\n      Key: `${id}.${extension}`,\n      Body: Buffer.from(await loadImage.arrayBuffer()),\n      ContentType: contentType,\n      ChecksumMode: 'DISABLED',\n    };\n\n    const command = new PutObjectCommand({ ...params });\n    await this._client.send(command);\n\n    return `${this._uploadUrl}/${id}.${extension}`;\n  }\n\n  async uploadFile(file: Express.Multer.File): Promise<any> {\n    try {\n      const id = makeId(10);\n      const extension = mime.extension(file.mimetype) || '';\n\n      // Create the PutObjectCommand to upload the file to Cloudflare R2\n      const command = new PutObjectCommand({\n        Bucket: this._bucketName,\n        ACL: 'public-read',\n        Key: `${id}.${extension}`,\n        Body: file.buffer,\n      });\n\n      await this._client.send(command);\n\n      return {\n        filename: `${id}.${extension}`,\n        mimetype: file.mimetype,\n        size: file.size,\n        buffer: file.buffer,\n        originalname: `${id}.${extension}`,\n        fieldname: 'file',\n        path: `${this._uploadUrl}/${id}.${extension}`,\n        destination: `${this._uploadUrl}/${id}.${extension}`,\n        encoding: '7bit',\n        stream: file.buffer as any,\n      };\n    } catch (err) {\n      console.error('Error uploading file to Cloudflare R2:', err);\n      throw err;\n    }\n  }\n\n  // Implement the removeFile method from IUploadProvider\n  async removeFile(filePath: string): Promise<void> {\n    // const fileName = filePath.split('/').pop(); // Extract the filename from the path\n    // const command = new DeleteObjectCommand({\n    //   Bucket: this._bucketName,\n    //   Key: fileName,\n    // });\n    // await this._client.send(command);\n  }\n}\n\nexport { CloudflareStorage };\nexport default CloudflareStorage;\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/custom.upload.validation.ts",
    "content": "import {\n  BadRequestException,\n  FileTypeValidator,\n  Injectable,\n  MaxFileSizeValidator,\n  ParseFilePipe,\n  PipeTransform,\n} from '@nestjs/common';\n\n@Injectable()\nexport class CustomFileValidationPipe implements PipeTransform {\n  async transform(value: any) {\n    if (!value) {\n      throw 'No file provided.';\n    }\n\n    if (!value.mimetype) {\n      return value;\n    }\n\n    // Set the maximum file size based on the MIME type\n    const maxSize = this.getMaxSize(value.mimetype);\n    const validation =\n      (value.mimetype.startsWith('image/') ||\n        value.mimetype.startsWith('video/mp4')) &&\n      value.size <= maxSize;\n\n    if (validation) {\n      return value;\n    }\n\n    throw new BadRequestException(\n      `File size exceeds the maximum allowed size of ${maxSize} bytes.`\n    );\n  }\n\n  private getMaxSize(mimeType: string): number {\n    if (mimeType.startsWith('image/')) {\n      return 10 * 1024 * 1024; // 10 MB\n    } else if (mimeType.startsWith('video/')) {\n      return 1024 * 1024 * 1024; // 1 GB\n    } else {\n      throw new BadRequestException('Unsupported file type.');\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/local.storage.ts",
    "content": "import { IUploadProvider } from './upload.interface';\nimport { mkdirSync, unlink, writeFileSync } from 'fs';\n// @ts-ignore\nimport mime from 'mime';\nimport { extname } from 'path';\nexport class LocalStorage implements IUploadProvider {\n  constructor(private uploadDirectory: string) {}\n\n  async uploadSimple(path: string) {\n    const loadImage = await fetch(path);\n    const contentType =\n      loadImage?.headers?.get('content-type') ||\n      loadImage?.headers?.get('Content-Type');\n    const findExtension = mime.getExtension(contentType)!;\n\n    const now = new Date();\n    const year = now.getFullYear();\n    const month = String(now.getMonth() + 1).padStart(2, '0');\n    const day = String(now.getDate()).padStart(2, '0');\n\n    const innerPath = `/${year}/${month}/${day}`;\n    const dir = `${this.uploadDirectory}${innerPath}`;\n    mkdirSync(dir, { recursive: true });\n\n    const randomName = Array(32)\n      .fill(null)\n      .map(() => Math.round(Math.random() * 16).toString(16))\n      .join('');\n\n    const filePath = `${dir}/${randomName}.${findExtension}`;\n    const publicPath = `${innerPath}/${randomName}.${findExtension}`;\n    // Logic to save the file to the filesystem goes here\n    writeFileSync(filePath, Buffer.from(await loadImage.arrayBuffer()));\n\n    return process.env.FRONTEND_URL + '/uploads' + publicPath;\n  }\n\n  async uploadFile(file: Express.Multer.File): Promise<any> {\n    try {\n      const now = new Date();\n      const year = now.getFullYear();\n      const month = String(now.getMonth() + 1).padStart(2, '0');\n      const day = String(now.getDate()).padStart(2, '0');\n\n      const innerPath = `/${year}/${month}/${day}`;\n      const dir = `${this.uploadDirectory}${innerPath}`;\n      mkdirSync(dir, { recursive: true });\n\n      const randomName = Array(32)\n        .fill(null)\n        .map(() => Math.round(Math.random() * 16).toString(16))\n        .join('');\n\n      const filePath = `${dir}/${randomName}${extname(file.originalname)}`;\n      const publicPath = `${innerPath}/${randomName}${extname(\n        file.originalname\n      )}`;\n\n      // Logic to save the file to the filesystem goes here\n      writeFileSync(filePath, file.buffer);\n\n      return {\n        filename: `${randomName}${extname(file.originalname)}`,\n        path: process.env.FRONTEND_URL + '/uploads' + publicPath,\n        mimetype: file.mimetype,\n        originalname: file.originalname,\n      };\n    } catch (err) {\n      console.error('Error uploading file to Local Storage:', err);\n      throw err;\n    }\n  }\n\n  async removeFile(filePath: string): Promise<void> {\n    // Logic to remove the file from the filesystem goes here\n    return new Promise((resolve, reject) => {\n      unlink(filePath, (err) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve();\n        }\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/r2.uploader.ts",
    "content": "import {\n  UploadPartCommand,\n  S3Client,\n  ListPartsCommand,\n  CreateMultipartUploadCommand,\n  CompleteMultipartUploadCommand,\n  AbortMultipartUploadCommand,\n  PutObjectCommand,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Request, Response } from 'express';\nimport crypto from 'crypto';\nimport path from 'path';\nimport { makeId } from '@gitroom/nestjs-libraries/services/make.is';\n\nconst {\n  CLOUDFLARE_ACCOUNT_ID,\n  CLOUDFLARE_ACCESS_KEY,\n  CLOUDFLARE_SECRET_ACCESS_KEY,\n  CLOUDFLARE_BUCKETNAME,\n  CLOUDFLARE_BUCKET_URL,\n} = process.env;\n\nconst R2 = new S3Client({\n  region: 'auto',\n  endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n  credentials: {\n    accessKeyId: CLOUDFLARE_ACCESS_KEY!,\n    secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY!,\n  },\n});\n\n// Function to generate a random string\nfunction generateRandomString() {\n  return makeId(20);\n}\n\nexport default async function handleR2Upload(\n  endpoint: string,\n  req: Request,\n  res: Response\n) {\n  switch (endpoint) {\n    case 'create-multipart-upload':\n      return createMultipartUpload(req, res);\n    case 'prepare-upload-parts':\n      return prepareUploadParts(req, res);\n    case 'complete-multipart-upload':\n      return completeMultipartUpload(req, res);\n    case 'list-parts':\n      return listParts(req, res);\n    case 'abort-multipart-upload':\n      return abortMultipartUpload(req, res);\n    case 'sign-part':\n      return signPart(req, res);\n  }\n  return res.status(404).end();\n}\n\nexport async function simpleUpload(\n  data: Buffer,\n  originalFilename: string,\n  contentType: string\n) {\n  const fileExtension = path.extname(originalFilename); // Extract extension\n  const randomFilename = generateRandomString() + fileExtension; // Append extension\n\n  const params = {\n    Bucket: CLOUDFLARE_BUCKETNAME,\n    Key: randomFilename,\n    Body: data,\n    ContentType: contentType,\n  };\n\n  const command = new PutObjectCommand({ ...params });\n  await R2.send(command);\n\n  return CLOUDFLARE_BUCKET_URL + '/' + randomFilename;\n}\n\nexport async function createMultipartUpload(req: Request, res: Response) {\n  const { file, fileHash, contentType } = req.body;\n  const fileExtension = path.extname(file.name); // Extract extension\n  const randomFilename = generateRandomString() + fileExtension; // Append extension\n\n  try {\n    const params = {\n      Bucket: CLOUDFLARE_BUCKETNAME,\n      Key: `${randomFilename}`,\n      ContentType: contentType,\n      Metadata: {\n        'x-amz-meta-file-hash': fileHash,\n      },\n    };\n\n    const command = new CreateMultipartUploadCommand({ ...params });\n    const response = await R2.send(command);\n    return res.status(200).json({\n      uploadId: response.UploadId,\n      key: response.Key,\n    });\n  } catch (err) {\n    console.log('Error', err);\n    return res.status(500).json({ source: { status: 500 } });\n  }\n}\n\nexport async function prepareUploadParts(req: Request, res: Response) {\n  const { partData } = req.body;\n\n  const parts = partData.parts;\n\n  const response = {\n    presignedUrls: {},\n  };\n\n  for (const part of parts) {\n    try {\n      const params = {\n        Bucket: CLOUDFLARE_BUCKETNAME,\n        Key: partData.key,\n        PartNumber: part.number,\n        UploadId: partData.uploadId,\n      };\n      const command = new UploadPartCommand({ ...params });\n      const url = await getSignedUrl(R2, command, { expiresIn: 3600 });\n\n      // @ts-ignore\n      response.presignedUrls[part.number] = url;\n    } catch (err) {\n      console.log('Error', err);\n      return res.status(500).json(err);\n    }\n  }\n\n  return res.status(200).json(response);\n}\n\nexport async function listParts(req: Request, res: Response) {\n  const { key, uploadId } = req.body;\n\n  try {\n    const params = {\n      Bucket: CLOUDFLARE_BUCKETNAME,\n      Key: key,\n      UploadId: uploadId,\n    };\n    const command = new ListPartsCommand({ ...params });\n    const response = await R2.send(command);\n\n    return res.status(200).json(response['Parts']);\n  } catch (err) {\n    console.log('Error', err);\n    return res.status(500).json(err);\n  }\n}\n\nexport async function completeMultipartUpload(req: Request, res: Response) {\n  const { key, uploadId, parts } = req.body;\n\n  try {\n    const params = {\n      Bucket: CLOUDFLARE_BUCKETNAME,\n      Key: key,\n      UploadId: uploadId,\n      MultipartUpload: { Parts: parts },\n    };\n\n    const command = new CompleteMultipartUploadCommand({\n      Bucket: CLOUDFLARE_BUCKETNAME,\n      Key: key,\n      UploadId: uploadId,\n      MultipartUpload: { Parts: parts },\n    });\n    const response = await R2.send(command);\n    response.Location =\n      process.env.CLOUDFLARE_BUCKET_URL +\n      '/' +\n      response?.Location?.split('/').at(-1);\n    return response;\n  } catch (err) {\n    console.log('Error', err);\n    return res.status(500).json(err);\n  }\n}\n\nexport async function abortMultipartUpload(req: Request, res: Response) {\n  const { key, uploadId } = req.body;\n\n  try {\n    const params = {\n      Bucket: CLOUDFLARE_BUCKETNAME,\n      Key: key,\n      UploadId: uploadId,\n    };\n    const command = new AbortMultipartUploadCommand({ ...params });\n    const response = await R2.send(command);\n\n    return res.status(200).json(response);\n  } catch (err) {\n    console.log('Error', err);\n    return res.status(500).json(err);\n  }\n}\n\nexport async function signPart(req: Request, res: Response) {\n  const { key, uploadId } = req.body;\n  const partNumber = parseInt(req.body.partNumber);\n\n  const params = {\n    Bucket: CLOUDFLARE_BUCKETNAME,\n    Key: key,\n    PartNumber: partNumber,\n    UploadId: uploadId,\n    Expires: 3600,\n  };\n\n  const command = new UploadPartCommand({ ...params });\n  const url = await getSignedUrl(R2, command, { expiresIn: 3600 });\n\n  return res.status(200).json({\n    url: url,\n  });\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/upload.factory.ts",
    "content": "import { CloudflareStorage } from './cloudflare.storage';\nimport { IUploadProvider } from './upload.interface';\nimport { LocalStorage } from './local.storage';\n\nexport class UploadFactory {\n  static createStorage(): IUploadProvider {\n    const storageProvider = process.env.STORAGE_PROVIDER || 'local';\n\n    switch (storageProvider) {\n      case 'local':\n        return new LocalStorage(process.env.UPLOAD_DIRECTORY!);\n      case 'cloudflare':\n        return new CloudflareStorage(\n          process.env.CLOUDFLARE_ACCOUNT_ID!,\n          process.env.CLOUDFLARE_ACCESS_KEY!,\n          process.env.CLOUDFLARE_SECRET_ACCESS_KEY!,\n          process.env.CLOUDFLARE_REGION!,\n          process.env.CLOUDFLARE_BUCKETNAME!,\n          process.env.CLOUDFLARE_BUCKET_URL!\n        );\n      default:\n        throw new Error(`Invalid storage type ${storageProvider}`);\n    }\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/upload.interface.ts",
    "content": "export interface IUploadProvider {\n  uploadSimple(path: string): Promise<string>;\n  uploadFile(file: Express.Multer.File): Promise<any>;\n  removeFile(filePath: string): Promise<void>;\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/upload/upload.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { UploadFactory } from './upload.factory';\nimport { CustomFileValidationPipe } from '@gitroom/nestjs-libraries/upload/custom.upload.validation';\n\n@Global()\n@Module({\n  providers: [UploadFactory, CustomFileValidationPipe],\n  exports: [UploadFactory, CustomFileValidationPipe],\n})\nexport class UploadModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/user/org.from.request.ts",
    "content": "import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nexport const GetOrgFromRequest = createParamDecorator(\n  (data: unknown, ctx: ExecutionContext) => {\n    const request = ctx.switchToHttp().getRequest();\n    return request.org;\n  }\n);\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/user/track.enum.ts",
    "content": "export enum TrackEnum {\n  ViewContent = 0,\n  CompleteRegistration = 1,\n  InitiateCheckout = 2,\n  StartTrial = 3,\n  Purchase = 4,\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/user/user.agent.ts",
    "content": "import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nexport const UserAgent = createParamDecorator(\n  (data: unknown, ctx: ExecutionContext): string => {\n    const request = ctx.switchToHttp().getRequest();\n    return request.headers['user-agent'];\n  }\n);\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/user/user.from.request.ts",
    "content": "import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nexport const GetUserFromRequest = createParamDecorator(\n  (data: unknown, ctx: ExecutionContext) => {\n    const request = ctx.switchToHttp().getRequest();\n    return request.user;\n  }\n);\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/videos/images-slides/images.slides.ts",
    "content": "import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';\nimport {\n  ExposeVideoFunction,\n  URL,\n  Video,\n  VideoAbstract,\n} from '@gitroom/nestjs-libraries/videos/video.interface';\nimport { chunk } from 'lodash';\nimport Transloadit from 'transloadit';\nimport { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';\nimport { Readable } from 'stream';\nimport { parseBuffer } from 'music-metadata';\nimport { stringifySync } from 'subtitle';\n\nimport pLimit from 'p-limit';\nimport { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';\nimport { IsString } from 'class-validator';\nimport { JSONSchema } from 'class-validator-jsonschema';\nconst limit = pLimit(2);\n\nconst transloadit = new Transloadit({\n  authKey: process.env.TRANSLOADIT_AUTH || 'just empty text',\n  authSecret: process.env.TRANSLOADIT_SECRET || 'just empty text',\n});\n\nasync function getAudioDuration(buffer: Buffer): Promise<number> {\n  const metadata = await parseBuffer(buffer, 'audio/mpeg');\n  return metadata.format.duration || 0;\n}\n\nclass ImagesSlidesParams {\n  @JSONSchema({\n    description: 'Elevenlabs voice id, use a special tool to get it, this is a required filed',\n  })\n  @IsString()\n  voice: string;\n\n  @JSONSchema({\n    description: 'Simple string of the prompt, not a json',\n  })\n  @IsString()\n  prompt: string;\n}\n\n@Video({\n  identifier: 'image-text-slides',\n  title: 'Image Text Slides',\n  description: 'Generate videos slides from images and text, Don\\'t break down the slides, provide only the first slide information',\n  placement: 'text-to-image',\n  tools: [{ functionName: 'loadVoices', output: 'voice id' }],\n  dto: ImagesSlidesParams,\n  trial: true,\n  available:\n    !!process.env.ELEVENSLABS_API_KEY &&\n    !!process.env.TRANSLOADIT_AUTH &&\n    !!process.env.TRANSLOADIT_SECRET &&\n    !!process.env.OPENAI_API_KEY &&\n    !!process.env.FAL_KEY,\n})\nexport class ImagesSlides extends VideoAbstract<ImagesSlidesParams> {\n  override dto = ImagesSlidesParams;\n  private storage = UploadFactory.createStorage();\n  constructor(\n    private _openaiService: OpenaiService,\n    private _falService: FalService\n  ) {\n    super();\n  }\n\n  async process(\n    output: 'vertical' | 'horizontal',\n    customParams: ImagesSlidesParams\n  ): Promise<URL> {\n    const list = await this._openaiService.generateSlidesFromText(\n      customParams.prompt\n    );\n\n    const generated = await Promise.all(\n      list.reduce((all, current) => {\n        all.push(\n          new Promise(async (res) => {\n            res({\n              len: 0,\n              url: await this._falService.generateImageFromText(\n                'ideogram/v2',\n                current.imagePrompt,\n                output === 'vertical'\n              ),\n            });\n          })\n        );\n\n        all.push(\n          new Promise(async (res) => {\n            const buffer = Buffer.from(\n              await (\n                await limit(() =>\n                  fetch(\n                    `https://api.elevenlabs.io/v1/text-to-speech/${customParams.voice}?output_format=mp3_44100_128`,\n                    {\n                      method: 'POST',\n                      headers: {\n                        'Content-Type': 'application/json',\n                        'xi-api-key': process.env.ELEVENSLABS_API_KEY || '',\n                      },\n                      body: JSON.stringify({\n                        text: current.voiceText,\n                        model_id: 'eleven_multilingual_v2',\n                      }),\n                    }\n                  )\n                )\n              ).arrayBuffer()\n            );\n\n            const { path } = await this.storage.uploadFile({\n              buffer,\n              mimetype: 'audio/mp3',\n              size: buffer.length,\n              path: '',\n              fieldname: '',\n              destination: '',\n              stream: new Readable(),\n              filename: '',\n              originalname: '',\n              encoding: '',\n            });\n\n            res({\n              len: await getAudioDuration(buffer),\n              url:\n                path.indexOf('http') === -1\n                  ? process.env.FRONTEND_URL +\n                    '/' +\n                    process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +\n                    path\n                  : path,\n            });\n          })\n        );\n\n        return all;\n      }, [] as Promise<any>[])\n    );\n\n    const split = chunk(generated, 2);\n\n    const srt = stringifySync(\n      list\n        .reduce((all, current, index) => {\n          const start = all.length ? all[all.length - 1].end : 0;\n          const end = start + split[index][1].len * 1000 + 1000;\n          all.push({\n            start: start,\n            end: end,\n            text: current.voiceText,\n          });\n\n          return all;\n        }, [] as { start: number; end: number; text: string }[])\n        .map((item) => ({\n          type: 'cue',\n          data: item,\n        })),\n      { format: 'SRT' }\n    );\n\n    console.log(split);\n\n    const { results } = await transloadit.createAssembly({\n      uploads: {\n        'subtitles.srt': srt,\n      },\n      waitForCompletion: true,\n      params: {\n        steps: {\n          ...split.reduce((all, current, index) => {\n            all[`image${index}`] = {\n              robot: '/http/import',\n              url: current[0].url,\n            };\n            all[`audio${index}`] = {\n              robot: '/http/import',\n              url: current[1].url,\n            };\n            all[`merge${index}`] = {\n              use: [\n                {\n                  name: `image${index}`,\n                  as: 'image',\n                },\n                {\n                  name: `audio${index}`,\n                  as: 'audio',\n                },\n              ],\n              robot: '/video/merge',\n              duration: current[1].len + 1,\n              audio_delay: 0.5,\n              preset: 'hls-1080p',\n              resize_strategy: 'min_fit',\n              loop: true,\n            };\n            return all;\n          }, {} as any),\n          concatenated: {\n            robot: '/video/concat',\n            result: false,\n            video_fade_seconds: 0.5,\n            use: split.map((p, index) => ({\n              name: `merge${index}`,\n              as: `video_${index + 1}`,\n            })),\n          },\n          subtitled: {\n            robot: '/video/subtitle',\n            result: true,\n            preset: 'hls-1080p',\n            use: {\n              bundle_steps: true,\n              steps: [\n                {\n                  name: 'concatenated',\n                  as: 'video',\n                },\n                {\n                  name: ':original',\n                  as: 'subtitles',\n                },\n              ],\n            },\n            position: 'center',\n            font_size: 8,\n            subtitles_type: 'burned',\n          },\n        },\n      },\n    });\n\n    return results.subtitled[0].url;\n  }\n\n  @ExposeVideoFunction()\n  async loadVoices(data: any) {\n    const { voices } = await (\n      await fetch(\n        'https://api.elevenlabs.io/v2/voices?page_size=40&category=premade',\n        {\n          method: 'GET',\n          headers: {\n            'Content-Type': 'application/json',\n            'xi-api-key': process.env.ELEVENSLABS_API_KEY || '',\n          },\n        }\n      )\n    ).json();\n\n    return {\n      voices: voices.map((voice: any) => ({\n        id: voice.voice_id,\n        name: voice.name,\n        preview_url: voice.preview_url,\n      })),\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/videos/veo3/veo3.ts",
    "content": "import {\n  URL,\n  Video,\n  VideoAbstract,\n} from '@gitroom/nestjs-libraries/videos/video.interface';\nimport { timer } from '@gitroom/helpers/utils/timer';\nimport { ArrayMaxSize, IsArray, IsString, ValidateNested } from 'class-validator';\nimport { Type } from 'class-transformer';\n\nclass Image {\n  @IsString()\n  id: string;\n\n  @IsString()\n  path: string;\n}\nclass Veo3Params {\n  @IsString()\n  prompt: string;\n\n  @Type(() => Image)\n  @ValidateNested({ each: true })\n  @IsArray()\n  @ArrayMaxSize(3)\n  images: Image[];\n}\n\n@Video({\n  identifier: 'veo3',\n  title: 'Veo3 (Audio + Video)',\n  description: 'Generate videos with the most advanced video model.',\n  placement: 'text-to-image',\n  dto: Veo3Params,\n  tools: [],\n  trial: false,\n  available: !!process.env.KIEAI_API_KEY,\n})\nexport class Veo3 extends VideoAbstract<Veo3Params> {\n  override dto = Veo3Params;\n  async process(\n    output: 'vertical' | 'horizontal',\n    customParams: Veo3Params\n  ): Promise<URL> {\n    const value = await (\n      await fetch('https://api.kie.ai/api/v1/veo/generate', {\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: `Bearer ${process.env.KIEAI_API_KEY}`,\n        },\n        method: 'POST',\n        body: JSON.stringify({\n          prompt: customParams.prompt,\n          imageUrls: customParams?.images?.map((p) => p.path) || [],\n          model: 'veo3_fast',\n          aspectRatio: output === 'horizontal' ? '16:9' : '9:16',\n        }),\n      })\n    ).json();\n\n    if (value.code !== 200 && value.code !== 201) {\n      throw new Error(`Failed to generate video`);\n    }\n\n    const taskId = value.data.taskId;\n    let videoUrl = [];\n    while (videoUrl.length === 0) {\n      console.log('waiting for video to be ready');\n      const data = await (\n        await fetch(\n          'https://api.kie.ai/api/v1/veo/record-info?taskId=' + taskId,\n          {\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: `Bearer ${process.env.KIEAI_API_KEY}`,\n            },\n          }\n        )\n      ).json();\n\n      if (data.code !== 200 && data.code !== 400) {\n        throw new Error(`Failed to get video info`);\n      }\n\n      videoUrl = data?.data?.response?.resultUrls || [];\n      await timer(10000);\n    }\n\n    return videoUrl[0];\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/videos/video.interface.ts",
    "content": "import { Injectable, Type, ValidationPipe } from '@nestjs/common';\n\nexport type URL = string;\n\nexport abstract class VideoAbstract<T> {\n  dto: Type<T>;\n\n  async processAndValidate(customParams?: T) {\n    const validationPipe = new ValidationPipe({\n      skipMissingProperties: false,\n      transform: true,\n      transformOptions: {\n        enableImplicitConversion: true,\n      },\n    });\n\n    await validationPipe.transform(customParams, {\n      type: 'body',\n      metatype: this.dto,\n    });\n  }\n\n  abstract process(\n    output: 'vertical' | 'horizontal',\n    customParams?: T\n  ): Promise<URL>;\n}\n\nexport interface VideoParams {\n  identifier: string;\n  title: string;\n  description: string;\n  dto: any;\n  placement: 'text-to-image' | 'image-to-video' | 'video-to-video';\n  tools: { functionName: string; output: string }[];\n  available: boolean;\n  trial: boolean;\n}\n\nexport function ExposeVideoFunction(description?: string) {\n  return function (\n    target: any,\n    propertyKey: string,\n    descriptor: PropertyDescriptor\n  ) {\n    Reflect.defineMetadata(\n      'video-function',\n      description || 'true',\n      descriptor.value\n    );\n  };\n}\n\nexport function Video(params: VideoParams) {\n  return function (target: any) {\n    // Apply @Injectable decorator to the target class\n    Injectable()(target);\n\n    // Retrieve existing metadata or initialize an empty array\n    const existingMetadata = Reflect.getMetadata('video', VideoAbstract) || [];\n\n    // Add the metadata information for this method\n    existingMetadata.push({ target, ...params });\n\n    // Define metadata on the class prototype (so it can be retrieved from the class)\n    Reflect.defineMetadata('video', existingMetadata, VideoAbstract);\n  };\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/videos/video.manager.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { ModuleRef } from '@nestjs/core';\n\nimport {\n  VideoAbstract,\n  VideoParams,\n} from '@gitroom/nestjs-libraries/videos/video.interface';\n\n@Injectable()\nexport class VideoManager {\n  constructor(private _moduleRef: ModuleRef) {}\n\n  getAllVideos(): {\n    identifier: string;\n    title: string;\n    dto: any;\n    description: string;\n    target: VideoAbstract<any>,\n    tools: { functionName: string; output: string }[];\n    placement: string;\n    trial: boolean;\n  }[] {\n    return (Reflect.getMetadata('video', VideoAbstract) || [])\n      .filter((f: any) => f.available)\n      .map((p: any) => ({\n        target: p.target,\n        identifier: p.identifier,\n        title: p.title,\n        tools: p.tools,\n        dto: p.dto,\n        description: p.description,\n        placement: p.placement,\n        trial: p.trial,\n      }));\n  }\n\n  checkAvailableVideoFunction(method: any) {\n    const videoFunction = Reflect.getMetadata('video-function', method);\n    return !videoFunction;\n  }\n\n  getVideoByName(\n    identifier: string\n  ): (VideoParams & { instance: VideoAbstract<any> }) | undefined {\n    const video = (Reflect.getMetadata('video', VideoAbstract) || []).find(\n      (p: any) => p.identifier === identifier\n    );\n\n    return {\n      ...video,\n      instance: this._moduleRef.get(video.target, {\n        strict: false,\n      }),\n    };\n  }\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/src/videos/video.module.ts",
    "content": "import { Global, Module } from '@nestjs/common';\nimport { ImagesSlides } from '@gitroom/nestjs-libraries/videos/images-slides/images.slides';\nimport { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';\nimport { Veo3 } from '@gitroom/nestjs-libraries/videos/veo3/veo3';\n\n@Global()\n@Module({\n  providers: [ImagesSlides, Veo3, VideoManager],\n  get exports() {\n    return this.providers;\n  },\n})\nexport class VideoModule {}\n"
  },
  {
    "path": "libraries/nestjs-libraries/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.lib.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "libraries/nestjs-libraries/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"declaration\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"src/**/*.spec.ts\", \"src/**/*.test.ts\"]\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/.eslintrc.json",
    "content": "{\n  \"extends\": [\"../../.eslintrc.json\"],\n  \"ignorePatterns\": [\"!**/*\"],\n  \"overrides\": [\n    {\n      \"files\": [\"*.ts\", \"*.tsx\", \"*.js\", \"*.jsx\"],\n      \"rules\": {}\n    },\n    {\n      \"files\": [\"*.ts\", \"*.tsx\"],\n      \"rules\": {}\n    },\n    {\n      \"files\": [\"*.js\", \"*.jsx\"],\n      \"rules\": {}\n    }\n  ]\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/README.md",
    "content": "# nestjs-libraries\n\nThis library was generated with [Nx](https://nx.dev).\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/button.tsx",
    "content": "'use client';\n\nimport {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  FC,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { clsx } from 'clsx';\nimport ReactLoading from 'react-loading';\nexport const Button: FC<\n  DetailedHTMLProps<\n    ButtonHTMLAttributes<HTMLButtonElement>,\n    HTMLButtonElement\n  > & {\n    secondary?: boolean;\n    loading?: boolean;\n    innerClassName?: string;\n  }\n> = ({ children, loading, innerClassName, ...props }) => {\n  const ref = useRef<HTMLButtonElement | null>(null);\n  const [height, setHeight] = useState<number | null>(null);\n  useEffect(() => {\n    setHeight(ref.current?.offsetHeight || 40);\n  }, []);\n  return (\n    <button\n      {...props}\n      type={props.type || 'button'}\n      ref={ref}\n      className={clsx(\n        (props.disabled || loading) && 'opacity-50 pointer-events-none',\n        `${\n          props.secondary ? 'bg-third' : 'bg-forth text-white'\n        } px-[24px] h-[40px] cursor-pointer items-center justify-center flex relative`,\n        props?.className\n      )}\n    >\n      {loading && (\n        <div className=\"absolute inset-0 flex items-center justify-center\">\n          <ReactLoading\n            type=\"spin\"\n            color=\"#fff\"\n            width={height! / 2}\n            height={height! / 2}\n          />\n        </div>\n      )}\n      <div\n        className={clsx(\n          innerClassName,\n          'flex-1 items-center justify-center flex',\n          loading && 'invisible'\n        )}\n      >\n        {children}\n      </div>\n    </button>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/canonical.tsx",
    "content": "'use client';\n\nimport React, {\n  DetailedHTMLProps,\n  FC,\n  InputHTMLAttributes,\n  useCallback,\n  useMemo,\n} from 'react';\nimport { clsx } from 'clsx';\nimport { useFormContext } from 'react-hook-form';\nimport dayjs from 'dayjs';\nimport { useShowPostSelector } from '../../../../apps/frontend/src/components/post-url-selector/post.url.selector';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const Canonical: FC<\n  DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {\n    error?: any;\n    date: dayjs.Dayjs;\n    disableForm?: boolean;\n    label: string;\n    name: string;\n    translationKey?: string;\n    translationParams?: Record<string, string | number>;\n  }\n> = (props) => {\n  const {\n    label,\n    date,\n    className,\n    disableForm,\n    error,\n    translationKey,\n    translationParams,\n    ...rest\n  } = props;\n  const form = useFormContext();\n  const err = useMemo(() => {\n    if (error) return error;\n    if (!form || !form.formState.errors[props?.name!]) return;\n    return form?.formState?.errors?.[props?.name!]?.message! as string;\n  }, [form?.formState?.errors?.[props?.name!]?.message, error]);\n  const postSelector = useShowPostSelector(date);\n  const onPostSelector = useCallback(async () => {\n    const id = await postSelector();\n    if (disableForm) {\n      // @ts-ignore\n      return rest.onChange({\n        // @ts-ignore\n        target: {\n          value: id,\n          name: props.name,\n        },\n      });\n    }\n    return form.setValue(props.name, id);\n  }, [form]);\n  return (\n    <div className=\"flex flex-col gap-[6px]\">\n      <div className=\"flex items-center gap-[3px]\">\n        <div className={`text-[14px]`}>\n          <TranslatedLabel\n            label={label}\n            translationKey={translationKey}\n            translationParams={translationParams}\n          />\n        </div>\n        <div>\n          <svg\n            onClick={onPostSelector}\n            className=\"cursor-pointer\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"13\"\n            height=\"13\"\n            viewBox=\"0 0 32 32\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M27.4602 14.6576C27.4039 14.4173 27.2893 14.1947 27.1264 14.0093C26.9635 13.824 26.7575 13.6817 26.5264 13.5951L19.7214 11.0438L21.4714 2.2938C21.5354 1.97378 21.4933 1.64163 21.3514 1.34773C21.2095 1.05382 20.9757 0.814201 20.6854 0.665207C20.395 0.516214 20.064 0.465978 19.7425 0.52212C19.421 0.578262 19.1266 0.737718 18.9039 0.976302L4.90393 15.9763C4.73549 16.1566 4.61413 16.3756 4.55059 16.614C4.48705 16.8525 4.4833 17.1028 4.53968 17.343C4.59605 17.5832 4.7108 17.8058 4.87377 17.9911C5.03673 18.1763 5.24287 18.3185 5.47393 18.4051L12.2789 20.9563L10.5289 29.7063C10.465 30.0263 10.5071 30.3585 10.649 30.6524C10.7908 30.9463 11.0247 31.1859 11.315 31.3349C11.6054 31.4839 11.9364 31.5341 12.2579 31.478C12.5794 31.4218 12.8738 31.2624 13.0964 31.0238L27.0964 16.0238C27.2647 15.8435 27.3859 15.6245 27.4494 15.3862C27.5128 15.1479 27.5165 14.8976 27.4602 14.6576ZM14.5064 25.1163L15.4714 20.2938C15.5412 19.9446 15.4845 19.5819 15.3113 19.2706C15.1382 18.9594 14.86 18.7199 14.5264 18.5951L8.62518 16.3838L17.4914 6.8838L16.5264 11.7063C16.4566 12.0555 16.5134 12.4182 16.6865 12.7295C16.8597 13.0407 17.1379 13.2802 17.4714 13.4051L23.3752 15.6163L14.5064 25.1163Z\"\n              fill=\"#fff\"\n            />\n          </svg>\n        </div>\n      </div>\n      <input\n        {...(disableForm ? {} : form.register(props.name))}\n        className={clsx(\n          'bg-input h-[44px] px-[16px] outline-none border-fifth border rounded-[4px] text-inputText placeholder-inputText',\n          className\n        )}\n        {...rest}\n      />\n      <div className=\"text-red-400 text-[12px]\">{err || <>&nbsp;</>}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/checkbox.tsx",
    "content": "'use client';\n\nimport { FC, forwardRef, useCallback, useState } from 'react';\nimport clsx from 'clsx';\nimport { useFormContext, useWatch } from 'react-hook-form';\nexport const Checkbox = forwardRef<\n  null,\n  {\n    checked?: boolean;\n    disableForm?: boolean;\n    name?: string;\n    className?: string;\n    label?: string;\n    onChange?: (event: {\n      target: {\n        name?: string;\n        value: boolean;\n      };\n    }) => void;\n    variant?: 'default' | 'hollow';\n  }\n>((props, ref: any) => {\n  const { checked, className, label, disableForm, variant } = props;\n  const form = useFormContext();\n  const register = disableForm ? {} : form.register(props.name!);\n  const watch = disableForm ? false : form.watch(props.name!);\n  const val = watch || checked;\n\n  const changeStatus = useCallback(() => {\n    props?.onChange?.({\n      target: {\n        name: props.name!,\n        value: !val,\n      },\n    });\n    if (!disableForm) {\n      // @ts-ignore\n      register?.onChange?.({\n        target: {\n          name: props.name!,\n          value: !val,\n        },\n      });\n    }\n  }, [val]);\n  return (\n    <div className=\"flex gap-[10px]\">\n      <div\n        ref={ref}\n        {...disableForm ? {} : form.register(props.name!)}\n        onClick={changeStatus}\n        className={clsx(\n          'cursor-pointer rounded-[4px] select-none w-[24px] h-[24px] justify-center items-center flex text-white',\n          variant === 'default' || !variant\n            ? 'bg-forth'\n            : 'border-customColor1 border-2 bg-customColor2',\n          className\n        )}\n      >\n        {val && (\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              viewBox=\"0 0 24 24\"\n              width=\"20\"\n              height=\"20\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <polyline points=\"20 6 9 17 4 12\"></polyline>\n            </svg>\n          </div>\n        )}\n      </div>\n      {!!label && <div>{label}</div>}\n    </div>\n  );\n});\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/color.picker.tsx",
    "content": "import { FC, useCallback, useState } from 'react';\nimport { HexColorPicker } from 'react-colorful';\nimport { useFormContext } from 'react-hook-form';\nimport { Button } from './button';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const ColorPicker: FC<{\n  name: string;\n  label: string;\n  enabled: boolean;\n  onChange?: (params: {\n    target: {\n      name: string;\n      value: string;\n    };\n  }) => void;\n  value?: string;\n  canBeCancelled: boolean;\n  translationKey?: string;\n  translationParams?: Record<string, string | number>;\n}> = (props) => {\n  const {\n    name,\n    label,\n    enabled,\n    value,\n    canBeCancelled,\n    onChange,\n    translationKey,\n    translationParams,\n  } = props;\n  const form = useFormContext();\n  const color = onChange\n    ? {\n        onChange,\n      }\n    : form.register(name);\n  const watch = onChange ? value : form.watch(name);\n  const [enabledState, setEnabledState] = useState(!!watch);\n  const enable = useCallback(async () => {\n    await color.onChange({\n      target: {\n        name,\n        value: '#FFFFFF',\n      },\n    });\n    setEnabledState(true);\n  }, []);\n  const cancel = useCallback(async () => {\n    await color.onChange({\n      target: {\n        name,\n        value: '',\n      },\n    });\n    setEnabledState(false);\n  }, []);\n\n  const t = useT();\n\n  if (!enabledState) {\n    return (\n      <div>\n        <Button onClick={enable}>\n          {t('enable_color_picker', 'Enable color picker')}\n        </Button>\n      </div>\n    );\n  }\n  return (\n    <div className=\"flex flex-col gap-[6px]\">\n      <div>\n        {!!label && (\n          <div className={`text-[14px]`}>\n            <TranslatedLabel\n              label={label}\n              translationKey={translationKey}\n              translationParams={translationParams}\n            />\n          </div>\n        )}\n      </div>\n      {canBeCancelled && (\n        <div>\n          <Button onClick={cancel}>\n            {t('cancel_the_color_picker', 'Cancel the color picker')}\n          </Button>\n        </div>\n      )}\n      <div className=\"flex items-end gap-[20px]\">\n        <div>\n          <HexColorPicker\n            color={watch}\n            onChange={(value) =>\n              color.onChange({\n                target: {\n                  name,\n                  value,\n                },\n              })\n            }\n          />\n        </div>\n        <div className=\"flex gap-[10px]\">\n          <div>\n            <div\n              className=\"w-[20px] h-[20px]\"\n              style={{\n                backgroundColor: watch,\n              }}\n            />\n          </div>\n          <div>{watch}</div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/custom.select.tsx",
    "content": "import {\n  FC,\n  ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { clsx } from 'clsx';\nimport { useFormContext } from 'react-hook-form';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const CustomSelect: FC<{\n  error?: any;\n  disableForm?: boolean;\n  label: string;\n  name: string;\n  placeholder?: string;\n  removeError?: boolean;\n  onChange?: () => void;\n  className?: string;\n  translationKey?: string;\n  translationParams?: Record<string, string | number>;\n  options: Array<{\n    value: string;\n    label: string;\n    icon?: ReactNode;\n  }>;\n}> = (props) => {\n  const {\n    options,\n    onChange,\n    placeholder,\n    className,\n    removeError,\n    label,\n    translationKey,\n    translationParams,\n    ...rest\n  } = props;\n  const form = useFormContext();\n  const value = form.watch(props.name);\n  const [isOpen, setIsOpen] = useState(false);\n  const err = useMemo(() => {\n    const split = (props.name + '.value').split('.');\n    let errIn = form?.formState?.errors;\n    for (let i = 0; i < split.length; i++) {\n      // @ts-ignore\n      errIn = errIn?.[split[i]];\n    }\n    return errIn?.message;\n  }, [props.name, form]);\n  const option = useMemo(() => {\n    if (value?.value && options.length) {\n      return (\n        options.find((option) => option.value === value.value) || {\n          label: placeholder,\n          icon: false,\n        }\n      );\n    }\n    return {\n      label: placeholder,\n    };\n  }, [value, options]);\n  const changeOpen = useCallback(() => {\n    setIsOpen(!isOpen);\n  }, [isOpen]);\n  const setOption = useCallback(\n    (newOption: any) => (e: any) => {\n      form.setValue(props.name, newOption);\n      setIsOpen(false);\n      e.stopPropagation();\n    },\n    []\n  );\n  useEffect(() => {\n    if (onChange) {\n      onChange();\n    }\n  }, [value]);\n  return (\n    <div className={clsx('flex flex-col gap-[6px] relative', className)}>\n      {!!label && (\n        <div className={`text-[14px]`}>\n          <TranslatedLabel\n            label={label}\n            translationKey={translationKey}\n            translationParams={translationParams}\n          />\n        </div>\n      )}\n      <div\n        className={clsx(\n          'bg-input h-[44px] border-fifth border rounded-[4px] text-inputText placeholder-inputText items-center justify-center flex'\n        )}\n        onClick={changeOpen}\n      >\n        <div className=\"flex-1 ps-[16px] text-[14px] select-none flex gap-[8px]\">\n          {!!option.icon && (\n            <div className=\"flex justify-center items-center\">\n              {option.icon}\n            </div>\n          )}\n\n          {option.label}\n        </div>\n        <div className=\"pe-[16px] flex gap-[8px]\">\n          <div>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M13.354 6.35378L8.35403 11.3538C8.30759 11.4003 8.25245 11.4372 8.19175 11.4623C8.13105 11.4875 8.06599 11.5004 8.00028 11.5004C7.93457 11.5004 7.86951 11.4875 7.80881 11.4623C7.74811 11.4372 7.69296 11.4003 7.64653 11.3538L2.64653 6.35378C2.55271 6.25996 2.5 6.13272 2.5 6.00003C2.5 5.86735 2.55271 5.7401 2.64653 5.64628C2.74035 5.55246 2.8676 5.49976 3.00028 5.49976C3.13296 5.49976 3.26021 5.55246 3.35403 5.64628L8.00028 10.2932L12.6465 5.64628C12.693 5.59983 12.7481 5.56298 12.8088 5.53784C12.8695 5.5127 12.9346 5.49976 13.0003 5.49976C13.066 5.49976 13.131 5.5127 13.1917 5.53784C13.2524 5.56298 13.3076 5.59983 13.354 5.64628C13.4005 5.69274 13.4373 5.74789 13.4625 5.80859C13.4876 5.86928 13.5006 5.93434 13.5006 6.00003C13.5006 6.06573 13.4876 6.13079 13.4625 6.19148C13.4373 6.25218 13.4005 6.30733 13.354 6.35378Z\"\n                fill=\"#64748B\"\n              />\n            </svg>\n          </div>\n          {!!value && (\n            <div onClick={setOption(undefined)}>\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"16\"\n                height=\"16\"\n                viewBox=\"0 0 16 16\"\n                fill=\"none\"\n              >\n                <path\n                  d=\"M12.854 12.1463C12.9005 12.1927 12.9373 12.2479 12.9625 12.3086C12.9876 12.3693 13.0006 12.4343 13.0006 12.5C13.0006 12.5657 12.9876 12.6308 12.9625 12.6915C12.9373 12.7522 12.9005 12.8073 12.854 12.8538C12.8076 12.9002 12.7524 12.9371 12.6917 12.9622C12.631 12.9874 12.566 13.0003 12.5003 13.0003C12.4346 13.0003 12.3695 12.9874 12.3088 12.9622C12.2481 12.9371 12.193 12.9002 12.1465 12.8538L8.00028 8.70691L3.85403 12.8538C3.76021 12.9476 3.63296 13.0003 3.50028 13.0003C3.3676 13.0003 3.24035 12.9476 3.14653 12.8538C3.05271 12.76 3 12.6327 3 12.5C3 12.3674 3.05271 12.2401 3.14653 12.1463L7.2934 8.00003L3.14653 3.85378C3.05271 3.75996 3 3.63272 3 3.50003C3 3.36735 3.05271 3.2401 3.14653 3.14628C3.24035 3.05246 3.3676 2.99976 3.50028 2.99976C3.63296 2.99976 3.76021 3.05246 3.85403 3.14628L8.00028 7.29316L12.1465 3.14628C12.2403 3.05246 12.3676 2.99976 12.5003 2.99976C12.633 2.99976 12.7602 3.05246 12.854 3.14628C12.9478 3.2401 13.0006 3.36735 13.0006 3.50003C13.0006 3.63272 12.9478 3.75996 12.854 3.85378L8.70715 8.00003L12.854 12.1463Z\"\n                  fill=\"#64748B\"\n                />\n              </svg>\n            </div>\n          )}\n        </div>\n      </div>\n      {isOpen && (\n        <div\n          className={clsx(\n            label && !removeError && '-mt-[23px]',\n            'z-[100] absolute w-full top-[100%] start-0 flex items-center rounded-bl-[4px] rounded-br-[4px] flex-col bg-fifth gap-[1px] border-l border-r border-b border-fifth overflow-hidden'\n          )}\n        >\n          {options.map((option) => (\n            <div\n              key={option.value}\n              onClick={setOption(option)}\n              className=\"px-[16px] py-[8px] bg-input w-full flex gap-[8px] hover:bg-customColor3 select-none cursor-pointer\"\n            >\n              {!!option.icon && (\n                <div className=\"flex justify-center items-center\">\n                  {option.icon}\n                </div>\n              )}\n              <div className=\"flex-1 text-[14px]\">{option.label}</div>\n            </div>\n          ))}\n        </div>\n      )}\n      {!removeError && (\n        <div className=\"text-red-400 text-[12px]\">\n          {(err as any) || <>&nbsp;</>}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/input.tsx",
    "content": "'use client';\n\nimport {\n  DetailedHTMLProps,\n  FC,\n  InputHTMLAttributes,\n  ReactNode,\n  useEffect,\n  useMemo,\n} from 'react';\nimport { clsx } from 'clsx';\nimport { useFormContext, useWatch } from 'react-hook-form';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const Input: FC<\n  DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {\n    removeError?: boolean;\n    error?: any;\n    disableForm?: boolean;\n    customUpdate?: () => void;\n    label: string;\n    name: string;\n    icon?: ReactNode;\n    translationKey?: string;\n    translationParams?: Record<string, string | number>;\n  }\n> = (props) => {\n  const {\n    label,\n    icon,\n    removeError,\n    customUpdate,\n    className,\n    disableForm,\n    error,\n    translationKey,\n    translationParams,\n    ...rest\n  } = props;\n  const form = useFormContext();\n  const err = useMemo(() => {\n    if (error) return error;\n    if (!form || !form.formState.errors[props?.name!]) return;\n    return form?.formState?.errors?.[props?.name!]?.message! as string;\n  }, [form?.formState?.errors?.[props?.name!]?.message, error]);\n  const watch = customUpdate ? form?.watch(props.name) : null;\n  useEffect(() => {\n    if (customUpdate) {\n      customUpdate();\n    }\n  }, [watch]);\n  return (\n    <div className=\"flex flex-col gap-[6px]\">\n      {!!label && (\n        <div className={`text-[14px]`}>\n          <TranslatedLabel\n            label={label}\n            translationKey={translationKey}\n            translationParams={translationParams}\n          />\n        </div>\n      )}\n      <div\n        className={clsx(\n          'bg-newBgColorInner h-[42px] border-newTableBorder border rounded-[8px] text-textColor placeholder-textColor flex items-center justify-center',\n          className\n        )}\n      >\n        {icon && <div className=\"ps-[16px]\">{icon}</div>}\n        <input\n          className={clsx(\n            'h-full bg-transparent outline-none flex-1 text-[14px] text-textColor',\n            icon ? 'pl-[8px] pe-[16px]' : 'px-[16px]'\n          )}\n          {...(disableForm ? {} : form.register(props.name))}\n          {...rest}\n        />\n      </div>\n      {!removeError && (\n        <div className=\"text-red-400 text-[12px]\">{err || <>&nbsp;</>}</div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/select.tsx",
    "content": "'use client';\n\nimport {\n  DetailedHTMLProps,\n  FC,\n  forwardRef,\n  SelectHTMLAttributes,\n  useMemo,\n} from 'react';\nimport { clsx } from 'clsx';\nimport { useFormContext } from 'react-hook-form';\nimport { RegisterOptions } from 'react-hook-form/dist/types/validator';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const Select: FC<\n  DetailedHTMLProps<\n    SelectHTMLAttributes<HTMLSelectElement>,\n    HTMLSelectElement\n  > & {\n    error?: any;\n    extraForm?: RegisterOptions<any>;\n    disableForm?: boolean;\n    label: string;\n    name: string;\n    hideErrors?: boolean;\n    translationKey?: string;\n    translationParams?: Record<string, string | number>;\n  }\n> = forwardRef((props, ref) => {\n  const {\n    label,\n    className,\n    hideErrors,\n    disableForm,\n    error,\n    extraForm,\n    translationKey,\n    translationParams,\n    ...rest\n  } = props;\n  const form = useFormContext();\n  const err = useMemo(() => {\n    if (error) return error;\n    if (!form || !form.formState.errors[props?.name!]) return;\n    return form?.formState?.errors?.[props?.name!]?.message! as string;\n  }, [form?.formState?.errors?.[props?.name!]?.message, error]);\n  return (\n    <div className={clsx('flex flex-col', label ? 'gap-[6px]' : '')}>\n      <div className={`text-[14px]`}>\n        <TranslatedLabel\n          label={label}\n          translationKey={translationKey}\n          translationParams={translationParams}\n        />\n      </div>\n      <select\n        ref={ref}\n        {...(disableForm ? {} : form.register(props.name, extraForm))}\n        className={clsx(\n          'h-[42px] bg-newBgColorInner px-[16px] outline-none border-newTableBorder border rounded-[8px] text-[14px]',\n          className\n        )}\n        {...rest}\n      />\n      {!hideErrors && (\n        <div className=\"text-red-400 text-[12px]\">{err || <>&nbsp;</>}</div>\n      )}\n    </div>\n  );\n});\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/slider.tsx",
    "content": "'use client';\n\nimport { FC, useCallback } from 'react';\nimport clsx from 'clsx';\nexport const Slider: FC<{\n  value: 'on' | 'off';\n  fill?: boolean;\n  onChange: (value: 'on' | 'off') => void;\n}> = (props) => {\n  const { value, onChange, fill } = props;\n  const change = useCallback(() => {\n    onChange(value === 'on' ? 'off' : 'on');\n  }, [value]);\n  return (\n    <div\n      className={clsx(\n        'w-[57px] h-[34px] p-[4px] border-fifth border rounded-[100px]',\n        value === 'on' && fill && 'bg-customColor4'\n      )}\n      onClick={change}\n    >\n      <div className=\"w-full h-full relative rounded-[100px]\">\n        <div\n          className={clsx(\n            'absolute left-0 top-0 w-[24px] h-[24px] bg-customColor5 rounded-full transition-all cursor-pointer',\n            value === 'on' ? 'left-[100%] -translate-x-[100%]' : 'left-0'\n          )}\n        />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/textarea.tsx",
    "content": "'use client';\n\nimport { DetailedHTMLProps, FC, InputHTMLAttributes, useMemo } from 'react';\nimport clsx from 'clsx';\nimport { useFormContext } from 'react-hook-form';\nimport { TranslatedLabel } from '../translation/translated-label';\n\nexport const Textarea: FC<\n  DetailedHTMLProps<\n    InputHTMLAttributes<HTMLTextAreaElement>,\n    HTMLTextAreaElement\n  > & {\n    error?: any;\n    disableForm?: boolean;\n    label: string;\n    name: string;\n    translationKey?: string;\n    translationParams?: Record<string, string | number>;\n  }\n> = (props) => {\n  const {\n    label,\n    className,\n    disableForm,\n    error,\n    translationKey,\n    translationParams,\n    ...rest\n  } = props;\n  const form = useFormContext();\n  const err = useMemo(() => {\n    if (error) return error;\n    if (!form || !form.formState.errors[props?.name!]) return;\n    return form?.formState?.errors?.[props?.name!]?.message! as string;\n  }, [form?.formState?.errors?.[props?.name!]?.message, error]);\n  return (\n    <div\n      className={clsx(\n        'flex flex-col gap-[6px]',\n        props.disabled && 'opacity-50'\n      )}\n    >\n      <div className={`text-[14px]`}>\n        <TranslatedLabel\n          label={label}\n          translationKey={translationKey}\n          translationParams={translationParams}\n        />\n      </div>\n      <textarea\n        {...(disableForm ? {} : form.register(props.name))}\n        className={clsx(\n          'bg-input min-h-[150px] p-[16px] outline-none border-fifth border rounded-[4px] text-inputText placeholder-inputText',\n          className\n        )}\n        {...rest}\n      />\n      <div className=\"text-red-400 text-[12px]\">{err || <>&nbsp;</>}</div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/form/total.tsx",
    "content": "import { FC, useCallback, useEffect } from 'react';\nimport { clsx } from 'clsx';\nimport { useFormContext } from 'react-hook-form';\nimport { useT } from '@gitroom/react/translation/get.transation.service.client';\nexport const Total: FC<{\n  name: string;\n  customOnChange?: () => void;\n}> = (props) => {\n  const { name, customOnChange } = props;\n  const form = useFormContext();\n  const value = form.watch(props.name);\n  const changeNumber = useCallback(\n    (value: number) => () => {\n      if (value === 0) {\n        return;\n      }\n      form.setValue(name, value);\n    },\n    [value]\n  );\n  useEffect(() => {\n    if (customOnChange) {\n      customOnChange();\n    }\n  }, [value, customOnChange]);\n\n  const t = useT();\n\n  return (\n    <div className=\"flex flex-col gap-[6px] relative w-[158px]\">\n      <div className={`text-[14px]`}>{t('total', 'Total')}</div>\n      <div\n        className={clsx(\n          'bg-input h-[44px] border-fifth border rounded-[4px] text-inputText placeholder-inputText items-center justify-center flex'\n        )}\n      >\n        <div className=\"flex-1 px-[16px] text-[14px] select-none flex gap-[8px] items-center\">\n          <div onClick={changeNumber(value - 1)}>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M11 8C11 8.13261 10.9473 8.25979 10.8536 8.35355C10.7598 8.44732 10.6326 8.5 10.5 8.5H5.5C5.36739 8.5 5.24022 8.44732 5.14645 8.35355C5.05268 8.25979 5 8.13261 5 8C5 7.86739 5.05268 7.74021 5.14645 7.64645C5.24022 7.55268 5.36739 7.5 5.5 7.5H10.5C10.6326 7.5 10.7598 7.55268 10.8536 7.64645C10.9473 7.74021 11 7.86739 11 8ZM14.5 8C14.5 9.28558 14.1188 10.5423 13.4046 11.6112C12.6903 12.6801 11.6752 13.5132 10.4874 14.0052C9.29973 14.4972 7.99279 14.6259 6.73192 14.3751C5.47104 14.1243 4.31285 13.5052 3.40381 12.5962C2.49477 11.6872 1.8757 10.529 1.6249 9.26809C1.37409 8.00721 1.50282 6.70028 1.99479 5.51256C2.48676 4.32484 3.31988 3.30968 4.3888 2.59545C5.45772 1.88122 6.71442 1.5 8 1.5C9.72335 1.50182 11.3756 2.18722 12.5942 3.40582C13.8128 4.62441 14.4982 6.27665 14.5 8ZM13.5 8C13.5 6.9122 13.1774 5.84883 12.5731 4.94436C11.9687 4.03989 11.1098 3.33494 10.1048 2.91866C9.09977 2.50238 7.9939 2.39346 6.92701 2.60568C5.86011 2.8179 4.8801 3.34172 4.11092 4.11091C3.34173 4.8801 2.8179 5.86011 2.60568 6.927C2.39347 7.9939 2.50238 9.09977 2.91867 10.1048C3.33495 11.1098 4.0399 11.9687 4.94437 12.5731C5.84884 13.1774 6.91221 13.5 8 13.5C9.45819 13.4983 10.8562 12.9184 11.8873 11.8873C12.9184 10.8562 13.4983 9.45818 13.5 8Z\"\n                fill={value === 1 ? '#64748B' : 'white'}\n              />\n            </svg>\n          </div>\n          <div className=\"flex-1 text-white text-[14px] text-center\">\n            {value}\n          </div>\n          <div onClick={changeNumber(value + 1)}>\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n            >\n              <path\n                d=\"M8 1.5C6.71442 1.5 5.45772 1.88122 4.3888 2.59545C3.31988 3.30968 2.48676 4.32484 1.99479 5.51256C1.50282 6.70028 1.37409 8.00721 1.6249 9.26809C1.8757 10.529 2.49477 11.6872 3.40381 12.5962C4.31285 13.5052 5.47104 14.1243 6.73192 14.3751C7.99279 14.6259 9.29973 14.4972 10.4874 14.0052C11.6752 13.5132 12.6903 12.6801 13.4046 11.6112C14.1188 10.5423 14.5 9.28558 14.5 8C14.4982 6.27665 13.8128 4.62441 12.5942 3.40582C11.3756 2.18722 9.72335 1.50182 8 1.5ZM8 13.5C6.91221 13.5 5.84884 13.1774 4.94437 12.5731C4.0399 11.9687 3.33495 11.1098 2.91867 10.1048C2.50238 9.09977 2.39347 7.9939 2.60568 6.927C2.8179 5.86011 3.34173 4.8801 4.11092 4.11091C4.8801 3.34172 5.86011 2.8179 6.92701 2.60568C7.9939 2.39346 9.09977 2.50238 10.1048 2.91866C11.1098 3.33494 11.9687 4.03989 12.5731 4.94436C13.1774 5.84883 13.5 6.9122 13.5 8C13.4983 9.45818 12.9184 10.8562 11.8873 11.8873C10.8562 12.9184 9.45819 13.4983 8 13.5ZM11 8C11 8.13261 10.9473 8.25979 10.8536 8.35355C10.7598 8.44732 10.6326 8.5 10.5 8.5H8.5V10.5C8.5 10.6326 8.44732 10.7598 8.35356 10.8536C8.25979 10.9473 8.13261 11 8 11C7.86739 11 7.74022 10.9473 7.64645 10.8536C7.55268 10.7598 7.5 10.6326 7.5 10.5V8.5H5.5C5.36739 8.5 5.24022 8.44732 5.14645 8.35355C5.05268 8.25979 5 8.13261 5 8C5 7.86739 5.05268 7.74021 5.14645 7.64645C5.24022 7.55268 5.36739 7.5 5.5 7.5H7.5V5.5C7.5 5.36739 7.55268 5.24021 7.64645 5.14645C7.74022 5.05268 7.86739 5 8 5C8.13261 5 8.25979 5.05268 8.35356 5.14645C8.44732 5.24021 8.5 5.36739 8.5 5.5V7.5H10.5C10.6326 7.5 10.7598 7.55268 10.8536 7.64645C10.9473 7.74021 11 7.86739 11 8Z\"\n                fill=\"white\"\n              />\n            </svg>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/delete.dialog.tsx",
    "content": "import i18next from '@gitroom/react/translation/i18next';\nimport { areYouSure } from '@gitroom/frontend/components/layout/new-modal';\n\nexport const deleteDialog = async (\n  message: string,\n  confirmButton?: string,\n  title?: string,\n  cancelButton?: string\n) => {\n  return areYouSure({\n    title: title || i18next.t('are_you_sure', 'Are you sure?'),\n    description: message,\n    approveLabel:\n      confirmButton || i18next.t('yes_delete_it', 'Yes, delete it!'),\n    cancelLabel: cancelButton || i18next.t('no_cancel', 'No, cancel!'),\n  });\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/image.with.fallback.tsx",
    "content": "import { FC, useEffect, useState } from 'react';\nimport Image from 'next/image';\ninterface ImageSrc {\n  src: string;\n  fallbackSrc: string;\n  width: number;\n  height: number;\n  [key: string]: any;\n}\nconst ImageWithFallback: FC<ImageSrc> = (props) => {\n  const { src, fallbackSrc, ...rest } = props;\n  const [imgSrc, setImgSrc] = useState(src);\n  useEffect(() => {\n    if (src !== imgSrc) {\n      setImgSrc(src);\n    }\n  }, [src]);\n  return (\n    <Image\n      alt=\"\"\n      {...rest}\n      src={imgSrc}\n      onError={() => {\n        setImgSrc(fallbackSrc);\n      }}\n    />\n  );\n};\nexport default ImageWithFallback;\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/is.general.tsx",
    "content": "import { loadVars } from './variable.context';\nexport const isGeneral = () => {\n  return typeof process.env.IS_GENERAL === 'undefined'\n    ? !!process.env.IS_GENERAL\n    : loadVars?.()?.isGeneral;\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx",
    "content": "'use client';\n\nimport { ReactNode } from 'react';\nimport {\n  DecisionEverywhere,\n  ModalManager,\n} from '@gitroom/frontend/components/layout/new-modal';\nexport const MantineWrapper = (props: { children: ReactNode }) => {\n  return (\n    <ModalManager>\n      <DecisionEverywhere />\n      {props.children}\n    </ModalManager>\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/posthog.tsx",
    "content": "'use client';\n\nimport posthog from 'posthog-js';\nimport { PostHogProvider } from 'posthog-js/react';\nimport { FC, ReactNode, useEffect } from 'react';\nexport const PHProvider: FC<{\n  children: ReactNode;\n  phkey?: string;\n  host?: string;\n}> = ({ children, phkey, host }) => {\n  useEffect(() => {\n    if (!phkey || !host) {\n      return;\n    }\n    posthog.init(phkey, {\n      api_host: host,\n      person_profiles: 'identified_only',\n      capture_pageview: false, // Disable automatic pageview capture, as we capture manually\n    });\n  }, []);\n  if (!phkey || !host) {\n    return <>{children}</>;\n  }\n  return <PostHogProvider client={posthog}>{children}</PostHogProvider>;\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/testomonials.tsx",
    "content": "export const testimonials1 = [\n  {\n    picture: '/auth/avatars/vincent.jpg',\n    name: 'Vincent L.',\n    description: 'Marketing Coordinator',\n    content: (\n      <>\n        The UI is friendly and the AI content assistant is surprisingly\n        effective for professional tones. I especially like how it adjusts to\n        different industries.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/dilini.jpeg',\n    name: 'Dilini R.',\n    description: 'AI & Tech Consultant',\n    content: (\n      <>\n        I just found out about Postiz, a tool for scheduling social media.{' '}\n        {'\\n'}\n        Exactly what I wish there was a few years back, I even thought of\n        building one myself at one point, but didn't have the time to. {'\\n'}\n        What I like about it so far: {'\\n\\n'}\n        It connects to LinkedIn, X, Instagram, Facebook (and others) from one\n        dashboard. {'\\n'}\n        {'\\n'}\n        Because it's open-source, you can see how it works and even tweak it if\n        you need to. {'\\n'}\n        {'\\n'}\n        I've used a few scheduling tools before and most of them are either\n        expensive or try to be \"all-in-one marketing platforms.\" {'\\n'}\n        {'\\n'}\n        Postiz seems to focus on just doing one thing well. {'\\n'}\n        {'\\n'}\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/johna.jpg',\n    name: 'Johannes D.',\n    description: 'CEO',\n    content: (\n      <>\n        As a privacy-first company we appreciate being able to self-host Postiz!\n        It brings all the core functionality of a social media scheduler plus a\n        lot of AI to make things faster. It's also very easy to deploy and use,\n        great work!\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/george.jpg',\n    name: 'George B.',\n    description: 'Marketing Assistant',\n    content: (\n      <>\n        It's so easy to jump in and start scheduling. I like that I can see all\n        planned posts at a glance and edit them quickly if needed.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/maria.jpg',\n    name: 'Maria Camila A.',\n    description: 'Data Analyst',\n    content: (\n      <>\n        Postiz changed how we manage our social media presence by aggregating\n        our platforms into one effective tool. Post scheduling, and AI post\n        ideation are two of the many features that come with Postiz, and have\n        made our management of social media simple and effective! Highly\n        recommend\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/bart.jpg',\n    name: 'Bartolomeo H.',\n    description: 'CEO',\n    content: (\n      <>\n        It only takes 10 minutes to set up your X scheduling automation. {'\\n'}\n        n8n + Postiz =🔥Never miss a day of posting again: {'\\n\\n'}→ Easy to get\n        started {'\\n'}→ Tutorial video included {'\\n'}→ Automated content\n        creation {'\\n'}→ Multi-platform publishing {'\\n'}→ Self-hosted (no\n        monthly fees) {'\\n'}→ Open-source (customize everything) {'\\n'}\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/henry.jpg',\n    name: 'Henry H.',\n    description: 'Social Media Coordinator',\n    content: (\n      <>\n        The interface is clean and simple. I love how the AI assistant helps\n        speed up caption writing without sounding generic. It's really helpful\n        when I'm on a tight schedule.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/andy.jpeg',\n    name: 'Andy C.',\n    description: 'AI Specialist',\n    content: (\n      <>\n        Manage all your social media accounts from a single place: Postiz, a\n        really cool tool I recently discovered :D! {'\\n'}\n        It comes with a bunch of cool tools for posting at specific times,\n        posting across multiple platforms simultaneously, etc. And all of this\n        can potentially be self-hosted for free; you just need a small server to\n        configure everything ;D! It works really well :D!\n      </>\n    ),\n  },\n];\n\nexport const testimonials2 = [\n  {\n    picture: '/auth/avatars/michael.jpeg',\n    name: 'Michael H.',\n    description: 'Senior frontend developer',\n    content: (\n      <>\n        🌟 Exciting news! 🚀 I've just started using Postiz, a fantastic new\n        tool for scheduling my social media content! {'\\n\\n'}\n        Why did I choose Postiz? The ability to self-host it means significant\n        savings for me! 💰 {'\\n\\n'}\n        Postiz is an open-source scheduling tool that allows you to plan and\n        automate posts across 19+ platforms, including X, LinkedIn, BlueSky, and\n        Mastodon. {'\\n\\n'}\n        With its powerful editor, you can easily connect your accounts, create\n        rich scheduled posts, and manage multiple channels all in one place.\n        Plus, it supports image uploads, recurring posts, and timezone-aware\n        scheduling! 📅✨ {'\\n\\n'}\n        Built with privacy and flexibility in mind, Postiz can run on your own\n        infrastructure or be used as a hosted service. It's perfect for\n        individuals, teams, and communities looking for control and automation\n        without the unnecessary bloat. {'\\n\\n'}\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/kiley.jpeg',\n    name: 'Kiley H.',\n    description: 'Content Creator',\n    content: (\n      <>\n        The unified dashboard helps me manage Instagram, Facebook, and LinkedIn\n        from one place. I love that it saves time and keeps our campaigns\n        aligned across all platforms.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/iorn.jpg',\n    name: 'Iornienge S.',\n    description: 'Social Media Manager',\n    content: (\n      <>\n        There are several things I love about this suite. Some of these things\n        include {'\\n'}- Ease of use {'\\n'}- Helps me organize my social media\n        accounts {'\\n'}- I get work done faster {'\\n'}- It does not consume my\n        time {'\\n'}- it has a professional interface {'\\n'}\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/david.jpg',\n    name: 'David C.',\n    description: 'Digital Marketing Manager',\n    content: (\n      <>\n        Postiz makes it so easy to plan ahead. The AI suggestions are relevant,\n        and the platform feels lightweight but powerful\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/serge.jpeg',\n    name: 'Serge A.',\n    description: 'CEO',\n    content: (\n      <>\n        Good tool for social media campaigns. The great thing is that the\n        platform constantly evolves - new features appear all the time, so I can\n        follow the latest trends (latest AI developments) without leaving\n        Postiz.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/anica.jpg',\n    name: 'Anica R.',\n    description: 'University Applications Specialist',\n    content: (\n      <>\n        It is easy to use, manages your posts simple.It is a helpful tool that\n        let you organize your content.\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/josh.jpg',\n    name: 'Josh W.',\n    description: 'Content Manager',\n    content: (\n      <>\n        It's super easy to use even if you're not very techy. The AI writing\n        tool gives good drafts so I don't have to start from scratch every time\n      </>\n    ),\n  },\n  {\n    picture: '/auth/avatars/vince.jpeg',\n    name: 'Vince C.',\n    description: 'Developer Relations Engineer',\n    content: (\n      <>\n        I work in Developer Relations, so having a tool that helps me manage and\n        crosspost to different platforms saves me so, so, so much time!\n      </>\n    ),\n  },\n];\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/uppy.upload.ts",
    "content": "import XHRUpload from '@uppy/xhr-upload';\nimport AwsS3Multipart from '@uppy/aws-s3';\nimport sha256 from 'sha256';\nimport Transloadit from '@uppy/transloadit';\nconst fetchUploadApiEndpoint = async (\n  fetch: any,\n  endpoint: string,\n  data: any\n) => {\n  const res = await fetch(`/media/${endpoint}`, {\n    method: 'POST',\n    body: JSON.stringify(data),\n    headers: {\n      accept: 'application/json',\n      'Content-Type': 'application/json',\n    },\n  });\n  return res.json();\n};\n\n// Define the factory to return appropriate Uppy configuration\nexport const getUppyUploadPlugin = (\n  provider: string,\n  fetch: any,\n  backendUrl: string,\n  transloadit: string[] = []\n) => {\n  switch (provider) {\n    case 'transloadit':\n      return {\n        plugin: Transloadit,\n        options: {\n          waitForEncoding: true,\n          alwaysRunAssembly: true,\n          assemblyOptions: {\n            params: {\n              auth: { key: transloadit[0] },\n              template_id: transloadit[1],\n            },\n          },\n        },\n      };\n    case 'cloudflare':\n      return {\n        plugin: AwsS3Multipart,\n        options: {\n          shouldUseMultipart: (file: any) => true,\n          endpoint: '',\n          createMultipartUpload: async (file: any) => {\n            let fileHash = '';\n            const contentType = file.type;\n\n            // Skip hash calculation for files larger than 100MB to avoid \"Invalid array length\" error\n            if (file.size <= 100 * 1024 * 1024) {\n              try {\n                const arrayBuffer = await new Response(file.data).arrayBuffer();\n                fileHash = sha256(Buffer.from(arrayBuffer));\n              } catch (error) {\n                console.warn(\n                  'Failed to calculate file hash, proceeding without hash:',\n                  error\n                );\n                fileHash = '';\n              }\n            }\n\n            return fetchUploadApiEndpoint(fetch, 'create-multipart-upload', {\n              file,\n              fileHash,\n              contentType,\n            });\n          },\n          listParts: (file: any, props: any) =>\n            fetchUploadApiEndpoint(fetch, 'list-parts', {\n              file,\n              ...props,\n            }),\n          signPart: (file: any, props: any) =>\n            fetchUploadApiEndpoint(fetch, 'sign-part', {\n              file,\n              ...props,\n            }),\n          abortMultipartUpload: (file: any, props: any) =>\n            fetchUploadApiEndpoint(fetch, 'abort-multipart-upload', {\n              file,\n              ...props,\n            }),\n          completeMultipartUpload: (file: any, props: any) =>\n            fetchUploadApiEndpoint(fetch, 'complete-multipart-upload', {\n              file,\n              ...props,\n            }),\n        },\n      };\n    case 'local':\n      return {\n        plugin: XHRUpload,\n        options: {\n          endpoint: `${backendUrl}/media/upload-server`,\n          withCredentials: true,\n        },\n      };\n\n    // Add more cases for other cloud providers\n    default:\n      throw new Error(`Unsupported storage provider: ${provider}`);\n  }\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/use.is.visible.tsx",
    "content": "'use client';\n\nimport { useEffect, useState } from 'react';\nexport function usePageVisibility(page: number) {\n  if (typeof document === 'undefined') {\n    return true;\n  }\n  const [isVisible, setIsVisible] = useState(!document.hidden);\n  useEffect(() => {\n    if (page > 1) {\n      return;\n    }\n    const handleVisibilityChange = () => {\n      setIsVisible(!document.hidden);\n    };\n    const onBlur = () => {\n      setIsVisible(false);\n    };\n    const onFocus = () => {\n      setIsVisible(true);\n    };\n    window.addEventListener('blur', onBlur);\n    window.addEventListener('focus', onFocus);\n    document.addEventListener('visibilitychange', handleVisibilityChange);\n    return () => {\n      document.removeEventListener('visibilitychange', handleVisibilityChange);\n      document.removeEventListener('blur', onBlur);\n      document.removeEventListener('focus', focus);\n    };\n  }, []);\n  return isVisible;\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/use.media.directory.ts",
    "content": "import { useCallback } from 'react';\nexport const useMediaDirectory = () => {\n  const set = useCallback((path: string) => {\n    return path;\n  }, []);\n  return {\n    set,\n  };\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/use.prevent.window.unload.tsx",
    "content": "import { useEffect } from 'react';\nexport const usePreventWindowUnload = (preventDefault: boolean) => {\n  useEffect(() => {\n    if (!preventDefault) return;\n    const handleBeforeUnload = (event: any) => event.preventDefault();\n    window.addEventListener('beforeunload', handleBeforeUnload);\n    return () => window.removeEventListener('beforeunload', handleBeforeUnload);\n  }, [preventDefault]);\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/use.state.callback.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from 'react';\nexport function useStateCallback<T>(\n  initialState: T\n): [T, (state: T, cb?: (state: T) => void) => void] {\n  const [state, setState] = useState(initialState);\n  const cbRef = useRef<((state: T) => void) | undefined>(undefined); // init mutable ref container for callbacks\n\n  const setStateCallback = useCallback((state: T, cb?: (state: T) => void) => {\n    cbRef.current = cb; // store current, passed callback in ref\n    setState(state);\n  }, []); // keep object reference stable, exactly like `useState`\n\n  useEffect(() => {\n    // cb.current is `undefined` on initial render,\n    // so we only invoke callback on state *updates*\n    if (cbRef.current) {\n      cbRef.current(state);\n      cbRef.current = undefined; // reset callback after execution\n    }\n  }, [state]);\n  return [state, setStateCallback];\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/use.track.tsx",
    "content": "import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';\nimport { useUser } from '@gitroom/frontend/components/layout/user.context';\nimport { useFetch } from '@gitroom/helpers/utils/custom.fetch';\nimport { useCallback } from 'react';\nimport { useVariables } from '@gitroom/react/helpers/variable.context';\nexport const useTrack = () => {\n  const user = useUser();\n  const fetch = useFetch();\n  const { facebookPixel } = useVariables();\n  return useCallback(\n    async (track: TrackEnum, additional?: Record<string, any>) => {\n      if (!facebookPixel) {\n        return;\n      }\n      try {\n        const { track: uq } = await (\n          await fetch(user ? `/user/t` : `/public/t`, {\n            method: 'POST',\n            credentials: 'include',\n            headers: {\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({\n              tt: track,\n              ...(additional\n                ? {\n                    additional,\n                  }\n                : {}),\n            }),\n          })\n        ).json();\n        if (window.fbq) {\n          // @ts-ignore\n          window.fbq('track', TrackEnum[track], additional, {\n            eventID: uq,\n          });\n        }\n      } catch (e) {\n        console.log(e);\n      }\n    },\n    [user]\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/utc.date.render.tsx",
    "content": "'use client';\n\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport { FC } from 'react';\ndayjs.extend(utc);\nexport const UtcToLocalDateRender: FC<{\n  date: string;\n  format: string;\n}> = (props) => {\n  const { date, format } = props;\n  return <>{dayjs.utc(date).local().format(format)}</>;\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/variable.context.tsx",
    "content": "'use client';\n\nimport { createContext, FC, ReactNode, useContext, useEffect } from 'react';\ninterface VariableContextInterface {\n  stripeClient: string;\n  billingEnabled: boolean;\n  isGeneral: boolean;\n  genericOauth: boolean;\n  oauthLogoUrl: string;\n  oauthDisplayName: string;\n  mcpUrl?: string;\n  frontEndUrl: string;\n  plontoKey: string;\n  storageProvider: 'local' | 'cloudflare';\n  backendUrl: string;\n  environment: string;\n  discordUrl: string;\n  uploadDirectory: string;\n  facebookPixel: string;\n  telegramBotName: string;\n  neynarClientId: string;\n  isSecured: boolean;\n  disableImageCompression: boolean;\n  disableXAnalytics: boolean;\n  language: string;\n  dub: boolean;\n  transloadit: string[];\n  sentryDsn: string;\n  extensionId: string;\n}\nconst VariableContext = createContext({\n  stripeClient: '',\n  billingEnabled: false,\n  isGeneral: true,\n  genericOauth: false,\n  oauthLogoUrl: '',\n  oauthDisplayName: '',\n  mcpUrl: '',\n  frontEndUrl: '',\n  storageProvider: 'local',\n  plontoKey: '',\n  backendUrl: '',\n  discordUrl: '',\n  uploadDirectory: '',\n  isSecured: false,\n  telegramBotName: '',\n  facebookPixel: '',\n  neynarClientId: '',\n  disableImageCompression: false,\n  disableXAnalytics: false,\n  language: '',\n  dub: false,\n  transloadit: [],\n  sentryDsn: '',\n  extensionId: '',\n} as VariableContextInterface);\nexport const VariableContextComponent: FC<\n  VariableContextInterface & {\n    children: ReactNode;\n  }\n> = (props) => {\n  const { children, ...otherProps } = props;\n  useEffect(() => {\n    if (typeof window !== 'undefined') {\n      // @ts-ignore\n      window.vars = otherProps;\n    }\n  }, []);\n  return (\n    <VariableContext.Provider value={otherProps}>\n      {children}\n    </VariableContext.Provider>\n  );\n};\nexport const useVariables = () => {\n  return useContext(VariableContext);\n};\nexport const loadVars = () => {\n  // @ts-ignore\n  return window.vars as VariableContextInterface;\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/video.frame.tsx",
    "content": "'use client';\n\nimport { FC } from 'react';\nexport const VideoFrame: FC<{\n  url: string;\n  autoplay?: boolean;\n}> = (props) => {\n  const { url } = props;\n  return (\n    <video\n      className=\"w-full h-full object-cover rounded-[4px]\"\n      src={url + '#t=0.1'}\n      preload=\"metadata\"\n      autoPlay={!!props?.autoplay}\n    />\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/helpers/video.or.image.tsx",
    "content": "import { FC } from 'react';\nimport { clsx } from 'clsx';\nexport const VideoOrImage: FC<{\n  src: string;\n  autoplay: boolean;\n  isContain?: boolean;\n  imageClassName?: string;\n  videoClassName?: string;\n}> = (props) => {\n  const { src, autoplay, isContain, imageClassName, videoClassName } = props;\n  if (src?.indexOf('mp4') > -1) {\n    return (\n      <video\n        src={src}\n        autoPlay={autoplay}\n        className={clsx('w-full h-full', videoClassName)}\n        muted={true}\n        loop={true}\n      />\n    );\n  }\n  return (\n    <img\n      className={clsx(\n        isContain ? 'object-contain' : 'object-cover',\n        'w-full h-full',\n        imageClassName\n      )}\n      src={src}\n    />\n  );\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts",
    "content": "import * as Sentry from '@sentry/nextjs';\nimport { initializeSentryBasic } from '@gitroom/react/sentry/initialize.sentry.next.basic';\n\nexport const initializeSentryClient = (environment: string, dsn: string) =>\n  initializeSentryBasic(environment, dsn, {\n    integrations: [\n      // Add default integrations back\n      Sentry.browserTracingIntegration(),\n      Sentry.browserProfilingIntegration(),\n      Sentry.replayIntegration({\n        maskAllText: true,\n        maskAllInputs: true,\n      }),\n      Sentry.feedbackIntegration({\n        // Disable the injection of the default widget\n        autoInject: false,\n      }),\n      Sentry.replayCanvasIntegration(),\n    ],\n    replaysSessionSampleRate: 1.0,\n    replaysOnErrorSampleRate: 1.0,\n\n    profilesSampleRate: environment === 'development' ? 1.0 : 0.75,\n  });\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/sentry/initialize.sentry.next.basic.ts",
    "content": "import * as Sentry from '@sentry/nextjs';\n\nexport const initializeSentryBasic = (environment: string, dsn: string, extension: any) => {\n  if (!dsn) {\n    return;\n  }\n\n  const ignorePatterns = [\n    /^Failed to fetch$/,\n    /^Failed to fetch .*/i,\n    /^Load failed$/i,\n    /^Load failed .*/i,\n    /^NetworkError when attempting to fetch resource\\.$/i,\n    /^NetworkError when attempting to fetch resource\\. .*/i,\n  ];\n\n  try {\n    Sentry.init({\n      initialScope: {\n        tags: {\n          service: 'frontend',\n          component: 'nextjs',\n          replaysEnabled: 'true',\n        },\n        contexts: {\n          app: {\n            name: 'Postiz Frontend',\n            version: process.env.NEXT_PUBLIC_APP_VERSION || '0.0.0',\n          },\n        },\n      },\n      integrations: [\n        Sentry.consoleLoggingIntegration({ levels: ['log', 'info', 'warn', 'error', 'debug', 'assert', 'trace'] }),\n      ],\n      environment: environment || 'development',\n      spotlight: process.env.SENTRY_SPOTLIGHT === '1',\n      dsn,\n      sendDefaultPii: true,\n      ...extension,\n      debug: environment === 'development',\n      tracesSampleRate: 1.0,\n\n      beforeSend(event, hint) {\n        if (event.exception && event.exception.values) {\n          for (const exception of event.exception.values) {\n            if (exception.value) {\n              for (const pattern of ignorePatterns) {\n                if (pattern.test(exception.value)) {\n                  return null; // Ignore the event\n                }\n              }\n            }\n          }\n\n          // If there's an exception and an event id, present the user report dialog.\n          if (event.event_id) {\n            // Only attempt to show the dialog in a browser environment.\n            if (typeof window !== 'undefined' && window.document) {\n              // Dynamically import the package that exports showReportDialog to avoid\n              // bundler errors when this shared lib is used in non-browser builds.\n              import('@sentry/react')\n                .then((mod) => {\n                  try {\n                    mod.showReportDialog({ eventId: event.event_id });\n                  } catch (err) {\n                    // eslint-disable-next-line no-console\n                    console.error('Sentry.showReportDialog failed:', err);\n                  }\n                })\n                .catch((importErr) => {\n                  // eslint-disable-next-line no-console\n                  console.error('Failed to import @sentry/react for report dialog:', importErr);\n                });\n            }\n          }\n        }\n\n        return event; // Send the event to Sentry\n      },\n    });\n  } catch (err) {\n    // Log initialization errors\n    // eslint-disable-next-line no-console\n    console.error('Sentry.init failed:', err);\n  }\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/sentry/initialize.sentry.server.ts",
    "content": "import * as Sentry from '@sentry/nextjs';\nimport { initializeSentryBasic } from '@gitroom/react/sentry/initialize.sentry.next.basic';\n\nexport const initializeSentryServer = (environment: string, dsn: string) =>\n  initializeSentryBasic(environment, dsn, {});\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/toaster/toaster.tsx",
    "content": "'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\nimport EventEmitter from 'events';\nimport clsx from 'clsx';\nconst toaster = new EventEmitter();\nexport const Toaster = () => {\n  const [showToaster, setShowToaster] = useState(false);\n  const [toasterText, setToasterText] = useState('');\n  const [toasterType, setToasterType] = useState<'success' | 'warning' | ''>(\n    ''\n  );\n  useEffect(() => {\n    toaster.on(\n      'show',\n      (params: { text: string; type?: 'success' | 'warning' }) => {\n        const { text, type } = params;\n        setToasterText(text);\n        setToasterType(type || 'success');\n        setShowToaster(true);\n        setTimeout(() => {\n          setShowToaster(false);\n        }, 4200);\n      }\n    );\n    return () => {\n      toaster.removeAllListeners();\n    };\n  }, []);\n  if (!showToaster) {\n    return <></>;\n  }\n  return (\n    <div\n      className={clsx(\n        'animate-fadeDown rounded-[8px] gap-[18px] flex items-center overflow-hidden bg-customColor8 p-[16px] min-w-[319px] fixed start-[50%] text-white z-[900] top-[32px] -translate-x-[50%] h-[56px]',\n        toasterType === 'success' ? 'shadow-greenToast' : 'shadow-yellowToast'\n      )}\n    >\n      <div>\n        {toasterType === 'success' ? (\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M12 2.25C10.0716 2.25 8.18657 2.82183 6.58319 3.89317C4.97982 4.96452 3.73013 6.48726 2.99218 8.26884C2.25422 10.0504 2.06114 12.0108 2.43735 13.9021C2.81355 15.7934 3.74215 17.5307 5.10571 18.8943C6.46928 20.2579 8.20656 21.1865 10.0979 21.5627C11.9892 21.9389 13.9496 21.7458 15.7312 21.0078C17.5127 20.2699 19.0355 19.0202 20.1068 17.4168C21.1782 15.8134 21.75 13.9284 21.75 12C21.7473 9.41498 20.7192 6.93661 18.8913 5.10872C17.0634 3.28084 14.585 2.25273 12 2.25ZM16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.2891 9.14969 15.3718 9.09442 15.4628 9.0567C15.5539 9.01899 15.6515 8.99958 15.75 8.99958C15.8486 8.99958 15.9461 9.01899 16.0372 9.0567C16.1282 9.09442 16.2109 9.14969 16.2806 9.21937C16.3503 9.28906 16.4056 9.37178 16.4433 9.46283C16.481 9.55387 16.5004 9.65145 16.5004 9.75C16.5004 9.84855 16.481 9.94613 16.4433 10.0372C16.4056 10.1282 16.3503 10.2109 16.2806 10.2806Z\"\n              fill=\"#6CE9A6\"\n            />\n          </svg>\n        ) : (\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n          >\n            <path\n              d=\"M22.201 17.6334L14.0026 3.39556C13.7977 3.04674 13.5052 2.75752 13.1541 2.55656C12.803 2.3556 12.4055 2.24988 12.001 2.24988C11.5965 2.24988 11.199 2.3556 10.8479 2.55656C10.4968 2.75752 10.2043 3.04674 9.99944 3.39556L1.80101 17.6334C1.60388 17.9708 1.5 18.3545 1.5 18.7453C1.5 19.136 1.60388 19.5197 1.80101 19.8571C2.00325 20.2081 2.29523 20.4989 2.64697 20.6997C2.99871 20.9005 3.39755 21.0041 3.80257 20.9999H20.1994C20.6041 21.0038 21.0026 20.9 21.354 20.6992C21.7054 20.4984 21.997 20.2078 22.1991 19.8571C22.3965 19.5199 22.5007 19.1363 22.5011 18.7455C22.5014 18.3548 22.3978 17.9709 22.201 17.6334ZM11.251 9.74994C11.251 9.55103 11.33 9.36026 11.4707 9.21961C11.6113 9.07896 11.8021 8.99994 12.001 8.99994C12.1999 8.99994 12.3907 9.07896 12.5313 9.21961C12.672 9.36026 12.751 9.55103 12.751 9.74994V13.4999C12.751 13.6989 12.672 13.8896 12.5313 14.0303C12.3907 14.1709 12.1999 14.2499 12.001 14.2499C11.8021 14.2499 11.6113 14.1709 11.4707 14.0303C11.33 13.8896 11.251 13.6989 11.251 13.4999V9.74994ZM12.001 17.9999C11.7785 17.9999 11.561 17.934 11.376 17.8103C11.191 17.6867 11.0468 17.511 10.9616 17.3055C10.8765 17.0999 10.8542 16.8737 10.8976 16.6555C10.941 16.4372 11.0482 16.2368 11.2055 16.0794C11.3628 15.9221 11.5633 15.815 11.7815 15.7716C11.9998 15.7281 12.226 15.7504 12.4315 15.8356C12.6371 15.9207 12.8128 16.0649 12.9364 16.2499C13.06 16.4349 13.126 16.6524 13.126 16.8749C13.126 17.1733 13.0075 17.4595 12.7965 17.6704C12.5855 17.8814 12.2994 17.9999 12.001 17.9999Z\"\n              fill=\"#FEC84B\"\n            />\n          </svg>\n        )}\n      </div>\n      <div className=\"flex-1 text-textColor\">{toasterText}</div>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"60\"\n        height=\"56\"\n        viewBox=\"0 0 60 56\"\n        fill=\"none\"\n        className=\"absolute top-0 start-0\"\n      >\n        <g filter=\"url(#filter0_f_376_2968)\">\n          <ellipse\n            cx=\"-12\"\n            cy=\"28\"\n            rx=\"28\"\n            ry=\"13\"\n            fill={toasterType === 'success' ? '#6CE9A6' : '#FEC84B'}\n          />\n        </g>\n        <defs>\n          <filter\n            id=\"filter0_f_376_2968\"\n            x=\"-84\"\n            y=\"-29\"\n            width=\"144\"\n            height=\"114\"\n            filterUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feFlood floodOpacity=\"0\" result=\"BackgroundImageFix\" />\n            <feBlend\n              mode=\"normal\"\n              in=\"SourceGraphic\"\n              in2=\"BackgroundImageFix\"\n              result=\"shape\"\n            />\n            <feGaussianBlur\n              stdDeviation=\"22\"\n              result=\"effect1_foregroundBlur_376_2968\"\n            />\n          </filter>\n        </defs>\n      </svg>\n    </div>\n  );\n};\nexport const useToaster = () => {\n  return {\n    show: useCallback((text: string, type?: 'success' | 'warning') => {\n      toaster.emit('show', {\n        text,\n        type,\n      });\n    }, []),\n  };\n};\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/get.transation.service.client.ts",
    "content": "'use client';\n\nimport i18next from './i18next';\nimport { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { UseTranslationOptions } from 'react-i18next/index';\n\nexport function useT(ns?: string, options?: UseTranslationOptions<any>) {\n  const { t } = useTranslation(ns, options);\n  return t;\n}\n\nexport function useTranslationSettings() {\n  const [savedI18next, setSavedI18next] = useState(i18next);\n\n  return savedI18next;\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/get.translation.service.backend.ts",
    "content": "import i18next from './i18next';\nimport { headerName } from './i18n.config';\nimport { headers } from 'next/headers';\nexport async function getT(ns?: string, options?: any) {\n  const headerList = await headers();\n  const lng = headerList.get(headerName);\n  if (lng && i18next.resolvedLanguage !== lng) {\n    await i18next.changeLanguage(lng);\n  }\n  if (ns && !i18next.hasLoadedNamespace(ns)) {\n    await i18next.loadNamespaces(ns);\n  }\n  return i18next.getFixedT(\n    lng ?? i18next.resolvedLanguage,\n    Array.isArray(ns) ? ns[0] : ns,\n    options?.keyPrefix\n  );\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/i18n.config.ts",
    "content": "export const fallbackLng = 'en';\nexport const languages = [\n  fallbackLng,\n  'he',\n  'ru',\n  'zh',\n  'fr',\n  'es',\n  'pt',\n  'de',\n  'it',\n  'ja',\n  'ko',\n  'ar',\n  'tr',\n  'vi',\n];\n\nexport const defaultNS = 'translation';\nexport const cookieName = 'i18next';\nexport const headerName = 'x-i18next-current-language';\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/i18next.ts",
    "content": "import i18next from 'i18next';\nimport LanguageDetector from 'i18next-browser-languagedetector';\nimport resourcesToBackend from 'i18next-resources-to-backend';\nimport { initReactI18next } from 'react-i18next/initReactI18next';\nimport { fallbackLng, languages, defaultNS } from './i18n.config';\nconst runsOnServerSide = typeof window === 'undefined';\n\ni18next\n  .use(initReactI18next)\n  .use(LanguageDetector)\n  .use(\n    resourcesToBackend((language: any, namespace: any) => {\n      return import(`./locales/${language}/${namespace}.json`);\n    })\n  )\n  .init({\n    // debug: true,\n    supportedLngs: languages,\n    fallbackLng,\n    lng: undefined,\n    // let detect the language on client side\n    fallbackNS: defaultNS,\n    defaultNS,\n    detection: {\n      order: ['cookie', 'header'],\n    },\n    preload: runsOnServerSide ? languages : [],\n  });\n\nexport default i18next;\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/ar/translation.json",
    "content": "{\n  \"calendar\": \"التقويم\",\n  \"webhooks\": \"روابط الويب (Webhooks)\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"روابط الويب هي وسيلة لتلقي الإشعارات عند حدوث شيء ما في Postiz عبر طلب HTTP.\",\n  \"name\": \"الاسم\",\n  \"url\": \"الرابط (URL)\",\n  \"edit\": \"تعديل\",\n  \"delete\": \"حذف\",\n  \"add_a_webhook\": \"إضافة رابط ويب\",\n  \"save\": \"حفظ\",\n  \"send_test\": \"إرسال اختبار\",\n  \"select_role\": \"اختر الدور\",\n  \"video_made_with_ai\": \"فيديو تم إنشاؤه بالذكاء الاصطناعي\",\n  \"please_add_at_least\": \"يرجى إضافة 20 حرفًا على الأقل\",\n  \"send_invitation_via_email\": \"إرسال دعوة عبر البريد الإلكتروني؟\",\n  \"global_settings\": \"الإعدادات العامة\",\n  \"copy_id\": \"نسخ معرف القناة\",\n  \"team_members\": \"أعضاء الفريق\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"ادعُ مساعدك أو أحد أعضاء فريقك لإدارة حسابك\",\n  \"remove\": \"إزالة\",\n  \"add_another_member\": \"إضافة عضو آخر\",\n  \"signatures\": \"التواقيع\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"يمكنك إضافة تواقيع إلى حسابك لاستخدامها في منشوراتك.\",\n  \"content\": \"المحتوى\",\n  \"auto_add\": \"إضافة تلقائية؟\",\n  \"delay_comment\": \"تعليق التأخير\",\n  \"actions\": \"الإجراءات\",\n  \"use_signature\": \"استخدم التوقيع\",\n  \"add_a_signature\": \"أضف توقيعًا\",\n  \"no\": \"لا\",\n  \"yes\": \"نعم\",\n  \"your_git_repository\": \"مستودع Git الخاص بك\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"قم بربط مستودع GitHub الخاص بك لتلقي التحديثات والتحليلات\",\n  \"connected\": \"متصل:\",\n  \"disconnect\": \"قطع الاتصال\",\n  \"connect_your_repository\": \"ربط المستودع الخاص بك\",\n  \"cancel\": \"إلغاء\",\n  \"connect\": \"اتصال\",\n  \"public_api\": \"واجهة برمجة التطبيقات العامة\",\n  \"check_n8n\": \"اطلع على عقدة N8N المخصصة لدينا لـ Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"استخدم واجهة برمجة تطبيقات Postiz للدمج مع أدواتك.\",\n  \"read_how_to_use_it_over_the_documentation\": \"اقرأ كيفية استخدامه في الوثائق.\",\n  \"reveal\": \"إظهار\",\n  \"copy_key\": \"نسخ المفتاح\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"قم بتوصيل خادم Postiz MCP بعميلك (بث HTTP) لجدولة منشوراتك بشكل أسرع!\",\n  \"share_with_a_client\": \"مشاركة مع عميل\",\n  \"post\": \"منشور\",\n  \"comments\": \"تعليقات\",\n  \"user\": \"مستخدم\",\n  \"login_register_to_add_comments\": \"تسجيل الدخول / التسجيل لإضافة تعليقات\",\n  \"status\": \"الحالة:\",\n  \"there_are_not_plugs_matching_your_channels\": \"لا توجد ملحقات مطابقة لقنواتك\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"يجب عليك إضافة: X أو LinkedIn أو Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"اذهب إلى التقويم لإضافة القنوات\",\n  \"channels\": \"القنوات\",\n  \"activate\": \"تفعيل\",\n  \"this_channel_needs_to_be_refreshed\": \"يجب تحديث هذه القناة،\",\n  \"click_here_to_refresh\": \"انقر هنا للتحديث\",\n  \"can_t_show_analytics_yet\": \"لا يمكن عرض التحليلات بعد\",\n  \"you_have_to_add_social_media_channels\": \"يجب عليك إضافة قنوات التواصل الاجتماعي\",\n  \"supported\": \"مدعوم:\",\n  \"step\": \"الخطوة\",\n  \"skip_onboarding\": \"تخطي الإعداد\",\n  \"onboarding\": \"الإعداد\",\n  \"next\": \"التالي\",\n  \"you_are_done_from_here_you_can\": \"لقد انتهيت، من هنا يمكنك:\",\n  \"view_analytics\": \"عرض التحليلات\",\n  \"schedule_a_new_post\": \"جدولة منشور جديد\",\n  \"to_sell_posts_you_would_have_to\": \"لبيع المنشورات يجب عليك:\",\n  \"1_connect_at_least_one_channel\": \"1. ربط قناة واحدة على الأقل\",\n  \"2_connect_you_bank_account\": \"2. ربط حسابك البنكي\",\n  \"go_back_to_connect_channels\": \"العودة لربط القنوات\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"انتقل إلى صفحة البائع لربط حسابك البنكي\",\n  \"connect_channels\": \"ربط القنوات\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"قم بربط قنوات التواصل الاجتماعي ومواقع النشر الخاصة بك لجدولة المنشورات لاحقًا\",\n  \"social\": \"اجتماعي\",\n  \"publishing_platforms\": \"منصات النشر\",\n  \"no_channels\": \"لا توجد قنوات بعد\",\n  \"connect_your_accounts\": \"قم بربط حساباتك الاجتماعية لبدء الجدولة، النشر، والتحليل — كل ذلك في مكان واحد.\",\n  \"notifications\": \"الإشعارات\",\n  \"no_notifications\": \"لا توجد إشعارات\",\n  \"send_message\": \"إرسال رسالة\",\n  \"mar_28\": \"٢٨ مارس\",\n  \"there_are_no_messages_yet\": \"لا توجد رسائل بعد.\",\n  \"checkout_the_marketplace\": \"تصفح السوق\",\n  \"go_to_marketplace\": \"اذهب إلى السوق\",\n  \"all_messages\": \"كل الرسائل\",\n  \"previous\": \"السابق\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"اختر أو ارفع صورًا (بحد أقصى 5 في المرة الواحدة)\",\n  \"you_can_also_drag_drop_pictures\": \"يمكنك أيضًا سحب وإفلات الصور\",\n  \"you_don_t_have_any_assets_yet\": \"ليس لديك أي أصول بعد.\",\n  \"click_the_button_below_to_upload_one\": \"انقر على الزر أدناه لرفع واحدة\",\n  \"click_the_button_below_to_upload_other\": \"انقر على الزر أدناه لتحميل عدة ملفات\",\n  \"add_selected_media\": \"أضف الوسائط المحددة\",\n  \"insert_media\": \"إدراج وسائط\",\n  \"design_media\": \"تصميم وسائط\",\n  \"select\": \"اختر\",\n  \"editor\": \"المحرر\",\n  \"clear\": \"مسح\",\n  \"order_completed\": \"اكتمل الطلب\",\n  \"the_order_has_been_completed\": \"تم اكتمال الطلب\",\n  \"post_has_been_published\": \"تم نشر المنشور\",\n  \"url_1\": \"الرابط:\",\n  \"new_offer\": \"عرض جديد\",\n  \"platform\": \"المنصة\",\n  \"posts\": \"المنشورات\",\n  \"pay_accept_offer\": \"ادفع وقبل العرض\",\n  \"accepted\": \"تم القبول\",\n  \"post_draft\": \"مسودة منشور\",\n  \"revision_needed\": \"مطلوب مراجعة\",\n  \"approve\": \"موافقة\",\n  \"preview\": \"معاينة\",\n  \"revision_requested\": \"تم طلب مراجعة\",\n  \"accepted_1\": \"مقبول\",\n  \"cancelled_by_the_seller\": \"أُلغي بواسطة البائع\",\n  \"please_select_your_country_where_your_business_is\": \"يرجى اختيار الدولة التي يقع فيها عملك.\",\n  \"select_country\": \"--اختر الدولة--\",\n  \"connect_bank_account\": \"ربط حساب بنكي\",\n  \"seller_mode\": \"وضع البائع\",\n  \"active\": \"نشط\",\n  \"details\": \"تفاصيل\",\n  \"audience_size\": \"حجم الجمهور\",\n  \"add_another_platform\": \"أضف منصة أخرى\",\n  \"send_an_offer_for\": \"أرسل عرضًا مقابل $\",\n  \"complete_order_and_pay_early\": \"أكمل الطلب وادفع مبكرًا\",\n  \"order_in_progress\": \"الطلب قيد التنفيذ\",\n  \"create_a_new_offer\": \"أنشئ عرضًا جديدًا\",\n  \"orders\": \"الطلبات\",\n  \"price\": \"السعر\",\n  \"state\": \"الحالة\",\n  \"showing\": \"عرض\",\n  \"to\": \"إلى\",\n  \"from\": \"من\",\n  \"results\": \"النتائج\",\n  \"content_writer\": \"كاتب محتوى\",\n  \"influencer\": \"مؤثر\",\n  \"request_service\": \"طلب خدمة\",\n  \"the_marketplace_is_not_opened_yet\": \"السوق لم يُفتح بعد\",\n  \"check_again_soon\": \"تحقق مرة أخرى قريبًا!\",\n  \"filter\": \"تصفية\",\n  \"result\": \"نتيجة\",\n  \"seller\": \"بائع\",\n  \"buyer\": \"مشتري\",\n  \"discord_support\": \"دعم ديسكورد\",\n  \"teams\": \"الفرق\",\n  \"webhooks_1\": \"ويب هوكس\",\n  \"auto_post\": \"نشر تلقائي\",\n  \"logout_from\": \"تسجيل الخروج من\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"انضم إلى أكثر من 10,000 رائد أعمال يستخدمون Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"لإدارة جميع قنوات التواصل الاجتماعي الخاصة بك\",\n  \"100_no_risk_trial\": \"تجربة خالية من المخاطر 100%\",\n  \"pay_nothing_for_the_first_7_days\": \"لا تدفع شيئًا لأول 7 أيام\",\n  \"cancel_anytime_hassle_free\": \"يمكنك الإلغاء في أي وقت من الإعدادات\",\n  \"add_free_subscription\": \"-- أضف اشتراكًا مجانيًا --\",\n  \"currently_impersonating\": \"يتم الآن الانتحال\",\n  \"user_1\": \"المستخدم:\",\n  \"drag_n_drop_some_files_here\": \"اسحب وأفلت بعض الملفات هنا\",\n  \"add_time_slot\": \"أضف فترة زمنية\",\n  \"add_slot\": \"إضافة خانة\",\n  \"cancel_publication\": \"إلغاء النشر\",\n  \"statistics\": \"الإحصائيات\",\n  \"loading\": \"جارٍ التحميل\",\n  \"short_link\": \"رابط مختصر\",\n  \"original_link\": \"الرابط الأصلي\",\n  \"clicks\": \"النقرات\",\n  \"selected_customer\": \"العميل المحدد\",\n  \"customer\": \"العميل:\",\n  \"repeat_post_every\": \"تكرار النشر كل...\",\n  \"use_this_media\": \"استخدم هذه الوسائط\",\n  \"create_new_post\": \"إنشاء منشور\",\n  \"update_post\": \"تحديث المنشور الحالي\",\n  \"merge_comments_into_one_post\": \"دمج التعليقات في منشور واحد\",\n  \"accounts_that_will_engage\": \"الحسابات التي ستتفاعل:\",\n  \"day\": \"يوم\",\n  \"week\": \"أسبوع\",\n  \"month\": \"شهر\",\n  \"remove_from_customer\": \"إزالة من العميل\",\n  \"show_more\": \"+ عرض المزيد\",\n  \"show_less\": \"- عرض أقل\",\n  \"upload\": \"رفع\",\n  \"ai\": \"الذكاء الاصطناعي\",\n  \"add_channel\": \"إضافة قناة\",\n  \"add_platform\": \"إضافة منصة\",\n  \"articles\": \"مقالات\",\n  \"add_comment\": \"أضف تعليقًا\",\n  \"add_post\": \"أضف منشورًا في سلسلة النقاش\",\n  \"add_comment_or_post\": \"أضف تعليقًا / منشورًا\",\n  \"you_are_in_global_editing_mode\": \"أنت في وضع التحرير العام\",\n  \"the_post_should_be_at_least_6_characters_long\": \"يجب أن يكون المنشور مكونًا من 6 أحرف على الأقل\",\n  \"are_you_sure_you_want_to_delete_post\": \"هل أنت متأكد أنك تريد حذف هذا المنشور؟\",\n  \"post_deleted_successfully\": \"تم حذف المنشور بنجاح\",\n  \"delete_post\": \"حذف المنشور\",\n  \"save_as_draft\": \"حفظ كمسودة\",\n  \"post_now\": \"انشر الآن\",\n  \"please_add\": \"يرجى إضافة\",\n  \"to_your_telegram_group_channel_and_click_here\": \"إلى مجموعة / قناة التليجرام الخاصة بك ثم اضغط هنا:\",\n  \"connect_telegram\": \"ربط التليجرام\",\n  \"please_add_the_following_command_in_your_chat\": \"يرجى إضافة الأمر التالي في الدردشة الخاصة بك:\",\n  \"copy\": \"نسخ\",\n  \"settings\": \"الإعدادات\",\n  \"integrations\": \"التكاملات\",\n  \"add_integration\": \"إضافة تكامل\",\n  \"you_are_now_editing_only\": \"أنت الآن تقوم بالتحرير فقط\",\n  \"tag_a_company\": \"الإشارة إلى شركة\",\n  \"video_length_is_invalid_must_be_up_to\": \"مدة الفيديو غير صالحة، يجب أن تكون حتى\",\n  \"seconds\": \"ثوانٍ\",\n  \"this_feature_available_only_for_photos\": \"هذه الميزة متاحة فقط للصور، وسيتم إضافة موسيقى افتراضية يمكنك تغييرها لاحقًا.\",\n  \"allow_user_to\": \"السماح للمستخدم بـ:\",\n  \"your_video_will_be_labeled_promotional\": \"سيتم تصنيف الفيديو الخاص بك كـ \\\"محتوى ترويجي\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"لا يمكن تغيير ذلك بعد نشر الفيديو الخاص بك.\",\n  \"turn_on_to_disclose_video_promotes\": \"فعّل هذا الخيار للإفصاح عن أن هذا الفيديو يروّج لسلع أو خدمات مقابل شيء ذي قيمة. قد يروّج الفيديو لنفسك أو لطرف ثالث أو لكليهما.\",\n  \"you_are_promoting_yourself\": \"أنت تروّج لنفسك أو لعلامتك التجارية الخاصة.\",\n  \"this_video_will_be_classified_brand_organic\": \"سيتم تصنيف هذا الفيديو كـ محتوى عضوي للعلامة التجارية.\",\n  \"you_are_promoting_another_brand\": \"أنت تروّج لعلامة تجارية أخرى أو لطرف ثالث.\",\n  \"this_video_will_be_classified_branded_content\": \"سيتم تصنيف هذا الفيديو كمحتوى يحمل علامة تجارية.\",\n  \"by_posting_you_agree_to_tiktoks\": \"بنشرك، أنت توافق على شروط تيك توك\",\n  \"music_usage_confirmation\": \"تأكيد استخدام الموسيقى\",\n  \"branded_content_policy\": \"سياسة المحتوى الممول\",\n  \"select_1\": \"--اختر--\",\n  \"select_flair\": \"--اختر الوسم--\",\n  \"link\": \"رابط\",\n  \"add_subreddit\": \"أضف منتدى فرعي\",\n  \"please_add_at_least_one_subreddit\": \"يرجى إضافة منتدى فرعي واحد على الأقل\",\n  \"add_community\": \"أضف مجتمع\",\n  \"select_post_type\": \"اختر نوع المنشور...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"لم نتمكن من العثور على أي نشاط تجاري مرتبط بصفحة لينكدإن الخاصة بك.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"يرجى إغلاق هذه النافذة، وإنشاء صفحة جديدة، ثم إضافة قناة جديدة مرة أخرى.\",\n  \"select_linkedin_page\": \"اختر صفحة لينكدإن:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"لم نتمكن من العثور على أي نشاط تجاري مرتبط بالصفحات المحددة.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"نوصي بربط جميع الصفحات وجميع الأنشطة التجارية.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"يرجى إغلاق هذه النافذة، حذف التكامل الخاص بك، ثم إضافة قناة جديدة مرة أخرى.\",\n  \"select_instagram_account\": \"اختر حساب إنستغرام:\",\n  \"select_page\": \"اختر الصفحة:\",\n  \"generate_image_with_ai\": \"توليد صورة بالذكاء الاصطناعي\",\n  \"reconnect_channel\": \"إعادة ربط القناة\",\n  \"update_credentials\": \"تحديث بيانات الاعتماد\",\n  \"additional_settings\": \"إعدادات إضافية\",\n  \"change_bot\": \"تغيير الروبوت\",\n  \"move_add_to_customer\": \"نقل / إضافة إلى العميل\",\n  \"edit_time_slots\": \"تعديل الفترات الزمنية\",\n  \"enable_channel\": \"تفعيل القناة\",\n  \"disable_channel\": \"تعطيل القناة\",\n  \"add\": \"إضافة\",\n  \"short_post\": \"منشور قصير\",\n  \"long_post\": \"منشور طويل\",\n  \"a_thread_with_short_posts\": \"سلسلة منشورات قصيرة\",\n  \"a_thread_with_long_posts\": \"سلسلة منشورات طويلة\",\n  \"personal_voice_i_am_happy_to_announce\": \"بصوت شخصي (\\\"يسعدني أن أعلن\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"بصوت الشركة (\\\"يسعدنا أن نعلن\\\")\",\n  \"generate\": \"توليد\",\n  \"generate_posts\": \"توليد منشورات\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"اشترِ حساب PRO مدى الحياة مقابل SOL (199 دولارًا)، يرجى العلم أنه لا يوجد استرداد لهذا الشراء.\",\n  \"purchase_now\": \"اشترِ الآن\",\n  \"pay_today\": \"ادفع اليوم\",\n  \"we_are_sorry_to_see_you_go\": \"نأسف لرؤيتك ترحل :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"هل تمانع أن تخبرنا باختصار كيف كان بإمكاننا أن نكون أفضل؟\",\n  \"cancel_subscription\": \"إلغاء الاشتراك\",\n  \"plans\": \"الخطط\",\n  \"monthly\": \"شهريًا\",\n  \"yearly\": \"سنويًا\",\n  \"reactivate_subscription\": \"إعادة تفعيل الاشتراك\",\n  \"update_payment_method_invoices_history\": \"تحديث طريقة الدفع / سجل الفواتير\",\n  \"cancel_subscription_1\": \"إلغاء الاشتراك\",\n  \"your_subscription_will_be_canceled_at\": \"سيتم إلغاء اشتراكك في\",\n  \"you_will_never_be_charged_again\": \"لن يتم خصم أي رسوم منك مرة أخرى\",\n  \"current_package\": \"الباقة الحالية:\",\n  \"next_package\": \"الباقة التالية:\",\n  \"claim\": \"مطالبة\",\n  \"frequently_asked_questions\": \"الأسئلة الشائعة\",\n  \"autopost\": \"النشر التلقائي\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"يمكن للنشر التلقائي نشر العناصر الجديدة من موجز RSS الخاص بك تلقائيًا على وسائل التواصل الاجتماعي\",\n  \"title\": \"العنوان\",\n  \"add_an_autopost\": \"إضافة نشر تلقائي\",\n  \"post_content\": \"محتوى المنشور\",\n  \"sign_up\": \"تسجيل حساب\",\n  \"or\": \"أو\",\n  \"by_registering_you_agree_to_our\": \"بتسجيلك، أنت توافق على\",\n  \"and\": \"و\",\n  \"terms_of_service\": \"شروط الخدمة\",\n  \"privacy_policy\": \"سياسة الخصوصية\",\n  \"create_account\": \"إنشاء حساب\",\n  \"already_have_an_account\": \"هل لديك حساب بالفعل؟\",\n  \"sign_in\": \"تسجيل الدخول\",\n  \"sign_in_1\": \"تسجيل الدخول\",\n  \"don_t_have_an_account\": \"ليس لديك حساب؟\",\n  \"forgot_password\": \"نسيت كلمة المرور\",\n  \"forgot_password_1\": \"نسيت كلمة المرور\",\n  \"send_password_reset_email\": \"إرسال بريد إعادة تعيين كلمة المرور\",\n  \"go_back_to_login\": \"العودة إلى تسجيل الدخول\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"لقد أرسلنا لك بريدًا إلكترونيًا يحتوي على رابط لإعادة تعيين كلمة المرور الخاصة بك.\",\n  \"change_password\": \"تغيير كلمة المرور\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"تمت إعادة تعيين كلمة المرور بنجاح. يمكنك الآن تسجيل الدخول باستخدام\",\n  \"click_here_to_go_back_to_login\": \"انقر هنا للعودة إلى تسجيل الدخول\",\n  \"activate_your_account\": \"فعّل حسابك\",\n  \"thank_you_for_registering\": \"شكرًا لتسجيلك!\",\n  \"please_check_your_email_to_activate_your_account\": \"يرجى التحقق من بريدك الإلكتروني لتفعيل حسابك.\",\n  \"sign_in_with\": \"تسجيل الدخول باستخدام\",\n  \"continue_with_google\": \"المتابعة باستخدام جوجل\",\n  \"sign_in_with_github\": \"تسجيل الدخول باستخدام جيت هب\",\n  \"continue_with_farcaster\": \"المتابعة باستخدام فاركاستر\",\n  \"continue_with_your_wallet\": \"المتابعة باستخدام محفظتك\",\n  \"stars_per_day\": \"النجوم في اليوم\",\n  \"media\": \"الوسائط\",\n  \"check_launch\": \"تحقق من الإطلاق\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"قم بتحميل مستودع GitHub الخاص بك من الإعدادات لعرض التحليلات\",\n  \"stars\": \"النجوم\",\n  \"processing_stars\": \"جاري معالجة النجوم...\",\n  \"forks\": \"التفريعات\",\n  \"registration_is_disabled\": \"التسجيل معطل\",\n  \"login_instead\": \"سجّل الدخول بدلًا من ذلك\",\n  \"gitroom\": \"جيت روم\",\n  \"select_a_conversation_and_chat_away\": \"اختر محادثة وابدأ الدردشة.\",\n  \"adding_channel_redirecting_you\": \"يتم إضافة القناة، جاري إعادة التوجيه\",\n  \"could_not_add_provider\": \"تعذر إضافة المزوّد.\",\n  \"you_are_being_redirected_back\": \"يتم إعادتك إلى الصفحة السابقة\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"نواجه بعض الصعوبات، حاول تحديث الصفحة\",\n  \"post_not_found\": \"لم يتم العثور على المنشور\",\n  \"publication_date\": \"تاريخ النشر:\",\n  \"analytics\": \"تحليلات\",\n  \"launches\": \"إطلاقات\",\n  \"plugs\": \"إعلانات\",\n  \"billing\": \"الفوترة\",\n  \"affiliate\": \"شريك\",\n  \"monday\": \"الاثنين\",\n  \"tuesday\": \"الثلاثاء\",\n  \"wednesday\": \"الأربعاء\",\n  \"thursday\": \"الخميس\",\n  \"friday\": \"الجمعة\",\n  \"saturday\": \"السبت\",\n  \"sunday\": \"الأحد\",\n  \"can_t_change_date_remove_post_from_publication\": \"لا يمكن تغيير التاريخ، أزل المنشور من النشر\",\n  \"predicted_github_trending_change\": \"التغير المتوقع في ترند GitHub\",\n  \"duplicate_post\": \"منشور مكرر\",\n  \"preview_post\": \"معاينة المنشور\",\n  \"post_statistics\": \"إحصائيات المنشور\",\n  \"draft\": \"مسودة\",\n  \"week_number\": \"الأسبوع {{number}}\",\n  \"top_title_edit_webhook\": \"تعديل Webhook\",\n  \"top_title_add_webhook\": \"إضافة Webhook\",\n  \"top_title_oh_no\": \"أوه لا\",\n  \"top_title_auto_plug\": \"التوصيل التلقائي: {{title}}\",\n  \"top_title_edit_autopost\": \"تعديل النشر التلقائي\",\n  \"top_title_add_autopost\": \"إضافة نشر تلقائي\",\n  \"top_title_send_a_new_offer\": \"إرسال عرض جديد\",\n  \"top_title_media_library\": \"مكتبة الوسائط\",\n  \"top_title_add_signature\": \"إضافة توقيع\",\n  \"top_title_send_a_message_to\": \"إرسال رسالة إلى {{name}}\",\n  \"top_title_configure_provider\": \"تهيئة المزود\",\n  \"top_title_add_member\": \"إضافة عضو\",\n  \"top_title_change_bot_picture\": \"تغيير صورة البوت\",\n  \"top_title_create_a_new_tag\": \"إنشاء وسم جديد\",\n  \"top_title_select_company\": \"اختيار الشركة\",\n  \"top_title_additional_settings\": \"إعدادات إضافية\",\n  \"top_title_time_table_slots\": \"فترات الجدول الزمني\",\n  \"top_title_design_media\": \"تصميم الوسائط\",\n  \"top_title_edit_post\": \"تعديل المنشور\",\n  \"top_title_create_post\": \"إنشاء منشور\",\n  \"top_title_move__add_to_customer\": \"نقل / إضافة إلى العميل\",\n  \"top_title_add_api_key_for\": \"إضافة مفتاح API لـ {{name}}\",\n  \"top_title_instance_url\": \"رابط النسخة\",\n  \"top_title_custom_url\": \"رابط مخصص\",\n  \"top_title_add_channel\": \"إضافة قناة\",\n  \"top_title_add_telegram\": \"إضافة تيليجرام\",\n  \"top_title_add_wrapcast\": \"إضافة Wrapcast\",\n  \"top_title_comments_for\": \"تعليقات ليوم {{date}}\",\n  \"top_title_edit_signature\": \"تعديل التوقيع\",\n  \"label_name\": \"الاسم\",\n  \"label_url\": \"الرابط\",\n  \"label_title\": \"العنوان\",\n  \"label_subtitle\": \"العنوان الفرعي\",\n  \"label_email\": \"البريد الإلكتروني\",\n  \"label_full_name\": \"الاسم الكامل\",\n  \"label_password\": \"كلمة المرور\",\n  \"label_confirm_password\": \"تأكيد كلمة المرور\",\n  \"label_api_key\": \"مفتاح API\",\n  \"label_instance_url\": \"رابط النسخة\",\n  \"label_custom_url\": \"رابط مخصص\",\n  \"label_feedback\": \"ملاحظات\",\n  \"label_bio\": \"نبذة تعريفية\",\n  \"label_role\": \"الدور\",\n  \"label_country\": \"الدولة\",\n  \"label_audience_size\": \"حجم الجمهور على جميع المنصات\",\n  \"label_pick_time\": \"اختر الوقت\",\n  \"label_nickname\": \"الاسم المستعار\",\n  \"label_write_anything\": \"اكتب أي شيء\",\n  \"label_output_format\": \"تنسيق الإخراج\",\n  \"label_add_pictures\": \"إضافة صور؟\",\n  \"label_hour\": \"ساعة\",\n  \"label_minutes\": \"دقائق\",\n  \"label_select_publication\": \"اختر النشر\",\n  \"label_canonical_link\": \"الرابط القانوني\",\n  \"label_cover_picture\": \"صورة الغلاف\",\n  \"label_tags\": \"الوسوم\",\n  \"label_topics\": \"المواضيع\",\n  \"label_tags_maximum_4\": \"الوسوم (بحد أقصى 4)\",\n  \"label_attachments\": \"المرفقات\",\n  \"label_type\": \"النوع\",\n  \"label_thumbnail\": \"الصورة المصغرة\",\n  \"label_who_can_see_this_video\": \"من يمكنه مشاهدة هذا الفيديو؟\",\n  \"label_content_posting_method\": \"طريقة نشر المحتوى\",\n  \"label_auto_add_music\": \"إضافة الموسيقى تلقائيًا\",\n  \"label_duet\": \"دويتو\",\n  \"label_stitch\": \"دمج\",\n  \"label_comments\": \"التعليقات\",\n  \"label_disclose_video_content\": \"الإفصاح عن محتوى الفيديو\",\n  \"label_your_brand\": \"علامتك التجارية\",\n  \"label_branded_content\": \"محتوى برعاية علامة تجارية\",\n  \"label_subreddit\": \"مجتمع فرعي\",\n  \"label_flair\": \"شارة\",\n  \"label_media\": \"الوسائط\",\n  \"label_search_subreddit\": \"البحث عن مجتمع فرعي\",\n  \"label_delay\": \"تأخير\",\n  \"label_post_type\": \"نوع المنشور\",\n  \"label_collaborators\": \"المتعاونون (بحد أقصى 3) - لا يمكن أن تكون الحسابات خاصة\",\n  \"label_community\": \"المجتمع\",\n  \"label_search_community\": \"ابحث في المجتمع\",\n  \"label_channel\": \"القناة\",\n  \"label_search_channel\": \"ابحث عن قناة\",\n  \"label_select_channel\": \"اختر قناة\",\n  \"label_new_password\": \"كلمة المرور الجديدة\",\n  \"label_repeat_password\": \"أعد إدخال كلمة المرور\",\n  \"label_platform\": \"المنصة\",\n  \"label_price_per_post\": \"السعر لكل منشور\",\n  \"label_integrations\": \"التكاملات\",\n  \"label_code\": \"الرمز\",\n  \"label_should_sync_last_post\": \"هل نزامن آخر منشور حالي؟\",\n  \"label_when_post\": \"متى يجب أن ننشره؟\",\n  \"label_autogenerate_content\": \"توليد المحتوى تلقائيًا\",\n  \"label_generate_picture\": \"توليد صورة؟\",\n  \"label_company\": \"الشركة\",\n  \"label_tag_color\": \"لون الوسم\",\n  \"label_select_board\": \"اختر لوحة\",\n  \"label_select_organization\": \"اختر منظمة\",\n  \"label_auto_add_signature\": \"إضافة توقيع تلقائيًا؟\",\n  \"enable_color_picker\": \"تفعيل منتقي الألوان\",\n  \"cancel_the_color_picker\": \"إلغاء منتقي الألوان\",\n  \"no_content_yet\": \"لا يوجد محتوى بعد\",\n  \"write_your_reply\": \"اكتب منشورك...\",\n  \"add_a_tag\": \"أضف وسمًا\",\n  \"add_to_calendar\": \"أضف إلى التقويم\",\n  \"select_channels_from_circles\": \"اختر القنوات من الدوائر أعلاه\",\n  \"not_matching_order\": \"الترتيب غير متطابق\",\n  \"submit_for_order\": \"إرسال للطلب\",\n  \"schedule\": \"جدولة\",\n  \"update\": \"تحديث\",\n  \"attachments\": \"المرفقات\",\n  \"tags\": \"الوسوم\",\n  \"public_to_everyone\": \"عام للجميع\",\n  \"mutual_follow_friends\": \"أصدقاء المتابعة المتبادلة\",\n  \"follower_of_creator\": \"متابع للمنشئ\",\n  \"self_only\": \"لنفسك فقط\",\n  \"post_content_directly_to_tiktok\": \"انشر المحتوى مباشرة على تيك توك\",\n  \"upload_content_to_tiktok_without_posting\": \"ارفع المحتوى إلى تيك توك دون نشره\",\n  \"choose_upload_without_posting_description\": \"اختر الرفع دون النشر إذا كنت ترغب في مراجعة وتحرير المحتوى الخاص بك داخل تطبيق تيك توك قبل النشر. هذا يمنحك إمكانية الوصول إلى أدوات التحرير المدمجة في تيك توك ويسمح لك بإجراء التعديلات النهائية قبل النشر.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"هل سيتم تحميلي رسوم من قبل Postiz؟\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"لتأكيد معلومات بطاقة الائتمان، سيحتجز Postiz مبلغ 2 دولار ويعيده فوراً، يمكنك إلغاء اشتراكك في أي وقت من الإعدادات دون الحاجة للتحدث مع أي شخص.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"هل يمكنني الوثوق بـ Postiz؟\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz مفتوح المصدر بكل فخر! نحن نؤمن بثقافة أخلاقية وشفافة، مما يعني أن Postiz سيبقى للأبد. يمكنك الاطلاع على الكود بالكامل أو استخدامه في مشاريعك الشخصية. لعرض مستودع المصدر المفتوح، <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">اضغط هنا</a>.\",\n  \"faq_what_are_channels\": \"ما هي القنوات؟\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"يتيح لك Postiz جدولة منشوراتك بين قنوات مختلفة.\\nالقناة هي منصة نشر يمكنك من خلالها جدولة منشوراتك.\\nعلى سبيل المثال، يمكنك جدولة منشوراتك على X، فيسبوك، إنستغرام، تيك توك، يوتيوب، ريديت، لينكدإن، Dribbble، Threads وPinterest.\",\n  \"faq_what_are_team_members\": \"من هم أعضاء الفريق؟\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"إذا كان لديك فريق يضم عدة أعضاء، يمكنك دعوتهم إلى مساحة العمل الخاصة بك للتعاون في منشوراتك وإضافة قنواتهم الشخصية\",\n  \"faq_what_is_ai_auto_complete\": \"ما هو الإكمال التلقائي بالذكاء الاصطناعي؟\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"نحن نستخدم ChatGPT لمساعدتك في كتابة المنشورات والمقالات الاجتماعية.\",\n  \"enter_email\": \"أدخل البريد الإلكتروني\",\n  \"are_you_sure\": \"هل أنت متأكد؟\",\n  \"no_cancel\": \"لا، إلغاء!\",\n  \"are_you_sure_you_want_to_delete\": \"هل أنت متأكد أنك تريد حذف {{name}}؟\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"هل أنت متأكد أنك تريد حذف الصورة؟\",\n  \"are_you_sure_you_want_to_logout\": \"هل أنت متأكد أنك تريد تسجيل الخروج؟\",\n  \"yes_logout\": \"نعم، تسجيل الخروج\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"هل أنت متأكد أنك تريد حذف هذه الخانة؟\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"هل أنت متأكد أنك تريد حذف هذا المجتمع؟\",\n  \"are_you_sure_you_want_to_close_the_window\": \"هل أنت متأكد أنك تريد إغلاق النافذة؟\",\n  \"yes_close\": \"نعم، أغلق\",\n  \"link_copied_to_clipboard\": \"تم نسخ الرابط إلى الحافظة\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"هل أنت متأكد أنك تريد إغلاق هذه النافذة؟ (سيتم فقدان جميع البيانات)\",\n  \"yes_close_it\": \"نعم، أغلقها!\",\n  \"uploading_pictures\": \"جاري رفع الصور...\",\n  \"agent_starting\": \"يتم بدء الوكيل\",\n  \"researching_your_content\": \"يتم البحث في المحتوى الخاص بك...\",\n  \"understanding_the_category\": \"يتم فهم الفئة...\",\n  \"finding_the_topic\": \"يتم العثور على الموضوع...\",\n  \"finding_popular_posts_to_match_with\": \"يتم البحث عن منشورات شائعة للمطابقة معها...\",\n  \"generating_hook\": \"يتم إنشاء جملة جذب...\",\n  \"generating_content\": \"يتم إنشاء المحتوى...\",\n  \"generating_pictures\": \"يتم إنشاء الصور...\",\n  \"finding_time_to_post\": \"يتم تحديد وقت النشر...\",\n  \"write_anything\": \"اكتب أي شيء\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"يمكنك كتابة أي شيء تريده، ويمكنك أيضًا إضافة الروابط، سنقوم نحن بالبحث من أجلك...\",\n  \"output_format\": \"تنسيق الإخراج\",\n  \"add_pictures\": \"إضافة صور؟\",\n  \"7_days\": \"7 أيام\",\n  \"30_days\": \"30 يومًا\",\n  \"90_days\": \"90 يومًا\",\n  \"start_7_days_free_trial\": \"ابدأ تجربة مجانية لمدة 7 أيام\",\n  \"change_language\": \"تغيير اللغة\",\n  \"that_a_wrap\": \"انتهينا!\\n\\nإذا أعجبك هذا التسلسل:\\n\\n1. تابعني على @{{username}} للمزيد من هذه المواضيع\\n2. أعد تغريد التغريدة أدناه لمشاركة هذا التسلسل مع جمهورك\\n\",\n  \"post_as_images_carousel\": \"انشر كعرض شرائح للصور\",\n  \"save_set\": \"حفظ المجموعة\",\n  \"separate_post\": \"فصل المنشور إلى عدة منشورات\",\n  \"label_who_can_reply_to_this_post\": \"من يمكنه الرد على هذا المنشور؟\",\n  \"delete_integration\": \"حذف التكامل\",\n  \"start_writing_your_post\": \"ابدأ بكتابة منشورك لمعاينة\",\n  \"billing_join_over\": \"انضم إلى أكثر من\",\n  \"billing_entrepreneurs_count\": \"أكثر من 20,000 رائد أعمال\",\n  \"billing_who_use\": \"الذين يستخدمون\",\n  \"billing_postiz_grow_social\": \"Postiz لتنمية حضورهم على وسائل التواصل الاجتماعي\",\n  \"billing_no_risk_trial\": \"تجربة مجانية بدون أي مخاطرة 100%\",\n  \"billing_pay_nothing_7_days\": \"لا تدفع شيئًا لأول 7 أيام\",\n  \"billing_cancel_anytime\": \"يمكنك الإلغاء في أي وقت من الإعدادات\",\n  \"billing_choose_plan\": \"اختر خطة\",\n  \"billing_monthly\": \"شهريًا\",\n  \"billing_yearly\": \"سنويًا\",\n  \"billing_20_percent_off\": \"خصم 20%\",\n  \"billing_features\": \"المميزات\",\n  \"billing_channel\": \"قناة\",\n  \"billing_channels\": \"قنوات\",\n  \"billing_unlimited\": \"غير محدود\",\n  \"billing_posts_per_month\": \"منشورات في الشهر\",\n  \"billing_unlimited_team_members\": \"عدد غير محدود من أعضاء الفريق\",\n  \"billing_ai_auto_complete\": \"إكمال تلقائي بالذكاء الاصطناعي\",\n  \"billing_ai_copilots\": \"مساعدون بالذكاء الاصطناعي\",\n  \"billing_ai_autocomplete\": \"إكمال تلقائي بالذكاء الاصطناعي\",\n  \"billing_advanced_picture_editor\": \"محرر صور متقدم\",\n  \"billing_ai_images_per_month\": \"صور بالذكاء الاصطناعي شهريًا\",\n  \"billing_ai_videos_per_month\": \"فيديوهات بالذكاء الاصطناعي شهريًا\",\n  \"billing_billing_address\": \"عنوان الفاتورة\",\n  \"billing_payment\": \"الدفع\",\n  \"billing_powered_by_stripe\": \"مدفوعات آمنة تتم معالجتها بواسطة\",\n  \"billing_your_7_day_trial_is\": \"فترة التجربة المجانية لمدة 7 أيام الخاصة بك هي\",\n  \"billing_100_percent_free\": \"مجانية 100%\",\n  \"billing_ending\": \"تنتهي\",\n  \"billing_cancel_anytime_short\": \"يمكنك الإلغاء في أي وقت من الإعدادات\",\n  \"billing_pay_0_start_trial\": \"ادفع 0 دولار اليوم - ابدأ تجربتك المجانية!\",\n  \"billing_pay_now\": \"ادفع الآن\",\n  \"billing_per_month\": \"/ شهريًا\",\n  \"billing_per_year\": \"/ سنوياً\",\n  \"billing_order_summary\": \"ملخص الطلب\",\n  \"billing_applied\": \"تم التطبيق\",\n  \"billing_due_today\": \"المستحق اليوم\",\n  \"billing_then\": \"ثم\",\n  \"billing_on\": \"في\",\n  \"billing_discount_applied\": \"تم تطبيق الخصم\",\n  \"billing_remove\": \"إزالة\",\n  \"billing_coupon_expires\": \"تنتهي صلاحية القسيمة في\",\n  \"billing_invalid_coupon\": \"رمز القسيمة غير صالح\",\n  \"billing_coupon_applied\": \"تم تطبيق القسيمة بنجاح!\",\n  \"billing_coupon_removed\": \"تمت إزالة القسيمة\",\n  \"billing_error_removing_coupon\": \"حدث خطأ أثناء إزالة القسيمة\",\n  \"billing_have_discount_coupon\": \"هل لديك قسيمة خصم؟\",\n  \"billing_discount_coupon\": \"قسيمة خصم\",\n  \"billing_cancel\": \"إلغاء\",\n  \"billing_enter_coupon_code\": \"أدخل رمز القسيمة\",\n  \"billing_applying\": \"جارٍ التطبيق...\",\n  \"billing_apply\": \"تطبيق\",\n  \"billing_subscription\": \"الاشتراك\",\n  \"billing_cancel_notice\": \"يمكنك الإلغاء في أي وقت من الإعدادات دون الحاجة للتحدث مع أي شخص ولن يتم خصم أي مبلغ منك.\",\n  \"select_channels\": \"اختر القنوات\",\n  \"start_a_new_chat\": \"ابدأ محادثة جديدة\",\n  \"your_assistant\": \"مساعدك\",\n  \"agent_welcome_message\": \"مرحبًا، أنا وكيل Postiz الخاص بك 🙌🏻.\\n\\nيمكنني جدولة منشور أو عدة منشورات لعدة قنوات، وتوليد الصور والفيديوهات.\\n\\nيمكنك اختيار القنوات التي تريد استخدامها من القائمة اليسرى.\\n\\nيمكنك رؤية محادثاتك السابقة من القائمة اليمنى.\\n\\nيمكنك أيضًا استخدامي كخادم MCP، تحقق من الإعدادات >> واجهة برمجة التطبيقات العامة (Public API)\",\n  \"last_github_trending\": \"آخر الترندات على Github\",\n  \"next_predicted_github_trending\": \"الترند المتوقع القادم على Github\",\n  \"repository\": \"المستودع\",\n  \"date\": \"التاريخ\",\n  \"total_stars\": \"إجمالي النجوم\",\n  \"total_forks\": \"إجمالي التفرعات\",\n  \"continue_with\": \"المتابعة باستخدام\",\n  \"email_address\": \"عنوان البريد الإلكتروني\",\n  \"email_already_exists\": \"البريد الإلكتروني موجود بالفعل\",\n  \"google\": \"جوجل\",\n  \"farcaster\": \"فاركاستر\",\n  \"edit_autopost\": \"تعديل النشر التلقائي\",\n  \"add_autopost_title\": \"إضافة نشر تلقائي\",\n  \"webhook_deleted_successfully\": \"تم حذف الويب هوك بنجاح\",\n  \"all_integrations\": \"جميع التكاملات\",\n  \"specific_integrations\": \"تكاملات محددة\",\n  \"post_on_next_available_slot\": \"النشر في أول وقت متاح\",\n  \"post_immediately\": \"انشر فورًا\",\n  \"could_not_use_rss_feed\": \"تعذر استخدام موجز RSS هذا\",\n  \"rss_valid\": \"موجز RSS صالح!\",\n  \"autopost_updated_successfully\": \"تم تحديث النشر التلقائي بنجاح\",\n  \"autopost_added_successfully\": \"تمت إضافة النشر التلقائي بنجاح\",\n  \"write_your_post_placeholder\": \"اكتب منشورك...\",\n  \"select_or_upload_pictures_max_5\": \"اختر أو حمّل صورًا (بحد أقصى 5 في المرة الواحدة).\",\n  \"you_can_drag_drop_pictures\": \"يمكنك أيضًا سحب وإفلات الصور.\",\n  \"you_dont_have_any_media_yet\": \"ليس لديك أي وسائط بعد\",\n  \"media_library\": \"مكتبة الوسائط\",\n  \"media_settings\": \"إعدادات الوسائط\",\n  \"media_editor\": \"محرر الوسائط\",\n  \"close\": \"إغلاق\",\n  \"me\": \"أنا\",\n  \"noname\": \"بدون اسم\",\n  \"password_reset_link_expired\": \"انتهت صلاحية رابط إعادة تعيين كلمة المرور. يرجى المحاولة مرة أخرى.\",\n  \"invalid_api_key\": \"مفتاح API غير صالح\",\n  \"could_not_connect_to_platform\": \"تعذر الاتصال بالمنصة\",\n  \"web3_provider\": \"مزود Web3\",\n  \"add_provider_title\": \"إضافة مزود\",\n  \"profile_updated\": \"تم تحديث الملف الشخصي\",\n  \"sets\": \"مجموعات\",\n  \"invitation_link_sent\": \"تم إرسال رابط الدعوة\",\n  \"send_invitation_link\": \"إرسال رابط الدعوة\",\n  \"copy_link\": \"نسخ الرابط\",\n  \"are_you_sure_remove_team_member\": \"هل أنت متأكد أنك تريد إزالة هذا العضو من الفريق؟\",\n  \"admin\": \"مشرف\",\n  \"super_admin\": \"مشرف عام\",\n  \"update_webhook\": \"تحديث Webhook\",\n  \"add_webhook\": \"إضافة ويب هوك\",\n  \"webhook_updated_successfully\": \"تم تحديث الويب هوك بنجاح\",\n  \"webhook_added_successfully\": \"تمت إضافة الويب هوك بنجاح\",\n  \"webhook_sent\": \"تم إرسال الويب هوك\",\n  \"today\": \"اليوم\",\n  \"channel_disconnected_click_to_reconnect\": \"تم فصل القناة، انقر لإعادة الاتصال.\",\n  \"channel_disabled_upgrade_plan\": \"تم تعطيل هذه القناة، يرجى ترقية خطتك لتفعيلها.\",\n  \"channel_added\": \"تمت إضافة القناة\",\n  \"are_you_sure_disable_channel\": \"هل أنت متأكد أنك تريد تعطيل هذه القناة؟\",\n  \"disable_channel_title\": \"تعطيل القناة\",\n  \"channel_disabled\": \"تم تعطيل القناة\",\n  \"are_you_sure_delete_channel\": \"هل أنت متأكد أنك تريد حذف هذه القناة؟\",\n  \"delete_channel_title\": \"حذف القناة\",\n  \"delete_posts_before_channel\": \"يجب عليك حذف جميع المنشورات المرتبطة بهذه القناة قبل حذفها\",\n  \"channel_deleted\": \"تم حذف القناة\",\n  \"channel_enabled\": \"تم تفعيل القناة\",\n  \"time_table_slots\": \"فترات الجدول الزمني\",\n  \"channel_id_copied\": \"تم نسخ معرف القناة إلى الحافظة\",\n  \"settings_updated\": \"تم تحديث الإعدادات\",\n  \"customer_updated\": \"تم تحديث العميل\",\n  \"custom_url\": \"رابط مخصص\",\n  \"picture\": \"صورة\",\n  \"upgrade_required\": \"تحتاج إلى الترقية لاستخدام هذه الميزة\",\n  \"move_to_billing\": \"الانتقال إلى الفوترة\",\n  \"payment_required\": \"الدفع مطلوب\",\n  \"no_content\": \"لا يوجد محتوى\",\n  \"select_customer_tooltip\": \"اختر العميل\",\n  \"customers\": \"العملاء\",\n  \"hour\": \"ساعة\",\n  \"minutes\": \"دقائق\",\n  \"updated\": \"تم التحديث\",\n  \"change_bot_picture_title\": \"تغيير صورة البوت\",\n  \"select_customer_label\": \"اختر العميل\",\n  \"start_typing\": \"ابدأ الكتابة...\",\n  \"choose_set_or_continue\": \"اختر مجموعة أو تابع بدون واحدة\",\n  \"continue_without_set\": \"المتابعة بدون مجموعة\",\n  \"select_set\": \"اختر مجموعة\",\n  \"channel_settings\": \"الإعدادات\",\n  \"post_needs_content_or_image\": \"يجب أن يحتوي منشورك على حرف واحد على الأقل أو صورة واحدة.\",\n  \"please_fix_your_settings\": \"يرجى تصحيح إعداداتك\",\n  \"shortlink_urls_question\": \"هل تريد تقصير الروابط؟ سيسمح لك ذلك بالحصول على إحصائيات حول النقرات\",\n  \"yes_shortlink_it\": \"نعم، قصّر الرابط!\",\n  \"added_successfully\": \"تمت الإضافة بنجاح\",\n  \"updated_successfully\": \"تم التحديث بنجاح\",\n  \"create_post_title\": \"إنشاء منشور\",\n  \"post_preview\": \"معاينة المنشور\",\n  \"check_circles_above\": \"تحقق من الدوائر أعلاه\",\n  \"create_output\": \"إنشاء المخرجات\",\n  \"assistant_initial_message\": \"مرحبًا! يمكنني مساعدتك في تحسين منشوراتك على وسائل التواصل الاجتماعي.\",\n  \"no_longer_global_mode\": \"لم تعد في الوضع العالمي\",\n  \"two_days\": \"يومان\",\n  \"three_days\": \"ثلاثة أيام\",\n  \"four_days\": \"أربعة أيام\",\n  \"five_days\": \"خمسة أيام\",\n  \"six_days\": \"ستة أيام\",\n  \"two_weeks\": \"أسبوعان\",\n  \"repeat_post_every_label\": \"تكرار النشر كل\",\n  \"add_new_tag\": \"إضافة وسم جديد\",\n  \"tag_name\": \"الاسم\",\n  \"post_is_too_long\": \"المنشور طويل جدًا، يرجى تعديله\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"يجب أن يحتوي منشورك على حرف واحد على الأقل أو صورة واحدة.\",\n  \"internal_edit\": \"تعديل داخلي\",\n  \"are_you_sure_go_back_to_global_mode\": \"هذا الإجراء لا يمكن التراجع عنه. هل أنت متأكد أنك تريد العودة إلى الوضع العام؟\",\n  \"yes_go_back_to_global_mode\": \"نعم، العودة إلى الوضع العام\",\n  \"are_you_sure_delete_this_post\": \"هل أنت متأكد أنك تريد حذف هذا المنشور؟\",\n  \"yes_delete_it\": \"نعم، احذفه!\",\n  \"cant_edit_networks_when_creating_set\": \"لا يمكنك تعديل الشبكات أثناء إنشاء مجموعة\",\n  \"click_to_exit_global_editing\": \"انقر على هذا الزر للخروج من التحرير العام وتخصيص المنشور لهذه القناة\",\n  \"edit_content\": \"تعديل المحتوى\",\n  \"editing_a_specific_network\": \"تعديل شبكة محددة\",\n  \"back_to_global\": \"العودة إلى الوضع العام\",\n  \"delete_post_tooltip\": \"حذف المنشور\",\n  \"drop_files_here_to_upload\": \"أسقط ملفاتك هنا للتحميل\",\n  \"insert_emoji\": \"إدراج رمز تعبيري\",\n  \"write_something\": \"اكتب شيئًا …\",\n  \"click_channel_to_add\": \"انقر على قناة لإضافتها\",\n  \"connect_your_channels\": \"قم بتوصيل قنواتك\",\n  \"connect_social_media_to_start\": \"قم بتوصيل حسابات التواصل الاجتماعي لبدء جدولة المنشورات\",\n  \"connected_channels\": \"القنوات المتصلة\",\n  \"continue\": \"متابعة\",\n  \"continue_without_channels\": \"متابعة بدون قنوات\",\n  \"watch_tutorial\": \"مشاهدة الدليل\",\n  \"watch_tutorial_title\": \"تعلم كيفية استخدام Postiz\",\n  \"watch_tutorial_description\": \"شاهد هذا الفيديو القصير لتتعلم كيف تستفيد من Postiz بأفضل شكل\",\n  \"back\": \"رجوع\",\n  \"get_started\": \"ابدأ\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/bn/translation.json",
    "content": "{\n  \"calendar\": \"ক্যালেন্ডার\",\n  \"webhooks\": \"ওয়েবহুক\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"ওয়েবহুক হল একটি HTTP অনুরোধের মাধ্যমে Postiz-এ কিছু ঘটলে বিজ্ঞপ্তি পাওয়ার একটি উপায়।\",\n  \"name\": \"নাম\",\n  \"url\": \"URL\",\n  \"edit\": \"সম্পাদনা\",\n  \"delete\": \"মুছে ফেলুন\",\n  \"add_a_webhook\": \"একটি ওয়েবহুক যোগ করুন\",\n  \"save\": \"সংরক্ষণ করুন\",\n  \"send_test\": \"পরীক্ষা পাঠান\",\n  \"select_role\": \"ভূমিকা নির্বাচন করুন\",\n  \"video_made_with_ai\": \"এআই দিয়ে তৈরি ভিডিও\",\n  \"please_add_at_least\": \"কমপক্ষে ২০ অক্ষর যোগ করুন\",\n  \"send_invitation_via_email\": \"ইমেইলের মাধ্যমে আমন্ত্রণ পাঠান?\",\n  \"global_settings\": \"গ্লোবাল সেটিংস\",\n  \"copy_id\": \"চ্যানেল আইডি কপি করুন\",\n  \"team_members\": \"দলের সদস্যরা\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"আপনার অ্যাকাউন্ট পরিচালনা করতে আপনার সহায়ক বা দলের সদস্যকে আমন্ত্রণ জানান\",\n  \"remove\": \"সরান\",\n  \"add_another_member\": \"আরেকটি সদস্য যোগ করুন\",\n  \"signatures\": \"স্বাক্ষর\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"আপনি আপনার পোস্টে ব্যবহারের জন্য আপনার অ্যাকাউন্টে স্বাক্ষর যোগ করতে পারেন।\",\n  \"content\": \"বিষয়বস্তু\",\n  \"auto_add\": \"স্বয়ংক্রিয় যোগ?\",\n  \"delay_comment\": \"বিলম্ব মন্তব্য\",\n  \"actions\": \"কার্যক্রম\",\n  \"use_signature\": \"স্বাক্ষর ব্যবহার করুন\",\n  \"add_a_signature\": \"একটি স্বাক্ষর যোগ করুন\",\n  \"no\": \"না\",\n  \"yes\": \"হ্যাঁ\",\n  \"your_git_repository\": \"আপনার Git রিপোজিটরি\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"আপডেট এবং বিশ্লেষণ পেতে আপনার GitHub রিপোজিটরি সংযুক্ত করুন\",\n  \"connected\": \"সংযুক্ত:\",\n  \"disconnect\": \"সংযোগ বিচ্ছিন্ন করুন\",\n  \"connect_your_repository\": \"আপনার রিপোজিটরি সংযুক্ত করুন\",\n  \"cancel\": \"বাতিল\",\n  \"connect\": \"সংযুক্ত করুন\",\n  \"public_api\": \"পাবলিক API\",\n  \"check_n8n\": \"Postiz-এর জন্য আমাদের N8N কাস্টম নোডটি দেখুন।\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"আপনার টুলগুলির সাথে একীভূত করতে Postiz API ব্যবহার করুন।\",\n  \"read_how_to_use_it_over_the_documentation\": \"ডকুমেন্টেশনে এটি কীভাবে ব্যবহার করবেন তা পড়ুন।\",\n  \"reveal\": \"প্রকাশ করুন\",\n  \"copy_key\": \"কী কপি করুন\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"আপনার ক্লায়েন্টকে (Http স্ট্রিমিং) Postiz MCP সার্ভারের সাথে সংযুক্ত করুন, যাতে আরও দ্রুত আপনার পোস্টগুলো নির্ধারণ করতে পারেন!\",\n  \"share_with_a_client\": \"একটি ক্লায়েন্টের সাথে শেয়ার করুন\",\n  \"post\": \"পোস্ট\",\n  \"comments\": \"মন্তব্য\",\n  \"user\": \"ব্যবহারকারী\",\n  \"login_register_to_add_comments\": \"মন্তব্য যোগ করতে লগইন / নিবন্ধন করুন\",\n  \"status\": \"অবস্থা:\",\n  \"there_are_not_plugs_matching_your_channels\": \"আপনার চ্যানেলের সাথে মিলে এমন কোনো প্লাগ নেই\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"আপনাকে যোগ করতে হবে: X বা LinkedIn বা Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"চ্যানেল যোগ করতে ক্যালেন্ডারে যান\",\n  \"channels\": \"চ্যানেল\",\n  \"activate\": \"সক্রিয় করুন\",\n  \"this_channel_needs_to_be_refreshed\": \"এই চ্যানেলটি রিফ্রেশ করা প্রয়োজন,\",\n  \"click_here_to_refresh\": \"রিফ্রেশ করতে এখানে ক্লিক করুন\",\n  \"can_t_show_analytics_yet\": \"এখনও বিশ্লেষণ দেখাতে পারছি না\",\n  \"you_have_to_add_social_media_channels\": \"আপনাকে সোশ্যাল মিডিয়া চ্যানেল যোগ করতে হবে\",\n  \"supported\": \"সমর্থিত:\",\n  \"step\": \"ধাপ\",\n  \"skip_onboarding\": \"অনবোর্ডিং এড়িয়ে যান\",\n  \"onboarding\": \"অনবোর্ডিং\",\n  \"next\": \"পরবর্তী\",\n  \"you_are_done_from_here_you_can\": \"আপনি সম্পন্ন করেছেন, এখান থেকে আপনি পারেন:\",\n  \"view_analytics\": \"বিশ্লেষণ দেখুন\",\n  \"schedule_a_new_post\": \"একটি নতুন পোস্ট সময়সূচী করুন\",\n  \"to_sell_posts_you_would_have_to\": \"পোস্ট বিক্রি করতে আপনাকে করতে হবে:\",\n  \"1_connect_at_least_one_channel\": \"১. কমপক্ষে একটি চ্যানেল সংযুক্ত করুন\",\n  \"2_connect_you_bank_account\": \"২. আপনার ব্যাংক অ্যাকাউন্ট সংযুক্ত করুন\",\n  \"go_back_to_connect_channels\": \"চ্যানেল সংযুক্ত করতে ফিরে যান\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"আপনার ব্যাংক সংযুক্ত করতে বিক্রেতা পৃষ্ঠায় যান\",\n  \"connect_channels\": \"চ্যানেল সংযুক্ত করুন\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"পরে পোস্ট সময়সূচী করতে আপনার সোশ্যাল মিডিয়া এবং প্রকাশনা ওয়েবসাইট চ্যানেল সংযুক্ত করুন\",\n  \"social\": \"সামাজিক\",\n  \"publishing_platforms\": \"প্রকাশনা প্ল্যাটফর্ম\",\n  \"no_channels\": \"এখনও কোনো চ্যানেল নেই\",\n  \"connect_your_accounts\": \"সময়সূচি নির্ধারণ, প্রকাশ এবং বিশ্লেষণ শুরু করতে আপনার সোশ্যাল অ্যাকাউন্টগুলো সংযুক্ত করুন — সবকিছু এক জায়গায়।\",\n  \"notifications\": \"বিজ্ঞপ্তি\",\n  \"no_notifications\": \"কোনো বিজ্ঞপ্তি নেই\",\n  \"send_message\": \"বার্তা পাঠান\",\n  \"mar_28\": \"মার্চ ২৮\",\n  \"there_are_no_messages_yet\": \"এখনও কোনো বার্তা নেই।\",\n  \"checkout_the_marketplace\": \"মার্কেটপ্লেস দেখুন\",\n  \"go_to_marketplace\": \"মার্কেটপ্লেসে যান\",\n  \"all_messages\": \"সব বার্তা\",\n  \"previous\": \"পূর্ববর্তী\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"ছবি নির্বাচন বা আপলোড করুন (একসাথে সর্বোচ্চ ৫টি)\",\n  \"you_can_also_drag_drop_pictures\": \"আপনি ছবি টেনে এনে ছাড়তেও পারেন\",\n  \"you_don_t_have_any_assets_yet\": \"আপনার এখনও কোনো সম্পদ নেই।\",\n  \"click_the_button_below_to_upload_one\": \"একটি আপলোড করতে নিচের বোতামে ক্লিক করুন\",\n  \"click_the_button_below_to_upload_other\": \"একাধিক আপলোড করার জন্য নীচের বাটন ক্লিক করুন\",\n  \"add_selected_media\": \"নির্বাচিত মিডিয়া যোগ করুন\",\n  \"insert_media\": \"মিডিয়া সন্নিবেশ করুন\",\n  \"design_media\": \"মিডিয়া ডিজাইন করুন\",\n  \"select\": \"নির্বাচন করুন\",\n  \"editor\": \"সম্পাদক\",\n  \"clear\": \"পরিষ্কার করুন\",\n  \"order_completed\": \"অর্ডার সম্পন্ন\",\n  \"the_order_has_been_completed\": \"অর্ডারটি সম্পন্ন হয়েছে\",\n  \"post_has_been_published\": \"পোস্ট প্রকাশিত হয়েছে\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"নতুন অফার\",\n  \"platform\": \"প্ল্যাটফর্ম\",\n  \"posts\": \"পোস্ট\",\n  \"pay_accept_offer\": \"পেমেন্ট করুন এবং অফার গ্রহণ করুন\",\n  \"accepted\": \"গৃহীত\",\n  \"post_draft\": \"পোস্ট খসড়া\",\n  \"revision_needed\": \"সংশোধন প্রয়োজন\",\n  \"approve\": \"অনুমোদন করুন\",\n  \"preview\": \"পূর্বরূপ\",\n  \"revision_requested\": \"সংশোধনের অনুরোধ\",\n  \"accepted_1\": \"গৃহীত\",\n  \"cancelled_by_the_seller\": \"বিক্রেতা দ্বারা বাতিল\",\n  \"please_select_your_country_where_your_business_is\": \"অনুগ্রহ করে আপনার দেশ নির্বাচন করুন যেখানে আপনার ব্যবসা রয়েছে।\",\n  \"select_country\": \"--দেশ নির্বাচন করুন--\",\n  \"connect_bank_account\": \"ব্যাংক অ্যাকাউন্ট সংযুক্ত করুন\",\n  \"seller_mode\": \"বিক্রেতা মোড\",\n  \"active\": \"সক্রিয়\",\n  \"details\": \"বিস্তারিত\",\n  \"audience_size\": \"দর্শক সংখ্যা\",\n  \"add_another_platform\": \"আরেকটি প্ল্যাটফর্ম যোগ করুন\",\n  \"send_an_offer_for\": \"এর জন্য একটি অফার পাঠান $\",\n  \"complete_order_and_pay_early\": \"অর্ডার সম্পন্ন করুন এবং আগাম পেমেন্ট করুন\",\n  \"order_in_progress\": \"অর্ডার চলমান\",\n  \"create_a_new_offer\": \"একটি নতুন অফার তৈরি করুন\",\n  \"orders\": \"অর্ডার\",\n  \"price\": \"মূল্য\",\n  \"state\": \"অবস্থা\",\n  \"showing\": \"দেখানো হচ্ছে\",\n  \"to\": \"থেকে\",\n  \"from\": \"পর্যন্ত\",\n  \"results\": \"ফলাফল\",\n  \"content_writer\": \"কন্টেন্ট লেখক\",\n  \"influencer\": \"প্রভাবশালী\",\n  \"request_service\": \"সেবার অনুরোধ\",\n  \"the_marketplace_is_not_opened_yet\": \"মার্কেটপ্লেস এখনও খোলা হয়নি\",\n  \"check_again_soon\": \"শীঘ্রই আবার চেক করুন!\",\n  \"filter\": \"ফিল্টার\",\n  \"result\": \"ফলাফল\",\n  \"seller\": \"বিক্রেতা\",\n  \"buyer\": \"ক্রেতা\",\n  \"discord_support\": \"Discord সাপোর্ট\",\n  \"teams\": \"দল\",\n  \"webhooks_1\": \"ওয়েবহুক\",\n  \"auto_post\": \"স্বয়ংক্রিয় পোস্ট\",\n  \"logout_from\": \"থেকে লগআউট\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"১০,০০০+ উদ্যোক্তার সঙ্গে Postiz ব্যবহার করুন\",\n  \"to_manage_all_your_social_media_channels\": \"আপনার সব সোশ্যাল মিডিয়া চ্যানেল পরিচালনা করতে\",\n  \"100_no_risk_trial\": \"১০০% ঝুঁকিমুক্ত ট্রায়াল\",\n  \"pay_nothing_for_the_first_7_days\": \"প্রথম ৭ দিনের জন্য কিছুই পেমেন্ট করুন না\",\n  \"cancel_anytime_hassle_free\": \"যেকোনো সময়, সেটিংস থেকে বাতিল করুন\",\n  \"add_free_subscription\": \"-- বিনামূল্যে সাবস্ক্রিপশন যোগ করুন --\",\n  \"currently_impersonating\": \"বর্তমানে ছদ্মবেশ ধারণ করছেন\",\n  \"user_1\": \"ব্যবহারকারী:\",\n  \"drag_n_drop_some_files_here\": \"এখানে কিছু ফাইল টেনে এনে ছাড়ুন\",\n  \"add_time_slot\": \"সময় স্লট যোগ করুন\",\n  \"add_slot\": \"স্লট যোগ করুন\",\n  \"cancel_publication\": \"প্রকাশনা বাতিল করুন\",\n  \"statistics\": \"পরিসংখ্যান\",\n  \"loading\": \"লোড হচ্ছে\",\n  \"short_link\": \"সংক্ষিপ্ত লিংক\",\n  \"original_link\": \"মূল লিংক\",\n  \"clicks\": \"ক্লিক\",\n  \"selected_customer\": \"নির্বাচিত গ্রাহক\",\n  \"customer\": \"গ্রাহক:\",\n  \"repeat_post_every\": \"প্রতি পোস্ট পুনরাবৃত্তি করুন...\",\n  \"use_this_media\": \"এই মিডিয়া ব্যবহার করুন\",\n  \"create_new_post\": \"নতুন পোস্ট তৈরি করুন\",\n  \"update_post\": \"বিদ্যমান পোস্ট আপডেট করুন\",\n  \"merge_comments_into_one_post\": \"মন্তব্যগুলি একটি পোস্টে একত্রিত করুন\",\n  \"accounts_that_will_engage\": \"যে অ্যাকাউন্টগুলি এনগেজ করবে:\",\n  \"day\": \"দিন\",\n  \"week\": \"সপ্তাহ\",\n  \"month\": \"মাস\",\n  \"remove_from_customer\": \"গ্রাহক থেকে সরান\",\n  \"show_more\": \"+ আরো দেখুন\",\n  \"show_less\": \"- কম দেখুন\",\n  \"upload\": \"আপলোড\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"চ্যানেল যোগ করুন\",\n  \"add_platform\": \"প্ল্যাটফর্ম যোগ করুন\",\n  \"articles\": \"নিবন্ধ\",\n  \"add_comment\": \"মন্তব্য যোগ করুন\",\n  \"add_post\": \"থ্রেডে পোস্ট যোগ করুন\",\n  \"add_comment_or_post\": \"মন্তব্য / পোস্ট যোগ করুন\",\n  \"you_are_in_global_editing_mode\": \"আপনি গ্লোবাল এডিটিং মোডে আছেন\",\n  \"the_post_should_be_at_least_6_characters_long\": \"পোস্টটি কমপক্ষে ৬ অক্ষরের হতে হবে\",\n  \"are_you_sure_you_want_to_delete_post\": \"আপনি কি নিশ্চিত যে আপনি এই পোস্টটি মুছে ফেলতে চান?\",\n  \"post_deleted_successfully\": \"পোস্ট সফলভাবে মুছে ফেলা হয়েছে\",\n  \"delete_post\": \"পোস্ট মুছে ফেলুন\",\n  \"save_as_draft\": \"খসড়া হিসেবে সংরক্ষণ করুন\",\n  \"post_now\": \"এখনই পোস্ট করুন\",\n  \"please_add\": \"অনুগ্রহ করে যোগ করুন\",\n  \"to_your_telegram_group_channel_and_click_here\": \"আপনার টেলিগ্রাম গ্রুপ / চ্যানেলে এবং এখানে ক্লিক করুন:\",\n  \"connect_telegram\": \"টেলিগ্রাম সংযুক্ত করুন\",\n  \"please_add_the_following_command_in_your_chat\": \"অনুগ্রহ করে আপনার চ্যাটে নিম্নলিখিত কমান্ড যোগ করুন:\",\n  \"copy\": \"কপি করুন\",\n  \"settings\": \"সেটিংস\",\n  \"integrations\": \"ইন্টিগ্রেশনসমূহ\",\n  \"add_integration\": \"ইন্টিগ্রেশন যোগ করুন\",\n  \"you_are_now_editing_only\": \"আপনি এখন শুধুমাত্র সম্পাদনা করছেন\",\n  \"tag_a_company\": \"একটি কোম্পানি ট্যাগ করুন\",\n  \"video_length_is_invalid_must_be_up_to\": \"ভিডিওর দৈর্ঘ্য অবৈধ, সর্বোচ্চ হতে হবে\",\n  \"seconds\": \"সেকেন্ড\",\n  \"this_feature_available_only_for_photos\": \"এই বৈশিষ্ট্যটি শুধুমাত্র ছবির জন্য উপলব্ধ, এটি একটি ডিফল্ট সঙ্গীত যোগ করবে যা আপনি পরে পরিবর্তন করতে পারেন।\",\n  \"allow_user_to\": \"ব্যবহারকারীকে অনুমতি দিন:\",\n  \"your_video_will_be_labeled_promotional\": \"আপনার ভিডিও \\\"প্রচারমূলক বিষয়বস্তু\\\" হিসেবে লেবেল করা হবে।\",\n  \"this_cannot_be_changed_once_posted\": \"একবার পোস্ট করার পর এটি পরিবর্তন করা যাবে না।\",\n  \"turn_on_to_disclose_video_promotes\": \"এই ভিডিও যে মূল্যবান কিছুর বিনিময়ে পণ্য বা সেবার প্রচার করে তা প্রকাশ করতে চালু করুন। আপনার ভিডিও নিজেকে, তৃতীয় পক্ষ বা উভয়ের প্রচার করতে পারে।\",\n  \"you_are_promoting_yourself\": \"আপনি নিজেকে বা আপনার নিজস্ব ব্র্যান্ডের প্রচার করছেন।\",\n  \"this_video_will_be_classified_brand_organic\": \"এই ভিডিওটি ব্র্যান্ড অর্গানিক হিসেবে শ্রেণীবদ্ধ হবে।\",\n  \"you_are_promoting_another_brand\": \"আপনি অন্য একটি ব্র্যান্ড বা তৃতীয় পক্ষের প্রচার করছেন।\",\n  \"this_video_will_be_classified_branded_content\": \"এই ভিডিওটি ব্র্যান্ডেড কন্টেন্ট হিসেবে শ্রেণীবদ্ধ হবে।\",\n  \"by_posting_you_agree_to_tiktoks\": \"পোস্ট করার মাধ্যমে, আপনি TikTok-এর সাথে সম্মত হচ্ছেন\",\n  \"music_usage_confirmation\": \"সঙ্গীত ব্যবহারের নিশ্চিতকরণ\",\n  \"branded_content_policy\": \"ব্র্যান্ডেড কন্টেন্ট নীতি\",\n  \"select_1\": \"--নির্বাচন করুন--\",\n  \"select_flair\": \"--ফ্লেয়ার নির্বাচন করুন--\",\n  \"link\": \"লিংক\",\n  \"add_subreddit\": \"সাবরেডিট যোগ করুন\",\n  \"please_add_at_least_one_subreddit\": \"অনুগ্রহ করে কমপক্ষে একটি সাবরেডিট যোগ করুন\",\n  \"add_community\": \"কমিউনিটি যোগ করুন\",\n  \"select_post_type\": \"পোস্টের ধরন নির্বাচন করুন...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"আমরা আপনার LinkedIn পৃষ্ঠার সাথে সংযুক্ত কোনো ব্যবসা খুঁজে পাইনি।\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"অনুগ্রহ করে এই ডায়ালগটি বন্ধ করুন, একটি নতুন পৃষ্ঠা তৈরি করুন এবং আবার একটি নতুন চ্যানেল যোগ করুন।\",\n  \"select_linkedin_page\": \"LinkedIn পৃষ্ঠা নির্বাচন করুন:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"আমরা নির্বাচিত পৃষ্ঠাগুলির সাথে সংযুক্ত কোনো ব্যবসা খুঁজে পাইনি।\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"আমরা আপনাকে সব পৃষ্ঠা এবং সব ব্যবসা সংযুক্ত করার পরামর্শ দিই।\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"অনুগ্রহ করে এই ডায়ালগটি বন্ধ করুন, আপনার ইন্টিগ্রেশন মুছে ফেলুন এবং আবার একটি নতুন চ্যানেল যোগ করুন।\",\n  \"select_instagram_account\": \"Instagram অ্যাকাউন্ট নির্বাচন করুন:\",\n  \"select_page\": \"পৃষ্ঠা নির্বাচন করুন:\",\n  \"generate_image_with_ai\": \"AI দিয়ে ছবি তৈরি করুন\",\n  \"reconnect_channel\": \"চ্যানেল পুনরায় সংযুক্ত করুন\",\n  \"update_credentials\": \"শংসাপত্র আপডেট করুন\",\n  \"additional_settings\": \"অতিরিক্ত সেটিংস\",\n  \"change_bot\": \"বট পরিবর্তন করুন\",\n  \"move_add_to_customer\": \"গ্রাহকের কাছে স্থানান্তর / যোগ করুন\",\n  \"edit_time_slots\": \"সময় স্লট সম্পাদনা করুন\",\n  \"enable_channel\": \"চ্যানেল সক্রিয় করুন\",\n  \"disable_channel\": \"চ্যানেল নিষ্ক্রিয় করুন\",\n  \"add\": \"যোগ করুন\",\n  \"short_post\": \"সংক্ষিপ্ত পোস্ট\",\n  \"long_post\": \"দীর্ঘ পোস্ট\",\n  \"a_thread_with_short_posts\": \"সংক্ষিপ্ত পোস্টের একটি থ্রেড\",\n  \"a_thread_with_long_posts\": \"দীর্ঘ পোস্টের একটি থ্রেড\",\n  \"personal_voice_i_am_happy_to_announce\": \"ব্যক্তিগত কণ্ঠস্বর (\\\"আমি ঘোষণা করতে খুশি\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"কোম্পানির কণ্ঠস্বর (\\\"আমরা ঘোষণা করতে খুশি\\\")\",\n  \"generate\": \"তৈরি করুন\",\n  \"generate_posts\": \"পোস্ট তৈরি করুন\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"SOL ($199) দিয়ে একটি লাইফ-টাইম PRO অ্যাকাউন্ট কিনুন, অনুগ্রহ করে মনে রাখবেন এই ক্রয়ের জন্য কোনো রিফান্ড নেই।\",\n  \"purchase_now\": \"এখনই কিনুন\",\n  \"pay_today\": \"আজই পেমেন্ট করুন\",\n  \"we_are_sorry_to_see_you_go\": \"আপনাকে যেতে দেখে আমরা দুঃখিত :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"আমরা কী আরও ভাল করতে পারতাম তা সংক্ষেপে বলতে কি আপনার আপত্তি আছে?\",\n  \"cancel_subscription\": \"সাবস্ক্রিপশন বাতিল করুন\",\n  \"plans\": \"পরিকল্পনা\",\n  \"monthly\": \"মাসিক\",\n  \"yearly\": \"বার্ষিক\",\n  \"reactivate_subscription\": \"সাবস্ক্রিপশন পুনরায় সক্রিয় করুন\",\n  \"update_payment_method_invoices_history\": \"পেমেন্ট পদ্ধতি / ইনভয়েস ইতিহাস আপডেট করুন\",\n  \"cancel_subscription_1\": \"সাবস্ক্রিপশন বাতিল করুন\",\n  \"your_subscription_will_be_canceled_at\": \"আপনার সাবস্ক্রিপশন বাতিল হবে\",\n  \"you_will_never_be_charged_again\": \"আপনার কাছ থেকে আর কখনো চার্জ নেওয়া হবে না\",\n  \"current_package\": \"বর্তমান প্যাকেজ:\",\n  \"next_package\": \"পরবর্তী প্যাকেজ:\",\n  \"claim\": \"দাবি করুন\",\n  \"frequently_asked_questions\": \"প্রায়শই জিজ্ঞাসিত প্রশ্ন\",\n  \"autopost\": \"অটোপোস্ট\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"অটোপোস্ট স্বয়ংক্রিয়ভাবে আপনার RSS নতুন আইটেমগুলি সোশ্যাল মিডিয়ায় পোস্ট করতে পারে\",\n  \"title\": \"শিরোনাম\",\n  \"add_an_autopost\": \"একটি অটোপোস্ট যোগ করুন\",\n  \"post_content\": \"পোস্ট বিষয়বস্তু\",\n  \"sign_up\": \"সাইন আপ\",\n  \"or\": \"অথবা\",\n  \"by_registering_you_agree_to_our\": \"নিবন্ধন করার মাধ্যমে আপনি আমাদের সাথে সম্মত হচ্ছেন\",\n  \"and\": \"এবং\",\n  \"terms_of_service\": \"সেবার শর্তাবলী\",\n  \"privacy_policy\": \"গোপনীয়তা নীতি\",\n  \"create_account\": \"অ্যাকাউন্ট তৈরি করুন\",\n  \"already_have_an_account\": \"ইতিমধ্যে একটি অ্যাকাউন্ট আছে?\",\n  \"sign_in\": \"সাইন ইন\",\n  \"sign_in_1\": \"সাইন ইন\",\n  \"don_t_have_an_account\": \"কোনো অ্যাকাউন্ট নেই?\",\n  \"forgot_password\": \"পাসওয়ার্ড ভুলে গেছেন\",\n  \"forgot_password_1\": \"পাসওয়ার্ড ভুলে গেছেন\",\n  \"send_password_reset_email\": \"পাসওয়ার্ড রিসেট ইমেইল পাঠান\",\n  \"go_back_to_login\": \"লগইনে ফিরে যান\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"আমরা আপনার পাসওয়ার্ড রিসেট করার জন্য একটি লিংক সহ একটি ইমেইল পাঠিয়েছি।\",\n  \"change_password\": \"পাসওয়ার্ড পরিবর্তন করুন\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"আমরা সফলভাবে আপনার পাসওয়ার্ড রিসেট করেছি। আপনি এখন আপনার সাথে লগইন করতে পারেন\",\n  \"click_here_to_go_back_to_login\": \"লগইনে ফিরে যেতে এখানে ক্লিক করুন\",\n  \"activate_your_account\": \"আপনার অ্যাকাউন্ট সক্রিয় করুন\",\n  \"thank_you_for_registering\": \"নিবন্ধনের জন্য ধন্যবাদ!\",\n  \"please_check_your_email_to_activate_your_account\": \"আপনার অ্যাকাউন্ট সক্রিয় করতে অনুগ্রহ করে আপনার ইমেইল চেক করুন।\",\n  \"sign_in_with\": \"এর সাথে সাইন ইন করুন\",\n  \"continue_with_google\": \"Google এর সাথে চালিয়ে যান\",\n  \"sign_in_with_github\": \"GitHub এর সাথে সাইন ইন করুন\",\n  \"continue_with_farcaster\": \"Farcaster দিয়ে চালিয়ে যান\",\n  \"continue_with_your_wallet\": \"আপনার ওয়ালেট দিয়ে চালিয়ে যান\",\n  \"stars_per_day\": \"প্রতিদিন স্টার\",\n  \"media\": \"মিডিয়া\",\n  \"check_launch\": \"লঞ্চ চেক করুন\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"বিশ্লেষণ দেখতে সেটিংস থেকে আপনার GitHub রিপোজিটরি লোড করুন\",\n  \"stars\": \"স্টার\",\n  \"processing_stars\": \"স্টার প্রক্রিয়াকরণ\",\n  \"forks\": \"ফর্ক\",\n  \"registration_is_disabled\": \"নিবন্ধন নিষ্ক্রিয়\",\n  \"login_instead\": \"পরিবর্তে লগইন করুন\",\n  \"gitroom\": \"গিটরুম\",\n  \"select_a_conversation_and_chat_away\": \"একটি কথোপকথন নির্বাচন করুন এবং চ্যাট করুন\",\n  \"adding_channel_redirecting_you\": \"চ্যানেল যোগ করা হচ্ছে, আপনাকে রিডাইরেক্ট করা হচ্ছে\",\n  \"could_not_add_provider\": \"প্রদানকারী যোগ করা যায়নি\",\n  \"you_are_being_redirected_back\": \"আপনাকে ফিরিয়ে নিয়ে যাওয়া হচ্ছে\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"আমরা কিছু সমস্যার সম্মুখীন হচ্ছি, পৃষ্ঠা রিফ্রেশ করার চেষ্টা করুন\",\n  \"post_not_found\": \"পোস্ট পাওয়া যায়নি\",\n  \"publication_date\": \"প্রকাশনার তারিখ\",\n  \"analytics\": \"বিশ্লেষণ\",\n  \"launches\": \"লঞ্চ\",\n  \"plugs\": \"প্লাগ\",\n  \"billing\": \"বিলিং\",\n  \"affiliate\": \"অ্যাফিলিয়েট\",\n  \"monday\": \"সোমবার\",\n  \"tuesday\": \"মঙ্গলবার\",\n  \"wednesday\": \"বুধবার\",\n  \"thursday\": \"বৃহস্পতিবার\",\n  \"friday\": \"শুক্রবার\",\n  \"saturday\": \"শনিবার\",\n  \"sunday\": \"রবিবার\",\n  \"can_t_change_date_remove_post_from_publication\": \"তারিখ পরিবর্তন করা যাবে না, প্রকাশনা থেকে পোস্ট সরান\",\n  \"predicted_github_trending_change\": \"পূর্বাভাসিত GitHub ট্রেন্ডিং পরিবর্তন\",\n  \"duplicate_post\": \"পোস্ট ডুপ্লিকেট করুন\",\n  \"preview_post\": \"পোস্ট প্রিভিউ\",\n  \"post_statistics\": \"পোস্ট পরিসংখ্যান\",\n  \"draft\": \"খসড়া\",\n  \"week_number\": \"সপ্তাহ নম্বর\",\n  \"top_title_edit_webhook\": \"ওয়েবহুক সম্পাদনা\",\n  \"top_title_add_webhook\": \"ওয়েবহুক যোগ করুন\",\n  \"top_title_oh_no\": \"ওহ না\",\n  \"top_title_auto_plug\": \"অটো প্লাগ\",\n  \"top_title_edit_autopost\": \"অটোপোস্ট সম্পাদনা\",\n  \"top_title_add_autopost\": \"অটোপোস্ট যোগ করুন\",\n  \"top_title_send_a_new_offer\": \"একটি নতুন অফার পাঠান\",\n  \"top_title_media_library\": \"মিডিয়া লাইব্রেরি\",\n  \"top_title_add_signature\": \"স্বাক্ষর যোগ করুন\",\n  \"top_title_send_a_message_to\": \"একটি বার্তা পাঠান\",\n  \"top_title_configure_provider\": \"প্রদানকারী কনফিগার করুন\",\n  \"top_title_add_member\": \"সদস্য যোগ করুন\",\n  \"top_title_change_bot_picture\": \"বট ছবি পরিবর্তন করুন\",\n  \"top_title_create_a_new_tag\": \"একটি নতুন ট্যাগ তৈরি করুন\",\n  \"top_title_select_company\": \"কোম্পানি নির্বাচন করুন\",\n  \"top_title_additional_settings\": \"অতিরিক্ত সেটিংস\",\n  \"top_title_time_table_slots\": \"সময়সূচী স্লট\",\n  \"top_title_design_media\": \"মিডিয়া ডিজাইন\",\n  \"top_title_edit_post\": \"পোস্ট সম্পাদনা\",\n  \"top_title_create_post\": \"পোস্ট তৈরি করুন\",\n  \"top_title_move__add_to_customer\": \"গ্রাহকের কাছে স্থানান্তর/যোগ করুন\",\n  \"top_title_add_api_key_for\": \"এর জন্য API কী যোগ করুন\",\n  \"top_title_instance_url\": \"ইনস্ট্যান্স URL\",\n  \"top_title_custom_url\": \"কাস্টম URL\",\n  \"top_title_add_channel\": \"চ্যানেল যোগ করুন\",\n  \"top_title_add_telegram\": \"টেলিগ্রাম যোগ করুন\",\n  \"top_title_add_wrapcast\": \"Wrapcast যোগ করুন\",\n  \"top_title_comments_for\": \"{{date}}-এর জন্য মন্তব্যসমূহ\",\n  \"top_title_edit_signature\": \"স্বাক্ষর সম্পাদনা করুন\",\n  \"label_name\": \"নাম\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"শিরোনাম\",\n  \"label_subtitle\": \"উপশিরোনাম\",\n  \"label_email\": \"ইমেইল\",\n  \"label_full_name\": \"পূর্ণ নাম\",\n  \"label_password\": \"পাসওয়ার্ড\",\n  \"label_confirm_password\": \"পাসওয়ার্ড নিশ্চিত করুন\",\n  \"label_api_key\": \"API কী\",\n  \"label_instance_url\": \"ইনস্ট্যান্স URL\",\n  \"label_custom_url\": \"কাস্টম URL\",\n  \"label_feedback\": \"প্রতিক্রিয়া\",\n  \"label_bio\": \"জীবনী\",\n  \"label_role\": \"ভূমিকা\",\n  \"label_country\": \"দেশ\",\n  \"label_audience_size\": \"দর্শক সংখ্যা\",\n  \"label_pick_time\": \"সময় নির্বাচন করুন\",\n  \"label_nickname\": \"ডাকনাম\",\n  \"label_write_anything\": \"যেকোনো কিছু লিখুন\",\n  \"label_output_format\": \"আউটপুট ফরম্যাট\",\n  \"label_add_pictures\": \"ছবি যোগ করুন\",\n  \"label_hour\": \"ঘন্টা\",\n  \"label_minutes\": \"মিনিট\",\n  \"label_select_publication\": \"প্রকাশনা নির্বাচন করুন\",\n  \"label_canonical_link\": \"ক্যানোনিক্যাল লিংক\",\n  \"label_cover_picture\": \"কভার ছবি\",\n  \"label_tags\": \"ট্যাগ\",\n  \"label_topics\": \"বিষয়\",\n  \"label_tags_maximum_4\": \"ট্যাগ (সর্বোচ্চ ৪টি)\",\n  \"label_attachments\": \"সংযুক্তি\",\n  \"label_type\": \"ধরন\",\n  \"label_thumbnail\": \"থাম্বনেইল\",\n  \"label_who_can_see_this_video\": \"কে এই ভিডিও দেখতে পারবে\",\n  \"label_content_posting_method\": \"কন্টেন্ট পোস্টিং পদ্ধতি\",\n  \"label_auto_add_music\": \"স্বয়ংক্রিয় সঙ্গীত যোগ\",\n  \"label_duet\": \"ডুয়েট\",\n  \"label_stitch\": \"স্টিচ\",\n  \"label_comments\": \"মন্তব্য\",\n  \"label_disclose_video_content\": \"ভিডিও কন্টেন্ট প্রকাশ করুন\",\n  \"label_your_brand\": \"আপনার ব্র্যান্ড\",\n  \"label_branded_content\": \"ব্র্যান্ডেড কন্টেন্ট\",\n  \"label_subreddit\": \"সাবরেডিট\",\n  \"label_flair\": \"ফ্লেয়ার\",\n  \"label_media\": \"মিডিয়া\",\n  \"label_search_subreddit\": \"সাবরেডিট অনুসন্ধান\",\n  \"label_delay\": \"বিলম্ব\",\n  \"label_post_type\": \"পোস্টের ধরন\",\n  \"label_collaborators\": \"সহযোগী\",\n  \"label_community\": \"কমিউনিটি\",\n  \"label_search_community\": \"কমিউনিটি অনুসন্ধান\",\n  \"label_channel\": \"চ্যানেল\",\n  \"label_search_channel\": \"চ্যানেল অনুসন্ধান\",\n  \"label_select_channel\": \"চ্যানেল নির্বাচন করুন\",\n  \"label_new_password\": \"নতুন পাসওয়ার্ড\",\n  \"label_repeat_password\": \"পাসওয়ার্ড পুনরায় দিন\",\n  \"label_platform\": \"প্ল্যাটফর্ম\",\n  \"label_price_per_post\": \"প্রতি পোস্টের মূল্য\",\n  \"label_integrations\": \"ইন্টিগ্রেশন\",\n  \"label_code\": \"কোড\",\n  \"label_should_sync_last_post\": \"শেষ পোস্ট সিঙ্ক করা উচিত\",\n  \"label_when_post\": \"পোস্ট সময়\",\n  \"label_autogenerate_content\": \"কন্টেন্ট স্বয়ংক্রিয় তৈরি করা\",\n  \"label_generate_picture\": \"ছবি তৈরি করা\",\n  \"label_company\": \"কমপানি\",\n  \"label_tag_color\": \"ট্যাগ কলর\",\n  \"label_select_board\": \"বোর্ড নির্বাচন করুন\",\n  \"label_select_organization\": \"সংস্থান নির্বাচন করুন\",\n  \"label_auto_add_signature\": \"স্বাক্ষর স্বয়ংক্রিয় তৈরি করা\",\n  \"enable_color_picker\": \"কলর পিকার সক্রিয় করা\",\n  \"cancel_the_color_picker\": \"কলর পিকার বাতিল করা\",\n  \"no_content_yet\": \"কন্টেন্ট নেই\",\n  \"write_your_reply\": \"আপনার পোস্ট লিখুন...\",\n  \"add_a_tag\": \"ট্যাগ যোগ করুন\",\n  \"add_to_calendar\": \"ক্যালেন্ডারে যোগ করুন\",\n  \"select_channels_from_circles\": \"বলার মাধ্যমে চ্যানেল নির্বাচন করুন\",\n  \"not_matching_order\": \"অর্ডার মিলনায়ন না\",\n  \"submit_for_order\": \"অর্ডারে জমা দিন\",\n  \"schedule\": \"সময়সূচী করা\",\n  \"update\": \"আপডেট করা\",\n  \"attachments\": \"সংযুক্তি\",\n  \"tags\": \"ট্যাগ\",\n  \"public_to_everyone\": \"সকলের জন্য পাবলিক\",\n  \"mutual_follow_friends\": \"সহযোগী ফ্রেন্ডস\",\n  \"follower_of_creator\": \"প্রকাশকের অন্তর্গত\",\n  \"self_only\": \"নিজের জন্য কেবল\",\n  \"post_content_directly_to_tiktok\": \"টিকটকে কন্টেন্ট পোস্ট করা\",\n  \"upload_content_to_tiktok_without_posting\": \"পোস্ট করার প্রয়োজন ছাড়া টিকটকে কন্টেন্ট আপলোড করা\",\n  \"choose_upload_without_posting_description\": \"পোস্ট করার প্রয়োজন ছাড়া টিকটকে কন্টেন্ট আপলোড করার বর্ণনা\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"পোস্টিজ দ্বারা চার্জ করা হবে কি?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"ক্রেডিট কার্ডের তথ্য নিশ্চিত করার জন্য Postiz $2 ধরে রাখবে এবং তা সঙ্গে সঙ্গে মুক্তি দেবে, আপনি সেটিংস থেকে যেকোনো সময় কাউকে না বলেই আপনার সাবস্ক্রিপশন বাতিল করতে পারেন\",\n  \"faq_can_i_trust_postiz_gitroom\": \"আমি কি Postiz-এ বিশ্বাস করতে পারি?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz গর্বের সাথে ওপেন-সোর্স! আমরা নৈতিক ও স্বচ্ছ সংস্কৃতিতে বিশ্বাস করি, যার মানে Postiz চিরকাল টিকে থাকবে। আপনি চাইলে সম্পূর্ণ কোড দেখতে পারেন বা ব্যক্তিগত প্রকল্পে ব্যবহার করতে পারেন। ওপেন-সোর্স রিপোজিটরি দেখতে, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">এখানে ক্লিক করুন</a>।\",\n  \"faq_what_are_channels\": \"চ্যানেল কী?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz আপনাকে বিভিন্ন চ্যানেলের মধ্যে আপনার পোস্টগুলো সময়সূচি নির্ধারণ করতে দেয়।\\nএকটি চ্যানেল হলো এমন একটি প্রকাশনা প্ল্যাটফর্ম যেখানে আপনি আপনার পোস্টগুলো সময়সূচি অনুযায়ী প্রকাশ করতে পারেন।\\nউদাহরণস্বরূপ, আপনি X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads এবং Pinterest-এ আপনার পোস্টগুলো সময়সূচি অনুযায়ী প্রকাশ করতে পারেন।\",\n  \"faq_what_are_team_members\": \"টিম সদস্য কী?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"যদি আপনার একাধিক সদস্যের একটি টিম থাকে\",\n  \"faq_what_is_ai_auto_complete\": \"AI অটো কমপ্লিট কী?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"আমরা আপনাকে লিখতে সাহায্য করার জন্য ChatGPT স্বয়ংক্রিয় করি\",\n  \"enter_email\": \"ইমেইল প্রবেশ করান\",\n  \"are_you_sure\": \"আপনি কি নিশ্চিত?\",\n  \"no_cancel\": \"না, বাতিল করুন\",\n  \"are_you_sure_you_want_to_delete\": \"আপনি কি নিশ্চিত যে আপনি মুছে ফেলতে চান?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"আপনি কি নিশ্চিত যে আপনি ছবিটি মুছে ফেলতে চান?\",\n  \"are_you_sure_you_want_to_logout\": \"আপনি কি নিশ্চিত যে আপনি লগআউট করতে চান?\",\n  \"yes_logout\": \"হ্যাঁ, লগআউট করুন\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"আপনি কি নিশ্চিত যে আপনি এই স্লটটি মুছে ফেলতে চান?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"আপনি কি নিশ্চিত যে আপনি এই সাবরেডিটটি মুছে ফেলতে চান?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"আপনি কি নিশ্চিত যে আপনি উইন্ডোটি বন্ধ করতে চান?\",\n  \"yes_close\": \"হ্যাঁ, বন্ধ করুন\",\n  \"link_copied_to_clipboard\": \"লিংক ক্লিপবোর্ডে কপি করা হয়েছে\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"আপনি কি নিশ্চিত যে আপনি এই মোডালটি বন্ধ করতে চান? সমস্ত ডেটা হারিয়ে যাবে\",\n  \"yes_close_it\": \"হ্যাঁ, বন্ধ করুন\",\n  \"uploading_pictures\": \"ছবি আপলোড করা হচ্ছে\",\n  \"agent_starting\": \"এজেন্ট শুরু হচ্ছে\",\n  \"researching_your_content\": \"আপনার কন্টেন্ট গবেষণা করা হচ্ছে\",\n  \"understanding_the_category\": \"বিভাগ বোঝা হচ্ছে\",\n  \"finding_the_topic\": \"বিষয় খোঁজা হচ্ছে\",\n  \"finding_popular_posts_to_match_with\": \"মিলানোর জন্য জনপ্রিয় পোস্ট খোঁজা হচ্ছে\",\n  \"generating_hook\": \"হুক তৈরি করা হচ্ছে\",\n  \"generating_content\": \"কন্টেন্ট তৈরি করা হচ্ছে\",\n  \"generating_pictures\": \"ছবি তৈরি করা হচ্ছে\",\n  \"finding_time_to_post\": \"পোস্ট করার সময় খোঁজা হচ্ছে\",\n  \"write_anything\": \"যেকোনো কিছু লিখুন\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"আপনি যা চান তা লিখতে পারেন এবং লিংকও যোগ করতে পারেন, আমরা আপনার জন্য গবেষণা করব\",\n  \"output_format\": \"আউটপুট ফরম্যাট\",\n  \"add_pictures\": \"ছবি যোগ করুন\",\n  \"7_days\": \"৭ দিন\",\n  \"30_days\": \"৩০ দিন\",\n  \"90_days\": \"৯০ দিন\",\n  \"start_7_days_free_trial\": \"৭ দিনের বিনামূল্যে ট্রায়াল শুরু করুন\",\n  \"change_language\": \"ভাষা পরিবর্তন করুন\",\n  \"that_a_wrap\": \"এটাই শেষ!\\n\\nযদি আপনি এই থ্রেডটি উপভোগ করে থাকেন:\\n\\n১. আরও এমন পোস্টের জন্য আমাকে @{{username}} ফলো করুন\\n২. আপনার অডিয়েন্সের সাথে এই থ্রেডটি শেয়ার করতে নিচের টুইটটি রিটুইট করুন\\n\",\n  \"post_as_images_carousel\": \"ছবির ক্যারোসেল হিসেবে পোস্ট করুন\",\n  \"save_set\": \"সেট সংরক্ষণ করুন\",\n  \"separate_post\": \"একটি পোস্টকে একাধিক পোস্টে ভাগ করুন\",\n  \"label_who_can_reply_to_this_post\": \"এই পোস্টে কে উত্তর দিতে পারবে?\",\n  \"delete_integration\": \"ইন্টিগ্রেশন মুছে ফেলুন\",\n  \"start_writing_your_post\": \"প্রিভিউর জন্য আপনার পোস্ট লেখা শুরু করুন\",\n  \"billing_join_over\": \"যোগ দিন\",\n  \"billing_entrepreneurs_count\": \"২০,০০০+ উদ্যোক্তা\",\n  \"billing_who_use\": \"যারা ব্যবহার করেন\",\n  \"billing_postiz_grow_social\": \"Postiz তাদের সামাজিক উপস্থিতি বাড়াতে\",\n  \"billing_no_risk_trial\": \"১০০% ঝুঁকিমুক্ত ফ্রি ট্রায়াল\",\n  \"billing_pay_nothing_7_days\": \"প্রথম ৭ দিন কিছুই দিতে হবে না\",\n  \"billing_cancel_anytime\": \"যেকোনো সময়, সেটিংস থেকে বাতিল করুন\",\n  \"billing_choose_plan\": \"একটি প্ল্যান বেছে নিন\",\n  \"billing_monthly\": \"মাসিক\",\n  \"billing_yearly\": \"বার্ষিক\",\n  \"billing_20_percent_off\": \"২০% ছাড়\",\n  \"billing_features\": \"বৈশিষ্ট্যাবলী\",\n  \"billing_channel\": \"চ্যানেল\",\n  \"billing_channels\": \"চ্যানেলসমূহ\",\n  \"billing_unlimited\": \"অসীম\",\n  \"billing_posts_per_month\": \"প্রতি মাসে পোস্ট\",\n  \"billing_unlimited_team_members\": \"অসীম টিম সদস্য\",\n  \"billing_ai_auto_complete\": \"এআই অটো-কমপ্লিট\",\n  \"billing_ai_copilots\": \"এআই কো-পাইলট\",\n  \"billing_ai_autocomplete\": \"এআই অটো-কমপ্লিট\",\n  \"billing_advanced_picture_editor\": \"উন্নত ছবি সম্পাদনা\",\n  \"billing_ai_images_per_month\": \"প্রতি মাসে এআই ছবি\",\n  \"billing_ai_videos_per_month\": \"প্রতি মাসে এআই ভিডিও\",\n  \"billing_billing_address\": \"বিলিং ঠিকানা\",\n  \"billing_payment\": \"পেমেন্ট\",\n  \"billing_powered_by_stripe\": \"নিরাপদ পেমেন্ট প্রক্রিয়াকরণ করেছে\",\n  \"billing_your_7_day_trial_is\": \"আপনার ৭ দিনের ট্রায়াল\",\n  \"billing_100_percent_free\": \"১০০% ফ্রি\",\n  \"billing_ending\": \"শেষ হচ্ছে\",\n  \"billing_cancel_anytime_short\": \"যেকোনো সময় বাতিল করুন।\",\n  \"billing_pay_0_start_trial\": \"আজ $0 দিন - আপনার ফ্রি ট্রায়াল শুরু করুন!\",\n  \"billing_pay_now\": \"এখনই পেমেন্ট করুন\",\n  \"billing_per_month\": \"/ মাস\",\n  \"billing_per_year\": \"/ বছর\",\n  \"billing_order_summary\": \"অর্ডার সারাংশ\",\n  \"billing_applied\": \"প্রয়োগ হয়েছে\",\n  \"billing_due_today\": \"আজকের বাকি\",\n  \"billing_then\": \"তারপর\",\n  \"billing_on\": \"তারিখে\",\n  \"billing_discount_applied\": \"ছাড় প্রয়োগ হয়েছে\",\n  \"billing_remove\": \"সরান\",\n  \"billing_coupon_expires\": \"কুপন মেয়াদ শেষ হবে\",\n  \"billing_invalid_coupon\": \"অবৈধ কুপন কোড\",\n  \"billing_coupon_applied\": \"কুপন সফলভাবে প্রয়োগ হয়েছে!\",\n  \"billing_coupon_removed\": \"কুপন সরানো হয়েছে\",\n  \"billing_error_removing_coupon\": \"কুপন সরাতে ত্রুটি\",\n  \"billing_have_discount_coupon\": \"আপনার কি ছাড়ের কুপন আছে?\",\n  \"billing_discount_coupon\": \"ছাড়ের কুপন\",\n  \"billing_cancel\": \"বাতিল করুন\",\n  \"billing_enter_coupon_code\": \"কুপন কোড লিখুন\",\n  \"billing_applying\": \"প্রয়োগ হচ্ছে...\",\n  \"billing_apply\": \"প্রয়োগ করুন\",\n  \"billing_subscription\": \"সাবস্ক্রিপশন\",\n  \"billing_cancel_notice\": \"সেটিংস থেকে যেকোনো সময় কাউকে না বলেই বাতিল করুন এবং কখনোই চার্জ হবে না।\",\n  \"select_channels\": \"চ্যানেল নির্বাচন করুন\",\n  \"start_a_new_chat\": \"নতুন চ্যাট শুরু করুন\",\n  \"your_assistant\": \"আপনার সহকারী\",\n  \"agent_welcome_message\": \"হ্যালো, আমি আপনার Postiz এজেন্ট 🙌🏻।\\n\\nআমি এক বা একাধিক চ্যানেলে পোস্ট নির্ধারণ করতে পারি এবং ছবি ও ভিডিও তৈরি করতে পারি।\\n\\nআপনি বাম মেনু থেকে আপনার পছন্দের চ্যানেলগুলো নির্বাচন করতে পারেন।\\n\\nডান মেনু থেকে আপনি আপনার পূর্ববর্তী কথোপকথন দেখতে পারবেন।\\n\\nআপনি আমাকে MCP সার্ভার হিসেবেও ব্যবহার করতে পারেন, সেটিংস >> পাবলিক API দেখুন।\",\n  \"last_github_trending\": \"সর্বশেষ গিটহাব ট্রেন্ডিং\",\n  \"next_predicted_github_trending\": \"পরবর্তী অনুমানকৃত গিটহাব ট্রেন্ডিং\",\n  \"repository\": \"রিপোজিটরি\",\n  \"date\": \"তারিখ\",\n  \"total_stars\": \"মোট স্টার\",\n  \"total_forks\": \"মোট ফর্ক\",\n  \"continue_with\": \"চালিয়ে যান\",\n  \"email_address\": \"ইমেইল ঠিকানা\",\n  \"email_already_exists\": \"ইমেইল ইতিমধ্যে বিদ্যমান\",\n  \"google\": \"গুগল\",\n  \"farcaster\": \"ফারকাস্টার\",\n  \"edit_autopost\": \"অটোপোস্ট সম্পাদনা করুন\",\n  \"add_autopost_title\": \"অটোপোস্ট যোগ করুন\",\n  \"webhook_deleted_successfully\": \"ওয়েবহুক সফলভাবে মুছে ফেলা হয়েছে\",\n  \"all_integrations\": \"সব ইন্টিগ্রেশন\",\n  \"specific_integrations\": \"নির্দিষ্ট ইন্টিগ্রেশন\",\n  \"post_on_next_available_slot\": \"পরবর্তী উপলব্ধ স্লটে পোস্ট করুন\",\n  \"post_immediately\": \"সঙ্গে সঙ্গে পোস্ট করুন\",\n  \"could_not_use_rss_feed\": \"এই RSS ফিড ব্যবহার করা যায়নি\",\n  \"rss_valid\": \"RSS বৈধ!\",\n  \"autopost_updated_successfully\": \"অটোপোস্ট সফলভাবে আপডেট হয়েছে\",\n  \"autopost_added_successfully\": \"অটোপোস্ট সফলভাবে যোগ হয়েছে\",\n  \"write_your_post_placeholder\": \"আপনার পোস্ট লিখুন...\",\n  \"select_or_upload_pictures_max_5\": \"ছবি নির্বাচন করুন বা আপলোড করুন (একসাথে সর্বাধিক ৫টি)।\",\n  \"you_can_drag_drop_pictures\": \"আপনি চাইলে ছবি ড্র্যাগ ও ড্রপও করতে পারেন।\",\n  \"you_dont_have_any_media_yet\": \"আপনার কাছে এখনও কোনো মিডিয়া নেই\",\n  \"media_library\": \"মিডিয়া লাইব্রেরি\",\n  \"media_settings\": \"মিডিয়া সেটিংস\",\n  \"media_editor\": \"মিডিয়া এডিটর\",\n  \"close\": \"বন্ধ করুন\",\n  \"me\": \"আমি\",\n  \"noname\": \"নামহীন\",\n  \"password_reset_link_expired\": \"আপনার পাসওয়ার্ড রিসেট লিঙ্কের মেয়াদ শেষ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।\",\n  \"invalid_api_key\": \"অবৈধ API কী\",\n  \"could_not_connect_to_platform\": \"প্ল্যাটফর্মে সংযোগ করা যায়নি\",\n  \"web3_provider\": \"Web3 প্রদানকারী\",\n  \"add_provider_title\": \"প্রদানকারী যোগ করুন\",\n  \"profile_updated\": \"প্রোফাইল আপডেট হয়েছে\",\n  \"sets\": \"সেট\",\n  \"invitation_link_sent\": \"আমন্ত্রণ লিঙ্ক পাঠানো হয়েছে\",\n  \"send_invitation_link\": \"আমন্ত্রণ লিঙ্ক পাঠান\",\n  \"copy_link\": \"লিঙ্ক কপি করুন\",\n  \"are_you_sure_remove_team_member\": \"আপনি কি নিশ্চিত এই টিম সদস্যকে সরাতে চান?\",\n  \"admin\": \"অ্যাডমিন\",\n  \"super_admin\": \"সুপার অ্যাডমিন\",\n  \"update_webhook\": \"ওয়েবহুক আপডেট করুন\",\n  \"add_webhook\": \"ওয়েবহুক যোগ করুন\",\n  \"webhook_updated_successfully\": \"ওয়েবহুক সফলভাবে আপডেট হয়েছে\",\n  \"webhook_added_successfully\": \"ওয়েবহুক সফলভাবে যোগ হয়েছে\",\n  \"webhook_sent\": \"ওয়েবহুক পাঠানো হয়েছে\",\n  \"today\": \"আজ\",\n  \"channel_disconnected_click_to_reconnect\": \"চ্যানেল সংযোগ বিচ্ছিন্ন হয়েছে, পুনরায় সংযোগ করতে ক্লিক করুন।\",\n  \"channel_disabled_upgrade_plan\": \"এই চ্যানেলটি নিষ্ক্রিয়, এটি সক্রিয় করতে আপনার প্ল্যান আপগ্রেড করুন।\",\n  \"channel_added\": \"চ্যানেল যোগ হয়েছে\",\n  \"are_you_sure_disable_channel\": \"আপনি কি নিশ্চিত যে আপনি এই চ্যানেলটি নিষ্ক্রিয় করতে চান?\",\n  \"disable_channel_title\": \"চ্যানেল নিষ্ক্রিয় করুন\",\n  \"channel_disabled\": \"চ্যানেল নিষ্ক্রিয় হয়েছে\",\n  \"are_you_sure_delete_channel\": \"আপনি কি নিশ্চিত যে আপনি এই চ্যানেলটি মুছে ফেলতে চান?\",\n  \"delete_channel_title\": \"চ্যানেল মুছে ফেলুন\",\n  \"delete_posts_before_channel\": \"এই চ্যানেলটি মুছে ফেলার আগে আপনাকে এর সাথে যুক্ত সব পোস্ট মুছে ফেলতে হবে\",\n  \"channel_deleted\": \"চ্যানেল মুছে ফেলা হয়েছে\",\n  \"channel_enabled\": \"চ্যানেল সক্রিয় হয়েছে\",\n  \"time_table_slots\": \"সময়সূচির স্লট\",\n  \"channel_id_copied\": \"চ্যানেল আইডি ক্লিপবোর্ডে কপি হয়েছে\",\n  \"settings_updated\": \"সেটিংস আপডেট হয়েছে\",\n  \"customer_updated\": \"গ্রাহক আপডেট হয়েছে\",\n  \"custom_url\": \"কাস্টম URL\",\n  \"picture\": \"ছবি\",\n  \"upgrade_required\": \"এই ফিচারটি ব্যবহার করতে আপনাকে আপগ্রেড করতে হবে\",\n  \"move_to_billing\": \"বিলিং-এ যান\",\n  \"payment_required\": \"পেমেন্ট প্রয়োজন\",\n  \"no_content\": \"কোনো বিষয়বস্তু নেই\",\n  \"select_customer_tooltip\": \"গ্রাহক নির্বাচন করুন\",\n  \"customers\": \"গ্রাহকগণ\",\n  \"hour\": \"ঘণ্টা\",\n  \"minutes\": \"মিনিট\",\n  \"updated\": \"আপডেট হয়েছে\",\n  \"change_bot_picture_title\": \"বটের ছবি পরিবর্তন করুন\",\n  \"select_customer_label\": \"গ্রাহক নির্বাচন করুন\",\n  \"start_typing\": \"টাইপ করা শুরু করুন...\",\n  \"choose_set_or_continue\": \"একটি সেট নির্বাচন করুন অথবা সেট ছাড়াই চালিয়ে যান\",\n  \"continue_without_set\": \"সেট ছাড়াই চালিয়ে যান\",\n  \"select_set\": \"একটি সেট নির্বাচন করুন\",\n  \"channel_settings\": \"সেটিংস\",\n  \"post_needs_content_or_image\": \"আপনার পোস্টে অন্তত একটি অক্ষর বা একটি ছবি থাকতে হবে।\",\n  \"please_fix_your_settings\": \"অনুগ্রহ করে আপনার সেটিংস ঠিক করুন\",\n  \"shortlink_urls_question\": \"আপনি কি URL গুলো শর্টলিংক করতে চান? এতে আপনি ক্লিকের পরিসংখ্যান পেতে পারবেন\",\n  \"yes_shortlink_it\": \"হ্যাঁ, শর্টলিংক করুন!\",\n  \"added_successfully\": \"সফলভাবে যোগ হয়েছে\",\n  \"updated_successfully\": \"সফলভাবে আপডেট হয়েছে\",\n  \"create_post_title\": \"পোস্ট তৈরি করুন\",\n  \"post_preview\": \"পোস্টের প্রিভিউ\",\n  \"check_circles_above\": \"উপরে বৃত্তগুলো চেক করুন\",\n  \"create_output\": \"আউটপুট তৈরি করুন\",\n  \"assistant_initial_message\": \"হাই! আমি আপনাকে আপনার সোশ্যাল মিডিয়া পোস্টগুলো আরও সুন্দর করতে সাহায্য করতে পারি।\",\n  \"no_longer_global_mode\": \"আর গ্লোবাল মোডে নেই\",\n  \"two_days\": \"দুই দিন\",\n  \"three_days\": \"তিন দিন\",\n  \"four_days\": \"চার দিন\",\n  \"five_days\": \"পাঁচ দিন\",\n  \"six_days\": \"ছয় দিন\",\n  \"two_weeks\": \"দুই সপ্তাহ\",\n  \"repeat_post_every_label\": \"প্রতি কতদিন পর পোস্ট পুনরাবৃত্তি হবে\",\n  \"add_new_tag\": \"নতুন ট্যাগ যোগ করুন\",\n  \"tag_name\": \"নাম\",\n  \"post_is_too_long\": \"পোস্টটি খুব বড়, অনুগ্রহ করে ঠিক করুন\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"আপনার পোস্টে অন্তত একটি অক্ষর বা একটি ছবি থাকতে হবে।\",\n  \"internal_edit\": \"অভ্যন্তরীণ সম্পাদনা\",\n  \"are_you_sure_go_back_to_global_mode\": \"এই কাজটি অপরিবর্তনীয়। আপনি কি নিশ্চিত যে আপনি গ্লোবাল মোডে ফিরে যেতে চান?\",\n  \"yes_go_back_to_global_mode\": \"হ্যাঁ, গ্লোবাল মোডে ফিরে যান\",\n  \"are_you_sure_delete_this_post\": \"আপনি কি নিশ্চিত যে আপনি এই পোস্টটি মুছে ফেলতে চান?\",\n  \"yes_delete_it\": \"হ্যাঁ, মুছে ফেলুন\",\n  \"cant_edit_networks_when_creating_set\": \"সেট তৈরি করার সময় আপনি নেটওয়ার্ক সম্পাদনা করতে পারবেন না\",\n  \"click_to_exit_global_editing\": \"গ্লোবাল সম্পাদনা থেকে বেরিয়ে এই চ্যানেলের জন্য পোস্টটি কাস্টমাইজ করতে এই বোতামে ক্লিক করুন\",\n  \"edit_content\": \"বিষয়বস্তু সম্পাদনা করুন\",\n  \"editing_a_specific_network\": \"নির্দিষ্ট নেটওয়ার্ক সম্পাদনা\",\n  \"back_to_global\": \"গ্লোবালে ফিরে যান\",\n  \"delete_post_tooltip\": \"পোস্ট মুছুন\",\n  \"drop_files_here_to_upload\": \"আপলোড করতে আপনার ফাইলগুলো এখানে ছেড়ে দিন\",\n  \"insert_emoji\": \"ইমোজি যুক্ত করুন\",\n  \"write_something\": \"কিছু লিখুন …\",\n  \"click_channel_to_add\": \"যোগ করার জন্য একটি চ্যানেলে ক্লিক করুন\",\n  \"connect_your_channels\": \"আপনার চ্যানেলগুলি সংযোগ করুন\",\n  \"connect_social_media_to_start\": \"পোস্ট নির্ধারণ করতে শুরু করতে আপনার সামাজিক মাধ্যম অ্যাকাউন্ট সংযোগ করুন\",\n  \"connected_channels\": \"সংযুক্ত চ্যানেলসমূহ\",\n  \"continue\": \"চালিয়ে যান\",\n  \"continue_without_channels\": \"চ্যানেল ছাড়াই চালিয়ে যান\",\n  \"watch_tutorial\": \"টিউটোরিয়াল দেখুন\",\n  \"watch_tutorial_title\": \"Postiz ব্যবহারের উপায় শিখুন\",\n  \"watch_tutorial_description\": \"Postiz সর্বোচ্চভাবে কাজে লাগাতে এই সংক্ষিপ্ত ভিডিওটি দেখুন\",\n  \"back\": \"পেছনে যান\",\n  \"get_started\": \"শুরু করুন\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/de/translation.json",
    "content": "{\n  \"calendar\": \"Kalender\",\n  \"webhooks\": \"Webhooks\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Webhooks sind eine Möglichkeit, benachrichtigt zu werden, wenn etwas in Postiz passiert,\\n über eine HTTP-Anfrage.\",\n  \"name\": \"Name\",\n  \"url\": \"URL\",\n  \"edit\": \"Bearbeiten\",\n  \"delete\": \"Löschen\",\n  \"add_a_webhook\": \"Webhook hinzufügen\",\n  \"save\": \"Speichern\",\n  \"send_test\": \"Test senden\",\n  \"select_role\": \"Rolle auswählen\",\n  \"video_made_with_ai\": \"Video mit KI erstellt\",\n  \"please_add_at_least\": \"Bitte fügen Sie mindestens 20 Zeichen hinzu\",\n  \"send_invitation_via_email\": \"Einladung per E-Mail senden?\",\n  \"global_settings\": \"Globale Einstellungen\",\n  \"copy_id\": \"Kanal-ID kopieren\",\n  \"team_members\": \"Teammitglieder\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Laden Sie Ihren Assistenten oder Ihre Teammitglieder ein, um ihren Account zu managen\",\n  \"remove\": \"Entfernen\",\n  \"add_another_member\": \"Weiteres Mitglied hinzufügen\",\n  \"signatures\": \"Signaturen\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Sie können ihrem Konto Signaturen hinzufügen, die in Ihren Beiträgen verwendet werden können.\",\n  \"content\": \"Inhalt\",\n  \"auto_add\": \"Automatisch hinzufügen?\",\n  \"delay_comment\": \"Verzögerungskommentar\",\n  \"actions\": \"Aktionen\",\n  \"use_signature\": \"Signatur verwenden\",\n  \"add_a_signature\": \"Signatur hinzufügen\",\n  \"no\": \"Nein\",\n  \"yes\": \"Ja\",\n  \"your_git_repository\": \"Ihr Git-Repository\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Verbinden Sie Ihr GitHub-Repository, um Updates und Analysen zu erhalten\",\n  \"connected\": \"Verbunden:\",\n  \"disconnect\": \"Trennen\",\n  \"connect_your_repository\": \"Repository verbinden\",\n  \"cancel\": \"Abbrechen\",\n  \"connect\": \"Verbinden\",\n  \"public_api\": \"Öffentliche API\",\n  \"check_n8n\": \"Schau dir unseren benutzerdefinierten n8n-Knoten für Postiz an.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Verwenden Sie die Postiz-API, um sie mit Ihren Tools zu integrieren.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Lesen Sie in der Dokumentation, wie Sie sie verwenden.\",\n  \"reveal\": \"Anzeigen\",\n  \"copy_key\": \"Schlüssel kopieren\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Verbinden Sie den Postiz MCP-Server mit Ihrem Client (HTTP-Streaming), um Ihre Beiträge schneller zu planen!\",\n  \"share_with_a_client\": \"Mit einem Kunden teilen\",\n  \"post\": \"Beitrag\",\n  \"comments\": \"Kommentare\",\n  \"user\": \"Benutzer\",\n  \"login_register_to_add_comments\": \"Anmelden / Registrieren, um Kommentare hinzuzufügen\",\n  \"status\": \"Status:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Es gibt keine Plugs, die zu Ihren Kanälen passen\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Sie müssen eines hinzufügen: X, LinkedIn oder Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Gehen Sie zum Kalender, um Kanäle hinzuzufügen\",\n  \"channels\": \"Kanäle\",\n  \"activate\": \"Aktivieren\",\n  \"this_channel_needs_to_be_refreshed\": \"Dieser Kanal muss aktualisiert werden,\",\n  \"click_here_to_refresh\": \"Hier klicken, um zu aktualisieren\",\n  \"can_t_show_analytics_yet\": \"Analysen können noch nicht angezeigt werden\",\n  \"you_have_to_add_social_media_channels\": \"Du musst Social-Media-Kanäle hinzufügen\",\n  \"supported\": \"Unterstützt:\",\n  \"step\": \"SCHRITT\",\n  \"skip_onboarding\": \"Onboarding überspringen\",\n  \"onboarding\": \"Onboarding\",\n  \"next\": \"Weiter\",\n  \"you_are_done_from_here_you_can\": \"Du bist fertig, von hier aus kannst du:\",\n  \"view_analytics\": \"Analysen anzeigen\",\n  \"schedule_a_new_post\": \"Einen neuen Beitrag planen\",\n  \"to_sell_posts_you_would_have_to\": \"Um Beiträge zu verkaufen, musst du:\",\n  \"1_connect_at_least_one_channel\": \"1. Mindestens einen Kanal verbinden\",\n  \"2_connect_you_bank_account\": \"2. Dein Bankkonto verbinden\",\n  \"go_back_to_connect_channels\": \"Zurückgehen, um Kanäle zu verbinden\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Zur Verkäuferseite wechseln, um dein Bankkonto zu verbinden\",\n  \"connect_channels\": \"Kanäle verbinden\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Verbinde deine Social-Media- und Publishing-Webseiten-Kanäle,\\n            um später Beiträge zu planen\",\n  \"social\": \"Soziale Netzwerke\",\n  \"publishing_platforms\": \"Publishing-Plattformen\",\n  \"no_channels\": \"Noch keine Kanäle\",\n  \"connect_your_accounts\": \"Verbinde deine sozialen Konten, um mit dem Planen, Veröffentlichen und Analysieren zu beginnen – alles an einem Ort.\",\n  \"notifications\": \"Benachrichtigungen\",\n  \"no_notifications\": \"Keine Benachrichtigungen\",\n  \"send_message\": \"Nachricht senden\",\n  \"mar_28\": \"28. März\",\n  \"there_are_no_messages_yet\": \"Es gibt noch keine Nachrichten.\",\n  \"checkout_the_marketplace\": \"Marktplatz ansehen\",\n  \"go_to_marketplace\": \"Zum Marktplatz gehen\",\n  \"all_messages\": \"Alle Nachrichten\",\n  \"previous\": \"Zurück\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Bilder auswählen oder hochladen (maximal 5 auf einmal)\",\n  \"you_can_also_drag_drop_pictures\": \"Sie können Bilder auch per Drag & Drop hinzufügen\",\n  \"you_don_t_have_any_assets_yet\": \"Sie haben noch keine Assets.\",\n  \"click_the_button_below_to_upload_one\": \"Klicken Sie auf die Schaltfläche unten, um eines hochzuladen\",\n  \"click_the_button_below_to_upload_other\": \"Klicken Sie auf die Schaltfläche unten, um mehrere hochzuladen\",\n  \"add_selected_media\": \"Ausgewählte Medien hinzufügen\",\n  \"insert_media\": \"Medien einfügen\",\n  \"design_media\": \"Medien gestalten\",\n  \"select\": \"Auswählen\",\n  \"editor\": \"Editor\",\n  \"clear\": \"Löschen\",\n  \"order_completed\": \"Bestellung abgeschlossen\",\n  \"the_order_has_been_completed\": \"Die Bestellung wurde abgeschlossen\",\n  \"post_has_been_published\": \"Beitrag wurde veröffentlicht\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Neues Angebot\",\n  \"platform\": \"Plattform\",\n  \"posts\": \"Beiträge\",\n  \"pay_accept_offer\": \"Bezahlen & Angebot annehmen\",\n  \"accepted\": \"Angenommen\",\n  \"post_draft\": \"Beitragsentwurf\",\n  \"revision_needed\": \"Überarbeitung erforderlich\",\n  \"approve\": \"Genehmigen\",\n  \"preview\": \"Vorschau\",\n  \"revision_requested\": \"Überarbeitung angefordert\",\n  \"accepted_1\": \"AKZEPTIERT\",\n  \"cancelled_by_the_seller\": \"Vom Verkäufer storniert\",\n  \"please_select_your_country_where_your_business_is\": \"Bitte wählen Sie das Land aus, in dem sich Ihr Unternehmen befindet.\",\n  \"select_country\": \"--LAND AUSWÄHLEN--\",\n  \"connect_bank_account\": \"Bankkonto verbinden\",\n  \"seller_mode\": \"Verkäufermodus\",\n  \"active\": \"Aktiv\",\n  \"details\": \"Details\",\n  \"audience_size\": \"Zielgruppengröße\",\n  \"add_another_platform\": \"Weitere Plattform hinzufügen\",\n  \"send_an_offer_for\": \"Angebot senden für €\",\n  \"complete_order_and_pay_early\": \"Bestellung abschließen und frühzeitig bezahlen\",\n  \"order_in_progress\": \"Bestellung in Bearbeitung\",\n  \"create_a_new_offer\": \"Neues Angebot erstellen\",\n  \"orders\": \"Bestellungen\",\n  \"price\": \"Preis\",\n  \"state\": \"Status\",\n  \"showing\": \"Anzeigen\",\n  \"to\": \"bis\",\n  \"from\": \"von\",\n  \"results\": \"Ergebnisse\",\n  \"content_writer\": \"Texter\",\n  \"influencer\": \"Influencer\",\n  \"request_service\": \"Dienst anfordern\",\n  \"the_marketplace_is_not_opened_yet\": \"Der Marktplatz ist noch nicht geöffnet\",\n  \"check_again_soon\": \"Schau bald wieder vorbei!\",\n  \"filter\": \"Filter\",\n  \"result\": \"Ergebnis\",\n  \"seller\": \"Verkäufer\",\n  \"buyer\": \"Käufer\",\n  \"discord_support\": \"Discord-Support\",\n  \"teams\": \"Teams\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Automatisches Posten\",\n  \"logout_from\": \"Abmelden von\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Schließe dich über 10.000 Unternehmern an, die Postiz nutzen\",\n  \"to_manage_all_your_social_media_channels\": \"Um all deine Social-Media-Kanäle zu verwalten\",\n  \"100_no_risk_trial\": \"100% risikofreie Testphase\",\n  \"pay_nothing_for_the_first_7_days\": \"Zahle in den ersten 7 Tagen nichts\",\n  \"cancel_anytime_hassle_free\": \"Jederzeit kündigen, in den Einstellungen\",\n  \"add_free_subscription\": \"-- KOSTENLOSES ABONNEMENT HINZUFÜGEN --\",\n  \"currently_impersonating\": \"Derzeit als jemand anderes angemeldet\",\n  \"user_1\": \"Benutzer:\",\n  \"drag_n_drop_some_files_here\": \"Dateien hierher ziehen und ablegen\",\n  \"add_time_slot\": \"Zeitfenster hinzufügen\",\n  \"add_slot\": \"Slot hinzufügen\",\n  \"cancel_publication\": \"Veröffentlichung abbrechen\",\n  \"statistics\": \"Statistiken\",\n  \"loading\": \"Lädt\",\n  \"short_link\": \"Kurzlink\",\n  \"original_link\": \"Originaler Link\",\n  \"clicks\": \"Klicks\",\n  \"selected_customer\": \"Ausgewählter Kunde\",\n  \"customer\": \"Kunde:\",\n  \"repeat_post_every\": \"Beitrag wiederholen alle...\",\n  \"use_this_media\": \"Diese Media verwenden\",\n  \"create_new_post\": \"Beitrag erstellen\",\n  \"update_post\": \"Vorhandenen Beitrag aktualisieren\",\n  \"merge_comments_into_one_post\": \"Kommentare zu einem Beitrag zusammenfassen\",\n  \"accounts_that_will_engage\": \"Konten, die interagieren werden:\",\n  \"day\": \"Tag\",\n  \"week\": \"Woche\",\n  \"month\": \"Monat\",\n  \"remove_from_customer\": \"Vom Kunden entfernen\",\n  \"show_more\": \"+ Mehr anzeigen\",\n  \"show_less\": \"- Weniger anzeigen\",\n  \"upload\": \"Hochladen\",\n  \"ai\": \"KI\",\n  \"add_channel\": \"Kanal hinzufügen\",\n  \"add_platform\": \"Plattform hinzufügen\",\n  \"articles\": \"Artikel\",\n  \"add_comment\": \"Kommentar hinzufügen\",\n  \"add_post\": \"Beitrag in einem Thread hinzufügen\",\n  \"add_comment_or_post\": \"Kommentar / Beitrag hinzufügen\",\n  \"you_are_in_global_editing_mode\": \"Sie befinden sich im globalen Bearbeitungsmodus\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Der Beitrag sollte mindestens 6 Zeichen lang sein\",\n  \"are_you_sure_you_want_to_delete_post\": \"Bist du sicher, dass du diesen Beitrag löschen möchtest?\",\n  \"post_deleted_successfully\": \"Beitrag erfolgreich gelöscht\",\n  \"delete_post\": \"Beitrag löschen\",\n  \"save_as_draft\": \"Als Entwurf speichern\",\n  \"post_now\": \"Jetzt posten\",\n  \"please_add\": \"Bitte hinzufügen\",\n  \"to_your_telegram_group_channel_and_click_here\": \"zu Ihrer\\n Telegram-Gruppe / Ihrem Kanal und klicken Sie hier:\",\n  \"connect_telegram\": \"Telegram verbinden\",\n  \"please_add_the_following_command_in_your_chat\": \"Bitte fügen Sie den folgenden Befehl in Ihrem Chat hinzu:\",\n  \"copy\": \"Kopieren\",\n  \"settings\": \"Einstellungen\",\n  \"integrations\": \"Integrationen\",\n  \"add_integration\": \"Integration hinzufügen\",\n  \"you_are_now_editing_only\": \"Sie bearbeiten jetzt nur\",\n  \"tag_a_company\": \"Unternehmen markieren\",\n  \"video_length_is_invalid_must_be_up_to\": \"Videolänge ist ungültig, sie darf maximal\",\n  \"seconds\": \"Sekunden\",\n  \"this_feature_available_only_for_photos\": \"Diese Funktion ist nur für Fotos verfügbar, es wird eine Standardmusik hinzugefügt, die Sie später ändern können.\",\n  \"allow_user_to\": \"Benutzer erlauben zu:\",\n  \"your_video_will_be_labeled_promotional\": \"Ihr Video wird als \\\"Werbeinhalte\\\" gekennzeichnet.\",\n  \"this_cannot_be_changed_once_posted\": \"Dies kann nicht mehr geändert werden, sobald Ihr Video gepostet wurde.\",\n  \"turn_on_to_disclose_video_promotes\": \"Aktivieren Sie diese Option, um offenzulegen, dass dieses Video Waren oder Dienstleistungen im Austausch für eine Gegenleistung bewirbt. Ihr Video könnte Sie selbst, eine dritte Partei oder beides bewerben.\",\n  \"you_are_promoting_yourself\": \"Sie bewerben sich selbst oder Ihre eigene Marke.\",\n  \"this_video_will_be_classified_brand_organic\": \"Dieses Video wird als Brand Organic eingestuft.\",\n  \"you_are_promoting_another_brand\": \"Sie bewerben eine andere Marke oder eine dritte Partei.\",\n  \"this_video_will_be_classified_branded_content\": \"Dieses Video wird als Markeninhalt eingestuft.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Mit dem Posten stimmen Sie den TikTok-Bedingungen zu\",\n  \"music_usage_confirmation\": \"Bestätigung der Musiknutzung\",\n  \"branded_content_policy\": \"Richtlinie für Markeninhalte\",\n  \"select_1\": \"--Auswählen--\",\n  \"select_flair\": \"--Flair auswählen--\",\n  \"link\": \"Link\",\n  \"add_subreddit\": \"Subreddit hinzufügen\",\n  \"please_add_at_least_one_subreddit\": \"Bitte fügen Sie mindestens einen Subreddit hinzu\",\n  \"add_community\": \"Community hinzufügen\",\n  \"select_post_type\": \"Beitragstyp auswählen...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Wir konnten kein Unternehmen finden, das mit Ihrer LinkedIn-Seite verbunden ist.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Bitte schließen Sie diesen Dialog, erstellen Sie eine neue Seite und fügen Sie erneut einen neuen Kanal hinzu.\",\n  \"select_linkedin_page\": \"LinkedIn-Seite auswählen:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Wir konnten kein Unternehmen finden, das mit den ausgewählten Seiten verbunden ist.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Wir empfehlen Ihnen, alle Seiten und alle Unternehmen zu verbinden.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Bitte schließen Sie diesen Dialog, löschen Sie Ihre Integration und fügen Sie erneut einen neuen Kanal \\n        hinzu.\",\n  \"select_instagram_account\": \"Instagram-Konto auswählen:\",\n  \"select_page\": \"Seite auswählen:\",\n  \"generate_image_with_ai\": \"Bild mit KI generieren\",\n  \"reconnect_channel\": \"Kanal erneut verbinden\",\n  \"update_credentials\": \"Zugangsdaten aktualisieren\",\n  \"additional_settings\": \"Zusätzliche Einstellungen\",\n  \"change_bot\": \"Bot wechseln\",\n  \"move_add_to_customer\": \"Zu Kunde verschieben / hinzufügen\",\n  \"edit_time_slots\": \"Zeitfenster bearbeiten\",\n  \"enable_channel\": \"Kanal aktivieren\",\n  \"disable_channel\": \"Kanal deaktivieren\",\n  \"add\": \"Hinzufügen\",\n  \"short_post\": \"Kurzer Beitrag\",\n  \"long_post\": \"Langer Beitrag\",\n  \"a_thread_with_short_posts\": \"Ein Thread mit kurzen Beiträgen\",\n  \"a_thread_with_long_posts\": \"Ein Thread mit langen Beiträgen\",\n  \"personal_voice_i_am_happy_to_announce\": \"Persönlicher Ton (\\\"Ich freue mich, bekannt zu geben\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Unternehmenston (\\\"Wir freuen uns, bekannt zu geben\\\")\",\n  \"generate\": \"Generieren\",\n  \"generate_posts\": \"Beiträge generieren\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Kaufe ein lebenslanges PRO-Konto mit SOL ($199). Bitte beachte, dass es für diesen Kauf keine Rückerstattung gibt.\",\n  \"purchase_now\": \"Jetzt kaufen\",\n  \"pay_today\": \"Heute bezahlen\",\n  \"we_are_sorry_to_see_you_go\": \"Es tut uns leid, dass Sie gehen :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Würden Sie uns kurz mitteilen, was wir hätten besser machen können?\",\n  \"cancel_subscription\": \"Abonnement kündigen\",\n  \"plans\": \"Pläne\",\n  \"monthly\": \"MONATLICH\",\n  \"yearly\": \"JÄHRLICH\",\n  \"reactivate_subscription\": \"Abonnement reaktivieren\",\n  \"update_payment_method_invoices_history\": \"Zahlungsmethode aktualisieren / Rechnungsverlauf\",\n  \"cancel_subscription_1\": \"Abonnement kündigen\",\n  \"your_subscription_will_be_canceled_at\": \"Ihr Abonnement wird gekündigt am\",\n  \"you_will_never_be_charged_again\": \"Ihnen werden keine weiteren Kosten berechnet\",\n  \"current_package\": \"Aktuelles Paket:\",\n  \"next_package\": \"Nächstes Paket:\",\n  \"claim\": \"Einlösen\",\n  \"frequently_asked_questions\": \"Häufig gestellte Fragen\",\n  \"autopost\": \"Automatisches Posten\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Autopost kann Ihre neuen RSS-Einträge automatisch in sozialen Medien veröffentlichen\",\n  \"title\": \"Titel\",\n  \"add_an_autopost\": \"Autopost hinzufügen\",\n  \"post_content\": \"Beitragsinhalt\",\n  \"sign_up\": \"Registrieren\",\n  \"or\": \"ODER\",\n  \"by_registering_you_agree_to_our\": \"Mit der Registrierung stimmen Sie unseren\",\n  \"and\": \"und\",\n  \"terms_of_service\": \"Nutzungsbedingungen\",\n  \"privacy_policy\": \"Datenschutzrichtlinien\",\n  \"create_account\": \"Konto erstellen\",\n  \"already_have_an_account\": \"Sie haben bereits ein Konto?\",\n  \"sign_in\": \"Anmelden\",\n  \"sign_in_1\": \"Anmelden\",\n  \"don_t_have_an_account\": \"Sie haben noch kein Konto?\",\n  \"forgot_password\": \"Passwort vergessen\",\n  \"forgot_password_1\": \"Passwort vergessen\",\n  \"send_password_reset_email\": \"E-Mail zum Zurücksetzen des Passworts senden\",\n  \"go_back_to_login\": \"Zurück zum Login\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Wir haben Ihnen eine E-Mail mit einem Link zum Zurücksetzen Ihres Passworts gesendet.\",\n  \"change_password\": \"Passwort ändern\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Wir haben Ihr Passwort erfolgreich zurückgesetzt. Sie können sich jetzt anmelden mit ihrem\",\n  \"click_here_to_go_back_to_login\": \"Klicken Sie hier, um zum Login zurückzukehren\",\n  \"activate_your_account\": \"Aktivieren Sie Ihr Konto\",\n  \"thank_you_for_registering\": \"Vielen Dank für Ihre Registrierung!\",\n  \"please_check_your_email_to_activate_your_account\": \"Bitte überprüfen Sie Ihre E-Mails, um Ihr Konto zu aktivieren.\",\n  \"sign_in_with\": \"Anmelden mit\",\n  \"continue_with_google\": \"Mit Google fortfahren\",\n  \"sign_in_with_github\": \"Mit GitHub anmelden\",\n  \"continue_with_farcaster\": \"Mit Farcaster fortfahren\",\n  \"continue_with_your_wallet\": \"Mit Ihrem Wallet fortfahren\",\n  \"stars_per_day\": \"Sterne pro Tag\",\n  \"media\": \"Medien\",\n  \"check_launch\": \"Launch prüfen\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Laden Sie Ihr GitHub-Repository in den Einstellungen, um Analysen zu sehen\",\n  \"stars\": \"Sterne\",\n  \"processing_stars\": \"Sterne werden verarbeitet...\",\n  \"forks\": \"Forks\",\n  \"registration_is_disabled\": \"Registrierung ist deaktiviert\",\n  \"login_instead\": \"Stattdessen anmelden\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Wählen Sie eine Unterhaltung und chatten Sie los.\",\n  \"adding_channel_redirecting_you\": \"Kanal wird hinzugefügt, Sie werden weitergeleitet\",\n  \"could_not_add_provider\": \"Anbieter konnte nicht hinzugefügt werden.\",\n  \"you_are_being_redirected_back\": \"Sie werden zurückgeleitet\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Wir haben derzeit Schwierigkeiten, bitte versuchen Sie, die Seite zu aktualisieren.\",\n  \"post_not_found\": \"Beitrag nicht gefunden\",\n  \"publication_date\": \"Veröffentlichungsdatum:\",\n  \"analytics\": \"Analytiken:\",\n  \"launches\": \"Veröffentlichungen\",\n  \"plugs\": \"Plugs\",\n  \"billing\": \"Abrechnung\",\n  \"affiliate\": \"Partnerprogramm\",\n  \"monday\": \"Montag\",\n  \"tuesday\": \"Dienstag\",\n  \"wednesday\": \"Mittwoch\",\n  \"thursday\": \"Donnerstag\",\n  \"friday\": \"Freitag\",\n  \"saturday\": \"Samstag\",\n  \"sunday\": \"Sonntag\",\n  \"can_t_change_date_remove_post_from_publication\": \"Datum kann nicht geändert werden, entfernen Sie den Beitrag aus der Veröffentlichung\",\n  \"predicted_github_trending_change\": \"Prognostizierte GitHub-Trendänderung\",\n  \"duplicate_post\": \"Dupliziere Beitrag\",\n  \"preview_post\": \"Beitragsvorschau\",\n  \"post_statistics\": \"Beitragsstatistiken\",\n  \"draft\": \"Entwurf\",\n  \"week_number\": \"Woche {{number}}\",\n  \"top_title_edit_webhook\": \"Webhook bearbeiten\",\n  \"top_title_add_webhook\": \"Webhook hinzufügen\",\n  \"top_title_oh_no\": \"Oh nein\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"Autopost bearbeiten\",\n  \"top_title_add_autopost\": \"Autopost hinzufügen\",\n  \"top_title_send_a_new_offer\": \"Neues Angebot senden\",\n  \"top_title_media_library\": \"Medienbibliothek\",\n  \"top_title_add_signature\": \"Signatur hinzufügen\",\n  \"top_title_send_a_message_to\": \"Nachricht an {{name}} senden\",\n  \"top_title_configure_provider\": \"Anbieter konfigurieren\",\n  \"top_title_add_member\": \"Mitglied hinzufügen\",\n  \"top_title_change_bot_picture\": \"Bot-Bild ändern\",\n  \"top_title_create_a_new_tag\": \"Neuen Tag erstellen\",\n  \"top_title_select_company\": \"Unternehmen auswählen\",\n  \"top_title_additional_settings\": \"Zusätzliche Einstellungen\",\n  \"top_title_time_table_slots\": \"Zeitplan-Slots\",\n  \"top_title_design_media\": \"Medien gestalten\",\n  \"top_title_edit_post\": \"Beitrag bearbeiten\",\n  \"top_title_create_post\": \"Beitrag erstellen\",\n  \"top_title_move__add_to_customer\": \"Zu Kunde verschieben / hinzufügen\",\n  \"top_title_add_api_key_for\": \"API-Schlüssel für {{name}} hinzufügen\",\n  \"top_title_instance_url\": \"Instanz-URL\",\n  \"top_title_custom_url\": \"Benutzerdefinierte URL\",\n  \"top_title_add_channel\": \"Kanal hinzufügen\",\n  \"top_title_add_telegram\": \"Telegram hinzufügen\",\n  \"top_title_add_wrapcast\": \"Wrapcast hinzufügen\",\n  \"top_title_comments_for\": \"Kommentare für {{date}}\",\n  \"top_title_edit_signature\": \"Signatur bearbeiten\",\n  \"label_name\": \"Name\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Titel\",\n  \"label_subtitle\": \"Untertitel\",\n  \"label_email\": \"E-Mail\",\n  \"label_full_name\": \"Vollständiger Name\",\n  \"label_password\": \"Passwort\",\n  \"label_confirm_password\": \"Passwort bestätigen\",\n  \"label_api_key\": \"API-Schlüssel\",\n  \"label_instance_url\": \"Instanz-URL\",\n  \"label_custom_url\": \"Benutzerdefinierte URL\",\n  \"label_feedback\": \"Feedback\",\n  \"label_bio\": \"Biografie\",\n  \"label_role\": \"Rolle\",\n  \"label_country\": \"Land\",\n  \"label_audience_size\": \"Publikumsgröße auf allen Plattformen\",\n  \"label_pick_time\": \"Zeit auswählen\",\n  \"label_nickname\": \"Spitzname\",\n  \"label_write_anything\": \"Schreibe irgendetwas\",\n  \"label_output_format\": \"Ausgabeformat\",\n  \"label_add_pictures\": \"Bilder hinzufügen?\",\n  \"label_hour\": \"Stunde\",\n  \"label_minutes\": \"Minuten\",\n  \"label_select_publication\": \"Publikation auswählen\",\n  \"label_canonical_link\": \"Kanonischer Link\",\n  \"label_cover_picture\": \"Titelbild\",\n  \"label_tags\": \"Tags\",\n  \"label_topics\": \"Themen\",\n  \"label_tags_maximum_4\": \"Tags (maximal 4)\",\n  \"label_attachments\": \"Anhänge\",\n  \"label_type\": \"Typ\",\n  \"label_thumbnail\": \"Vorschaubild\",\n  \"label_who_can_see_this_video\": \"Wer kann dieses Video sehen?\",\n  \"label_content_posting_method\": \"Methode zum Posten von Inhalten\",\n  \"label_auto_add_music\": \"Musik automatisch hinzufügen\",\n  \"label_duet\": \"Duett\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Kommentare\",\n  \"label_disclose_video_content\": \"Videoinhalt offenlegen\",\n  \"label_your_brand\": \"Deine Marke\",\n  \"label_branded_content\": \"Markeninhalt\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Medien\",\n  \"label_search_subreddit\": \"Subreddit suchen\",\n  \"label_delay\": \"Verzögerung\",\n  \"label_post_type\": \"Beitragstyp\",\n  \"label_collaborators\": \"Kollaboratoren (max. 3) – Konten dürfen nicht privat sein\",\n  \"label_community\": \"Community\",\n  \"label_search_community\": \"Community durchsuchen\",\n  \"label_channel\": \"Kanal\",\n  \"label_search_channel\": \"Kanal durchsuchen\",\n  \"label_select_channel\": \"Kanal auswählen\",\n  \"label_new_password\": \"Neues Passwort\",\n  \"label_repeat_password\": \"Passwort wiederholen\",\n  \"label_platform\": \"Plattform\",\n  \"label_price_per_post\": \"Preis pro Beitrag\",\n  \"label_integrations\": \"Integrationen\",\n  \"label_code\": \"Code\",\n  \"label_should_sync_last_post\": \"Sollen wir den aktuellen letzten Beitrag synchronisieren?\",\n  \"label_when_post\": \"Wann sollen wir es posten?\",\n  \"label_autogenerate_content\": \"Inhalt automatisch generieren\",\n  \"label_generate_picture\": \"Bild generieren?\",\n  \"label_company\": \"Unternehmen\",\n  \"label_tag_color\": \"Tag-Farbe\",\n  \"label_select_board\": \"Board auswählen\",\n  \"label_select_organization\": \"Organisation auswählen\",\n  \"label_auto_add_signature\": \"Signatur automatisch hinzufügen?\",\n  \"enable_color_picker\": \"Farbwähler aktivieren\",\n  \"cancel_the_color_picker\": \"Farbwähler abbrechen\",\n  \"no_content_yet\": \"Noch kein Inhalt\",\n  \"write_your_reply\": \"Schreibe deinen Beitrag...\",\n  \"add_a_tag\": \"Tag hinzufügen\",\n  \"add_to_calendar\": \"Zum Kalender hinzufügen\",\n  \"select_channels_from_circles\": \"Wähle Kanäle aus den obigen Kreisen aus\",\n  \"not_matching_order\": \"Nicht übereinstimmende Reihenfolge\",\n  \"submit_for_order\": \"Zur Bestellung einreichen\",\n  \"schedule\": \"Planen\",\n  \"update\": \"Aktualisieren\",\n  \"attachments\": \"Anhänge\",\n  \"tags\": \"Tags\",\n  \"public_to_everyone\": \"Öffentlich für alle\",\n  \"mutual_follow_friends\": \"Gegenseitig gefolgte Freunde\",\n  \"follower_of_creator\": \"Follower des Erstellers\",\n  \"self_only\": \"Nur ich\",\n  \"post_content_directly_to_tiktok\": \"Inhalt direkt auf TikTok posten\",\n  \"upload_content_to_tiktok_without_posting\": \"Inhalt auf TikTok hochladen, ohne zu posten\",\n  \"choose_upload_without_posting_description\": \"Wähle 'Hochladen ohne Posten', wenn du deinen Inhalt in der TikTok-App vor der Veröffentlichung überprüfen und bearbeiten möchtest. So hast du Zugriff auf die integrierten Bearbeitungstools von TikTok und kannst vor dem Posten letzte Anpassungen vornehmen.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Werde ich von Postiz Gebühren berechnet bekommen?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Um die Kreditkarteninformationen zu bestätigen, wird Postiz 2 $ vorübergehend einbehalten und sofort wieder freigeben. Sie können Ihr Abonnement jederzeit in den Einstellungen kündigen, ohne mit einer Person sprechen zu müssen.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Kann ich Postiz vertrauen?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz ist stolz darauf, Open Source zu sein! Wir glauben an eine ethische und transparente Kultur, was bedeutet, dass Postiz für immer bestehen wird. Du kannst den gesamten Code einsehen oder für persönliche Projekte nutzen. Um das Open-Source-Repository anzusehen, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">klicke hier</a>.\",\n  \"faq_what_are_channels\": \"Was sind Kanäle?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz ermöglicht es dir, deine Beiträge auf verschiedenen Kanälen zu planen.\\nEin Kanal ist eine Veröffentlichungsplattform, auf der du deine Beiträge planen kannst.\\nZum Beispiel kannst du deine Beiträge auf X, Facebook, Instagram, TikTok, YouTube, Reddit, LinkedIn, Dribbble, Threads oder Pinterest planen.\",\n  \"faq_what_are_team_members\": \"Was sind Teammitglieder?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Wenn du ein Team mit mehreren Mitgliedern hast, kannst du sie in deinen Arbeitsbereich einladen, um gemeinsam an Beiträgen zu arbeiten und ihre persönlichen Kanäle hinzuzufügen.\",\n  \"faq_what_is_ai_auto_complete\": \"Was ist KI-Autovervollständigung?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Wir automatisieren ChatGPT, um dir beim Schreiben von Social-Media-Beiträgen und Artikeln zu helfen.\",\n  \"enter_email\": \"E-Mail eingeben\",\n  \"are_you_sure\": \"Bist du sicher?\",\n  \"no_cancel\": \"Nein, abbrechen!\",\n  \"are_you_sure_you_want_to_delete\": \"Bist du sicher, dass du {{name}} löschen möchtest?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Bist du sicher, dass du das Bild löschen möchtest?\",\n  \"are_you_sure_you_want_to_logout\": \"Bist du sicher, dass du dich abmelden möchtest?\",\n  \"yes_logout\": \"Ja, abmelden\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Bist du sicher, dass du diesen Slot löschen möchtest?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Bist du sicher, dass du dieses Subreddit löschen möchtest?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Bist du sicher, dass du das Fenster schließen möchtest?\",\n  \"yes_close\": \"Ja, schließen\",\n  \"link_copied_to_clipboard\": \"Link in die Zwischenablage kopiert\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Bist du sicher, dass du dieses Fenster schließen möchtest? (Alle Daten gehen verloren)\",\n  \"yes_close_it\": \"Ja, schließe es!\",\n  \"uploading_pictures\": \"Bilder werden hochgeladen...\",\n  \"agent_starting\": \"Agent startet\",\n  \"researching_your_content\": \"Dein Inhalt wird recherchiert...\",\n  \"understanding_the_category\": \"Kategorie wird verstanden...\",\n  \"finding_the_topic\": \"Thema wird gefunden...\",\n  \"finding_popular_posts_to_match_with\": \"Beliebte Beiträge werden gesucht...\",\n  \"generating_hook\": \"Hook wird generiert...\",\n  \"generating_content\": \"Inhalt wird generiert...\",\n  \"generating_pictures\": \"Bilder werden generiert...\",\n  \"finding_time_to_post\": \"Zeit zum Posten wird gesucht...\",\n  \"write_anything\": \"Schreibe irgendetwas\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Sie können alles schreiben, was Sie möchten, und auch Links hinzufügen. Wir übernehmen die Recherche für Sie...\",\n  \"output_format\": \"Ausgabeformat\",\n  \"add_pictures\": \"Bilder hinzufügen?\",\n  \"7_days\": \"7 Tage\",\n  \"30_days\": \"30 Tage\",\n  \"90_days\": \"90 Tage\",\n  \"start_7_days_free_trial\": \"7-tägige kostenlose Testversion starten\",\n  \"change_language\": \"Sprache ändern\",\n  \"that_a_wrap\": \"Das war's!\\n\\nWenn dir dieser Thread gefallen hat:\\n\\n1. Folge mir @{{username}} für mehr davon\\n2. Retweete den untenstehenden Tweet, um diesen Thread mit deinem Publikum zu teilen\\n\",\n  \"post_as_images_carousel\": \"Als Bilderkarussell posten\",\n  \"save_set\": \"Set speichern\",\n  \"separate_post\": \"Beitrag in mehrere Beiträge aufteilen\",\n  \"label_who_can_reply_to_this_post\": \"Wer kann auf diesen Beitrag antworten?\",\n  \"delete_integration\": \"Integration löschen\",\n  \"start_writing_your_post\": \"Beginne, deinen Beitrag für eine Vorschau zu schreiben\",\n  \"billing_join_over\": \"Schließen Sie sich über\",\n  \"billing_entrepreneurs_count\": \"20.000+ Unternehmer\",\n  \"billing_who_use\": \"die nutzen\",\n  \"billing_postiz_grow_social\": \"Postiz, um ihre Social-Media-Präsenz zu steigern\",\n  \"billing_no_risk_trial\": \"100% risikofreie Testphase\",\n  \"billing_pay_nothing_7_days\": \"Zahlen Sie NICHTS in den ersten 7 Tagen\",\n  \"billing_cancel_anytime\": \"Jederzeit kündigen, in den Einstellungen\",\n  \"billing_choose_plan\": \"Wählen Sie einen Plan\",\n  \"billing_monthly\": \"Monatlich\",\n  \"billing_yearly\": \"Jährlich\",\n  \"billing_20_percent_off\": \"20% Rabatt\",\n  \"billing_features\": \"Funktionen\",\n  \"billing_channel\": \"Kanal\",\n  \"billing_channels\": \"Kanäle\",\n  \"billing_unlimited\": \"Unbegrenzt\",\n  \"billing_posts_per_month\": \"Beiträge pro Monat\",\n  \"billing_unlimited_team_members\": \"Unbegrenzte Teammitglieder\",\n  \"billing_ai_auto_complete\": \"KI-Autovervollständigung\",\n  \"billing_ai_copilots\": \"KI-Copiloten\",\n  \"billing_ai_autocomplete\": \"KI-Autovervollständigung\",\n  \"billing_advanced_picture_editor\": \"Erweiterter Bildeditor\",\n  \"billing_ai_images_per_month\": \"KI-Bilder pro Monat\",\n  \"billing_ai_videos_per_month\": \"KI-Videos pro Monat\",\n  \"billing_billing_address\": \"Rechnungsadresse\",\n  \"billing_payment\": \"Zahlung\",\n  \"billing_powered_by_stripe\": \"Sichere Zahlungen abgewickelt von\",\n  \"billing_your_7_day_trial_is\": \"Ihre 7-tägige Testphase ist\",\n  \"billing_100_percent_free\": \"100% kostenlos\",\n  \"billing_ending\": \"endet\",\n  \"billing_cancel_anytime_short\": \"Jederzeit kündbar.\",\n  \"billing_pay_0_start_trial\": \"Heute 0 $ zahlen – Starten Sie Ihre kostenlose Testphase!\",\n  \"billing_pay_now\": \"Jetzt bezahlen\",\n  \"billing_per_month\": \"/ Monat\",\n  \"billing_per_year\": \"/ Jahr\",\n  \"billing_order_summary\": \"Bestellübersicht\",\n  \"billing_applied\": \"Angewendet\",\n  \"billing_due_today\": \"Heute fällig\",\n  \"billing_then\": \"Dann\",\n  \"billing_on\": \"am\",\n  \"billing_discount_applied\": \"angewendet\",\n  \"billing_remove\": \"Entfernen\",\n  \"billing_coupon_expires\": \"Gutschein läuft ab am\",\n  \"billing_invalid_coupon\": \"Ungültiger Gutscheincode\",\n  \"billing_coupon_applied\": \"Gutschein erfolgreich angewendet!\",\n  \"billing_coupon_removed\": \"Gutschein entfernt\",\n  \"billing_error_removing_coupon\": \"Fehler beim Entfernen des Gutscheins\",\n  \"billing_have_discount_coupon\": \"Haben Sie einen Rabattgutschein?\",\n  \"billing_discount_coupon\": \"Rabattgutschein\",\n  \"billing_cancel\": \"Abbrechen\",\n  \"billing_enter_coupon_code\": \"Gutscheincode eingeben\",\n  \"billing_applying\": \"Wird angewendet...\",\n  \"billing_apply\": \"Anwenden\",\n  \"billing_subscription\": \"Abonnement\",\n  \"billing_cancel_notice\": \"Sie können jederzeit in den Einstellungen kündigen, ohne mit einer Person sprechen zu müssen, und werden nie belastet.\",\n  \"select_channels\": \"Kanäle auswählen\",\n  \"start_a_new_chat\": \"Neuen Chat starten\",\n  \"your_assistant\": \"Ihr Assistent\",\n  \"agent_welcome_message\": \"Hallo, ich bin Ihr Postiz-Agent 🙌🏻.\\n\\nIch kann einen oder mehrere Beiträge für mehrere Kanäle planen und Bilder sowie Videos generieren.\\n\\nSie können die gewünschten Kanäle im linken Menü auswählen.\\n\\nIhre bisherigen Unterhaltungen finden Sie im rechten Menü.\\n\\nSie können mich auch als MCP-Server verwenden, siehe Einstellungen >> Öffentliche API.\",\n  \"last_github_trending\": \"Letzte Github-Trends\",\n  \"next_predicted_github_trending\": \"Nächste vorhergesagte Github-Trends\",\n  \"repository\": \"Repository\",\n  \"date\": \"Datum\",\n  \"total_stars\": \"Gesamtanzahl Sterne\",\n  \"total_forks\": \"Gesamtanzahl Forks\",\n  \"continue_with\": \"Weiter mit\",\n  \"email_address\": \"E-Mail-Adresse\",\n  \"email_already_exists\": \"E-Mail existiert bereits\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Autopost bearbeiten\",\n  \"add_autopost_title\": \"Autopost hinzufügen\",\n  \"webhook_deleted_successfully\": \"Webhook erfolgreich gelöscht\",\n  \"all_integrations\": \"Alle Integrationen\",\n  \"specific_integrations\": \"Spezifische Integrationen\",\n  \"post_on_next_available_slot\": \"Im nächsten verfügbaren Zeitfenster posten\",\n  \"post_immediately\": \"Sofort posten\",\n  \"could_not_use_rss_feed\": \"Dieser RSS-Feed konnte nicht verwendet werden\",\n  \"rss_valid\": \"RSS gültig!\",\n  \"autopost_updated_successfully\": \"Autopost erfolgreich aktualisiert\",\n  \"autopost_added_successfully\": \"Autopost erfolgreich hinzugefügt\",\n  \"write_your_post_placeholder\": \"Schreibe deinen Beitrag...\",\n  \"select_or_upload_pictures_max_5\": \"Wähle Bilder aus oder lade sie hoch (maximal 5 auf einmal).\",\n  \"you_can_drag_drop_pictures\": \"Du kannst Bilder auch per Drag & Drop hinzufügen.\",\n  \"you_dont_have_any_media_yet\": \"Du hast noch keine Medien\",\n  \"media_library\": \"Medienbibliothek\",\n  \"media_settings\": \"Medieneinstellungen\",\n  \"media_editor\": \"Medieneditor\",\n  \"close\": \"Schließen\",\n  \"me\": \"Ich\",\n  \"noname\": \"Kein Name\",\n  \"password_reset_link_expired\": \"Dein Passwort-Zurücksetzungslink ist abgelaufen. Bitte versuche es erneut.\",\n  \"invalid_api_key\": \"Ungültiger API-Schlüssel\",\n  \"could_not_connect_to_platform\": \"Verbindung zur Plattform konnte nicht hergestellt werden\",\n  \"web3_provider\": \"Web3-Anbieter\",\n  \"add_provider_title\": \"Anbieter hinzufügen\",\n  \"profile_updated\": \"Profil aktualisiert\",\n  \"sets\": \"Sets\",\n  \"invitation_link_sent\": \"Einladungslink gesendet\",\n  \"send_invitation_link\": \"Einladungslink senden\",\n  \"copy_link\": \"Link kopieren\",\n  \"are_you_sure_remove_team_member\": \"Bist du sicher, dass du dieses Teammitglied entfernen möchtest?\",\n  \"admin\": \"Admin\",\n  \"super_admin\": \"Super-Admin\",\n  \"update_webhook\": \"Webhook aktualisieren\",\n  \"add_webhook\": \"Webhook hinzufügen\",\n  \"webhook_updated_successfully\": \"Webhook erfolgreich aktualisiert\",\n  \"webhook_added_successfully\": \"Webhook erfolgreich hinzugefügt\",\n  \"webhook_sent\": \"Webhook gesendet\",\n  \"today\": \"Heute\",\n  \"channel_disconnected_click_to_reconnect\": \"Kanal getrennt, klicken Sie zum Wiederverbinden.\",\n  \"channel_disabled_upgrade_plan\": \"Dieser Kanal ist deaktiviert, bitte aktualisieren Sie Ihren Plan, um ihn zu aktivieren.\",\n  \"channel_added\": \"Kanal hinzugefügt\",\n  \"are_you_sure_disable_channel\": \"Sind Sie sicher, dass Sie diesen Kanal deaktivieren möchten?\",\n  \"disable_channel_title\": \"Kanal deaktivieren\",\n  \"channel_disabled\": \"Kanal deaktiviert\",\n  \"are_you_sure_delete_channel\": \"Sind Sie sicher, dass Sie diesen Kanal löschen möchten?\",\n  \"delete_channel_title\": \"Kanal löschen\",\n  \"delete_posts_before_channel\": \"Sie müssen alle mit diesem Kanal verbundenen Beiträge löschen, bevor Sie ihn löschen können\",\n  \"channel_deleted\": \"Kanal gelöscht\",\n  \"channel_enabled\": \"Kanal aktiviert\",\n  \"time_table_slots\": \"Zeitplan-Slots\",\n  \"channel_id_copied\": \"Kanal-ID in die Zwischenablage kopiert\",\n  \"settings_updated\": \"Einstellungen aktualisiert\",\n  \"customer_updated\": \"Kunde aktualisiert\",\n  \"custom_url\": \"Benutzerdefinierte URL\",\n  \"picture\": \"Bild\",\n  \"upgrade_required\": \"Sie müssen ein Upgrade durchführen, um diese Funktion zu nutzen\",\n  \"move_to_billing\": \"Zur Abrechnung wechseln\",\n  \"payment_required\": \"Zahlung erforderlich\",\n  \"no_content\": \"kein Inhalt\",\n  \"select_customer_tooltip\": \"Kunden auswählen\",\n  \"customers\": \"Kunden\",\n  \"hour\": \"Stunde\",\n  \"minutes\": \"Minuten\",\n  \"updated\": \"Aktualisiert\",\n  \"change_bot_picture_title\": \"Bot-Bild ändern\",\n  \"select_customer_label\": \"Kunden auswählen\",\n  \"start_typing\": \"Tippen Sie hier...\",\n  \"choose_set_or_continue\": \"Wählen Sie ein Set oder fahren Sie ohne fort\",\n  \"continue_without_set\": \"Ohne Set fortfahren\",\n  \"select_set\": \"Set auswählen\",\n  \"channel_settings\": \"Einstellungen\",\n  \"post_needs_content_or_image\": \"Ihr Beitrag sollte mindestens ein Zeichen oder ein Bild enthalten.\",\n  \"please_fix_your_settings\": \"Bitte korrigieren Sie Ihre Einstellungen\",\n  \"shortlink_urls_question\": \"Möchten Sie die URLs kürzen? So erhalten Sie Statistiken über Klicks.\",\n  \"yes_shortlink_it\": \"Ja, bitte kürzen!\",\n  \"added_successfully\": \"Erfolgreich hinzugefügt\",\n  \"updated_successfully\": \"Erfolgreich aktualisiert\",\n  \"create_post_title\": \"Beitrag erstellen\",\n  \"post_preview\": \"Beitragsvorschau\",\n  \"check_circles_above\": \"Überprüfen Sie die Kreise oben\",\n  \"create_output\": \"Ausgabe erstellen\",\n  \"assistant_initial_message\": \"Hallo! Ich kann Ihnen helfen, Ihre Social-Media-Beiträge zu optimieren.\",\n  \"no_longer_global_mode\": \"Nicht mehr im globalen Modus\",\n  \"two_days\": \"Zwei Tage\",\n  \"three_days\": \"Drei Tage\",\n  \"four_days\": \"Vier Tage\",\n  \"five_days\": \"Fünf Tage\",\n  \"six_days\": \"Sechs Tage\",\n  \"two_weeks\": \"Zwei Wochen\",\n  \"repeat_post_every_label\": \"Beitrag wiederholen alle\",\n  \"add_new_tag\": \"Neues Schlagwort hinzufügen\",\n  \"tag_name\": \"Name\",\n  \"post_is_too_long\": \"Beitrag ist zu lang, bitte korrigieren\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Dein Beitrag sollte mindestens ein Zeichen oder ein Bild enthalten.\",\n  \"internal_edit\": \"Interne Bearbeitung\",\n  \"are_you_sure_go_back_to_global_mode\": \"Diese Aktion ist unwiderruflich. Bist du sicher, dass du in den globalen Modus zurückkehren möchtest?\",\n  \"yes_go_back_to_global_mode\": \"Ja, zum globalen Modus zurückkehren\",\n  \"are_you_sure_delete_this_post\": \"Bist du sicher, dass du diesen Beitrag löschen möchtest?\",\n  \"yes_delete_it\": \"Ja, löschen!\",\n  \"cant_edit_networks_when_creating_set\": \"Du kannst Netzwerke beim Erstellen eines Sets nicht bearbeiten.\",\n  \"click_to_exit_global_editing\": \"Klicke auf diese Schaltfläche, um die globale Bearbeitung zu beenden und den Beitrag für diesen Kanal anzupassen.\",\n  \"edit_content\": \"Inhalt bearbeiten\",\n  \"editing_a_specific_network\": \"Bearbeitung eines bestimmten Netzwerks\",\n  \"back_to_global\": \"Zurück zum globalen Modus\",\n  \"delete_post_tooltip\": \"Beitrag löschen\",\n  \"drop_files_here_to_upload\": \"Dateien hierher ziehen, um sie hochzuladen\",\n  \"insert_emoji\": \"Emoji einfügen\",\n  \"write_something\": \"Schreibe etwas …\",\n  \"click_channel_to_add\": \"Klicke auf einen Kanal, um ihn hinzuzufügen\",\n  \"connect_your_channels\": \"Verbinde deine Kanäle\",\n  \"connect_social_media_to_start\": \"Verbinde deine Social-Media-Konten, um mit der Planung von Beiträgen zu beginnen\",\n  \"connected_channels\": \"Verbundene Kanäle\",\n  \"continue\": \"Weiter\",\n  \"continue_without_channels\": \"Ohne Kanäle fortfahren\",\n  \"watch_tutorial\": \"Tutorial ansehen\",\n  \"watch_tutorial_title\": \"Lerne, wie du Postiz benutzt\",\n  \"watch_tutorial_description\": \"Sieh dir dieses kurze Video an, um das Beste aus Postiz herauszuholen\",\n  \"back\": \"Zurück\",\n  \"get_started\": \"Loslegen\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/en/translation.json",
    "content": "{\n  \"calendar\": \"Calendar\",\n  \"webhooks\": \"Webhooks\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Webhooks are a way to get notified when something happens in Postiz via\\n an HTTP request.\",\n  \"name\": \"Name\",\n  \"url\": \"URL\",\n  \"edit\": \"Edit\",\n  \"delete\": \"Delete\",\n  \"add_a_webhook\": \"Add a webhook\",\n  \"save\": \"Save\",\n  \"send_test\": \"Send Test\",\n  \"select_role\": \"Select Role\",\n  \"video_made_with_ai\": \"Video made with AI\",\n  \"please_add_at_least\": \"Please add at least 20 characters\",\n  \"send_invitation_via_email\": \"Send invitation via email?\",\n  \"global_settings\": \"Global Settings\",\n  \"copy_id\": \"Copy Channel ID\",\n  \"team_members\": \"Team Members\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Invite your assistant or team member to manage your account\",\n  \"remove\": \"Remove\",\n  \"add_another_member\": \"Add another member\",\n  \"signatures\": \"Signatures\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"You can add signatures to your account to be used in your posts.\",\n  \"content\": \"Content\",\n  \"auto_add\": \"Auto Add?\",\n  \"delay_comment\": \"Delay comment\",\n  \"actions\": \"Actions\",\n  \"use_signature\": \"Use Signature\",\n  \"add_a_signature\": \"Add a signature\",\n  \"no\": \"No\",\n  \"yes\": \"Yes\",\n  \"your_git_repository\": \"Your Git Repository\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Connect your GitHub repository to receive updates and analytics\",\n  \"connected\": \"Connected:\",\n  \"disconnect\": \"Disconnect\",\n  \"connect_your_repository\": \"Connect your repository\",\n  \"cancel\": \"Cancel\",\n  \"connect\": \"Connect\",\n  \"public_api\": \"Public API\",\n  \"check_n8n\": \"Check out our N8N custom node for Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Use Postiz API to integrate with your tools.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Read how to use it over the documentation.\",\n  \"reveal\": \"Reveal\",\n  \"copy_key\": \"Copy Key\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Connect Postiz MCP server to your client (Http streaming) to schedule your posts faster!\",\n  \"share_with_a_client\": \"Share with a client\",\n  \"post\": \"Post\",\n  \"comments\": \"Comments\",\n  \"user\": \"User\",\n  \"login_register_to_add_comments\": \"Login / Register to add comments\",\n  \"status\": \"Status:\",\n  \"there_are_not_plugs_matching_your_channels\": \"There are not plugs matching your channels\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"You have to add: X or LinkedIn or Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Go to the calendar to add channels\",\n  \"channels\": \"Channels\",\n  \"activate\": \"Activate\",\n  \"this_channel_needs_to_be_refreshed\": \"This channel needs to be refreshed,\",\n  \"click_here_to_refresh\": \"click here to refresh\",\n  \"can_t_show_analytics_yet\": \"Can't show analytics yet\",\n  \"you_have_to_add_social_media_channels\": \"You have to add Social Media channels\",\n  \"supported\": \"Supported:\",\n  \"step\": \"STEP\",\n  \"skip_onboarding\": \"Skip onboarding\",\n  \"onboarding\": \"Onboarding\",\n  \"next\": \"Next\",\n  \"you_are_done_from_here_you_can\": \"You are done, from here you can:\",\n  \"view_analytics\": \"View Analytics\",\n  \"schedule_a_new_post\": \"Schedule a new post\",\n  \"to_sell_posts_you_would_have_to\": \"To sell posts you would have to:\",\n  \"1_connect_at_least_one_channel\": \"1. Connect at least one channel\",\n  \"2_connect_you_bank_account\": \"2. Connect you bank account\",\n  \"go_back_to_connect_channels\": \"Go back to connect channels\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Move to the seller page to connect you bank\",\n  \"connect_channels\": \"Connect Channels\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Connect your social media and publishing websites channels to\\n            schedule posts later\",\n  \"social\": \"Social\",\n  \"publishing_platforms\": \"Publishing Platforms\",\n  \"no_channels\": \"No channels yet\",\n  \"connect_your_accounts\": \"Connect your social accounts to start scheduling, publishing, and analyzing — all in one place.\",\n  \"notifications\": \"Notifications\",\n  \"no_notifications\": \"No notifications\",\n  \"send_message\": \"Send Message\",\n  \"mar_28\": \"Mar 28\",\n  \"there_are_no_messages_yet\": \"There are no messages yet.\",\n  \"checkout_the_marketplace\": \"Checkout the Marketplace\",\n  \"go_to_marketplace\": \"Go to marketplace\",\n  \"all_messages\": \"All Messages\",\n  \"previous\": \"Previous\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Select or upload pictures (maximum 5 at a time)\",\n  \"you_can_also_drag_drop_pictures\": \"You can also drag & drop pictures\",\n  \"you_don_t_have_any_assets_yet\": \"You don't have any assets yet.\",\n  \"click_the_button_below_to_upload_one\": \"Click the button below to upload one\",\n  \"click_the_button_below_to_upload_other\": \"Click the button below to upload multiple\",\n  \"add_selected_media\": \"Add selected media\",\n  \"insert_media\": \"Insert Media\",\n  \"design_media\": \"Design Media\",\n  \"select\": \"Select\",\n  \"editor\": \"Editor\",\n  \"clear\": \"Clear\",\n  \"order_completed\": \"Order completed\",\n  \"the_order_has_been_completed\": \"The order has been completed\",\n  \"post_has_been_published\": \"post has been published\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"New Offer\",\n  \"platform\": \"Platform\",\n  \"posts\": \"Posts\",\n  \"pay_accept_offer\": \"Pay & Accept Offer\",\n  \"accepted\": \"Accepted\",\n  \"post_draft\": \"Post Draft\",\n  \"revision_needed\": \"Revision Needed\",\n  \"approve\": \"Approve\",\n  \"preview\": \"Preview\",\n  \"revision_requested\": \"Revision Requested\",\n  \"accepted_1\": \"ACCEPTED\",\n  \"cancelled_by_the_seller\": \"Cancelled by the seller\",\n  \"please_select_your_country_where_your_business_is\": \"Please select your country where your business is.\",\n  \"select_country\": \"--SELECT COUNTRY--\",\n  \"connect_bank_account\": \"Connect Bank Account\",\n  \"seller_mode\": \"Seller Mode\",\n  \"active\": \"Active\",\n  \"details\": \"Details\",\n  \"audience_size\": \"Audience Size\",\n  \"add_another_platform\": \"Add another platform\",\n  \"send_an_offer_for\": \"Send an offer for $\",\n  \"complete_order_and_pay_early\": \"Complete order and pay early\",\n  \"order_in_progress\": \"Order in progress\",\n  \"create_a_new_offer\": \"Create a new offer\",\n  \"orders\": \"Orders\",\n  \"price\": \"Price\",\n  \"state\": \"State\",\n  \"showing\": \"Showing\",\n  \"to\": \"to\",\n  \"from\": \"from\",\n  \"results\": \"Results\",\n  \"content_writer\": \"Content Writer\",\n  \"influencer\": \"Influencer\",\n  \"request_service\": \"Request Service\",\n  \"the_marketplace_is_not_opened_yet\": \"The marketplace is not opened yet\",\n  \"check_again_soon\": \"Check again soon!\",\n  \"filter\": \"Filter\",\n  \"result\": \"Result\",\n  \"seller\": \"Seller\",\n  \"buyer\": \"Buyer\",\n  \"discord_support\": \"Discord Support\",\n  \"teams\": \"Teams\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Auto Post\",\n  \"logout_from\": \"Logout from\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Join 10,000+ Entrepreneurs Who Use Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"To Manage All Your Social Media Channels\",\n  \"100_no_risk_trial\": \"100% no-risk trial\",\n  \"pay_nothing_for_the_first_7_days\": \"Pay nothing for the first 7 days\",\n  \"cancel_anytime_hassle_free\": \"Cancel anytime, from settings\",\n  \"add_free_subscription\": \"-- ADD FREE SUBSCRIPTION --\",\n  \"currently_impersonating\": \"Currently Impersonating\",\n  \"user_1\": \"user:\",\n  \"drag_n_drop_some_files_here\": \"Drag n drop some files here\",\n  \"add_time_slot\": \"Add Time Slot\",\n  \"add_slot\": \"Add Slot\",\n  \"cancel_publication\": \"Cancel publication\",\n  \"statistics\": \"Statistics\",\n  \"loading\": \"Loading\",\n  \"short_link\": \"Short Link\",\n  \"original_link\": \"Original Link\",\n  \"clicks\": \"Clicks\",\n  \"selected_customer\": \"Selected Customer\",\n  \"customer\": \"Customer:\",\n  \"repeat_post_every\": \"Repeat Post Every...\",\n  \"use_this_media\": \"Use this media\",\n  \"create_new_post\": \"Create Post\",\n  \"update_post\": \"Update Existing Post\",\n  \"merge_comments_into_one_post\": \"Merge comments into one post\",\n  \"accounts_that_will_engage\": \"Accounts that will engage:\",\n  \"day\": \"Day\",\n  \"week\": \"Week\",\n  \"month\": \"Month\",\n  \"remove_from_customer\": \"Remove from customer\",\n  \"show_more\": \"+ Show more\",\n  \"show_less\": \"- Show less\",\n  \"upload\": \"Upload\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"Add Channel\",\n  \"add_platform\": \"Add platform\",\n  \"articles\": \"Articles\",\n  \"add_comment\": \"Add comment\",\n  \"add_post\": \"Add post in a thread\",\n  \"add_comment_or_post\": \"Add comment / post\",\n  \"you_are_in_global_editing_mode\": \"You are in global editing mode\",\n  \"the_post_should_be_at_least_6_characters_long\": \"The post should be at least 6 characters long\",\n  \"are_you_sure_you_want_to_delete_post\": \"Are you sure you want to delete this post?\",\n  \"post_deleted_successfully\": \"Post deleted successfully\",\n  \"delete_post\": \"Delete Post\",\n  \"save_as_draft\": \"Save as draft\",\n  \"post_now\": \"Post now\",\n  \"please_add\": \"Please add\",\n  \"to_your_telegram_group_channel_and_click_here\": \"to your\\n telegram group / channel and click here:\",\n  \"connect_telegram\": \"Connect Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Please add the following command in your chat:\",\n  \"copy\": \"Copy\",\n  \"settings\": \"Settings\",\n  \"integrations\": \"Integrations\",\n  \"add_integration\": \"Add Integration\",\n  \"you_are_now_editing_only\": \"You are now editing only\",\n  \"tag_a_company\": \"Tag a company\",\n  \"video_length_is_invalid_must_be_up_to\": \"Video length is invalid, must be up to\",\n  \"seconds\": \"seconds\",\n  \"this_feature_available_only_for_photos\": \"This feature available only for photos, it will add a default music that you can change later.\",\n  \"allow_user_to\": \"Allow User To:\",\n  \"your_video_will_be_labeled_promotional\": \"Your video will be labeled \\\"Promotional Content\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"This cannot be changed once your video is posted.\",\n  \"turn_on_to_disclose_video_promotes\": \"Turn on to disclose that this video promotes goods or services in exchange for something of value. You video could promote yourself, a third party, or both.\",\n  \"you_are_promoting_yourself\": \"You are promoting yourself or your own brand.\",\n  \"this_video_will_be_classified_brand_organic\": \"This video will be classified as Brand Organic.\",\n  \"you_are_promoting_another_brand\": \"You are promoting another brand or a third party.\",\n  \"this_video_will_be_classified_branded_content\": \"This video will be classified as Branded Content.\",\n  \"by_posting_you_agree_to_tiktoks\": \"By posting, you agree to TikTok's\",\n  \"music_usage_confirmation\": \"Music Usage Confirmation\",\n  \"branded_content_policy\": \"Branded Content Policy\",\n  \"select_1\": \"--Select--\",\n  \"select_flair\": \"--Select Flair--\",\n  \"link\": \"Link\",\n  \"add_subreddit\": \"Add Subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Please add at least one Subreddit\",\n  \"add_community\": \"Add Community\",\n  \"select_post_type\": \"Select Post Type...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"We couldn't find any business connected to your LinkedIn Page.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Please close this dialog, create a new page, and add a new channel again.\",\n  \"select_linkedin_page\": \"Select Linkedin Page:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"We couldn't find any business connected to the selected pages.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"We recommend you to connect all the pages and all the businesses.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Please close this dialog, delete your integration and add a new channel\\n        again.\",\n  \"select_instagram_account\": \"Select Instagram Account:\",\n  \"select_page\": \"Select Page:\",\n  \"generate_image_with_ai\": \"Generate image with AI\",\n  \"reconnect_channel\": \"Reconnect channel\",\n  \"update_credentials\": \"Update Credentials\",\n  \"additional_settings\": \"Additional Settings\",\n  \"change_bot\": \"Change Bot\",\n  \"move_add_to_customer\": \"Move / add to customer\",\n  \"edit_time_slots\": \"Edit Time Slots\",\n  \"enable_channel\": \"Enable Channel\",\n  \"disable_channel\": \"Disable Channel\",\n  \"add\": \"Add\",\n  \"short_post\": \"Short post\",\n  \"long_post\": \"Long post\",\n  \"a_thread_with_short_posts\": \"A thread with short posts\",\n  \"a_thread_with_long_posts\": \"A thread with long posts\",\n  \"personal_voice_i_am_happy_to_announce\": \"Personal voice (\\\"I am happy to announce\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Company voice (\\\"We are happy to announce\\\")\",\n  \"generate\": \"Generate\",\n  \"generate_posts\": \"Generate Posts\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Purchase a Life-time PRO account with SOL ($199), Please be advised that there is no refund for this purchase.\",\n  \"purchase_now\": \"Purchase now\",\n  \"pay_today\": \"Pay Today\",\n  \"we_are_sorry_to_see_you_go\": \"We are sorry to see you go :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Would you mind shortly tell us what we could have done better?\",\n  \"cancel_subscription\": \"Cancel Subscription\",\n  \"plans\": \"Plans\",\n  \"monthly\": \"MONTHLY\",\n  \"yearly\": \"YEARLY\",\n  \"reactivate_subscription\": \"Reactivate subscription\",\n  \"update_payment_method_invoices_history\": \"Update Payment Method / Invoices History\",\n  \"cancel_subscription_1\": \"Cancel subscription\",\n  \"your_subscription_will_be_canceled_at\": \"Your subscription will be canceled at\",\n  \"you_will_never_be_charged_again\": \"You will never be charged again\",\n  \"current_package\": \"Current Package:\",\n  \"next_package\": \"Next Package:\",\n  \"claim\": \"Claim\",\n  \"frequently_asked_questions\": \"Frequently Asked Questions\",\n  \"autopost\": \"Autopost\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Autopost can automatically posts your RSS new items to social media\",\n  \"title\": \"Title\",\n  \"add_an_autopost\": \"Add an autopost\",\n  \"post_content\": \"Post content\",\n  \"sign_up\": \"Sign Up\",\n  \"or\": \"OR\",\n  \"by_registering_you_agree_to_our\": \"By registering you agree to our\",\n  \"and\": \"and\",\n  \"terms_of_service\": \"Terms of Service\",\n  \"privacy_policy\": \"Privacy Policy\",\n  \"create_account\": \"Create Account\",\n  \"already_have_an_account\": \"Already Have An Account?\",\n  \"sign_in\": \"Sign In\",\n  \"sign_in_1\": \"Sign in\",\n  \"don_t_have_an_account\": \"Don't Have An Account?\",\n  \"forgot_password\": \"Forgot password\",\n  \"forgot_password_1\": \"Forgot Password\",\n  \"send_password_reset_email\": \"Send Password Reset Email\",\n  \"go_back_to_login\": \"Go back to login\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"We have send you an email with a link to reset your password.\",\n  \"change_password\": \"Change Password\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"We successfully reset your password. You can now login with your\",\n  \"click_here_to_go_back_to_login\": \"Click here to go back to login\",\n  \"activate_your_account\": \"Activate your account\",\n  \"thank_you_for_registering\": \"Thank you for registering!\",\n  \"please_check_your_email_to_activate_your_account\": \"Please check your email to activate your account.\",\n  \"sign_in_with\": \"Sign in with\",\n  \"continue_with_google\": \"Continue with Google\",\n  \"sign_in_with_github\": \"Sign in with GitHub\",\n  \"continue_with_farcaster\": \"Continue with Farcaster\",\n  \"continue_with_your_wallet\": \"Continue with your Wallet\",\n  \"stars_per_day\": \"Stars per day\",\n  \"media\": \"Media\",\n  \"check_launch\": \"Check Launch\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Load your GitHub repository from settings to see analytics\",\n  \"stars\": \"Stars\",\n  \"processing_stars\": \"Processing stars...\",\n  \"forks\": \"Forks\",\n  \"registration_is_disabled\": \"Registration is disabled\",\n  \"login_instead\": \"Login instead\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Select a conversation and chat away.\",\n  \"adding_channel_redirecting_you\": \"Adding channel, Redirecting You\",\n  \"could_not_add_provider\": \"Could not add provider.\",\n  \"you_are_being_redirected_back\": \"You are being redirected back\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"We are experiencing some difficulty, try to refresh the page\",\n  \"post_not_found\": \"Post not found\",\n  \"publication_date\": \"Publication Date:\",\n  \"analytics\": \"Analytics\",\n  \"launches\": \"Launches\",\n  \"plugs\": \"Plugs\",\n  \"billing\": \"Billing\",\n  \"affiliate\": \"Affiliate\",\n  \"monday\": \"Monday\",\n  \"tuesday\": \"Tuesday\",\n  \"wednesday\": \"Wednesday\",\n  \"thursday\": \"Thursday\",\n  \"friday\": \"Friday\",\n  \"saturday\": \"Saturday\",\n  \"sunday\": \"Sunday\",\n  \"can_t_change_date_remove_post_from_publication\": \"Can't change date, remove post from publication\",\n  \"predicted_github_trending_change\": \"Predicted GitHub Trending Change\",\n  \"duplicate_post\": \"Duplicate Post\",\n  \"preview_post\": \"Preview Post\",\n  \"post_statistics\": \"Post Statistics\",\n  \"draft\": \"Draft\",\n  \"week_number\": \"Week {{number}}\",\n  \"top_title_edit_webhook\": \"Edit webhook\",\n  \"top_title_add_webhook\": \"Add webhook\",\n  \"top_title_oh_no\": \"Oh no\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"Edit autopost\",\n  \"top_title_add_autopost\": \"Add autopost\",\n  \"top_title_send_a_new_offer\": \"Send a new offer\",\n  \"top_title_media_library\": \"Media Library\",\n  \"top_title_add_signature\": \"Add signature\",\n  \"top_title_send_a_message_to\": \"Send a message to {{name}}\",\n  \"top_title_configure_provider\": \"Configure Provider\",\n  \"top_title_add_member\": \"Add Member\",\n  \"top_title_change_bot_picture\": \"Change Bot Picture\",\n  \"top_title_create_a_new_tag\": \"Create a new tag\",\n  \"top_title_select_company\": \"Select Company\",\n  \"top_title_additional_settings\": \"Additional Settings\",\n  \"top_title_time_table_slots\": \"Time Table Slots\",\n  \"top_title_design_media\": \"Design Media\",\n  \"top_title_edit_post\": \"Edit Post\",\n  \"top_title_create_post\": \"Create Post\",\n  \"top_title_move__add_to_customer\": \"Move / Add to customer\",\n  \"top_title_add_api_key_for\": \"Add API key for {{name}}\",\n  \"top_title_instance_url\": \"Instance URL\",\n  \"top_title_custom_url\": \"Custom URL\",\n  \"top_title_add_channel\": \"Add Channel\",\n  \"top_title_add_telegram\": \"Add Telegram\",\n  \"top_title_add_wrapcast\": \"Add Wrapcast\",\n  \"top_title_comments_for\": \"Comments for {{date}}\",\n  \"top_title_edit_signature\": \"Edit Signature\",\n  \"label_name\": \"Name\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Title\",\n  \"label_subtitle\": \"Subtitle\",\n  \"label_email\": \"Email\",\n  \"label_full_name\": \"Full Name\",\n  \"label_password\": \"Password\",\n  \"label_confirm_password\": \"Confirm Password\",\n  \"label_api_key\": \"API Key\",\n  \"label_instance_url\": \"Instance URL\",\n  \"label_custom_url\": \"Custom URL\",\n  \"label_feedback\": \"Feedback\",\n  \"label_bio\": \"Bio\",\n  \"label_role\": \"Role\",\n  \"label_country\": \"Country\",\n  \"label_audience_size\": \"Audience size on all platforms\",\n  \"label_pick_time\": \"Pick time\",\n  \"label_nickname\": \"Nickname\",\n  \"label_write_anything\": \"Write anything\",\n  \"label_output_format\": \"Output format\",\n  \"label_add_pictures\": \"Add pictures?\",\n  \"label_hour\": \"Hour\",\n  \"label_minutes\": \"Minutes\",\n  \"label_select_publication\": \"Select publication\",\n  \"label_canonical_link\": \"Canonical Link\",\n  \"label_cover_picture\": \"Cover picture\",\n  \"label_tags\": \"Tags\",\n  \"label_topics\": \"Topics\",\n  \"label_tags_maximum_4\": \"Tags (Maximum 4)\",\n  \"label_attachments\": \"Attachments\",\n  \"label_type\": \"Type\",\n  \"label_thumbnail\": \"Thumbnail\",\n  \"label_who_can_see_this_video\": \"Who can see this video?\",\n  \"label_content_posting_method\": \"Content posting method\",\n  \"label_auto_add_music\": \"Auto add music\",\n  \"label_duet\": \"Duet\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Comments\",\n  \"label_disclose_video_content\": \"Disclose Video Content\",\n  \"label_your_brand\": \"Your brand\",\n  \"label_branded_content\": \"Branded content\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Media\",\n  \"label_search_subreddit\": \"Search Subreddit\",\n  \"label_delay\": \"Delay\",\n  \"label_post_type\": \"Post Type\",\n  \"label_collaborators\": \"Collaborators (max 3) - accounts can't be private\",\n  \"label_community\": \"Community\",\n  \"label_search_community\": \"Search Community\",\n  \"label_channel\": \"Channel\",\n  \"label_search_channel\": \"Search Channel\",\n  \"label_select_channel\": \"Select Channel\",\n  \"label_new_password\": \"New Password\",\n  \"label_repeat_password\": \"Repeat Password\",\n  \"label_platform\": \"Platform\",\n  \"label_price_per_post\": \"Price per post\",\n  \"label_integrations\": \"Integrations\",\n  \"label_code\": \"Code\",\n  \"label_should_sync_last_post\": \"Should we sync the current last post?\",\n  \"label_when_post\": \"When should we post it?\",\n  \"label_autogenerate_content\": \"Autogenerate content\",\n  \"label_generate_picture\": \"Generate Picture?\",\n  \"label_company\": \"Company\",\n  \"label_tag_color\": \"Tag Color\",\n  \"label_select_board\": \"Select board\",\n  \"label_select_organization\": \"Select organization\",\n  \"label_auto_add_signature\": \"Auto add signature?\",\n  \"enable_color_picker\": \"Enable color picker\",\n  \"cancel_the_color_picker\": \"Cancel the color picker\",\n  \"no_content_yet\": \"No Content Yet\",\n  \"write_your_reply\": \"Write your post...\",\n  \"add_a_tag\": \"Add a tag\",\n  \"add_to_calendar\": \"Add to Calendar\",\n  \"select_channels_from_circles\": \"Select channels from the circles above\",\n  \"not_matching_order\": \"Not matching order\",\n  \"submit_for_order\": \"Submit for order\",\n  \"schedule\": \"Schedule\",\n  \"update\": \"Update\",\n  \"attachments\": \"Attachments\",\n  \"tags\": \"Tags\",\n  \"public_to_everyone\": \"Public to everyone\",\n  \"mutual_follow_friends\": \"Mutual follow friends\",\n  \"follower_of_creator\": \"Follower of creator\",\n  \"self_only\": \"Self only\",\n  \"post_content_directly_to_tiktok\": \"Post content directly to TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Upload content to TikTok without posting it\",\n  \"choose_upload_without_posting_description\": \"Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Am I going to be charged by Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"To confirm credit card information Postiz will hold $2 and release it immediately, you can cancel your subscription anytime from settings without talking to a person\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Can I trust Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz is proudly open-source! We believe in an ethical and transparent culture, meaning that Postiz will live forever. You can check out the entire code or use it for personal projects. To view the open-source repository, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">click here</a>.\",\n  \"faq_what_are_channels\": \"What are channels?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz allows you to schedule your posts between different channels.\\nA channel is a publishing platform where you can schedule your posts.\\nFor example, you can schedule your posts on X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads and Pinterest.\",\n  \"faq_what_are_team_members\": \"What are team members?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"If you have a team with multiple members, you can invite them to your workspace to collaborate on your posts and add their personal channels\",\n  \"faq_what_is_ai_auto_complete\": \"What is AI auto-complete?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"We automate ChatGPT to help you write social posts and articles.\",\n  \"enter_email\": \"Enter email\",\n  \"are_you_sure\": \"Are you sure?\",\n  \"no_cancel\": \"No, cancel!\",\n  \"are_you_sure_you_want_to_delete\": \"Are you sure you want to delete {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Are you sure you want to delete the image?\",\n  \"are_you_sure_you_want_to_logout\": \"Are you sure you want to logout?\",\n  \"yes_logout\": \"Yes logout\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Are you sure you want to delete this slot?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Are you sure you want to delete this Subreddit?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Are you sure you want to close the window?\",\n  \"yes_close\": \"Yes, close\",\n  \"link_copied_to_clipboard\": \"Link copied to clipboard\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Are you sure you want to close this modal? (all data will be lost)\",\n  \"yes_close_it\": \"Yes, close it!\",\n  \"uploading_pictures\": \"Uploading pictures...\",\n  \"agent_starting\": \"Agent starting\",\n  \"researching_your_content\": \"Researching your content...\",\n  \"understanding_the_category\": \"Understanding the category...\",\n  \"finding_the_topic\": \"Finding the topic...\",\n  \"finding_popular_posts_to_match_with\": \"Finding popular posts to match with...\",\n  \"generating_hook\": \"Generating hook...\",\n  \"generating_content\": \"Generating content...\",\n  \"generating_pictures\": \"Generating pictures...\",\n  \"finding_time_to_post\": \"Finding time to post...\",\n  \"write_anything\": \"Write anything\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"You can write anything you want, and also add links, we will do the research for you...\",\n  \"output_format\": \"Output format\",\n  \"add_pictures\": \"Add pictures?\",\n  \"7_days\": \"7 Days\",\n  \"30_days\": \"30 Days\",\n  \"90_days\": \"90 Days\",\n  \"start_7_days_free_trial\": \"Start 7 days free trial\",\n  \"change_language\": \"Change Language\",\n  \"that_a_wrap\": \"That's a wrap!\\n\\nIf you enjoyed this thread:\\n\\n1. Follow me @{{username}} for more of these\\n2. RT the tweet below to share this thread with your audience\\n\",\n  \"post_as_images_carousel\": \"Post as images carousel\",\n  \"save_set\": \"Save Set\",\n  \"separate_post\": \"Separate post to multiple posts\",\n  \"label_who_can_reply_to_this_post\": \"Who can reply to this post?\",\n  \"delete_integration\": \"Delete Integration\",\n  \"start_writing_your_post\": \"Start writing your post for a preview\",\n  \"billing_join_over\": \"Join Over\",\n  \"billing_entrepreneurs_count\": \"20,000+ Entrepreneurs\",\n  \"billing_who_use\": \"who use\",\n  \"billing_postiz_grow_social\": \"Postiz To Grow Their Social Presence\",\n  \"billing_no_risk_trial\": \"100% No-Risk Free Trial\",\n  \"billing_pay_nothing_7_days\": \"Pay NOTHING for the first 7-days\",\n  \"billing_cancel_anytime\": \"Cancel anytime, from settings\",\n  \"billing_choose_plan\": \"Choose a Plan\",\n  \"billing_monthly\": \"Monthly\",\n  \"billing_yearly\": \"Yearly\",\n  \"billing_20_percent_off\": \"20% Off\",\n  \"billing_features\": \"Features\",\n  \"billing_channel\": \"channel\",\n  \"billing_channels\": \"channels\",\n  \"billing_unlimited\": \"Unlimited\",\n  \"billing_posts_per_month\": \"posts per month\",\n  \"billing_unlimited_team_members\": \"Unlimited team members\",\n  \"billing_ai_auto_complete\": \"AI auto-complete\",\n  \"billing_ai_copilots\": \"AI copilots\",\n  \"billing_ai_autocomplete\": \"AI Autocomplete\",\n  \"billing_advanced_picture_editor\": \"Advanced Picture Editor\",\n  \"billing_ai_images_per_month\": \"AI Images per month\",\n  \"billing_ai_videos_per_month\": \"AI Videos per month\",\n  \"billing_billing_address\": \"Billing Address\",\n  \"billing_payment\": \"Payment\",\n  \"billing_powered_by_stripe\": \"Secure payments processed by\",\n  \"billing_your_7_day_trial_is\": \"Your 7-day trial is\",\n  \"billing_100_percent_free\": \"100% free\",\n  \"billing_ending\": \"ending\",\n  \"billing_cancel_anytime_short\": \"Cancel anytime from settings\",\n  \"billing_pay_0_start_trial\": \"Pay $0 Today - Start your free trial!\",\n  \"billing_pay_now\": \"Pay Now\",\n  \"billing_per_month\": \"/ month\",\n  \"billing_per_year\": \"/ year\",\n  \"billing_order_summary\": \"Order Summary\",\n  \"billing_applied\": \"Applied\",\n  \"billing_due_today\": \"Due today\",\n  \"billing_then\": \"Then\",\n  \"billing_on\": \"on\",\n  \"billing_discount_applied\": \"applied\",\n  \"billing_remove\": \"Remove\",\n  \"billing_coupon_expires\": \"Coupon expires on\",\n  \"billing_invalid_coupon\": \"Invalid coupon code\",\n  \"billing_coupon_applied\": \"Coupon applied successfully!\",\n  \"billing_coupon_removed\": \"Coupon removed\",\n  \"billing_error_removing_coupon\": \"Error removing coupon\",\n  \"billing_have_discount_coupon\": \"Have a discount coupon?\",\n  \"billing_discount_coupon\": \"Discount Coupon\",\n  \"billing_cancel\": \"Cancel\",\n  \"billing_enter_coupon_code\": \"Enter coupon code\",\n  \"billing_applying\": \"Applying...\",\n  \"billing_apply\": \"Apply\",\n  \"billing_subscription\": \"Subscription\",\n  \"billing_cancel_notice\": \"Cancel anytime from settings without talking to a person and never be charged.\",\n  \"select_channels\": \"Select Channels\",\n  \"start_a_new_chat\": \"Start a new chat\",\n  \"your_assistant\": \"Your Assistant\",\n  \"agent_welcome_message\": \"Hello, I am your Postiz agent 🙌🏻.\\n\\nI can schedule a post or multiple posts to multiple channels and generate pictures and videos.\\n\\nYou can select the channels you want to use from the left menu.\\n\\nYou can see your previous conversations from the right menu.\\n\\nYou can also use me as an MCP Server, check Settings >> Public API\",\n  \"last_github_trending\": \"Last Github Trending\",\n  \"next_predicted_github_trending\": \"Next Predicted GitHub Trending\",\n  \"repository\": \"Repository\",\n  \"date\": \"Date\",\n  \"total_stars\": \"Total Stars\",\n  \"total_forks\": \"Total Forks\",\n  \"continue_with\": \"Continue With\",\n  \"email_address\": \"Email Address\",\n  \"label_company\": \"Company\",\n  \"email_already_exists\": \"Email already exists\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Edit Autopost\",\n  \"add_autopost_title\": \"Add Autopost\",\n  \"webhook_deleted_successfully\": \"Webhook deleted successfully\",\n  \"all_integrations\": \"All integrations\",\n  \"specific_integrations\": \"Specific integrations\",\n  \"post_on_next_available_slot\": \"Post on the next available slot\",\n  \"post_immediately\": \"Post Immediately\",\n  \"could_not_use_rss_feed\": \"Could not use this RSS feed\",\n  \"rss_valid\": \"RSS valid!\",\n  \"autopost_updated_successfully\": \"Autopost updated successfully\",\n  \"autopost_added_successfully\": \"Autopost added successfully\",\n  \"write_your_post_placeholder\": \"Write your post...\",\n  \"select_or_upload_pictures_max_5\": \"Select or upload pictures (maximum 5 at a time).\",\n  \"you_can_drag_drop_pictures\": \"You can also drag & drop pictures.\",\n  \"you_dont_have_any_media_yet\": \"You don't have any media yet\",\n  \"media_library\": \"Media Library\",\n  \"media_settings\": \"Media Settings\",\n  \"media_editor\": \"Media Editor\",\n  \"close\": \"Close\",\n  \"me\": \"Me\",\n  \"noname\": \"Noname\",\n  \"password_reset_link_expired\": \"Your password reset link has expired. Please try again.\",\n  \"invalid_api_key\": \"Invalid API key\",\n  \"could_not_connect_to_platform\": \"Could not connect to the platform\",\n  \"web3_provider\": \"Web3 provider\",\n  \"add_provider_title\": \"Add Provider\",\n  \"profile_updated\": \"Profile updated\",\n  \"sets\": \"Sets\",\n  \"email_address\": \"Email Address\",\n  \"invitation_link_sent\": \"Invitation link sent\",\n  \"send_invitation_link\": \"Send Invitation Link\",\n  \"copy_link\": \"Copy Link\",\n  \"are_you_sure_remove_team_member\": \"Are you sure you want to remove this team member?\",\n  \"admin\": \"Admin\",\n  \"super_admin\": \"Super Admin\",\n  \"update_webhook\": \"Update webhook\",\n  \"add_webhook\": \"Add webhook\",\n  \"webhook_updated_successfully\": \"Webhook updated successfully\",\n  \"webhook_added_successfully\": \"Webhook added successfully\",\n  \"webhook_sent\": \"Webhook send\",\n  \"today\": \"Today\",\n  \"channel_disconnected_click_to_reconnect\": \"Channel disconnected, click to reconnect.\",\n  \"channel_disabled_upgrade_plan\": \"This channel is disabled, please upgrade your plan to enable it.\",\n  \"channel_added\": \"Channel added\",\n  \"are_you_sure_disable_channel\": \"Are you sure you want to disable this channel?\",\n  \"disable_channel_title\": \"Disable Channel\",\n  \"channel_disabled\": \"Channel Disabled\",\n  \"are_you_sure_delete_channel\": \"Are you sure you want to delete this channel?\",\n  \"delete_channel_title\": \"Delete Channel\",\n  \"delete_posts_before_channel\": \"You have to delete all the posts associated with this channel before deleting it\",\n  \"channel_deleted\": \"Channel Deleted\",\n  \"channel_enabled\": \"Channel Enabled\",\n  \"time_table_slots\": \"Time Table Slots\",\n  \"channel_id_copied\": \"Channel ID copied to clipboard\",\n  \"settings_updated\": \"Settings Updated\",\n  \"customer_updated\": \"Customer Updated\",\n  \"custom_url\": \"Custom URL\",\n  \"picture\": \"Picture\",\n  \"upgrade_required\": \"You need to upgrade to use this feature\",\n  \"move_to_billing\": \"Move to billing\",\n  \"payment_required\": \"Payment Required\",\n  \"no_content\": \"no content\",\n  \"select_customer_tooltip\": \"Select Customer\",\n  \"customers\": \"Customers\",\n  \"hour\": \"Hour\",\n  \"minutes\": \"Minutes\",\n  \"updated\": \"Updated\",\n  \"change_bot_picture_title\": \"Change Bot Picture\",\n  \"select_customer_label\": \"Select Customer\",\n  \"start_typing\": \"Start typing...\",\n  \"choose_set_or_continue\": \"Choose a set or continue without one\",\n  \"continue_without_set\": \"Continue without set\",\n  \"select_set\": \"Select a Set\",\n  \"channel_settings\": \"Settings\",\n  \"post_needs_content_or_image\": \"Your post should have at least one character or one image.\",\n  \"please_fix_your_settings\": \"Please fix your settings\",\n  \"shortlink_urls_question\": \"Do you want to shortlink the URLs? it will let you get statistics over clicks\",\n  \"yes_shortlink_it\": \"Yes, shortlink it!\",\n  \"added_successfully\": \"Added successfully\",\n  \"updated_successfully\": \"Updated successfully\",\n  \"create_post_title\": \"Create Post\",\n  \"post_preview\": \"Post Preview\",\n  \"check_circles_above\": \"Check the circles above\",\n  \"create_output\": \"Create output\",\n  \"assistant_initial_message\": \"Hi! I can help you to refine your social media posts.\",\n  \"no_longer_global_mode\": \"No longer in global mode\",\n  \"two_days\": \"Two Days\",\n  \"three_days\": \"Three Days\",\n  \"four_days\": \"Four Days\",\n  \"five_days\": \"Five Days\",\n  \"six_days\": \"Six Days\",\n  \"two_weeks\": \"Two Weeks\",\n  \"repeat_post_every_label\": \"Repeat Post Every\",\n  \"add_new_tag\": \"Add New Tag\",\n  \"tag_name\": \"Name\",\n  \"post_is_too_long\": \"post is too long, please fix it\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Your post should have at least one character or one image.\",\n  \"internal_edit\": \"Internal Edit\",\n  \"are_you_sure_go_back_to_global_mode\": \"This action is irreversible. Are you sure you want to go back to global mode?\",\n  \"yes_go_back_to_global_mode\": \"Yes, go back to global mode\",\n  \"are_you_sure_delete_this_post\": \"Are you sure you want to delete this post?\",\n  \"yes_delete_it\": \"Yes, delete it!\",\n  \"cant_edit_networks_when_creating_set\": \"You can't edit networks when creating a set\",\n  \"click_to_exit_global_editing\": \"Click this button to exit global editing and customize the post for this channel\",\n  \"edit_content\": \"Edit content\",\n  \"editing_a_specific_network\": \"Editing a Specific Network\",\n  \"back_to_global\": \"Back to global\",\n  \"delete_post_tooltip\": \"Delete Post\",\n  \"drop_files_here_to_upload\": \"Drop your files here to upload\",\n  \"insert_emoji\": \"Insert Emoji\",\n  \"write_something\": \"Write something …\",\n  \"click_channel_to_add\": \"Click a channel to add it\",\n  \"connect_your_channels\": \"Connect Your Channels\",\n  \"connect_social_media_to_start\": \"Connect your social media accounts to start scheduling posts\",\n  \"connected_channels\": \"Connected Channels\",\n  \"continue\": \"Continue\",\n  \"continue_without_channels\": \"Continue without channels\",\n  \"watch_tutorial\": \"Watch Tutorial\",\n  \"watch_tutorial_title\": \"Learn How to Use Postiz\",\n  \"watch_tutorial_description\": \"Watch this short video to learn how to get the most out of Postiz\",\n  \"back\": \"Back\",\n  \"get_started\": \"Get Started\",\n  \"kick_select_channel\": \"Select Channel\"\n}"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/es/translation.json",
    "content": "{\n  \"calendar\": \"Calendario\",\n  \"webhooks\": \"Webhooks\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Los webhooks son una forma de recibir notificaciones cuando algo sucede en Postiz mediante una solicitud HTTP.\",\n  \"name\": \"Nombre\",\n  \"url\": \"URL\",\n  \"edit\": \"Editar\",\n  \"delete\": \"Eliminar\",\n  \"add_a_webhook\": \"Agregar un webhook\",\n  \"save\": \"Guardar\",\n  \"send_test\": \"Enviar prueba\",\n  \"select_role\": \"Seleccionar rol\",\n  \"video_made_with_ai\": \"Video hecho con IA\",\n  \"please_add_at_least\": \"Por favor, añade al menos 20 caracteres\",\n  \"send_invitation_via_email\": \"¿Enviar invitación por correo electrónico?\",\n  \"global_settings\": \"Configuración global\",\n  \"copy_id\": \"Copiar ID del canal\",\n  \"team_members\": \"Miembros del equipo\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Invita a tu asistente o miembro del equipo para que gestione tu cuenta\",\n  \"remove\": \"Eliminar\",\n  \"add_another_member\": \"Agregar otro miembro\",\n  \"signatures\": \"Firmas\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Puedes agregar firmas a tu cuenta para usarlas en tus publicaciones.\",\n  \"content\": \"Contenido\",\n  \"auto_add\": \"¿Agregar automáticamente?\",\n  \"delay_comment\": \"Comentario de demora\",\n  \"actions\": \"Acciones\",\n  \"use_signature\": \"Usar firma\",\n  \"add_a_signature\": \"Agregar una firma\",\n  \"no\": \"No\",\n  \"yes\": \"Sí\",\n  \"your_git_repository\": \"Tu repositorio Git\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Conecta tu repositorio de GitHub para recibir actualizaciones y análisis\",\n  \"connected\": \"Conectado:\",\n  \"disconnect\": \"Desconectar\",\n  \"connect_your_repository\": \"Conecta tu repositorio\",\n  \"cancel\": \"Cancelar\",\n  \"connect\": \"Conectar\",\n  \"public_api\": \"API pública\",\n  \"check_n8n\": \"Echa un vistazo a nuestro nodo personalizado de N8N para Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Usa la API de Postiz para integrarla con tus herramientas.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Lee cómo usarla en la documentación.\",\n  \"reveal\": \"Revelar\",\n  \"copy_key\": \"Copiar clave\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"¡Conecta el servidor MCP de Postiz a tu cliente (transmisión HTTP) para programar tus publicaciones más rápido!\",\n  \"share_with_a_client\": \"Compartir con un cliente\",\n  \"post\": \"Publicar\",\n  \"comments\": \"Comentarios\",\n  \"user\": \"Usuario\",\n  \"login_register_to_add_comments\": \"Inicia sesión / Regístrate para agregar comentarios\",\n  \"status\": \"Estado:\",\n  \"there_are_not_plugs_matching_your_channels\": \"No hay conectores que coincidan con tus canales\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Debes agregar: X o LinkedIn o Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Ve al calendario para agregar canales\",\n  \"channels\": \"Canales\",\n  \"activate\": \"Activar\",\n  \"this_channel_needs_to_be_refreshed\": \"Este canal necesita ser actualizado,\",\n  \"click_here_to_refresh\": \"haz clic aquí para actualizar\",\n  \"can_t_show_analytics_yet\": \"Aún no se pueden mostrar las analíticas\",\n  \"you_have_to_add_social_media_channels\": \"Tienes que añadir canales de redes sociales\",\n  \"supported\": \"Soportado:\",\n  \"step\": \"PASO\",\n  \"skip_onboarding\": \"Omitir introducción\",\n  \"onboarding\": \"Introducción\",\n  \"next\": \"Siguiente\",\n  \"you_are_done_from_here_you_can\": \"Has terminado, desde aquí puedes:\",\n  \"view_analytics\": \"Ver analíticas\",\n  \"schedule_a_new_post\": \"Programar una nueva publicación\",\n  \"to_sell_posts_you_would_have_to\": \"Para vender publicaciones debes:\",\n  \"1_connect_at_least_one_channel\": \"1. Conectar al menos un canal\",\n  \"2_connect_you_bank_account\": \"2. Conectar tu cuenta bancaria\",\n  \"go_back_to_connect_channels\": \"Volver para conectar canales\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Ir a la página de vendedor para conectar tu banco\",\n  \"connect_channels\": \"Conectar canales\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Conecta tus canales de redes sociales y sitios de publicación para\\n            programar publicaciones más tarde\",\n  \"social\": \"Social\",\n  \"publishing_platforms\": \"Plataformas de publicación\",\n  \"no_channels\": \"Aún no hay canales\",\n  \"connect_your_accounts\": \"Conecta tus cuentas sociales para empezar a programar, publicar y analizar, todo en un solo lugar.\",\n  \"notifications\": \"Notificaciones\",\n  \"no_notifications\": \"Sin notificaciones\",\n  \"send_message\": \"Enviar mensaje\",\n  \"mar_28\": \"28 de mar\",\n  \"there_are_no_messages_yet\": \"Aún no hay mensajes.\",\n  \"checkout_the_marketplace\": \"Explora el Marketplace\",\n  \"go_to_marketplace\": \"Ir al Marketplace\",\n  \"all_messages\": \"Todos los mensajes\",\n  \"previous\": \"Anterior\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Selecciona o sube imágenes (máximo 5 a la vez)\",\n  \"you_can_also_drag_drop_pictures\": \"También puedes arrastrar y soltar imágenes\",\n  \"you_don_t_have_any_assets_yet\": \"Aún no tienes ningún recurso.\",\n  \"click_the_button_below_to_upload_one\": \"Haz clic en el botón de abajo para subir uno\",\n  \"click_the_button_below_to_upload_other\": \"Haz clic en el botón de abajo para subir varios\",\n  \"add_selected_media\": \"Agregar medios seleccionados\",\n  \"insert_media\": \"Insertar medios\",\n  \"design_media\": \"Diseñar medios\",\n  \"select\": \"Seleccionar\",\n  \"editor\": \"Editor\",\n  \"clear\": \"Limpiar\",\n  \"order_completed\": \"Pedido completado\",\n  \"the_order_has_been_completed\": \"El pedido ha sido completado\",\n  \"post_has_been_published\": \"La publicación ha sido publicada\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Nueva oferta\",\n  \"platform\": \"Plataforma\",\n  \"posts\": \"Publicaciones\",\n  \"pay_accept_offer\": \"Pagar y aceptar oferta\",\n  \"accepted\": \"Aceptado\",\n  \"post_draft\": \"Borrador de publicación\",\n  \"revision_needed\": \"Revisión necesaria\",\n  \"approve\": \"Aprobar\",\n  \"preview\": \"Vista previa\",\n  \"revision_requested\": \"Revisión solicitada\",\n  \"accepted_1\": \"ACEPTADO\",\n  \"cancelled_by_the_seller\": \"Cancelado por el vendedor\",\n  \"please_select_your_country_where_your_business_is\": \"Por favor, selecciona el país donde se encuentra tu negocio.\",\n  \"select_country\": \"--SELECCIONA PAÍS--\",\n  \"connect_bank_account\": \"Conectar cuenta bancaria\",\n  \"seller_mode\": \"Modo vendedor\",\n  \"active\": \"Activo\",\n  \"details\": \"Detalles\",\n  \"audience_size\": \"Tamaño de la audiencia\",\n  \"add_another_platform\": \"Agregar otra plataforma\",\n  \"send_an_offer_for\": \"Enviar una oferta por $\",\n  \"complete_order_and_pay_early\": \"Completar pedido y pagar anticipadamente\",\n  \"order_in_progress\": \"Pedido en curso\",\n  \"create_a_new_offer\": \"Crear una nueva oferta\",\n  \"orders\": \"Pedidos\",\n  \"price\": \"Precio\",\n  \"state\": \"Estado\",\n  \"showing\": \"Mostrando\",\n  \"to\": \"a\",\n  \"from\": \"de\",\n  \"results\": \"Resultados\",\n  \"content_writer\": \"Redactor de contenido\",\n  \"influencer\": \"Influencer\",\n  \"request_service\": \"Solicitar servicio\",\n  \"the_marketplace_is_not_opened_yet\": \"El mercado aún no está abierto\",\n  \"check_again_soon\": \"¡Vuelve a comprobar pronto!\",\n  \"filter\": \"Filtrar\",\n  \"result\": \"Resultado\",\n  \"seller\": \"Vendedor\",\n  \"buyer\": \"Comprador\",\n  \"discord_support\": \"Soporte de Discord\",\n  \"teams\": \"Equipos\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Publicación automática\",\n  \"logout_from\": \"Cerrar sesión de\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Únete a más de 10,000 emprendedores que usan Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Para gestionar todos tus canales de redes sociales\",\n  \"100_no_risk_trial\": \"Prueba sin riesgo 100%\",\n  \"pay_nothing_for_the_first_7_days\": \"No pagues nada durante los primeros 7 días\",\n  \"cancel_anytime_hassle_free\": \"Cancela en cualquier momento, desde la configuración\",\n  \"add_free_subscription\": \"-- AÑADIR SUSCRIPCIÓN GRATUITA --\",\n  \"currently_impersonating\": \"Actualmente suplantando\",\n  \"user_1\": \"usuario:\",\n  \"drag_n_drop_some_files_here\": \"Arrastra y suelta algunos archivos aquí\",\n  \"add_time_slot\": \"Agregar franja horaria\",\n  \"add_slot\": \"Agregar espacio\",\n  \"cancel_publication\": \"Cancelar publicación\",\n  \"statistics\": \"Estadísticas\",\n  \"loading\": \"Cargando\",\n  \"short_link\": \"Enlace corto\",\n  \"original_link\": \"Enlace original\",\n  \"clicks\": \"Clics\",\n  \"selected_customer\": \"Cliente seleccionado\",\n  \"customer\": \"Cliente:\",\n  \"repeat_post_every\": \"Repetir publicación cada...\",\n  \"use_this_media\": \"Usar este medio\",\n  \"create_new_post\": \"Crear publicación\",\n  \"update_post\": \"Actualizar publicación existente\",\n  \"merge_comments_into_one_post\": \"Unir comentarios en una sola publicación\",\n  \"accounts_that_will_engage\": \"Cuentas que interactuarán:\",\n  \"day\": \"Día\",\n  \"week\": \"Semana\",\n  \"month\": \"Mes\",\n  \"remove_from_customer\": \"Eliminar del cliente\",\n  \"show_more\": \"+ Mostrar más\",\n  \"show_less\": \"- Mostrar menos\",\n  \"upload\": \"Subir\",\n  \"ai\": \"IA\",\n  \"add_channel\": \"Agregar canal\",\n  \"add_platform\": \"Agregar plataforma\",\n  \"articles\": \"Artículos\",\n  \"add_comment\": \"Agregar comentario\",\n  \"add_post\": \"Agregar publicación en un hilo\",\n  \"add_comment_or_post\": \"Agregar comentario / publicación\",\n  \"you_are_in_global_editing_mode\": \"Estás en modo de edición global\",\n  \"the_post_should_be_at_least_6_characters_long\": \"La publicación debe tener al menos 6 caracteres\",\n  \"are_you_sure_you_want_to_delete_post\": \"¿Estás seguro de que deseas eliminar esta publicación?\",\n  \"post_deleted_successfully\": \"Publicación eliminada con éxito\",\n  \"delete_post\": \"Eliminar publicación\",\n  \"save_as_draft\": \"Guardar como borrador\",\n  \"post_now\": \"Publicar ahora\",\n  \"please_add\": \"Por favor agrega\",\n  \"to_your_telegram_group_channel_and_click_here\": \"a tu\\ngrupo/canal de Telegram y haz clic aquí:\",\n  \"connect_telegram\": \"Conectar Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Por favor agrega el siguiente comando en tu chat:\",\n  \"copy\": \"Copiar\",\n  \"settings\": \"Configuración\",\n  \"integrations\": \"Integraciones\",\n  \"add_integration\": \"Agregar integración\",\n  \"you_are_now_editing_only\": \"Ahora solo estás editando\",\n  \"tag_a_company\": \"Etiquetar una empresa\",\n  \"video_length_is_invalid_must_be_up_to\": \"La duración del video no es válida, debe ser de hasta\",\n  \"seconds\": \"segundos\",\n  \"this_feature_available_only_for_photos\": \"Esta función está disponible solo para fotos, agregará una música predeterminada que podrás cambiar después.\",\n  \"allow_user_to\": \"Permitir al usuario:\",\n  \"your_video_will_be_labeled_promotional\": \"Tu video será etiquetado como \\\"Contenido promocional\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"Esto no se puede cambiar una vez que tu video sea publicado.\",\n  \"turn_on_to_disclose_video_promotes\": \"Activa para revelar que este video promociona bienes o servicios a cambio de algo de valor. Tu video podría promocionarte a ti, a un tercero o a ambos.\",\n  \"you_are_promoting_yourself\": \"Te estás promocionando a ti mismo o a tu propia marca.\",\n  \"this_video_will_be_classified_brand_organic\": \"Este video será clasificado como Marca Orgánica.\",\n  \"you_are_promoting_another_brand\": \"Estás promocionando otra marca o un tercero.\",\n  \"this_video_will_be_classified_branded_content\": \"Este video será clasificado como Contenido de Marca.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Al publicar, aceptas las condiciones de TikTok\",\n  \"music_usage_confirmation\": \"Confirmación de uso de música\",\n  \"branded_content_policy\": \"Política de contenido de marca\",\n  \"select_1\": \"--Seleccionar--\",\n  \"select_flair\": \"--Seleccionar distintivo--\",\n  \"link\": \"Enlace\",\n  \"add_subreddit\": \"Agregar Subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Por favor, agrega al menos un Subreddit\",\n  \"add_community\": \"Agregar comunidad\",\n  \"select_post_type\": \"Seleccionar tipo de publicación...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"No pudimos encontrar ningún negocio conectado a tu página de LinkedIn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Por favor, cierra este diálogo, crea una nueva página y agrega un nuevo canal nuevamente.\",\n  \"select_linkedin_page\": \"Selecciona la página de LinkedIn:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"No pudimos encontrar ningún negocio conectado a las páginas seleccionadas.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Te recomendamos conectar todas las páginas y todos los negocios.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Por favor, cierra este diálogo, elimina tu integración y agrega un nuevo canal nuevamente.\",\n  \"select_instagram_account\": \"Selecciona la cuenta de Instagram:\",\n  \"select_page\": \"Selecciona la página:\",\n  \"generate_image_with_ai\": \"Generar imagen con IA\",\n  \"reconnect_channel\": \"Reconectar canal\",\n  \"update_credentials\": \"Actualizar credenciales\",\n  \"additional_settings\": \"Configuraciones adicionales\",\n  \"change_bot\": \"Cambiar bot\",\n  \"move_add_to_customer\": \"Mover / agregar al cliente\",\n  \"edit_time_slots\": \"Editar franjas horarias\",\n  \"enable_channel\": \"Habilitar canal\",\n  \"disable_channel\": \"Deshabilitar canal\",\n  \"add\": \"Agregar\",\n  \"short_post\": \"Publicación corta\",\n  \"long_post\": \"Publicación larga\",\n  \"a_thread_with_short_posts\": \"Un hilo con publicaciones cortas\",\n  \"a_thread_with_long_posts\": \"Un hilo con publicaciones largas\",\n  \"personal_voice_i_am_happy_to_announce\": \"Voz personal (\\\"Me complace anunciar\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Voz de la empresa (\\\"Nos complace anunciar\\\")\",\n  \"generate\": \"Generar\",\n  \"generate_posts\": \"Generar publicaciones\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Compra una cuenta PRO de por vida con SOL ($199). Ten en cuenta que no hay reembolso para esta compra.\",\n  \"purchase_now\": \"Comprar ahora\",\n  \"pay_today\": \"Pagar hoy\",\n  \"we_are_sorry_to_see_you_go\": \"Lamentamos verte partir :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"¿Podrías decirnos brevemente qué podríamos haber hecho mejor?\",\n  \"cancel_subscription\": \"Cancelar suscripción\",\n  \"plans\": \"Planes\",\n  \"monthly\": \"MENSUAL\",\n  \"yearly\": \"ANUAL\",\n  \"reactivate_subscription\": \"Reactivar suscripción\",\n  \"update_payment_method_invoices_history\": \"Actualizar método de pago / Historial de facturas\",\n  \"cancel_subscription_1\": \"Cancelar suscripción\",\n  \"your_subscription_will_be_canceled_at\": \"Tu suscripción se cancelará el\",\n  \"you_will_never_be_charged_again\": \"Nunca se te volverá a cobrar\",\n  \"current_package\": \"Paquete actual:\",\n  \"next_package\": \"Próximo paquete:\",\n  \"claim\": \"Reclamar\",\n  \"frequently_asked_questions\": \"Preguntas frecuentes\",\n  \"autopost\": \"Autopublicar\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"La autopublicación puede publicar automáticamente tus nuevos elementos RSS en las redes sociales\",\n  \"title\": \"Título\",\n  \"add_an_autopost\": \"Agregar una autopublicación\",\n  \"post_content\": \"Contenido de la publicación\",\n  \"sign_up\": \"Registrarse\",\n  \"or\": \"O\",\n  \"by_registering_you_agree_to_our\": \"Al registrarte, aceptas nuestros\",\n  \"and\": \"y\",\n  \"terms_of_service\": \"Términos de servicio\",\n  \"privacy_policy\": \"Política de privacidad\",\n  \"create_account\": \"Crear cuenta\",\n  \"already_have_an_account\": \"¿Ya tienes una cuenta?\",\n  \"sign_in\": \"Iniciar sesión\",\n  \"sign_in_1\": \"Iniciar sesión\",\n  \"don_t_have_an_account\": \"¿No tienes una cuenta?\",\n  \"forgot_password\": \"¿Olvidaste tu contraseña?\",\n  \"forgot_password_1\": \"¿Olvidaste tu contraseña?\",\n  \"send_password_reset_email\": \"Enviar correo para restablecer contraseña\",\n  \"go_back_to_login\": \"Volver al inicio de sesión\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Te hemos enviado un correo electrónico con un enlace para restablecer tu contraseña.\",\n  \"change_password\": \"Cambiar contraseña\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Hemos restablecido tu contraseña correctamente. Ahora puedes iniciar sesión con tu\",\n  \"click_here_to_go_back_to_login\": \"Haz clic aquí para volver a iniciar sesión\",\n  \"activate_your_account\": \"Activa tu cuenta\",\n  \"thank_you_for_registering\": \"¡Gracias por registrarte!\",\n  \"please_check_your_email_to_activate_your_account\": \"Por favor, revisa tu correo electrónico para activar tu cuenta.\",\n  \"sign_in_with\": \"Iniciar sesión con\",\n  \"continue_with_google\": \"Continuar con Google\",\n  \"sign_in_with_github\": \"Iniciar sesión con GitHub\",\n  \"continue_with_farcaster\": \"Continuar con Farcaster\",\n  \"continue_with_your_wallet\": \"Continuar con tu Wallet\",\n  \"stars_per_day\": \"Estrellas por día\",\n  \"media\": \"Medios\",\n  \"check_launch\": \"Verificar lanzamiento\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Carga tu repositorio de GitHub desde la configuración para ver las analíticas\",\n  \"stars\": \"Estrellas\",\n  \"processing_stars\": \"Procesando estrellas...\",\n  \"forks\": \"Forks\",\n  \"registration_is_disabled\": \"El registro está deshabilitado\",\n  \"login_instead\": \"Inicia sesión en su lugar\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Selecciona una conversación y comienza a chatear.\",\n  \"adding_channel_redirecting_you\": \"Agregando canal, redirigiéndote\",\n  \"could_not_add_provider\": \"No se pudo agregar el proveedor.\",\n  \"you_are_being_redirected_back\": \"Estás siendo redirigido de vuelta\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Estamos experimentando algunas dificultades, intenta actualizar la página\",\n  \"post_not_found\": \"Publicación no encontrada\",\n  \"publication_date\": \"Fecha de publicación:\",\n  \"analytics\": \"Analíticas\",\n  \"launches\": \"Lanzamientos\",\n  \"plugs\": \"Enchufes\",\n  \"billing\": \"Facturación\",\n  \"affiliate\": \"Afiliado\",\n  \"monday\": \"Lunes\",\n  \"tuesday\": \"Martes\",\n  \"wednesday\": \"Miércoles\",\n  \"thursday\": \"Jueves\",\n  \"friday\": \"Viernes\",\n  \"saturday\": \"Sábado\",\n  \"sunday\": \"Domingo\",\n  \"can_t_change_date_remove_post_from_publication\": \"No se puede cambiar la fecha, elimina la publicación de la publicación\",\n  \"predicted_github_trending_change\": \"Cambio de tendencia de GitHub previsto\",\n  \"duplicate_post\": \"Publicación duplicada\",\n  \"preview_post\": \"Vista previa de la publicación\",\n  \"post_statistics\": \"Estadísticas de la publicación\",\n  \"draft\": \"Borrador\",\n  \"week_number\": \"Semana {{number}}\",\n  \"top_title_edit_webhook\": \"Editar webhook\",\n  \"top_title_add_webhook\": \"Agregar webhook\",\n  \"top_title_oh_no\": \"Oh no\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"Editar autopublicación\",\n  \"top_title_add_autopost\": \"Agregar autopublicación\",\n  \"top_title_send_a_new_offer\": \"Enviar una nueva oferta\",\n  \"top_title_media_library\": \"Biblioteca de medios\",\n  \"top_title_add_signature\": \"Agregar firma\",\n  \"top_title_send_a_message_to\": \"Enviar un mensaje a {{name}}\",\n  \"top_title_configure_provider\": \"Configurar proveedor\",\n  \"top_title_add_member\": \"Agregar miembro\",\n  \"top_title_change_bot_picture\": \"Cambiar imagen del bot\",\n  \"top_title_create_a_new_tag\": \"Crear una nueva etiqueta\",\n  \"top_title_select_company\": \"Seleccionar empresa\",\n  \"top_title_additional_settings\": \"Configuraciones adicionales\",\n  \"top_title_time_table_slots\": \"Intervalos de horario\",\n  \"top_title_design_media\": \"Diseñar medios\",\n  \"top_title_edit_post\": \"Editar publicación\",\n  \"top_title_create_post\": \"Crear publicación\",\n  \"top_title_move__add_to_customer\": \"Mover / Agregar al cliente\",\n  \"top_title_add_api_key_for\": \"Agregar clave API para {{name}}\",\n  \"top_title_instance_url\": \"URL de la instancia\",\n  \"top_title_custom_url\": \"URL personalizada\",\n  \"top_title_add_channel\": \"Agregar canal\",\n  \"top_title_add_telegram\": \"Agregar Telegram\",\n  \"top_title_add_wrapcast\": \"Agregar Wrapcast\",\n  \"top_title_comments_for\": \"Comentarios para {{date}}\",\n  \"top_title_edit_signature\": \"Editar firma\",\n  \"label_name\": \"Nombre\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Título\",\n  \"label_subtitle\": \"Subtítulo\",\n  \"label_email\": \"Correo electrónico\",\n  \"label_full_name\": \"Nombre completo\",\n  \"label_password\": \"Contraseña\",\n  \"label_confirm_password\": \"Confirmar contraseña\",\n  \"label_api_key\": \"Clave API\",\n  \"label_instance_url\": \"URL de la instancia\",\n  \"label_custom_url\": \"URL personalizada\",\n  \"label_feedback\": \"Comentarios\",\n  \"label_bio\": \"Biografía\",\n  \"label_role\": \"Rol\",\n  \"label_country\": \"País\",\n  \"label_audience_size\": \"Tamaño de la audiencia en todas las plataformas\",\n  \"label_pick_time\": \"Seleccionar hora\",\n  \"label_nickname\": \"Apodo\",\n  \"label_write_anything\": \"Escribe cualquier cosa\",\n  \"label_output_format\": \"Formato de salida\",\n  \"label_add_pictures\": \"¿Agregar imágenes?\",\n  \"label_hour\": \"Hora\",\n  \"label_minutes\": \"Minutos\",\n  \"label_select_publication\": \"Seleccionar publicación\",\n  \"label_canonical_link\": \"Enlace canónico\",\n  \"label_cover_picture\": \"Imagen de portada\",\n  \"label_tags\": \"Etiquetas\",\n  \"label_topics\": \"Temas\",\n  \"label_tags_maximum_4\": \"Etiquetas (máximo 4)\",\n  \"label_attachments\": \"Archivos adjuntos\",\n  \"label_type\": \"Tipo\",\n  \"label_thumbnail\": \"Miniatura\",\n  \"label_who_can_see_this_video\": \"¿Quién puede ver este video?\",\n  \"label_content_posting_method\": \"Método de publicación de contenido\",\n  \"label_auto_add_music\": \"Agregar música automáticamente\",\n  \"label_duet\": \"Dúo\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Comentarios\",\n  \"label_disclose_video_content\": \"Revelar contenido del video\",\n  \"label_your_brand\": \"Tu marca\",\n  \"label_branded_content\": \"Contenido de marca\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Distintivo\",\n  \"label_media\": \"Medios\",\n  \"label_search_subreddit\": \"Buscar Subreddit\",\n  \"label_delay\": \"Retraso\",\n  \"label_post_type\": \"Tipo de publicación\",\n  \"label_collaborators\": \"Colaboradores (máx. 3) - las cuentas no pueden ser privadas\",\n  \"label_community\": \"Comunidad\",\n  \"label_search_community\": \"Buscar comunidad\",\n  \"label_channel\": \"Canal\",\n  \"label_search_channel\": \"Buscar canal\",\n  \"label_select_channel\": \"Seleccionar canal\",\n  \"label_new_password\": \"Nueva contraseña\",\n  \"label_repeat_password\": \"Repetir contraseña\",\n  \"label_platform\": \"Plataforma\",\n  \"label_price_per_post\": \"Precio por publicación\",\n  \"label_integrations\": \"Integraciones\",\n  \"label_code\": \"Código\",\n  \"label_should_sync_last_post\": \"¿Deberíamos sincronizar la última publicación actual?\",\n  \"label_when_post\": \"¿Cuándo deberíamos publicarlo?\",\n  \"label_autogenerate_content\": \"Autogenerar contenido\",\n  \"label_generate_picture\": \"¿Generar imagen?\",\n  \"label_company\": \"Empresa\",\n  \"label_tag_color\": \"Color de la etiqueta\",\n  \"label_select_board\": \"Seleccionar tablero\",\n  \"label_select_organization\": \"Seleccionar organización\",\n  \"label_auto_add_signature\": \"¿Agregar firma automáticamente?\",\n  \"enable_color_picker\": \"Habilitar selector de color\",\n  \"cancel_the_color_picker\": \"Cancelar el selector de color\",\n  \"no_content_yet\": \"Aún no hay contenido\",\n  \"write_your_reply\": \"Escribe tu publicación...\",\n  \"add_a_tag\": \"Agregar una etiqueta\",\n  \"add_to_calendar\": \"Agregar al calendario\",\n  \"select_channels_from_circles\": \"Selecciona canales de los círculos de arriba\",\n  \"not_matching_order\": \"El orden no coincide\",\n  \"submit_for_order\": \"Enviar para pedido\",\n  \"schedule\": \"Programar\",\n  \"update\": \"Actualizar\",\n  \"attachments\": \"Archivos adjuntos\",\n  \"tags\": \"Etiquetas\",\n  \"public_to_everyone\": \"Público para todos\",\n  \"mutual_follow_friends\": \"Amigos con seguimiento mutuo\",\n  \"follower_of_creator\": \"Seguidor del creador\",\n  \"self_only\": \"Solo yo\",\n  \"post_content_directly_to_tiktok\": \"Publicar contenido directamente en TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Subir contenido a TikTok sin publicarlo\",\n  \"choose_upload_without_posting_description\": \"Elige subir sin publicar si quieres revisar y editar tu contenido en la aplicación de TikTok antes de publicarlo. Esto te da acceso a las herramientas de edición integradas de TikTok y te permite hacer ajustes finales antes de publicar.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"¿Me va a cobrar Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Para confirmar la información de la tarjeta de crédito, Postiz retendrá $2 y los liberará de inmediato. Puedes cancelar tu suscripción en cualquier momento desde la configuración sin hablar con una persona.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"¿Puedo confiar en Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"¡Postiz es orgullosamente de código abierto! Creemos en una cultura ética y transparente, lo que significa que Postiz vivirá para siempre. Puedes revisar todo el código o usarlo para proyectos personales. Para ver el repositorio de código abierto, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">haz clic aquí</a>.\",\n  \"faq_what_are_channels\": \"¿Qué son los canales?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz te permite programar tus publicaciones entre diferentes canales.\\nUn canal es una plataforma de publicación donde puedes programar tus publicaciones.\\nPor ejemplo, puedes programar tus publicaciones en X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads y Pinterest.\",\n  \"faq_what_are_team_members\": \"¿Qué son los miembros del equipo?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Si tienes un equipo con varios miembros, puedes invitarlos a tu espacio de trabajo para colaborar en tus publicaciones y agregar sus canales personales\",\n  \"faq_what_is_ai_auto_complete\": \"¿Qué es la autocompletación con IA?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Automatizamos ChatGPT para ayudarte a escribir publicaciones sociales y artículos.\",\n  \"enter_email\": \"Introduce el correo electrónico\",\n  \"are_you_sure\": \"¿Estás seguro?\",\n  \"no_cancel\": \"No, cancelar\",\n  \"are_you_sure_you_want_to_delete\": \"¿Estás seguro de que quieres eliminar {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"¿Estás seguro de que quieres eliminar la imagen?\",\n  \"are_you_sure_you_want_to_logout\": \"¿Estás seguro de que quieres cerrar sesión?\",\n  \"yes_logout\": \"Sí, cerrar sesión\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"¿Estás seguro de que quieres eliminar este espacio?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"¿Estás seguro de que quieres eliminar este Subreddit?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"¿Estás seguro de que quieres cerrar la ventana?\",\n  \"yes_close\": \"Sí, cerrar\",\n  \"link_copied_to_clipboard\": \"Enlace copiado al portapapeles\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"¿Estás seguro de que quieres cerrar este modal? (todos los datos se perderán)\",\n  \"yes_close_it\": \"¡Sí, ciérralo!\",\n  \"uploading_pictures\": \"Subiendo imágenes...\",\n  \"agent_starting\": \"Iniciando agente\",\n  \"researching_your_content\": \"Investigando tu contenido...\",\n  \"understanding_the_category\": \"Entendiendo la categoría...\",\n  \"finding_the_topic\": \"Buscando el tema...\",\n  \"finding_popular_posts_to_match_with\": \"Buscando publicaciones populares para emparejar...\",\n  \"generating_hook\": \"Generando gancho...\",\n  \"generating_content\": \"Generando contenido...\",\n  \"generating_pictures\": \"Generando imágenes...\",\n  \"finding_time_to_post\": \"Buscando el mejor momento para publicar...\",\n  \"write_anything\": \"Escribe lo que sea\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Puedes escribir lo que quieras y también añadir enlaces, nosotros haremos la investigación por ti...\",\n  \"output_format\": \"Formato de salida\",\n  \"add_pictures\": \"¿Agregar imágenes?\",\n  \"7_days\": \"7 días\",\n  \"30_days\": \"30 días\",\n  \"90_days\": \"90 días\",\n  \"start_7_days_free_trial\": \"Comienza la prueba gratuita de 7 días\",\n  \"change_language\": \"Cambiar idioma\",\n  \"that_a_wrap\": \"¡Eso es todo!\\n\\nSi te gustó este hilo:\\n\\n1. Sígueme en @{{username}} para más contenido como este\\n2. Haz RT al tuit de abajo para compartir este hilo con tu audiencia\\n\",\n  \"post_as_images_carousel\": \"Publicar como carrusel de imágenes\",\n  \"save_set\": \"Guardar conjunto\",\n  \"separate_post\": \"Separar publicación en varias publicaciones\",\n  \"label_who_can_reply_to_this_post\": \"¿Quién puede responder a esta publicación?\",\n  \"delete_integration\": \"Eliminar integración\",\n  \"start_writing_your_post\": \"Comienza a escribir tu publicación para obtener una vista previa\",\n  \"billing_join_over\": \"Únete a más de\",\n  \"billing_entrepreneurs_count\": \"Más de 20,000 emprendedores\",\n  \"billing_who_use\": \"que usan\",\n  \"billing_postiz_grow_social\": \"Postiz para hacer crecer su presencia en redes sociales\",\n  \"billing_no_risk_trial\": \"Prueba gratuita 100% sin riesgo\",\n  \"billing_pay_nothing_7_days\": \"No pagues NADA durante los primeros 7 días\",\n  \"billing_cancel_anytime\": \"Cancela en cualquier momento, desde la configuración\",\n  \"billing_choose_plan\": \"Elige un plan\",\n  \"billing_monthly\": \"Mensual\",\n  \"billing_yearly\": \"Anual\",\n  \"billing_20_percent_off\": \"20% de descuento\",\n  \"billing_features\": \"Características\",\n  \"billing_channel\": \"canal\",\n  \"billing_channels\": \"canales\",\n  \"billing_unlimited\": \"Ilimitado\",\n  \"billing_posts_per_month\": \"publicaciones por mes\",\n  \"billing_unlimited_team_members\": \"Miembros de equipo ilimitados\",\n  \"billing_ai_auto_complete\": \"Autocompletado por IA\",\n  \"billing_ai_copilots\": \"Copilotos de IA\",\n  \"billing_ai_autocomplete\": \"Autocompletado por IA\",\n  \"billing_advanced_picture_editor\": \"Editor de imágenes avanzado\",\n  \"billing_ai_images_per_month\": \"Imágenes de IA por mes\",\n  \"billing_ai_videos_per_month\": \"Videos de IA por mes\",\n  \"billing_billing_address\": \"Dirección de facturación\",\n  \"billing_payment\": \"Pago\",\n  \"billing_powered_by_stripe\": \"Pagos seguros procesados por\",\n  \"billing_your_7_day_trial_is\": \"Tu prueba de 7 días es\",\n  \"billing_100_percent_free\": \"100% gratis\",\n  \"billing_ending\": \"finaliza\",\n  \"billing_cancel_anytime_short\": \"Cancela en cualquier momento.\",\n  \"billing_pay_0_start_trial\": \"¡Paga $0 hoy - Comienza tu prueba gratis!\",\n  \"billing_pay_now\": \"Pagar ahora\",\n  \"billing_per_month\": \"/ mes\",\n  \"billing_per_year\": \"/ año\",\n  \"billing_order_summary\": \"Resumen del pedido\",\n  \"billing_applied\": \"Aplicado\",\n  \"billing_due_today\": \"A pagar hoy\",\n  \"billing_then\": \"Luego\",\n  \"billing_on\": \"el\",\n  \"billing_discount_applied\": \"descuento aplicado\",\n  \"billing_remove\": \"Eliminar\",\n  \"billing_coupon_expires\": \"El cupón vence el\",\n  \"billing_invalid_coupon\": \"Código de cupón inválido\",\n  \"billing_coupon_applied\": \"¡Cupón aplicado con éxito!\",\n  \"billing_coupon_removed\": \"Cupón eliminado\",\n  \"billing_error_removing_coupon\": \"Error al eliminar el cupón\",\n  \"billing_have_discount_coupon\": \"¿Tienes un cupón de descuento?\",\n  \"billing_discount_coupon\": \"Cupón de descuento\",\n  \"billing_cancel\": \"Cancelar\",\n  \"billing_enter_coupon_code\": \"Ingresa el código del cupón\",\n  \"billing_applying\": \"Aplicando...\",\n  \"billing_apply\": \"Aplicar\",\n  \"billing_subscription\": \"Suscripción\",\n  \"billing_cancel_notice\": \"Cancela en cualquier momento desde la configuración sin hablar con una persona y nunca se te cobrará.\",\n  \"select_channels\": \"Seleccionar canales\",\n  \"start_a_new_chat\": \"Iniciar un nuevo chat\",\n  \"your_assistant\": \"Tu Asistente\",\n  \"agent_welcome_message\": \"Hola, soy tu agente de Postiz 🙌🏻.\\n\\nPuedo programar una publicación o varias publicaciones en múltiples canales y generar imágenes y videos.\\n\\nPuedes seleccionar los canales que deseas usar desde el menú de la izquierda.\\n\\nPuedes ver tus conversaciones anteriores desde el menú de la derecha.\\n\\nTambién puedes usarme como un servidor MCP, revisa Configuración >> API Pública\",\n  \"last_github_trending\": \"Últimas tendencias de Github\",\n  \"next_predicted_github_trending\": \"Próximas tendencias previstas de GitHub\",\n  \"repository\": \"Repositorio\",\n  \"date\": \"Fecha\",\n  \"total_stars\": \"Estrellas totales\",\n  \"total_forks\": \"Forks totales\",\n  \"continue_with\": \"Continuar con\",\n  \"email_address\": \"Dirección de correo electrónico\",\n  \"email_already_exists\": \"El correo electrónico ya existe\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Editar autopublicación\",\n  \"add_autopost_title\": \"Agregar autopublicación\",\n  \"webhook_deleted_successfully\": \"Webhook eliminado correctamente\",\n  \"all_integrations\": \"Todas las integraciones\",\n  \"specific_integrations\": \"Integraciones específicas\",\n  \"post_on_next_available_slot\": \"Publicar en el siguiente espacio disponible\",\n  \"post_immediately\": \"Publicar inmediatamente\",\n  \"could_not_use_rss_feed\": \"No se pudo usar este feed RSS\",\n  \"rss_valid\": \"¡RSS válido!\",\n  \"autopost_updated_successfully\": \"Autopublicación actualizada correctamente\",\n  \"autopost_added_successfully\": \"Autopublicación añadida con éxito\",\n  \"write_your_post_placeholder\": \"Escribe tu publicación...\",\n  \"select_or_upload_pictures_max_5\": \"Selecciona o sube imágenes (máximo 5 a la vez).\",\n  \"you_can_drag_drop_pictures\": \"También puedes arrastrar y soltar imágenes.\",\n  \"you_dont_have_any_media_yet\": \"Aún no tienes ningún medio\",\n  \"media_library\": \"Biblioteca de medios\",\n  \"media_settings\": \"Configuración de medios\",\n  \"media_editor\": \"Editor de medios\",\n  \"close\": \"Cerrar\",\n  \"me\": \"Yo\",\n  \"noname\": \"Sin nombre\",\n  \"password_reset_link_expired\": \"Tu enlace para restablecer la contraseña ha expirado. Por favor, inténtalo de nuevo.\",\n  \"invalid_api_key\": \"Clave API inválida\",\n  \"could_not_connect_to_platform\": \"No se pudo conectar a la plataforma\",\n  \"web3_provider\": \"Proveedor Web3\",\n  \"add_provider_title\": \"Agregar proveedor\",\n  \"profile_updated\": \"Perfil actualizado\",\n  \"sets\": \"Conjuntos\",\n  \"invitation_link_sent\": \"Enlace de invitación enviado\",\n  \"send_invitation_link\": \"Enviar enlace de invitación\",\n  \"copy_link\": \"Copiar enlace\",\n  \"are_you_sure_remove_team_member\": \"¿Estás seguro de que deseas eliminar a este miembro del equipo?\",\n  \"admin\": \"Administrador\",\n  \"super_admin\": \"Súper administrador\",\n  \"update_webhook\": \"Actualizar webhook\",\n  \"add_webhook\": \"Agregar webhook\",\n  \"webhook_updated_successfully\": \"Webhook actualizado correctamente\",\n  \"webhook_added_successfully\": \"Webhook agregado correctamente\",\n  \"webhook_sent\": \"Webhook enviado\",\n  \"today\": \"Hoy\",\n  \"channel_disconnected_click_to_reconnect\": \"Canal desconectado, haz clic para reconectar.\",\n  \"channel_disabled_upgrade_plan\": \"Este canal está deshabilitado, por favor actualiza tu plan para habilitarlo.\",\n  \"channel_added\": \"Canal agregado\",\n  \"are_you_sure_disable_channel\": \"¿Estás seguro de que deseas deshabilitar este canal?\",\n  \"disable_channel_title\": \"Deshabilitar canal\",\n  \"channel_disabled\": \"Canal deshabilitado\",\n  \"are_you_sure_delete_channel\": \"¿Estás seguro de que deseas eliminar este canal?\",\n  \"delete_channel_title\": \"Eliminar canal\",\n  \"delete_posts_before_channel\": \"Debes eliminar todas las publicaciones asociadas a este canal antes de eliminarlo\",\n  \"channel_deleted\": \"Canal eliminado\",\n  \"channel_enabled\": \"Canal habilitado\",\n  \"time_table_slots\": \"Intervalos de horario\",\n  \"channel_id_copied\": \"ID del canal copiado al portapapeles\",\n  \"settings_updated\": \"Configuración actualizada\",\n  \"customer_updated\": \"Cliente actualizado\",\n  \"custom_url\": \"URL personalizada\",\n  \"picture\": \"Imagen\",\n  \"upgrade_required\": \"Necesitas actualizar para usar esta función\",\n  \"move_to_billing\": \"Ir a facturación\",\n  \"payment_required\": \"Pago requerido\",\n  \"no_content\": \"sin contenido\",\n  \"select_customer_tooltip\": \"Seleccionar cliente\",\n  \"customers\": \"Clientes\",\n  \"hour\": \"Hora\",\n  \"minutes\": \"Minutos\",\n  \"updated\": \"Actualizado\",\n  \"change_bot_picture_title\": \"Cambiar imagen del bot\",\n  \"select_customer_label\": \"Seleccionar cliente\",\n  \"start_typing\": \"Empieza a escribir...\",\n  \"choose_set_or_continue\": \"Elige un conjunto o continúa sin uno\",\n  \"continue_without_set\": \"Continuar sin conjunto\",\n  \"select_set\": \"Seleccionar un conjunto\",\n  \"channel_settings\": \"Configuración\",\n  \"post_needs_content_or_image\": \"Tu publicación debe tener al menos un carácter o una imagen.\",\n  \"please_fix_your_settings\": \"Por favor, corrige tu configuración\",\n  \"shortlink_urls_question\": \"¿Quieres acortar las URLs? Esto te permitirá obtener estadísticas sobre los clics\",\n  \"yes_shortlink_it\": \"¡Sí, acórtala!\",\n  \"added_successfully\": \"Agregado exitosamente\",\n  \"updated_successfully\": \"Actualizado exitosamente\",\n  \"create_post_title\": \"Crear publicación\",\n  \"post_preview\": \"Vista previa de la publicación\",\n  \"check_circles_above\": \"Revisa los círculos de arriba\",\n  \"create_output\": \"Crear resultado\",\n  \"assistant_initial_message\": \"¡Hola! Puedo ayudarte a mejorar tus publicaciones en redes sociales.\",\n  \"no_longer_global_mode\": \"Ya no estás en modo global\",\n  \"two_days\": \"Dos días\",\n  \"three_days\": \"Tres días\",\n  \"four_days\": \"Cuatro días\",\n  \"five_days\": \"Cinco días\",\n  \"six_days\": \"Seis días\",\n  \"two_weeks\": \"Dos semanas\",\n  \"repeat_post_every_label\": \"Repetir publicación cada\",\n  \"add_new_tag\": \"Agregar nueva etiqueta\",\n  \"tag_name\": \"Nombre\",\n  \"post_is_too_long\": \"la publicación es demasiado larga, por favor arréglala\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Tu publicación debe tener al menos un carácter o una imagen.\",\n  \"internal_edit\": \"Edición interna\",\n  \"are_you_sure_go_back_to_global_mode\": \"Esta acción es irreversible. ¿Estás seguro de que quieres volver al modo global?\",\n  \"yes_go_back_to_global_mode\": \"Sí, volver al modo global\",\n  \"are_you_sure_delete_this_post\": \"¿Estás seguro de que quieres eliminar esta publicación?\",\n  \"yes_delete_it\": \"¡Sí, bórralo!\",\n  \"cant_edit_networks_when_creating_set\": \"No puedes editar redes al crear un conjunto\",\n  \"click_to_exit_global_editing\": \"Haz clic en este botón para salir de la edición global y personalizar la publicación para este canal\",\n  \"edit_content\": \"Editar contenido\",\n  \"editing_a_specific_network\": \"Editando una red específica\",\n  \"back_to_global\": \"Volver a global\",\n  \"delete_post_tooltip\": \"Eliminar publicación\",\n  \"drop_files_here_to_upload\": \"Suelta tus archivos aquí para subirlos\",\n  \"insert_emoji\": \"Insertar emoji\",\n  \"write_something\": \"Escribe algo…\",\n  \"click_channel_to_add\": \"Haz clic en un canal para agregarlo\",\n  \"connect_your_channels\": \"Conecta tus canales\",\n  \"connect_social_media_to_start\": \"Conecta tus cuentas de redes sociales para empezar a programar publicaciones\",\n  \"connected_channels\": \"Canales conectados\",\n  \"continue\": \"Continuar\",\n  \"continue_without_channels\": \"Continuar sin canales\",\n  \"watch_tutorial\": \"Ver tutorial\",\n  \"watch_tutorial_title\": \"Aprende a usar Postiz\",\n  \"watch_tutorial_description\": \"Mira este breve video para aprender a sacar el máximo provecho de Postiz\",\n  \"back\": \"Atrás\",\n  \"get_started\": \"Comenzar\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/fr/translation.json",
    "content": "{\n  \"calendar\": \"Calendrier\",\n  \"webhooks\": \"Webhooks\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Les webhooks sont un moyen d'être notifié lorsqu'il se passe quelque chose dans Postiz via une requête HTTP.\",\n  \"name\": \"Nom\",\n  \"url\": \"URL\",\n  \"edit\": \"Modifier\",\n  \"delete\": \"Supprimer\",\n  \"add_a_webhook\": \"Ajouter un webhook\",\n  \"save\": \"Enregistrer\",\n  \"send_test\": \"Envoyer un test\",\n  \"select_role\": \"Sélectionner un rôle\",\n  \"video_made_with_ai\": \"Vidéo réalisée avec l'IA\",\n  \"please_add_at_least\": \"Veuillez ajouter au moins 20 caractères\",\n  \"send_invitation_via_email\": \"Envoyer l'invitation par e-mail ?\",\n  \"global_settings\": \"Paramètres globaux\",\n  \"copy_id\": \"Copier l'identifiant de la chaîne\",\n  \"team_members\": \"Membres de l'équipe\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Invitez votre assistant ou un membre de votre équipe à gérer votre compte\",\n  \"remove\": \"Retirer\",\n  \"add_another_member\": \"Ajouter un autre membre\",\n  \"signatures\": \"Signatures\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Vous pouvez ajouter des signatures à votre compte pour les utiliser dans vos publications.\",\n  \"content\": \"Contenu\",\n  \"auto_add\": \"Ajout automatique ?\",\n  \"delay_comment\": \"Commentaire de retard\",\n  \"actions\": \"Actions\",\n  \"use_signature\": \"Utiliser la signature\",\n  \"add_a_signature\": \"Ajouter une signature\",\n  \"no\": \"Non\",\n  \"yes\": \"Oui\",\n  \"your_git_repository\": \"Votre dépôt Git\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Connectez votre dépôt GitHub pour recevoir des mises à jour et des analyses\",\n  \"connected\": \"Connecté :\",\n  \"disconnect\": \"Déconnecter\",\n  \"connect_your_repository\": \"Connectez votre dépôt\",\n  \"cancel\": \"Annuler\",\n  \"connect\": \"Connecter\",\n  \"public_api\": \"API publique\",\n  \"check_n8n\": \"Découvrez notre nœud personnalisé N8N pour Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Utilisez l'API Postiz pour l'intégrer à vos outils.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Lisez comment l'utiliser dans la documentation.\",\n  \"reveal\": \"Révéler\",\n  \"copy_key\": \"Copier la clé\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Connectez le serveur MCP de Postiz à votre client (streaming HTTP) pour programmer vos publications plus rapidement !\",\n  \"share_with_a_client\": \"Partager avec un client\",\n  \"post\": \"Publication\",\n  \"comments\": \"Commentaires\",\n  \"user\": \"Utilisateur\",\n  \"login_register_to_add_comments\": \"Connectez-vous / Inscrivez-vous pour ajouter des commentaires\",\n  \"status\": \"Statut :\",\n  \"there_are_not_plugs_matching_your_channels\": \"Il n'y a pas de connecteurs correspondant à vos canaux\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Vous devez ajouter : X ou LinkedIn ou Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Allez dans le calendrier pour ajouter des canaux\",\n  \"channels\": \"Canaux\",\n  \"activate\": \"Activer\",\n  \"this_channel_needs_to_be_refreshed\": \"Ce canal doit être actualisé,\",\n  \"click_here_to_refresh\": \"cliquez ici pour actualiser\",\n  \"can_t_show_analytics_yet\": \"Impossible d'afficher les analyses pour le moment\",\n  \"you_have_to_add_social_media_channels\": \"Vous devez ajouter des canaux de réseaux sociaux\",\n  \"supported\": \"Pris en charge :\",\n  \"step\": \"ÉTAPE\",\n  \"skip_onboarding\": \"Passer l'intégration\",\n  \"onboarding\": \"Intégration\",\n  \"next\": \"Suivant\",\n  \"you_are_done_from_here_you_can\": \"C'est terminé, à partir d'ici vous pouvez :\",\n  \"view_analytics\": \"Voir les analyses\",\n  \"schedule_a_new_post\": \"Programmer une nouvelle publication\",\n  \"to_sell_posts_you_would_have_to\": \"Pour vendre des publications, vous devez :\",\n  \"1_connect_at_least_one_channel\": \"1. Connecter au moins un canal\",\n  \"2_connect_you_bank_account\": \"2. Connecter votre compte bancaire\",\n  \"go_back_to_connect_channels\": \"Revenir pour connecter des canaux\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Aller à la page vendeur pour connecter votre banque\",\n  \"connect_channels\": \"Connecter des canaux\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Connectez vos canaux de réseaux sociaux et de sites de publication pour\\n            programmer des publications ultérieurement\",\n  \"social\": \"Réseaux sociaux\",\n  \"publishing_platforms\": \"Plateformes de publication\",\n  \"no_channels\": \"Aucun canal pour le moment\",\n  \"connect_your_accounts\": \"Connectez vos comptes sociaux pour commencer à planifier, publier et analyser — tout en un seul endroit.\",\n  \"notifications\": \"Notifications\",\n  \"no_notifications\": \"Aucune notification\",\n  \"send_message\": \"Envoyer un message\",\n  \"mar_28\": \"28 mars\",\n  \"there_are_no_messages_yet\": \"Il n'y a pas encore de messages.\",\n  \"checkout_the_marketplace\": \"Découvrir la place de marché\",\n  \"go_to_marketplace\": \"Aller à la place de marché\",\n  \"all_messages\": \"Tous les messages\",\n  \"previous\": \"Précédent\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Sélectionnez ou téléversez des images (maximum 5 à la fois)\",\n  \"you_can_also_drag_drop_pictures\": \"Vous pouvez aussi glisser-déposer des images\",\n  \"you_don_t_have_any_assets_yet\": \"Vous n'avez pas encore d'actifs.\",\n  \"click_the_button_below_to_upload_one\": \"Cliquez sur le bouton ci-dessous pour en téléverser un\",\n  \"click_the_button_below_to_upload_other\": \"Cliquez sur le bouton ci-dessous pour télécharger plusieurs fichiers.\",\n  \"add_selected_media\": \"Ajouter les médias sélectionnés\",\n  \"insert_media\": \"Insérer un média\",\n  \"design_media\": \"Média de conception\",\n  \"select\": \"Sélectionner\",\n  \"editor\": \"Éditeur\",\n  \"clear\": \"Effacer\",\n  \"order_completed\": \"Commande terminée\",\n  \"the_order_has_been_completed\": \"La commande a été terminée\",\n  \"post_has_been_published\": \"La publication a été publiée\",\n  \"url_1\": \"URL :\",\n  \"new_offer\": \"Nouvelle offre\",\n  \"platform\": \"Plateforme\",\n  \"posts\": \"Publications\",\n  \"pay_accept_offer\": \"Payer et accepter l'offre\",\n  \"accepted\": \"Accepté\",\n  \"post_draft\": \"Brouillon de publication\",\n  \"revision_needed\": \"Révision nécessaire\",\n  \"approve\": \"Approuver\",\n  \"preview\": \"Aperçu\",\n  \"revision_requested\": \"Révision demandée\",\n  \"accepted_1\": \"ACCEPTÉ\",\n  \"cancelled_by_the_seller\": \"Annulé par le vendeur\",\n  \"please_select_your_country_where_your_business_is\": \"Veuillez sélectionner le pays où se trouve votre entreprise.\",\n  \"select_country\": \"--SÉLECTIONNER UN PAYS--\",\n  \"connect_bank_account\": \"Connecter un compte bancaire\",\n  \"seller_mode\": \"Mode vendeur\",\n  \"active\": \"Actif\",\n  \"details\": \"Détails\",\n  \"audience_size\": \"Taille de l'audience\",\n  \"add_another_platform\": \"Ajouter une autre plateforme\",\n  \"send_an_offer_for\": \"Envoyer une offre pour $\",\n  \"complete_order_and_pay_early\": \"Terminer la commande et payer à l'avance\",\n  \"order_in_progress\": \"Commande en cours\",\n  \"create_a_new_offer\": \"Créer une nouvelle offre\",\n  \"orders\": \"Commandes\",\n  \"price\": \"Prix\",\n  \"state\": \"État\",\n  \"showing\": \"Affichage\",\n  \"to\": \"à\",\n  \"from\": \"de\",\n  \"results\": \"Résultats\",\n  \"content_writer\": \"Rédacteur de contenu\",\n  \"influencer\": \"Influenceur\",\n  \"request_service\": \"Demander un service\",\n  \"the_marketplace_is_not_opened_yet\": \"La place de marché n'est pas encore ouverte\",\n  \"check_again_soon\": \"Revenez bientôt !\",\n  \"filter\": \"Filtrer\",\n  \"result\": \"Résultat\",\n  \"seller\": \"Vendeur\",\n  \"buyer\": \"Acheteur\",\n  \"discord_support\": \"Support Discord\",\n  \"teams\": \"Équipes\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Publication automatique\",\n  \"logout_from\": \"Se déconnecter de\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Rejoignez plus de 10 000 entrepreneurs qui utilisent Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Pour gérer tous vos réseaux sociaux\",\n  \"100_no_risk_trial\": \"Essai 100% sans risque\",\n  \"pay_nothing_for_the_first_7_days\": \"Ne payez rien pendant les 7 premiers jours\",\n  \"cancel_anytime_hassle_free\": \"Annulez à tout moment, depuis les paramètres\",\n  \"add_free_subscription\": \"-- AJOUTER UN ABONNEMENT GRATUIT --\",\n  \"currently_impersonating\": \"Actuellement en mode usurpation\",\n  \"user_1\": \"utilisateur :\",\n  \"drag_n_drop_some_files_here\": \"Glissez-déposez des fichiers ici\",\n  \"add_time_slot\": \"Ajouter un créneau horaire\",\n  \"add_slot\": \"Ajouter un créneau\",\n  \"cancel_publication\": \"Annuler la publication\",\n  \"statistics\": \"Statistiques\",\n  \"loading\": \"Chargement\",\n  \"short_link\": \"Lien court\",\n  \"original_link\": \"Lien original\",\n  \"clicks\": \"Clics\",\n  \"selected_customer\": \"Client sélectionné\",\n  \"customer\": \"Client :\",\n  \"repeat_post_every\": \"Répéter la publication tous les...\",\n  \"use_this_media\": \"Utiliser ce média\",\n  \"create_new_post\": \"Créer une publication\",\n  \"update_post\": \"Mettre à jour le post existant\",\n  \"merge_comments_into_one_post\": \"Fusionner les commentaires en une seule publication\",\n  \"accounts_that_will_engage\": \"Comptes qui vont interagir :\",\n  \"day\": \"Jour\",\n  \"week\": \"Semaine\",\n  \"month\": \"Mois\",\n  \"remove_from_customer\": \"Retirer du client\",\n  \"show_more\": \"+ Afficher plus\",\n  \"show_less\": \"- Afficher moins\",\n  \"upload\": \"Téléverser\",\n  \"ai\": \"IA\",\n  \"add_channel\": \"Ajouter un canal\",\n  \"add_platform\": \"Ajouter une plateforme\",\n  \"articles\": \"Articles\",\n  \"add_comment\": \"Ajouter un commentaire\",\n  \"add_post\": \"Ajouter une publication dans un fil de discussion\",\n  \"add_comment_or_post\": \"Ajouter un commentaire / une publication\",\n  \"you_are_in_global_editing_mode\": \"Vous êtes en mode d'édition global\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Le post doit contenir au moins 6 caractères\",\n  \"are_you_sure_you_want_to_delete_post\": \"Êtes-vous sûr de vouloir supprimer cette publication ?\",\n  \"post_deleted_successfully\": \"Publication supprimée avec succès\",\n  \"delete_post\": \"Supprimer le post\",\n  \"save_as_draft\": \"Enregistrer comme brouillon\",\n  \"post_now\": \"Publier maintenant\",\n  \"please_add\": \"Veuillez ajouter\",\n  \"to_your_telegram_group_channel_and_click_here\": \"à votre groupe / canal Telegram et cliquez ici :\",\n  \"connect_telegram\": \"Connecter Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Veuillez ajouter la commande suivante dans votre chat :\",\n  \"copy\": \"Copier\",\n  \"settings\": \"Paramètres\",\n  \"integrations\": \"Intégrations\",\n  \"add_integration\": \"Ajouter une intégration\",\n  \"you_are_now_editing_only\": \"Vous modifiez maintenant uniquement\",\n  \"tag_a_company\": \"Taguer une entreprise\",\n  \"video_length_is_invalid_must_be_up_to\": \"La durée de la vidéo est invalide, elle doit être au maximum de\",\n  \"seconds\": \"secondes\",\n  \"this_feature_available_only_for_photos\": \"Cette fonctionnalité est disponible uniquement pour les photos, elle ajoutera une musique par défaut que vous pourrez changer plus tard.\",\n  \"allow_user_to\": \"Autoriser l'utilisateur à :\",\n  \"your_video_will_be_labeled_promotional\": \"Votre vidéo sera étiquetée « Contenu promotionnel ».\",\n  \"this_cannot_be_changed_once_posted\": \"Ceci ne pourra plus être modifié une fois votre vidéo publiée.\",\n  \"turn_on_to_disclose_video_promotes\": \"Activez pour indiquer que cette vidéo fait la promotion de biens ou services en échange d'une contrepartie. Votre vidéo peut faire la promotion de vous-même, d'un tiers, ou des deux.\",\n  \"you_are_promoting_yourself\": \"Vous faites la promotion de vous-même ou de votre propre marque.\",\n  \"this_video_will_be_classified_brand_organic\": \"Cette vidéo sera classée comme contenu de marque organique.\",\n  \"you_are_promoting_another_brand\": \"Vous faites la promotion d'une autre marque ou d'un tiers.\",\n  \"this_video_will_be_classified_branded_content\": \"Cette vidéo sera classée comme contenu de marque.\",\n  \"by_posting_you_agree_to_tiktoks\": \"En publiant, vous acceptez les conditions de TikTok\",\n  \"music_usage_confirmation\": \"Confirmation d'utilisation de la musique\",\n  \"branded_content_policy\": \"Politique sur le contenu de marque\",\n  \"select_1\": \"--Sélectionner--\",\n  \"select_flair\": \"--Sélectionner un flair--\",\n  \"link\": \"Lien\",\n  \"add_subreddit\": \"Ajouter un subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Veuillez ajouter au moins un subreddit\",\n  \"add_community\": \"Ajouter une communauté\",\n  \"select_post_type\": \"Sélectionner le type de publication...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Nous n'avons trouvé aucune entreprise liée à votre page LinkedIn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Veuillez fermer cette fenêtre, créer une nouvelle page et ajouter à nouveau un nouveau canal.\",\n  \"select_linkedin_page\": \"Sélectionnez la page LinkedIn :\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Nous n'avons trouvé aucune entreprise liée aux pages sélectionnées.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Nous vous recommandons de connecter toutes les pages et toutes les entreprises.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Veuillez fermer cette fenêtre, supprimer votre intégration et ajouter à nouveau un nouveau canal.\",\n  \"select_instagram_account\": \"Sélectionnez le compte Instagram :\",\n  \"select_page\": \"Sélectionnez la page :\",\n  \"generate_image_with_ai\": \"Générer une image avec l'IA\",\n  \"reconnect_channel\": \"Reconnecter le canal\",\n  \"update_credentials\": \"Mettre à jour les identifiants\",\n  \"additional_settings\": \"Paramètres supplémentaires\",\n  \"change_bot\": \"Changer de bot\",\n  \"move_add_to_customer\": \"Déplacer / ajouter au client\",\n  \"edit_time_slots\": \"Modifier les créneaux horaires\",\n  \"enable_channel\": \"Activer le canal\",\n  \"disable_channel\": \"Désactiver le canal\",\n  \"add\": \"Ajouter\",\n  \"short_post\": \"Message court\",\n  \"long_post\": \"Message long\",\n  \"a_thread_with_short_posts\": \"Un fil avec des messages courts\",\n  \"a_thread_with_long_posts\": \"Un fil avec des messages longs\",\n  \"personal_voice_i_am_happy_to_announce\": \"Voix personnelle (« Je suis heureux d'annoncer »)\",\n  \"company_voice_we_are_happy_to_announce\": \"Voix d'entreprise (« Nous sommes heureux d'annoncer »)\",\n  \"generate\": \"Générer\",\n  \"generate_posts\": \"Générer des messages\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Achetez un compte PRO à vie avec SOL (199 $). Veuillez noter qu’aucun remboursement n’est possible pour cet achat.\",\n  \"purchase_now\": \"Acheter maintenant\",\n  \"pay_today\": \"Payer aujourd'hui\",\n  \"we_are_sorry_to_see_you_go\": \"Nous sommes désolés de vous voir partir :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Pourriez-vous nous dire brièvement ce que nous aurions pu mieux faire ?\",\n  \"cancel_subscription\": \"Annuler l'abonnement\",\n  \"plans\": \"Formules\",\n  \"monthly\": \"MENSUEL\",\n  \"yearly\": \"ANNUEL\",\n  \"reactivate_subscription\": \"Réactiver l'abonnement\",\n  \"update_payment_method_invoices_history\": \"Mettre à jour le mode de paiement / Historique des factures\",\n  \"cancel_subscription_1\": \"Annuler l'abonnement\",\n  \"your_subscription_will_be_canceled_at\": \"Votre abonnement sera annulé le\",\n  \"you_will_never_be_charged_again\": \"Vous ne serez plus jamais facturé\",\n  \"current_package\": \"Forfait actuel :\",\n  \"next_package\": \"Forfait suivant :\",\n  \"claim\": \"Réclamer\",\n  \"frequently_asked_questions\": \"Foire aux questions\",\n  \"autopost\": \"Publication automatique\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"La publication automatique peut publier vos nouveaux éléments RSS sur les réseaux sociaux.\",\n  \"title\": \"Titre\",\n  \"add_an_autopost\": \"Ajouter une publication automatique\",\n  \"post_content\": \"Contenu de la publication\",\n  \"sign_up\": \"S'inscrire\",\n  \"or\": \"OU\",\n  \"by_registering_you_agree_to_our\": \"En vous inscrivant, vous acceptez nos\",\n  \"and\": \"et\",\n  \"terms_of_service\": \"Conditions d'utilisation\",\n  \"privacy_policy\": \"Politique de confidentialité\",\n  \"create_account\": \"Créer un compte\",\n  \"already_have_an_account\": \"Vous avez déjà un compte ?\",\n  \"sign_in\": \"Se connecter\",\n  \"sign_in_1\": \"Se connecter\",\n  \"don_t_have_an_account\": \"Vous n'avez pas de compte ?\",\n  \"forgot_password\": \"Mot de passe oublié\",\n  \"forgot_password_1\": \"Mot de passe oublié\",\n  \"send_password_reset_email\": \"Envoyer l'e-mail de réinitialisation du mot de passe\",\n  \"go_back_to_login\": \"Retour à la connexion\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Nous vous avons envoyé un e-mail avec un lien pour réinitialiser votre mot de passe.\",\n  \"change_password\": \"Changer le mot de passe\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Nous avons réinitialisé votre mot de passe avec succès. Vous pouvez maintenant vous connecter avec votre\",\n  \"click_here_to_go_back_to_login\": \"Cliquez ici pour revenir à la connexion\",\n  \"activate_your_account\": \"Activez votre compte\",\n  \"thank_you_for_registering\": \"Merci pour votre inscription !\",\n  \"please_check_your_email_to_activate_your_account\": \"Veuillez vérifier votre e-mail pour activer votre compte.\",\n  \"sign_in_with\": \"Se connecter avec\",\n  \"continue_with_google\": \"Continuer avec Google\",\n  \"sign_in_with_github\": \"Se connecter avec GitHub\",\n  \"continue_with_farcaster\": \"Continuer avec Farcaster\",\n  \"continue_with_your_wallet\": \"Continuer avec votre portefeuille\",\n  \"stars_per_day\": \"Étoiles par jour\",\n  \"media\": \"Médias\",\n  \"check_launch\": \"Vérifier le lancement\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Chargez votre dépôt GitHub depuis les paramètres pour voir les analyses\",\n  \"stars\": \"Étoiles\",\n  \"processing_stars\": \"Traitement des étoiles...\",\n  \"forks\": \"Forks\",\n  \"registration_is_disabled\": \"L'inscription est désactivée\",\n  \"login_instead\": \"Connectez-vous à la place\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Sélectionnez une conversation et discutez.\",\n  \"adding_channel_redirecting_you\": \"Ajout du canal, redirection en cours\",\n  \"could_not_add_provider\": \"Impossible d'ajouter le fournisseur.\",\n  \"you_are_being_redirected_back\": \"Vous êtes en cours de redirection\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Nous rencontrons quelques difficultés, essayez de rafraîchir la page\",\n  \"post_not_found\": \"Publication introuvable\",\n  \"publication_date\": \"Date de publication :\",\n  \"analytics\": \"Analyses\",\n  \"launches\": \"Lancements\",\n  \"plugs\": \"Plugs\",\n  \"billing\": \"Facturation\",\n  \"affiliate\": \"Affiliation\",\n  \"monday\": \"Lundi\",\n  \"tuesday\": \"Mardi\",\n  \"wednesday\": \"Mercredi\",\n  \"thursday\": \"Jeudi\",\n  \"friday\": \"Vendredi\",\n  \"saturday\": \"Samedi\",\n  \"sunday\": \"Dimanche\",\n  \"can_t_change_date_remove_post_from_publication\": \"Impossible de changer la date, retirez la publication de la publication\",\n  \"predicted_github_trending_change\": \"Changement de tendance GitHub prédit\",\n  \"duplicate_post\": \"Publication en double\",\n  \"preview_post\": \"Aperçu de la publication\",\n  \"post_statistics\": \"Statistiques de la publication\",\n  \"draft\": \"Brouillon\",\n  \"week_number\": \"Semaine {{number}}\",\n  \"top_title_edit_webhook\": \"Modifier le webhook\",\n  \"top_title_add_webhook\": \"Ajouter un webhook\",\n  \"top_title_oh_no\": \"Oh non\",\n  \"top_title_auto_plug\": \"Auto Plug : {{title}}\",\n  \"top_title_edit_autopost\": \"Modifier l'autopost\",\n  \"top_title_add_autopost\": \"Ajouter un autopost\",\n  \"top_title_send_a_new_offer\": \"Envoyer une nouvelle offre\",\n  \"top_title_media_library\": \"Médiathèque\",\n  \"top_title_add_signature\": \"Ajouter une signature\",\n  \"top_title_send_a_message_to\": \"Envoyer un message à {{name}}\",\n  \"top_title_configure_provider\": \"Configurer le fournisseur\",\n  \"top_title_add_member\": \"Ajouter un membre\",\n  \"top_title_change_bot_picture\": \"Changer l'image du bot\",\n  \"top_title_create_a_new_tag\": \"Créer un nouveau tag\",\n  \"top_title_select_company\": \"Sélectionner une entreprise\",\n  \"top_title_additional_settings\": \"Paramètres supplémentaires\",\n  \"top_title_time_table_slots\": \"Créneaux horaires\",\n  \"top_title_design_media\": \"Concevoir un média\",\n  \"top_title_edit_post\": \"Modifier la publication\",\n  \"top_title_create_post\": \"Créer une publication\",\n  \"top_title_move__add_to_customer\": \"Déplacer / Ajouter au client\",\n  \"top_title_add_api_key_for\": \"Ajouter une clé API pour {{name}}\",\n  \"top_title_instance_url\": \"URL de l'instance\",\n  \"top_title_custom_url\": \"URL personnalisée\",\n  \"top_title_add_channel\": \"Ajouter un canal\",\n  \"top_title_add_telegram\": \"Ajouter Telegram\",\n  \"top_title_add_wrapcast\": \"Ajouter Wrapcast\",\n  \"top_title_comments_for\": \"Commentaires pour {{date}}\",\n  \"top_title_edit_signature\": \"Modifier la signature\",\n  \"label_name\": \"Nom\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Titre\",\n  \"label_subtitle\": \"Sous-titre\",\n  \"label_email\": \"E-mail\",\n  \"label_full_name\": \"Nom complet\",\n  \"label_password\": \"Mot de passe\",\n  \"label_confirm_password\": \"Confirmer le mot de passe\",\n  \"label_api_key\": \"Clé API\",\n  \"label_instance_url\": \"URL de l'instance\",\n  \"label_custom_url\": \"URL personnalisée\",\n  \"label_feedback\": \"Retour d'information\",\n  \"label_bio\": \"Bio\",\n  \"label_role\": \"Rôle\",\n  \"label_country\": \"Pays\",\n  \"label_audience_size\": \"Taille de l'audience sur toutes les plateformes\",\n  \"label_pick_time\": \"Choisir l'heure\",\n  \"label_nickname\": \"Surnom\",\n  \"label_write_anything\": \"Écrire n'importe quoi\",\n  \"label_output_format\": \"Format de sortie\",\n  \"label_add_pictures\": \"Ajouter des images ?\",\n  \"label_hour\": \"Heure\",\n  \"label_minutes\": \"Minutes\",\n  \"label_select_publication\": \"Sélectionner la publication\",\n  \"label_canonical_link\": \"Lien canonique\",\n  \"label_cover_picture\": \"Image de couverture\",\n  \"label_tags\": \"Tags\",\n  \"label_topics\": \"Sujets\",\n  \"label_tags_maximum_4\": \"Tags (maximum 4)\",\n  \"label_attachments\": \"Pièces jointes\",\n  \"label_type\": \"Type\",\n  \"label_thumbnail\": \"Vignette\",\n  \"label_who_can_see_this_video\": \"Qui peut voir cette vidéo ?\",\n  \"label_content_posting_method\": \"Méthode de publication du contenu\",\n  \"label_auto_add_music\": \"Ajouter automatiquement de la musique\",\n  \"label_duet\": \"Duet\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Commentaires\",\n  \"label_disclose_video_content\": \"Divulguer le contenu de la vidéo\",\n  \"label_your_brand\": \"Votre marque\",\n  \"label_branded_content\": \"Contenu de marque\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Média\",\n  \"label_search_subreddit\": \"Rechercher un subreddit\",\n  \"label_delay\": \"Délai\",\n  \"label_post_type\": \"Type de publication\",\n  \"label_collaborators\": \"Collaborateurs (max 3) - les comptes ne peuvent pas être privés\",\n  \"label_community\": \"Communauté\",\n  \"label_search_community\": \"Rechercher une communauté\",\n  \"label_channel\": \"Canal\",\n  \"label_search_channel\": \"Rechercher un canal\",\n  \"label_select_channel\": \"Sélectionner un canal\",\n  \"label_new_password\": \"Nouveau mot de passe\",\n  \"label_repeat_password\": \"Répéter le mot de passe\",\n  \"label_platform\": \"Plateforme\",\n  \"label_price_per_post\": \"Prix par publication\",\n  \"label_integrations\": \"Intégrations\",\n  \"label_code\": \"Code\",\n  \"label_should_sync_last_post\": \"Faut-il synchroniser la dernière publication actuelle ?\",\n  \"label_when_post\": \"Quand devons-nous la publier ?\",\n  \"label_autogenerate_content\": \"Générer automatiquement le contenu\",\n  \"label_generate_picture\": \"Générer une image ?\",\n  \"label_company\": \"Entreprise\",\n  \"label_tag_color\": \"Couleur de l'étiquette\",\n  \"label_select_board\": \"Sélectionner un tableau\",\n  \"label_select_organization\": \"Sélectionner une organisation\",\n  \"label_auto_add_signature\": \"Ajouter automatiquement une signature ?\",\n  \"enable_color_picker\": \"Activer le sélecteur de couleurs\",\n  \"cancel_the_color_picker\": \"Annuler le sélecteur de couleurs\",\n  \"no_content_yet\": \"Pas encore de contenu\",\n  \"write_your_reply\": \"Écrivez votre post...\",\n  \"add_a_tag\": \"Ajouter une étiquette\",\n  \"add_to_calendar\": \"Ajouter au calendrier\",\n  \"select_channels_from_circles\": \"Sélectionnez des canaux à partir des cercles ci-dessus\",\n  \"not_matching_order\": \"Ordre non correspondant\",\n  \"submit_for_order\": \"Soumettre la commande\",\n  \"schedule\": \"Planifier\",\n  \"update\": \"Mettre à jour\",\n  \"attachments\": \"Pièces jointes\",\n  \"tags\": \"Tags\",\n  \"public_to_everyone\": \"Public pour tous\",\n  \"mutual_follow_friends\": \"Amis avec abonnement mutuel\",\n  \"follower_of_creator\": \"Abonné du créateur\",\n  \"self_only\": \"Moi uniquement\",\n  \"post_content_directly_to_tiktok\": \"Publier le contenu directement sur TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Télécharger le contenu sur TikTok sans le publier\",\n  \"choose_upload_without_posting_description\": \"Choisissez de télécharger sans publier si vous souhaitez revoir et modifier votre contenu dans l’application TikTok avant de le publier. Cela vous donne accès aux outils d’édition intégrés de TikTok et vous permet de faire des ajustements finaux avant la publication.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Vais-je être facturé par Postiz ?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Pour confirmer les informations de carte de crédit, Postiz retiendra 2 $ et les libérera immédiatement. Vous pouvez annuler votre abonnement à tout moment depuis les paramètres, sans avoir à parler à quelqu'un.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Puis-je faire confiance à Postiz ?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz est fièrement open source ! Nous croyons en une culture éthique et transparente, ce qui signifie que Postiz existera toujours. Vous pouvez consulter l'intégralité du code ou l'utiliser pour des projets personnels. Pour voir le dépôt open source, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">cliquez ici</a>.\",\n  \"faq_what_are_channels\": \"Que sont les canaux ?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz vous permet de planifier vos publications sur différents canaux.\\nUn canal est une plateforme de publication où vous pouvez programmer vos publications.\\nPar exemple, vous pouvez planifier vos publications sur X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads et Pinterest.\",\n  \"faq_what_are_team_members\": \"Que sont les membres de l’équipe ?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Si vous avez une équipe avec plusieurs membres, vous pouvez les inviter dans votre espace de travail pour collaborer sur vos publications et ajouter leurs canaux personnels\",\n  \"faq_what_is_ai_auto_complete\": \"Qu’est-ce que la saisie automatique par IA ?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Nous automatisons ChatGPT pour vous aider à rédiger des publications sociales et des articles.\",\n  \"enter_email\": \"Saisissez l’e-mail\",\n  \"are_you_sure\": \"Êtes-vous sûr ?\",\n  \"no_cancel\": \"Non, annuler !\",\n  \"are_you_sure_you_want_to_delete\": \"Êtes-vous sûr de vouloir supprimer {{name}} ?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Êtes-vous sûr de vouloir supprimer l'image ?\",\n  \"are_you_sure_you_want_to_logout\": \"Êtes-vous sûr de vouloir vous déconnecter ?\",\n  \"yes_logout\": \"Oui, se déconnecter\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Êtes-vous sûr de vouloir supprimer cet emplacement ?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Êtes-vous sûr de vouloir supprimer ce Subreddit ?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Êtes-vous sûr de vouloir fermer la fenêtre ?\",\n  \"yes_close\": \"Oui, fermer\",\n  \"link_copied_to_clipboard\": \"Lien copié dans le presse-papiers\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Êtes-vous sûr de vouloir fermer cette fenêtre ? (toutes les données seront perdues)\",\n  \"yes_close_it\": \"Oui, fermez-la !\",\n  \"uploading_pictures\": \"Téléchargement des images...\",\n  \"agent_starting\": \"Agent en cours de démarrage\",\n  \"researching_your_content\": \"Recherche de votre contenu...\",\n  \"understanding_the_category\": \"Compréhension de la catégorie...\",\n  \"finding_the_topic\": \"Recherche du sujet...\",\n  \"finding_popular_posts_to_match_with\": \"Recherche de publications populaires correspondantes...\",\n  \"generating_hook\": \"Génération du crochet...\",\n  \"generating_content\": \"Génération du contenu...\",\n  \"generating_pictures\": \"Génération des images...\",\n  \"finding_time_to_post\": \"Recherche du meilleur moment pour publier...\",\n  \"write_anything\": \"Écrivez n'importe quoi\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Vous pouvez écrire tout ce que vous voulez, et aussi ajouter des liens, nous ferons les recherches pour vous...\",\n  \"output_format\": \"Format de sortie\",\n  \"add_pictures\": \"Ajouter des images ?\",\n  \"7_days\": \"7 jours\",\n  \"30_days\": \"30 jours\",\n  \"90_days\": \"90 jours\",\n  \"start_7_days_free_trial\": \"Commencez l’essai gratuit de 7 jours\",\n  \"change_language\": \"Changer de langue\",\n  \"that_a_wrap\": \"C'est terminé !\\n\\nSi vous avez aimé ce fil :\\n\\n1. Suivez-moi @{{username}} pour en voir d'autres\\n2. Retweetez le tweet ci-dessous pour partager ce fil avec votre audience\\n\",\n  \"post_as_images_carousel\": \"Publier en carrousel d’images\",\n  \"save_set\": \"Enregistrer l'ensemble\",\n  \"separate_post\": \"Séparer le post en plusieurs publications\",\n  \"label_who_can_reply_to_this_post\": \"Qui peut répondre à ce post ?\",\n  \"delete_integration\": \"Supprimer l'intégration\",\n  \"start_writing_your_post\": \"Commencez à écrire votre post pour un aperçu\",\n  \"billing_join_over\": \"Rejoignez plus de\",\n  \"billing_entrepreneurs_count\": \"Plus de 20 000 entrepreneurs\",\n  \"billing_who_use\": \"qui utilisent\",\n  \"billing_postiz_grow_social\": \"Postiz pour développer leur présence sur les réseaux sociaux\",\n  \"billing_no_risk_trial\": \"Essai gratuit 100% sans risque\",\n  \"billing_pay_nothing_7_days\": \"Ne payez RIEN pendant les 7 premiers jours\",\n  \"billing_cancel_anytime\": \"Annulez à tout moment, depuis les paramètres\",\n  \"billing_choose_plan\": \"Choisissez un forfait\",\n  \"billing_monthly\": \"Mensuel\",\n  \"billing_yearly\": \"Annuel\",\n  \"billing_20_percent_off\": \"20% de réduction\",\n  \"billing_features\": \"Fonctionnalités\",\n  \"billing_channel\": \"canal\",\n  \"billing_channels\": \"canaux\",\n  \"billing_unlimited\": \"Illimité\",\n  \"billing_posts_per_month\": \"publications par mois\",\n  \"billing_unlimited_team_members\": \"Membres d'équipe illimités\",\n  \"billing_ai_auto_complete\": \"Saisie automatique par IA\",\n  \"billing_ai_copilots\": \"Copilotes IA\",\n  \"billing_ai_autocomplete\": \"Saisie automatique IA\",\n  \"billing_advanced_picture_editor\": \"Éditeur d'images avancé\",\n  \"billing_ai_images_per_month\": \"Images IA par mois\",\n  \"billing_ai_videos_per_month\": \"Vidéos IA par mois\",\n  \"billing_billing_address\": \"Adresse de facturation\",\n  \"billing_payment\": \"Paiement\",\n  \"billing_powered_by_stripe\": \"Paiements sécurisés traités par\",\n  \"billing_your_7_day_trial_is\": \"Votre essai de 7 jours est\",\n  \"billing_100_percent_free\": \"100% gratuit\",\n  \"billing_ending\": \"se termine\",\n  \"billing_cancel_anytime_short\": \"Annulez à tout moment.\",\n  \"billing_pay_0_start_trial\": \"Payez 0 $ aujourd'hui - Commencez votre essai gratuit !\",\n  \"billing_pay_now\": \"Payer maintenant\",\n  \"billing_per_month\": \"/ mois\",\n  \"billing_per_year\": \"/ an\",\n  \"billing_order_summary\": \"Récapitulatif de la commande\",\n  \"billing_applied\": \"Appliqué\",\n  \"billing_due_today\": \"À payer aujourd'hui\",\n  \"billing_then\": \"Ensuite\",\n  \"billing_on\": \"le\",\n  \"billing_discount_applied\": \"appliqué\",\n  \"billing_remove\": \"Supprimer\",\n  \"billing_coupon_expires\": \"Le coupon expire le\",\n  \"billing_invalid_coupon\": \"Code coupon invalide\",\n  \"billing_coupon_applied\": \"Coupon appliqué avec succès !\",\n  \"billing_coupon_removed\": \"Coupon supprimé\",\n  \"billing_error_removing_coupon\": \"Erreur lors de la suppression du coupon\",\n  \"billing_have_discount_coupon\": \"Vous avez un coupon de réduction ?\",\n  \"billing_discount_coupon\": \"Coupon de réduction\",\n  \"billing_cancel\": \"Annuler\",\n  \"billing_enter_coupon_code\": \"Entrez le code du coupon\",\n  \"billing_applying\": \"Application en cours...\",\n  \"billing_apply\": \"Appliquer\",\n  \"billing_subscription\": \"Abonnement\",\n  \"billing_cancel_notice\": \"Annulez à tout moment depuis les paramètres sans avoir à parler à quelqu'un et ne soyez jamais facturé.\",\n  \"select_channels\": \"Sélectionner les canaux\",\n  \"start_a_new_chat\": \"Démarrer une nouvelle conversation\",\n  \"your_assistant\": \"Votre assistant\",\n  \"agent_welcome_message\": \"Bonjour, je suis votre agent Postiz 🙌🏻.\\n\\nJe peux programmer une ou plusieurs publications sur plusieurs canaux et générer des images et des vidéos.\\n\\nVous pouvez sélectionner les canaux que vous souhaitez utiliser dans le menu de gauche.\\n\\nVous pouvez voir vos conversations précédentes dans le menu de droite.\\n\\nVous pouvez également m'utiliser comme serveur MCP, consultez Paramètres >> API publique\",\n  \"last_github_trending\": \"Dernières tendances GitHub\",\n  \"next_predicted_github_trending\": \"Prochaines tendances GitHub prévues\",\n  \"repository\": \"Dépôt\",\n  \"date\": \"Date\",\n  \"total_stars\": \"Nombre total d'étoiles\",\n  \"total_forks\": \"Nombre total de forks\",\n  \"continue_with\": \"Continuer avec\",\n  \"email_address\": \"Adresse e-mail\",\n  \"email_already_exists\": \"L'adresse e-mail existe déjà\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Modifier l'autopost\",\n  \"add_autopost_title\": \"Ajouter un autopost\",\n  \"webhook_deleted_successfully\": \"Webhook supprimé avec succès\",\n  \"all_integrations\": \"Toutes les intégrations\",\n  \"specific_integrations\": \"Intégrations spécifiques\",\n  \"post_on_next_available_slot\": \"Publier au prochain créneau disponible\",\n  \"post_immediately\": \"Publier immédiatement\",\n  \"could_not_use_rss_feed\": \"Impossible d'utiliser ce flux RSS\",\n  \"rss_valid\": \"RSS valide !\",\n  \"autopost_updated_successfully\": \"Autopost mis à jour avec succès\",\n  \"autopost_added_successfully\": \"Publication automatique ajoutée avec succès\",\n  \"write_your_post_placeholder\": \"Écrivez votre publication...\",\n  \"select_or_upload_pictures_max_5\": \"Sélectionnez ou téléversez des images (maximum 5 à la fois).\",\n  \"you_can_drag_drop_pictures\": \"Vous pouvez aussi glisser-déposer des images.\",\n  \"you_dont_have_any_media_yet\": \"Vous n'avez pas encore de média\",\n  \"media_library\": \"Bibliothèque de médias\",\n  \"media_settings\": \"Paramètres des médias\",\n  \"media_editor\": \"Éditeur de médias\",\n  \"close\": \"Fermer\",\n  \"me\": \"Moi\",\n  \"noname\": \"Sans nom\",\n  \"password_reset_link_expired\": \"Votre lien de réinitialisation du mot de passe a expiré. Veuillez réessayer.\",\n  \"invalid_api_key\": \"Clé API invalide\",\n  \"could_not_connect_to_platform\": \"Impossible de se connecter à la plateforme\",\n  \"web3_provider\": \"Fournisseur Web3\",\n  \"add_provider_title\": \"Ajouter un fournisseur\",\n  \"profile_updated\": \"Profil mis à jour\",\n  \"sets\": \"Ensembles\",\n  \"invitation_link_sent\": \"Lien d'invitation envoyé\",\n  \"send_invitation_link\": \"Envoyer le lien d'invitation\",\n  \"copy_link\": \"Copier le lien\",\n  \"are_you_sure_remove_team_member\": \"Êtes-vous sûr de vouloir retirer ce membre de l'équipe ?\",\n  \"admin\": \"Admin\",\n  \"super_admin\": \"Super admin\",\n  \"update_webhook\": \"Mettre à jour le webhook\",\n  \"add_webhook\": \"Ajouter un webhook\",\n  \"webhook_updated_successfully\": \"Webhook mis à jour avec succès\",\n  \"webhook_added_successfully\": \"Webhook ajouté avec succès\",\n  \"webhook_sent\": \"Webhook envoyé\",\n  \"today\": \"Aujourd'hui\",\n  \"channel_disconnected_click_to_reconnect\": \"Canal déconnecté, cliquez pour reconnecter.\",\n  \"channel_disabled_upgrade_plan\": \"Ce canal est désactivé, veuillez mettre à niveau votre abonnement pour l'activer.\",\n  \"channel_added\": \"Canal ajouté\",\n  \"are_you_sure_disable_channel\": \"Êtes-vous sûr de vouloir désactiver ce canal ?\",\n  \"disable_channel_title\": \"Désactiver le canal\",\n  \"channel_disabled\": \"Canal désactivé\",\n  \"are_you_sure_delete_channel\": \"Êtes-vous sûr de vouloir supprimer ce canal ?\",\n  \"delete_channel_title\": \"Supprimer le canal\",\n  \"delete_posts_before_channel\": \"Vous devez supprimer tous les messages associés à ce canal avant de le supprimer\",\n  \"channel_deleted\": \"Canal supprimé\",\n  \"channel_enabled\": \"Canal activé\",\n  \"time_table_slots\": \"Créneaux horaires\",\n  \"channel_id_copied\": \"ID du canal copié dans le presse-papiers\",\n  \"settings_updated\": \"Paramètres mis à jour\",\n  \"customer_updated\": \"Client mis à jour\",\n  \"custom_url\": \"URL personnalisée\",\n  \"picture\": \"Image\",\n  \"upgrade_required\": \"Vous devez mettre à niveau pour utiliser cette fonctionnalité\",\n  \"move_to_billing\": \"Aller à la facturation\",\n  \"payment_required\": \"Paiement requis\",\n  \"no_content\": \"aucun contenu\",\n  \"select_customer_tooltip\": \"Sélectionner un client\",\n  \"customers\": \"Clients\",\n  \"hour\": \"Heure\",\n  \"minutes\": \"Minutes\",\n  \"updated\": \"Mis à jour\",\n  \"change_bot_picture_title\": \"Changer l'image du bot\",\n  \"select_customer_label\": \"Sélectionner un client\",\n  \"start_typing\": \"Commencez à taper...\",\n  \"choose_set_or_continue\": \"Choisissez un ensemble ou continuez sans\",\n  \"continue_without_set\": \"Continuer sans ensemble\",\n  \"select_set\": \"Sélectionner un ensemble\",\n  \"channel_settings\": \"Paramètres\",\n  \"post_needs_content_or_image\": \"Votre publication doit contenir au moins un caractère ou une image.\",\n  \"please_fix_your_settings\": \"Veuillez corriger vos paramètres\",\n  \"shortlink_urls_question\": \"Voulez-vous raccourcir les URL ? Cela vous permettra d'obtenir des statistiques sur les clics.\",\n  \"yes_shortlink_it\": \"Oui, raccourcissez-les !\",\n  \"added_successfully\": \"Ajouté avec succès\",\n  \"updated_successfully\": \"Mis à jour avec succès\",\n  \"create_post_title\": \"Créer une publication\",\n  \"post_preview\": \"Aperçu de la publication\",\n  \"check_circles_above\": \"Cochez les cercles ci-dessus\",\n  \"create_output\": \"Créer la sortie\",\n  \"assistant_initial_message\": \"Bonjour ! Je peux vous aider à améliorer vos publications sur les réseaux sociaux.\",\n  \"no_longer_global_mode\": \"N'est plus en mode global\",\n  \"two_days\": \"Deux jours\",\n  \"three_days\": \"Trois jours\",\n  \"four_days\": \"Quatre jours\",\n  \"five_days\": \"Cinq jours\",\n  \"six_days\": \"Six jours\",\n  \"two_weeks\": \"Deux semaines\",\n  \"repeat_post_every_label\": \"Répéter la publication tous les\",\n  \"add_new_tag\": \"Ajouter un nouveau tag\",\n  \"tag_name\": \"Nom\",\n  \"post_is_too_long\": \"le message est trop long, veuillez le corriger\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Votre publication doit contenir au moins un caractère ou une image.\",\n  \"internal_edit\": \"Modification interne\",\n  \"are_you_sure_go_back_to_global_mode\": \"Cette action est irréversible. Êtes-vous sûr de vouloir revenir au mode global ?\",\n  \"yes_go_back_to_global_mode\": \"Oui, revenir au mode global\",\n  \"are_you_sure_delete_this_post\": \"Êtes-vous sûr de vouloir supprimer cette publication ?\",\n  \"yes_delete_it\": \"Oui, supprimez-le !\",\n  \"cant_edit_networks_when_creating_set\": \"Vous ne pouvez pas modifier les réseaux lors de la création d’un ensemble\",\n  \"click_to_exit_global_editing\": \"Cliquez sur ce bouton pour quitter l’édition globale et personnaliser la publication pour ce canal\",\n  \"edit_content\": \"Modifier le contenu\",\n  \"editing_a_specific_network\": \"Modification d’un réseau spécifique\",\n  \"back_to_global\": \"Retour au global\",\n  \"delete_post_tooltip\": \"Supprimer la publication\",\n  \"drop_files_here_to_upload\": \"Déposez vos fichiers ici pour les télécharger\",\n  \"insert_emoji\": \"Insérer un emoji\",\n  \"write_something\": \"Écrivez quelque chose…\",\n  \"click_channel_to_add\": \"Cliquez sur un canal pour l'ajouter\",\n  \"connect_your_channels\": \"Connectez vos canaux\",\n  \"connect_social_media_to_start\": \"Connectez vos comptes de réseaux sociaux pour commencer à programmer des publications\",\n  \"connected_channels\": \"Canaux connectés\",\n  \"continue\": \"Continuer\",\n  \"continue_without_channels\": \"Continuer sans canaux\",\n  \"watch_tutorial\": \"Regarder le tutoriel\",\n  \"watch_tutorial_title\": \"Apprenez à utiliser Postiz\",\n  \"watch_tutorial_description\": \"Regardez cette courte vidéo pour apprendre à tirer le meilleur parti de Postiz\",\n  \"back\": \"Retour\",\n  \"get_started\": \"Commencer\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/he/translation.json",
    "content": "{\n  \"calendar\": \"לוח שנה\",\n  \"webhooks\": \"וובהוקים\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"וובהוקים הם דרך לקבל התראה כאשר משהו קורה ב-Postiz באמצעות בקשת HTTP.\",\n  \"name\": \"שם\",\n  \"url\": \"כתובת URL\",\n  \"edit\": \"ערוך\",\n  \"delete\": \"מחק\",\n  \"add_a_webhook\": \"הוסף וובהוק\",\n  \"save\": \"שמור\",\n  \"send_test\": \"שלח בדיקה\",\n  \"select_role\": \"בחר תפקיד\",\n  \"video_made_with_ai\": \"וידאו שנוצר באמצעות בינה מלאכותית\",\n  \"please_add_at_least\": \"אנא הוסף לפחות 20 תווים\",\n  \"send_invitation_via_email\": \"לשלוח הזמנה בדוא\\\"ל?\",\n  \"global_settings\": \"הגדרות כלליות\",\n  \"copy_id\": \"העתק מזהה ערוץ\",\n  \"team_members\": \"חברי צוות\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"הזמן את העוזר או חבר הצוות שלך לנהל את החשבון שלך\",\n  \"remove\": \"הסר\",\n  \"add_another_member\": \"הוסף חבר נוסף\",\n  \"signatures\": \"חתימות\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"ניתן להוסיף חתימות לחשבונך לשימוש בפוסטים שלך.\",\n  \"content\": \"תוכן\",\n  \"auto_add\": \"הוספה אוטומטית?\",\n  \"delay_comment\": \"הערת עיכוב\",\n  \"actions\": \"פעולות\",\n  \"use_signature\": \"השתמש בחתימה\",\n  \"add_a_signature\": \"הוסף חתימה\",\n  \"no\": \"לא\",\n  \"yes\": \"כן\",\n  \"your_git_repository\": \"מאגר ה-Git שלך\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"חבר את מאגר ה-GitHub שלך כדי לקבל עדכונים וניתוחים\",\n  \"connected\": \"מחובר:\",\n  \"disconnect\": \"נתק\",\n  \"connect_your_repository\": \"חבר את המאגר שלך\",\n  \"cancel\": \"ביטול\",\n  \"connect\": \"חיבור\",\n  \"public_api\": \"API ציבורי\",\n  \"check_n8n\": \"בדקו את הצומת המותאם שלנו ל-N8N עבור Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"השתמש ב-API של Postiz כדי להשתלב עם הכלים שלך.\",\n  \"read_how_to_use_it_over_the_documentation\": \"קרא כיצד להשתמש בזה בתיעוד.\",\n  \"reveal\": \"הצג\",\n  \"copy_key\": \"העתק מפתח\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"חבר את שרת ה-MCP של Postiz ללקוח שלך (הזרמת Http) כדי לתזמן את הפוסטים שלך מהר יותר!\",\n  \"share_with_a_client\": \"שתף עם לקוח\",\n  \"post\": \"פוסט\",\n  \"comments\": \"תגובות\",\n  \"user\": \"משתמש\",\n  \"login_register_to_add_comments\": \"התחבר / הירשם כדי להוסיף תגובות\",\n  \"status\": \"סטטוס:\",\n  \"there_are_not_plugs_matching_your_channels\": \"אין פלאגים התואמים לערוצים שלך\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"עליך להוסיף: X או LinkedIn או Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"עבור ללוח השנה כדי להוסיף ערוצים\",\n  \"channels\": \"ערוצים\",\n  \"activate\": \"הפעל\",\n  \"this_channel_needs_to_be_refreshed\": \"הערוץ הזה צריך לרענון,\",\n  \"click_here_to_refresh\": \"לחץ כאן לרענון\",\n  \"can_t_show_analytics_yet\": \"לא ניתן להציג נתוני אנליטיקה עדיין\",\n  \"you_have_to_add_social_media_channels\": \"עליך להוסיף ערוצי מדיה חברתית\",\n  \"supported\": \"נתמך:\",\n  \"step\": \"שלב\",\n  \"skip_onboarding\": \"דלג על תהליך ההתחלה\",\n  \"onboarding\": \"תהליך התחלה\",\n  \"next\": \"הבא\",\n  \"you_are_done_from_here_you_can\": \"סיימת, מכאן תוכל:\",\n  \"view_analytics\": \"הצג אנליטיקה\",\n  \"schedule_a_new_post\": \"תזמן פוסט חדש\",\n  \"to_sell_posts_you_would_have_to\": \"כדי למכור פוסטים עליך:\",\n  \"1_connect_at_least_one_channel\": \"1. לחבר לפחות ערוץ אחד\",\n  \"2_connect_you_bank_account\": \"2. לחבר את חשבון הבנק שלך\",\n  \"go_back_to_connect_channels\": \"חזור לחיבור ערוצים\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"עבור לעמוד המוכר כדי לחבר את חשבון הבנק שלך\",\n  \"connect_channels\": \"חבר ערוצים\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"חבר את ערוצי המדיה החברתית ואתרי הפרסום שלך כדי\\n            לתזמן פוסטים מאוחר יותר\",\n  \"social\": \"חברתי\",\n  \"publishing_platforms\": \"פלטפורמות פרסום\",\n  \"no_channels\": \"אין ערוצים עדיין\",\n  \"connect_your_accounts\": \"חברו את החשבונות החברתיים שלכם כדי להתחיל לתזמן, לפרסם ולנתח — הכל במקום אחד.\",\n  \"notifications\": \"התראות\",\n  \"no_notifications\": \"אין התראות\",\n  \"send_message\": \"שלח הודעה\",\n  \"mar_28\": \"28 במרץ\",\n  \"there_are_no_messages_yet\": \"עדיין אין הודעות.\",\n  \"checkout_the_marketplace\": \"בדוק את השוק\",\n  \"go_to_marketplace\": \"עבור לשוק\",\n  \"all_messages\": \"כל ההודעות\",\n  \"previous\": \"הקודם\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"בחר או העלה תמונות (מקסימום 5 בכל פעם)\",\n  \"you_can_also_drag_drop_pictures\": \"ניתן גם לגרור ולשחרר תמונות\",\n  \"you_don_t_have_any_assets_yet\": \"עדיין אין לך נכסים.\",\n  \"click_the_button_below_to_upload_one\": \"לחץ על הכפתור למטה כדי להעלות אחד\",\n  \"click_the_button_below_to_upload_other\": \"לחץ על הכפתור למטה כדי להעלות קבצים מרובים\",\n  \"add_selected_media\": \"הוסף מדיה שנבחרה\",\n  \"insert_media\": \"הוסף מדיה\",\n  \"design_media\": \"עצב מדיה\",\n  \"select\": \"בחר\",\n  \"editor\": \"עורך\",\n  \"clear\": \"נקה\",\n  \"order_completed\": \"ההזמנה הושלמה\",\n  \"the_order_has_been_completed\": \"ההזמנה הושלמה\",\n  \"post_has_been_published\": \"הפוסט פורסם\",\n  \"url_1\": \"כתובת URL:\",\n  \"new_offer\": \"הצעה חדשה\",\n  \"platform\": \"פלטפורמה\",\n  \"posts\": \"פוסטים\",\n  \"pay_accept_offer\": \"שלם וקבל הצעה\",\n  \"accepted\": \"התקבל\",\n  \"post_draft\": \"טיוטת פוסט\",\n  \"revision_needed\": \"נדרשת עריכה\",\n  \"approve\": \"אשר\",\n  \"preview\": \"תצוגה מקדימה\",\n  \"revision_requested\": \"בקשת עריכה\",\n  \"accepted_1\": \"התקבל\",\n  \"cancelled_by_the_seller\": \"בוטל על ידי המוכר\",\n  \"please_select_your_country_where_your_business_is\": \"אנא בחר את המדינה שבה העסק שלך נמצא.\",\n  \"select_country\": \"--בחר מדינה--\",\n  \"connect_bank_account\": \"חבר חשבון בנק\",\n  \"seller_mode\": \"מצב מוכר\",\n  \"active\": \"פעיל\",\n  \"details\": \"פרטים\",\n  \"audience_size\": \"גודל קהל\",\n  \"add_another_platform\": \"הוסף פלטפורמה נוספת\",\n  \"send_an_offer_for\": \"שלח הצעה עבור $\",\n  \"complete_order_and_pay_early\": \"השלם הזמנה ושלם מראש\",\n  \"order_in_progress\": \"הזמנה בתהליך\",\n  \"create_a_new_offer\": \"צור הצעה חדשה\",\n  \"orders\": \"הזמנות\",\n  \"price\": \"מחיר\",\n  \"state\": \"מדינה\",\n  \"showing\": \"מציג\",\n  \"to\": \"עד\",\n  \"from\": \"מ\",\n  \"results\": \"תוצאות\",\n  \"content_writer\": \"כותב תוכן\",\n  \"influencer\": \"משפיען\",\n  \"request_service\": \"בקש שירות\",\n  \"the_marketplace_is_not_opened_yet\": \"המרקטפלייס עדיין לא נפתח\",\n  \"check_again_soon\": \"בדוק שוב בקרוב!\",\n  \"filter\": \"סנן\",\n  \"result\": \"תוצאה\",\n  \"seller\": \"מוכר\",\n  \"buyer\": \"קונה\",\n  \"discord_support\": \"תמיכה בדיסקורד\",\n  \"teams\": \"צוותים\",\n  \"webhooks_1\": \"וובהוקים\",\n  \"auto_post\": \"פוסט אוטומטי\",\n  \"logout_from\": \"התנתק מ\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"הצטרפו ל-10,000+ יזמים שמשתמשים ב-Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"לניהול כל ערוצי המדיה החברתית שלך\",\n  \"100_no_risk_trial\": \"100% ניסיון ללא סיכון\",\n  \"pay_nothing_for_the_first_7_days\": \"לא תשלם כלום ב-7 הימים הראשונים\",\n  \"cancel_anytime_hassle_free\": \"ניתן לבטל בכל עת, מההגדרות\",\n  \"add_free_subscription\": \"-- הוסף מנוי חינמי --\",\n  \"currently_impersonating\": \"מתחזה כרגע\",\n  \"user_1\": \"משתמש:\",\n  \"drag_n_drop_some_files_here\": \"גרור ושחרר קבצים כאן\",\n  \"add_time_slot\": \"הוסף משבצת זמן\",\n  \"add_slot\": \"הוסף משבצת\",\n  \"cancel_publication\": \"בטל פרסום\",\n  \"statistics\": \"סטטיסטיקות\",\n  \"loading\": \"טוען\",\n  \"short_link\": \"קישור מקוצר\",\n  \"original_link\": \"קישור מקורי\",\n  \"clicks\": \"קליקים\",\n  \"selected_customer\": \"לקוח נבחר\",\n  \"customer\": \"לקוח:\",\n  \"repeat_post_every\": \"חזור על הפוסט כל...\",\n  \"use_this_media\": \"השתמש במדיה זו\",\n  \"create_new_post\": \"צור פוסט\",\n  \"update_post\": \"עדכן פוסט קיים\",\n  \"merge_comments_into_one_post\": \"מזג תגובות לפוסט אחד\",\n  \"accounts_that_will_engage\": \"חשבונות שיתקשרו:\",\n  \"day\": \"יום\",\n  \"week\": \"שבוע\",\n  \"month\": \"חודש\",\n  \"remove_from_customer\": \"הסר מהלקוח\",\n  \"show_more\": \"+ הצג עוד\",\n  \"show_less\": \"- הצג פחות\",\n  \"upload\": \"העלה\",\n  \"ai\": \"בינה מלאכותית\",\n  \"add_channel\": \"הוסף ערוץ\",\n  \"add_platform\": \"הוסף פלטפורמה\",\n  \"articles\": \"מאמרים\",\n  \"add_comment\": \"הוסף תגובה\",\n  \"add_post\": \"הוסף פוסט בדיון\",\n  \"add_comment_or_post\": \"הוסף תגובה / פוסט\",\n  \"you_are_in_global_editing_mode\": \"אתה נמצא במצב עריכה גלובלי\",\n  \"the_post_should_be_at_least_6_characters_long\": \"הפוסט צריך להיות באורך של לפחות 6 תווים\",\n  \"are_you_sure_you_want_to_delete_post\": \"האם אתה בטוח שברצונך למחוק את הפוסט הזה?\",\n  \"post_deleted_successfully\": \"הפוסט נמחק בהצלחה\",\n  \"delete_post\": \"מחק פוסט\",\n  \"save_as_draft\": \"שמור כטיוטה\",\n  \"post_now\": \"פרסם עכשיו\",\n  \"please_add\": \"אנא הוסף\",\n  \"to_your_telegram_group_channel_and_click_here\": \"לקבוצת / ערוץ הטלגרם שלך ולחץ כאן:\",\n  \"connect_telegram\": \"חבר טלגרם\",\n  \"please_add_the_following_command_in_your_chat\": \"אנא הוסף את הפקודה הבאה בצ'אט שלך:\",\n  \"copy\": \"העתק\",\n  \"settings\": \"הגדרות\",\n  \"integrations\": \"אינטגרציות\",\n  \"add_integration\": \"הוסף אינטגרציה\",\n  \"you_are_now_editing_only\": \"אתה עורך כעת רק\",\n  \"tag_a_company\": \"תייג חברה\",\n  \"video_length_is_invalid_must_be_up_to\": \"אורך הווידאו לא תקין, חייב להיות עד\",\n  \"seconds\": \"שניות\",\n  \"this_feature_available_only_for_photos\": \"פונקציה זו זמינה רק לתמונות, היא תוסיף מוזיקה ברירת מחדל שתוכל לשנות מאוחר יותר.\",\n  \"allow_user_to\": \"אפשר למשתמש ל-\",\n  \"your_video_will_be_labeled_promotional\": \"הווידאו שלך יסומן כ\\\"תוכן פרסומי\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"לא ניתן לשנות זאת לאחר שהווידאו פורסם.\",\n  \"turn_on_to_disclose_video_promotes\": \"הפעל כדי לחשוף שהווידאו הזה מקדם מוצרים או שירותים בתמורה למשהו בעל ערך. הווידאו שלך יכול לקדם את עצמך, צד שלישי, או שניהם.\",\n  \"you_are_promoting_yourself\": \"אתה מקדם את עצמך או את המותג שלך.\",\n  \"this_video_will_be_classified_brand_organic\": \"הווידאו הזה יסווג כמותג אורגני.\",\n  \"you_are_promoting_another_brand\": \"אתה מקדם מותג אחר או צד שלישי.\",\n  \"this_video_will_be_classified_branded_content\": \"הווידאו הזה יסווג כתוכן ממותג.\",\n  \"by_posting_you_agree_to_tiktoks\": \"על ידי פרסום, אתה מסכים לתנאי השימוש של TikTok\",\n  \"music_usage_confirmation\": \"אישור שימוש במוזיקה\",\n  \"branded_content_policy\": \"מדיניות תוכן ממותג\",\n  \"select_1\": \"--בחר--\",\n  \"select_flair\": \"--בחר תיוג--\",\n  \"link\": \"קישור\",\n  \"add_subreddit\": \"הוסף סאב-רדיט\",\n  \"please_add_at_least_one_subreddit\": \"אנא הוסף לפחות סאב-רדיט אחד\",\n  \"add_community\": \"הוסף קהילה\",\n  \"select_post_type\": \"בחר סוג פוסט...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"לא הצלחנו למצוא עסק שמחובר לעמוד הלינקדאין שלך.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"אנא סגור את החלון הזה, צור עמוד חדש והוסף ערוץ חדש שוב.\",\n  \"select_linkedin_page\": \"בחר עמוד לינקדאין:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"לא הצלחנו למצוא עסק שמחובר לעמודים שנבחרו.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"אנו ממליצים לחבר את כל העמודים וכל העסקים.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"אנא סגור את החלון הזה, מחק את האינטגרציה שלך והוסף ערוץ חדש שוב.\",\n  \"select_instagram_account\": \"בחר חשבון אינסטגרם:\",\n  \"select_page\": \"בחר עמוד:\",\n  \"generate_image_with_ai\": \"צור תמונה עם בינה מלאכותית\",\n  \"reconnect_channel\": \"חבר ערוץ מחדש\",\n  \"update_credentials\": \"עדכן פרטי גישה\",\n  \"additional_settings\": \"הגדרות נוספות\",\n  \"change_bot\": \"החלף בוט\",\n  \"move_add_to_customer\": \"העבר / הוסף ללקוח\",\n  \"edit_time_slots\": \"ערוך חלונות זמן\",\n  \"enable_channel\": \"הפעל ערוץ\",\n  \"disable_channel\": \"השבת ערוץ\",\n  \"add\": \"הוסף\",\n  \"short_post\": \"פוסט קצר\",\n  \"long_post\": \"פוסט ארוך\",\n  \"a_thread_with_short_posts\": \"שרשור עם פוסטים קצרים\",\n  \"a_thread_with_long_posts\": \"שרשור עם פוסטים ארוכים\",\n  \"personal_voice_i_am_happy_to_announce\": \"קול אישי (\\\"אני שמח להודיע\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"קול חברה (\\\"אנחנו שמחים להודיע\\\")\",\n  \"generate\": \"צור\",\n  \"generate_posts\": \"צור פוסטים\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"רכוש חשבון PRO לכל החיים עם SOL ($199). לידיעתך, אין החזר כספי עבור רכישה זו.\",\n  \"purchase_now\": \"רכוש עכשיו\",\n  \"pay_today\": \"שלם היום\",\n  \"we_are_sorry_to_see_you_go\": \"מצטערים לראות אותך עוזב :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"האם תוכל לספר לנו בקצרה מה יכולנו לעשות טוב יותר?\",\n  \"cancel_subscription\": \"בטל מנוי\",\n  \"plans\": \"תוכניות\",\n  \"monthly\": \"חודשי\",\n  \"yearly\": \"שנתי\",\n  \"reactivate_subscription\": \"הפעל מחדש את המנוי\",\n  \"update_payment_method_invoices_history\": \"עדכן אמצעי תשלום / היסטוריית חשבוניות\",\n  \"cancel_subscription_1\": \"בטל מנוי\",\n  \"your_subscription_will_be_canceled_at\": \"המנוי שלך יבוטל בתאריך\",\n  \"you_will_never_be_charged_again\": \"לא תחויב שוב לעולם\",\n  \"current_package\": \"חבילה נוכחית:\",\n  \"next_package\": \"החבילה הבאה:\",\n  \"claim\": \"תבע\",\n  \"frequently_asked_questions\": \"שאלות נפוצות\",\n  \"autopost\": \"פרסום אוטומטי\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"פרסום אוטומטי יכול לפרסם פריטים חדשים מה-RSS שלך לרשתות החברתיות באופן אוטומטי\",\n  \"title\": \"כותרת\",\n  \"add_an_autopost\": \"הוסף פרסום אוטומטי\",\n  \"post_content\": \"תוכן הפוסט\",\n  \"sign_up\": \"הרשמה\",\n  \"or\": \"או\",\n  \"by_registering_you_agree_to_our\": \"בהרשמתך אתה מסכים ל\",\n  \"and\": \"ו\",\n  \"terms_of_service\": \"תנאי השירות\",\n  \"privacy_policy\": \"מדיניות פרטיות\",\n  \"create_account\": \"צור חשבון\",\n  \"already_have_an_account\": \"כבר יש לך חשבון?\",\n  \"sign_in\": \"התחבר\",\n  \"sign_in_1\": \"התחבר\",\n  \"don_t_have_an_account\": \"אין לך חשבון?\",\n  \"forgot_password\": \"שכחת סיסמה\",\n  \"forgot_password_1\": \"שכחת סיסמה\",\n  \"send_password_reset_email\": \"שלח אימייל לאיפוס סיסמה\",\n  \"go_back_to_login\": \"חזור להתחברות\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"שלחנו לך אימייל עם קישור לאיפוס הסיסמה שלך.\",\n  \"change_password\": \"שנה סיסמה\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"איפסנו את הסיסמה שלך בהצלחה. כעת תוכל להתחבר עם\",\n  \"click_here_to_go_back_to_login\": \"לחץ כאן כדי לחזור להתחברות\",\n  \"activate_your_account\": \"הפעל את החשבון שלך\",\n  \"thank_you_for_registering\": \"תודה שנרשמת!\",\n  \"please_check_your_email_to_activate_your_account\": \"אנא בדוק את האימייל שלך כדי להפעיל את החשבון.\",\n  \"sign_in_with\": \"התחבר באמצעות\",\n  \"continue_with_google\": \"המשך עם Google\",\n  \"sign_in_with_github\": \"התחבר עם GitHub\",\n  \"continue_with_farcaster\": \"המשך עם Farcaster\",\n  \"continue_with_your_wallet\": \"המשך עם הארנק שלך\",\n  \"stars_per_day\": \"כוכבים ליום\",\n  \"media\": \"מדיה\",\n  \"check_launch\": \"בדוק השקה\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"טען את מאגר ה-GitHub שלך מההגדרות כדי לראות ניתוחים\",\n  \"stars\": \"כוכבים\",\n  \"processing_stars\": \"מעבד כוכבים...\",\n  \"forks\": \"פיצולים\",\n  \"registration_is_disabled\": \"ההרשמה מושבתת\",\n  \"login_instead\": \"התחבר במקום\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"בחר שיחה והתחל לצ'וטט.\",\n  \"adding_channel_redirecting_you\": \"מוסיף ערוץ, מפנה אותך\",\n  \"could_not_add_provider\": \"לא ניתן להוסיף ספק.\",\n  \"you_are_being_redirected_back\": \"אתה מנותב חזרה\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"אנו חווים קושי, נסה לרענן את הדף\",\n  \"post_not_found\": \"הפוסט לא נמצא\",\n  \"publication_date\": \"תאריך פרסום:\",\n  \"analytics\": \"אנליטיקה\",\n  \"launches\": \"השקות\",\n  \"plugs\": \"פלאגים\",\n  \"billing\": \"חיוב\",\n  \"affiliate\": \"שותפים\",\n  \"monday\": \"יום שני\",\n  \"tuesday\": \"יום שלישי\",\n  \"wednesday\": \"יום רביעי\",\n  \"thursday\": \"יום חמישי\",\n  \"friday\": \"יום שישי\",\n  \"saturday\": \"יום שבת\",\n  \"sunday\": \"יום ראשון\",\n  \"can_t_change_date_remove_post_from_publication\": \"לא ניתן לשנות את התאריך, הסר את הפוסט מהפרסום\",\n  \"predicted_github_trending_change\": \"שינוי מגמה חזוי ב-GitHub\",\n  \"duplicate_post\": \"פוסט כפול\",\n  \"preview_post\": \"תצוגה מקדימה של הפוסט\",\n  \"post_statistics\": \"סטטיסטיקות פוסט\",\n  \"draft\": \"טיוטה\",\n  \"week_number\": \"שבוע {{number}}\",\n  \"top_title_edit_webhook\": \"ערוך Webhook\",\n  \"top_title_add_webhook\": \"הוסף Webhook\",\n  \"top_title_oh_no\": \"אוי לא\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"ערוך פוסט אוטומטי\",\n  \"top_title_add_autopost\": \"הוסף פוסט אוטומטי\",\n  \"top_title_send_a_new_offer\": \"שלח הצעה חדשה\",\n  \"top_title_media_library\": \"ספריית מדיה\",\n  \"top_title_add_signature\": \"הוסף חתימה\",\n  \"top_title_send_a_message_to\": \"שלח הודעה אל {{name}}\",\n  \"top_title_configure_provider\": \"הגדר ספק\",\n  \"top_title_add_member\": \"הוסף חבר\",\n  \"top_title_change_bot_picture\": \"שנה תמונת בוט\",\n  \"top_title_create_a_new_tag\": \"צור תג חדש\",\n  \"top_title_select_company\": \"בחר חברה\",\n  \"top_title_additional_settings\": \"הגדרות נוספות\",\n  \"top_title_time_table_slots\": \"משבצות לוח זמנים\",\n  \"top_title_design_media\": \"עיצוב מדיה\",\n  \"top_title_edit_post\": \"ערוך פוסט\",\n  \"top_title_create_post\": \"צור פוסט\",\n  \"top_title_move__add_to_customer\": \"העבר / הוסף ללקוח\",\n  \"top_title_add_api_key_for\": \"הוסף מפתח API עבור {{name}}\",\n  \"top_title_instance_url\": \"כתובת מופע (Instance URL)\",\n  \"top_title_custom_url\": \"כתובת מותאמת אישית\",\n  \"top_title_add_channel\": \"הוסף ערוץ\",\n  \"top_title_add_telegram\": \"הוסף טלגרם\",\n  \"top_title_add_wrapcast\": \"הוסף Wrapcast\",\n  \"top_title_comments_for\": \"הערות עבור {{date}}\",\n  \"top_title_edit_signature\": \"ערוך חתימה\",\n  \"label_name\": \"שם\",\n  \"label_url\": \"כתובת URL\",\n  \"label_title\": \"כותרת\",\n  \"label_subtitle\": \"כותרת משנה\",\n  \"label_email\": \"אימייל\",\n  \"label_full_name\": \"שם מלא\",\n  \"label_password\": \"סיסמה\",\n  \"label_confirm_password\": \"אישור סיסמה\",\n  \"label_api_key\": \"מפתח API\",\n  \"label_instance_url\": \"כתובת מופע\",\n  \"label_custom_url\": \"כתובת מותאמת אישית\",\n  \"label_feedback\": \"משוב\",\n  \"label_bio\": \"ביוגרפיה\",\n  \"label_role\": \"תפקיד\",\n  \"label_country\": \"מדינה\",\n  \"label_audience_size\": \"גודל קהל בכל הפלטפורמות\",\n  \"label_pick_time\": \"בחר זמן\",\n  \"label_nickname\": \"כינוי\",\n  \"label_write_anything\": \"כתוב כל דבר\",\n  \"label_output_format\": \"פורמט פלט\",\n  \"label_add_pictures\": \"להוסיף תמונות?\",\n  \"label_hour\": \"שעה\",\n  \"label_minutes\": \"דקות\",\n  \"label_select_publication\": \"בחר פרסום\",\n  \"label_canonical_link\": \"קישור קנוני\",\n  \"label_cover_picture\": \"תמונת שער\",\n  \"label_tags\": \"תגיות\",\n  \"label_topics\": \"נושאים\",\n  \"label_tags_maximum_4\": \"תגיות (מקסימום 4)\",\n  \"label_attachments\": \"קבצים מצורפים\",\n  \"label_type\": \"סוג\",\n  \"label_thumbnail\": \"תמונה ממוזערת\",\n  \"label_who_can_see_this_video\": \"מי יכול לראות את הסרטון הזה?\",\n  \"label_content_posting_method\": \"שיטת פרסום תוכן\",\n  \"label_auto_add_music\": \"הוספת מוזיקה אוטומטית\",\n  \"label_duet\": \"דואט\",\n  \"label_stitch\": \"סטיץ'\",\n  \"label_comments\": \"תגובות\",\n  \"label_disclose_video_content\": \"חשיפת תוכן וידאו\",\n  \"label_your_brand\": \"המותג שלך\",\n  \"label_branded_content\": \"תוכן ממותג\",\n  \"label_subreddit\": \"סאב-רדיט\",\n  \"label_flair\": \"פלייר\",\n  \"label_media\": \"מדיה\",\n  \"label_search_subreddit\": \"חיפוש סאב-רדיט\",\n  \"label_delay\": \"השהיה\",\n  \"label_post_type\": \"סוג פוסט\",\n  \"label_collaborators\": \"משתפי פעולה (מקסימום 3) - חשבונות לא יכולים להיות פרטיים\",\n  \"label_community\": \"קהילה\",\n  \"label_search_community\": \"חפש קהילה\",\n  \"label_channel\": \"ערוץ\",\n  \"label_search_channel\": \"חפש ערוץ\",\n  \"label_select_channel\": \"בחר ערוץ\",\n  \"label_new_password\": \"סיסמה חדשה\",\n  \"label_repeat_password\": \"חזור על הסיסמה\",\n  \"label_platform\": \"פלטפורמה\",\n  \"label_price_per_post\": \"מחיר לפוסט\",\n  \"label_integrations\": \"אינטגרציות\",\n  \"label_code\": \"קוד\",\n  \"label_should_sync_last_post\": \"האם לסנכרן את הפוסט האחרון הנוכחי?\",\n  \"label_when_post\": \"מתי לפרסם את זה?\",\n  \"label_autogenerate_content\": \"צור תוכן אוטומטית\",\n  \"label_generate_picture\": \"להפיק תמונה?\",\n  \"label_company\": \"חברה\",\n  \"label_tag_color\": \"צבע תגית\",\n  \"label_select_board\": \"בחר לוח\",\n  \"label_select_organization\": \"בחר ארגון\",\n  \"label_auto_add_signature\": \"להוסיף חתימה אוטומטית?\",\n  \"enable_color_picker\": \"הפעל בורר צבעים\",\n  \"cancel_the_color_picker\": \"בטל את בורר הצבעים\",\n  \"no_content_yet\": \"אין תוכן עדיין\",\n  \"write_your_reply\": \"כתוב את הפוסט שלך...\",\n  \"add_a_tag\": \"הוסף תגית\",\n  \"add_to_calendar\": \"הוסף ליומן\",\n  \"select_channels_from_circles\": \"בחר ערוצים מהעיגולים למעלה\",\n  \"not_matching_order\": \"הסדר לא תואם\",\n  \"submit_for_order\": \"שלח להזמנה\",\n  \"schedule\": \"תזמון\",\n  \"update\": \"עדכן\",\n  \"attachments\": \"קבצים מצורפים\",\n  \"tags\": \"תגיות\",\n  \"public_to_everyone\": \"פומבי לכולם\",\n  \"mutual_follow_friends\": \"חברים עם מעקב הדדי\",\n  \"follower_of_creator\": \"עוקב של היוצר\",\n  \"self_only\": \"לעצמי בלבד\",\n  \"post_content_directly_to_tiktok\": \"פרסם תוכן ישירות ל-TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"העלה תוכן ל-TikTok מבלי לפרסם אותו\",\n  \"choose_upload_without_posting_description\": \"בחר העלאה ללא פרסום אם ברצונך לעיין ולערוך את התוכן שלך באפליקציית TikTok לפני הפרסום. זה יאפשר לך להשתמש בכלי העריכה המובנים של TikTok ולבצע התאמות אחרונות לפני הפרסום.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"האם אחויב על ידי Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"כדי לאמת את פרטי כרטיס האשראי, פוסטיז תחזיק בסכום של 2$ ותשחרר אותו מיד. ניתן לבטל את המנוי בכל עת מההגדרות מבלי לדבר עם נציג.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"האם אפשר לסמוך על Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz היא קוד פתוח בגאווה! אנו מאמינים בתרבות אתית ושקופה, מה שאומר ש-Postiz תמשיך להתקיים לנצח. אתם יכולים לעיין בכל הקוד או להשתמש בו לפרויקטים אישיים. לצפייה במאגר הקוד הפתוח, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">לחצו כאן</a>.\",\n  \"faq_what_are_channels\": \"מהם ערוצים?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz מאפשרת לכם לתזמן פוסטים בין ערוצים שונים.\\nערוץ הוא פלטפורמת פרסום שבה ניתן לתזמן את הפוסטים שלכם.\\nלדוגמה, תוכלו לתזמן פוסטים ב-X, פייסבוק, אינסטגרם, טיקטוק, יוטיוב, רדיט, לינקדאין, Dribbble, Threads ופינטרסט.\",\n  \"faq_what_are_team_members\": \"מהם חברי צוות?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"אם יש לך צוות עם מספר חברים, תוכל להזמין אותם לסביבת העבודה שלך כדי לשתף פעולה על הפוסטים ולהוסיף את הערוצים האישיים שלהם\",\n  \"faq_what_is_ai_auto_complete\": \"מהו השלמה אוטומטית בינה מלאכותית?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"אנו משתמשים ב-ChatGPT כדי לעזור לך לכתוב פוסטים ומאמרים לרשתות החברתיות.\",\n  \"enter_email\": \"הזן אימייל\",\n  \"are_you_sure\": \"האם אתה בטוח?\",\n  \"no_cancel\": \"לא, בטל!\",\n  \"are_you_sure_you_want_to_delete\": \"האם אתה בטוח שברצונך למחוק את {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"האם אתה בטוח שברצונך למחוק את התמונה?\",\n  \"are_you_sure_you_want_to_logout\": \"האם אתה בטוח שברצונך להתנתק?\",\n  \"yes_logout\": \"כן, התנתק\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"האם אתה בטוח שברצונך למחוק את המשבצת הזו?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"האם אתה בטוח שברצונך למחוק את הסאב-רדיט הזה?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"האם אתה בטוח שברצונך לסגור את החלון?\",\n  \"yes_close\": \"כן, סגור\",\n  \"link_copied_to_clipboard\": \"הקישור הועתק ללוח\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"האם אתה בטוח שברצונך לסגור את החלון הזה? (כל המידע יאבד)\",\n  \"yes_close_it\": \"כן, סגור את זה!\",\n  \"uploading_pictures\": \"מעלה תמונות...\",\n  \"agent_starting\": \"הסוכן מתחיל\",\n  \"researching_your_content\": \"חוקר את התוכן שלך...\",\n  \"understanding_the_category\": \"מבין את הקטגוריה...\",\n  \"finding_the_topic\": \"מאתר את הנושא...\",\n  \"finding_popular_posts_to_match_with\": \"מאתר פוסטים פופולריים להתאים אליהם...\",\n  \"generating_hook\": \"יוצר הוק...\",\n  \"generating_content\": \"יוצר תוכן...\",\n  \"generating_pictures\": \"יוצר תמונות...\",\n  \"finding_time_to_post\": \"מאתר זמן לפרסום...\",\n  \"write_anything\": \"כתוב כל דבר\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"אתה יכול לכתוב כל מה שתרצה, וגם להוסיף קישורים, אנחנו נעשה את המחקר בשבילך...\",\n  \"output_format\": \"פורמט פלט\",\n  \"add_pictures\": \"להוסיף תמונות?\",\n  \"7_days\": \"7 ימים\",\n  \"30_days\": \"30 ימים\",\n  \"90_days\": \"90 ימים\",\n  \"start_7_days_free_trial\": \"התחל תקופת ניסיון חינם ל-7 ימים\",\n  \"change_language\": \"שנה שפה\",\n  \"that_a_wrap\": \"זה הסוף!\\n\\nאם נהנית מהשרשור הזה:\\n\\n1. עקוב אחרי @{{username}} לעוד תכנים כאלה\\n2. רטווט את הציוץ למטה כדי לשתף את השרשור עם הקהל שלך\\n\",\n  \"post_as_images_carousel\": \"פרסם כתמונות בגלריה\",\n  \"save_set\": \"שמור סט\",\n  \"separate_post\": \"פצל פוסט למספר פוסטים\",\n  \"label_who_can_reply_to_this_post\": \"מי יכול להגיב לפוסט הזה?\",\n  \"delete_integration\": \"מחק אינטגרציה\",\n  \"start_writing_your_post\": \"התחל לכתוב את הפוסט שלך לתצוגה מקדימה\",\n  \"billing_join_over\": \"הצטרפו ליותר מ-\",\n  \"billing_entrepreneurs_count\": \"20,000+ יזמים\",\n  \"billing_who_use\": \"שמשתמשים ב-\",\n  \"billing_postiz_grow_social\": \"Postiz כדי להגדיל את הנוכחות החברתית שלהם\",\n  \"billing_no_risk_trial\": \"ניסיון חינם ללא סיכון ב-100%\",\n  \"billing_pay_nothing_7_days\": \"לא משלמים כלום ב-7 הימים הראשונים\",\n  \"billing_cancel_anytime\": \"ניתן לבטל בכל עת, מההגדרות\",\n  \"billing_choose_plan\": \"בחרו תוכנית\",\n  \"billing_monthly\": \"חודשי\",\n  \"billing_yearly\": \"שנתי\",\n  \"billing_20_percent_off\": \"20% הנחה\",\n  \"billing_features\": \"תכונות\",\n  \"billing_channel\": \"ערוץ\",\n  \"billing_channels\": \"ערוצים\",\n  \"billing_unlimited\": \"ללא הגבלה\",\n  \"billing_posts_per_month\": \"פוסטים בחודש\",\n  \"billing_unlimited_team_members\": \"חברי צוות ללא הגבלה\",\n  \"billing_ai_auto_complete\": \"השלמה אוטומטית בינה מלאכותית\",\n  \"billing_ai_copilots\": \"קופיילוטים מבוססי בינה מלאכותית\",\n  \"billing_ai_autocomplete\": \"השלמה אוטומטית בינה מלאכותית\",\n  \"billing_advanced_picture_editor\": \"עורך תמונות מתקדם\",\n  \"billing_ai_images_per_month\": \"תמונות בינה מלאכותית בחודש\",\n  \"billing_ai_videos_per_month\": \"סרטוני בינה מלאכותית בחודש\",\n  \"billing_billing_address\": \"כתובת לחיוב\",\n  \"billing_payment\": \"תשלום\",\n  \"billing_powered_by_stripe\": \"תשלומים מאובטחים מעובדים על ידי\",\n  \"billing_your_7_day_trial_is\": \"תקופת הניסיון שלך ל-7 ימים היא\",\n  \"billing_100_percent_free\": \"100% חינם\",\n  \"billing_ending\": \"מסתיימת\",\n  \"billing_cancel_anytime_short\": \"ניתן לבטל בכל עת.\",\n  \"billing_pay_0_start_trial\": \"שלם 0$ היום - התחל את תקופת הניסיון שלך!\",\n  \"billing_pay_now\": \"שלם עכשיו\",\n  \"billing_per_month\": \"/ חודש\",\n  \"billing_per_year\": \"/ שנה\",\n  \"billing_order_summary\": \"סיכום הזמנה\",\n  \"billing_applied\": \"הוחל\",\n  \"billing_due_today\": \"לתשלום היום\",\n  \"billing_then\": \"לאחר מכן\",\n  \"billing_on\": \"בתאריך\",\n  \"billing_discount_applied\": \"ההנחה הוחלה\",\n  \"billing_remove\": \"הסר\",\n  \"billing_coupon_expires\": \"הקופון פג בתאריך\",\n  \"billing_invalid_coupon\": \"קוד קופון לא תקין\",\n  \"billing_coupon_applied\": \"הקופון הוחל בהצלחה!\",\n  \"billing_coupon_removed\": \"הקופון הוסר\",\n  \"billing_error_removing_coupon\": \"שגיאה בהסרת הקופון\",\n  \"billing_have_discount_coupon\": \"יש לך קופון הנחה?\",\n  \"billing_discount_coupon\": \"קופון הנחה\",\n  \"billing_cancel\": \"ביטול\",\n  \"billing_enter_coupon_code\": \"הזן קוד קופון\",\n  \"billing_applying\": \"מוחל...\",\n  \"billing_apply\": \"החל\",\n  \"billing_subscription\": \"מנוי\",\n  \"billing_cancel_notice\": \"ניתן לבטל בכל עת מההגדרות מבלי לדבר עם נציג ולעולם לא תחויב.\",\n  \"select_channels\": \"בחר ערוצים\",\n  \"start_a_new_chat\": \"התחל שיחה חדשה\",\n  \"your_assistant\": \"העוזר שלך\",\n  \"agent_welcome_message\": \"שלום, אני הסוכן של Postiz שלך 🙌🏻.\\n\\nאני יכול לתזמן פוסט או מספר פוסטים למספר ערוצים וליצור תמונות וסרטונים.\\n\\nאתה יכול לבחור את הערוצים שברצונך להשתמש בהם מהתפריט השמאלי.\\n\\nאתה יכול לראות את השיחות הקודמות שלך מהתפריט הימני.\\n\\nאתה יכול גם להשתמש בי כשרת MCP, בדוק הגדרות >> API ציבורי\",\n  \"last_github_trending\": \"הטרנדים האחרונים ב-GitHub\",\n  \"next_predicted_github_trending\": \"הטרנד הבא הצפוי ב-GitHub\",\n  \"repository\": \"מאגר\",\n  \"date\": \"תאריך\",\n  \"total_stars\": \"סך כל הכוכבים\",\n  \"total_forks\": \"סך כל הפורקים\",\n  \"continue_with\": \"המשך עם\",\n  \"email_address\": \"כתובת אימייל\",\n  \"email_already_exists\": \"האימייל כבר קיים\",\n  \"google\": \"גוגל\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"ערוך פרסום אוטומטי\",\n  \"add_autopost_title\": \"הוסף פרסום אוטומטי\",\n  \"webhook_deleted_successfully\": \"ה-Webhook נמחק בהצלחה\",\n  \"all_integrations\": \"כל האינטגרציות\",\n  \"specific_integrations\": \"אינטגרציות מסוימות\",\n  \"post_on_next_available_slot\": \"פרסם במועד הפנוי הבא\",\n  \"post_immediately\": \"פרסם מיד\",\n  \"could_not_use_rss_feed\": \"לא ניתן היה להשתמש ב-RSS זה\",\n  \"rss_valid\": \"ה-RSS תקין!\",\n  \"autopost_updated_successfully\": \"הפרסום האוטומטי עודכן בהצלחה\",\n  \"autopost_added_successfully\": \"הפוסט האוטומטי נוסף בהצלחה\",\n  \"write_your_post_placeholder\": \"כתוב את הפוסט שלך...\",\n  \"select_or_upload_pictures_max_5\": \"בחר או העלה תמונות (מקסימום 5 בכל פעם).\",\n  \"you_can_drag_drop_pictures\": \"ניתן גם לגרור ולשחרר תמונות.\",\n  \"you_dont_have_any_media_yet\": \"עדיין אין לך מדיה\",\n  \"media_library\": \"ספריית מדיה\",\n  \"media_settings\": \"הגדרות מדיה\",\n  \"media_editor\": \"עורך מדיה\",\n  \"close\": \"סגור\",\n  \"me\": \"אני\",\n  \"noname\": \"ללא שם\",\n  \"password_reset_link_expired\": \"קישור איפוס הסיסמה שלך פג תוקף. נסה שוב.\",\n  \"invalid_api_key\": \"מפתח API לא תקין\",\n  \"could_not_connect_to_platform\": \"לא ניתן להתחבר לפלטפורמה\",\n  \"web3_provider\": \"ספק Web3\",\n  \"add_provider_title\": \"הוסף ספק\",\n  \"profile_updated\": \"הפרופיל עודכן\",\n  \"sets\": \"סטים\",\n  \"invitation_link_sent\": \"קישור ההזמנה נשלח\",\n  \"send_invitation_link\": \"שלח קישור הזמנה\",\n  \"copy_link\": \"העתק קישור\",\n  \"are_you_sure_remove_team_member\": \"האם אתה בטוח שברצונך להסיר את חבר הצוות הזה?\",\n  \"admin\": \"מנהל\",\n  \"super_admin\": \"מנהל ראשי\",\n  \"update_webhook\": \"עדכן Webhook\",\n  \"add_webhook\": \"הוסף וובהוק\",\n  \"webhook_updated_successfully\": \"הוובהוק עודכן בהצלחה\",\n  \"webhook_added_successfully\": \"הוובהוק נוסף בהצלחה\",\n  \"webhook_sent\": \"וובהוק נשלח\",\n  \"today\": \"היום\",\n  \"channel_disconnected_click_to_reconnect\": \"הערוץ מנותק, לחץ כדי להתחבר מחדש.\",\n  \"channel_disabled_upgrade_plan\": \"הערוץ הזה מושבת, אנא שדרג את התוכנית שלך כדי להפעיל אותו.\",\n  \"channel_added\": \"הערוץ נוסף\",\n  \"are_you_sure_disable_channel\": \"האם אתה בטוח שברצונך להשבית את הערוץ הזה?\",\n  \"disable_channel_title\": \"השבת ערוץ\",\n  \"channel_disabled\": \"הערוץ הושבת\",\n  \"are_you_sure_delete_channel\": \"האם אתה בטוח שברצונך למחוק את הערוץ הזה?\",\n  \"delete_channel_title\": \"מחק ערוץ\",\n  \"delete_posts_before_channel\": \"עליך למחוק את כל הפוסטים המשויכים לערוץ הזה לפני מחיקתו\",\n  \"channel_deleted\": \"הערוץ נמחק\",\n  \"channel_enabled\": \"הערוץ הופעל\",\n  \"time_table_slots\": \"משבצות לוח זמנים\",\n  \"channel_id_copied\": \"מזהה הערוץ הועתק ללוח\",\n  \"settings_updated\": \"ההגדרות עודכנו\",\n  \"customer_updated\": \"הלקוח עודכן\",\n  \"custom_url\": \"כתובת URL מותאמת אישית\",\n  \"picture\": \"תמונה\",\n  \"upgrade_required\": \"עליך לשדרג כדי להשתמש בתכונה זו\",\n  \"move_to_billing\": \"עבור לחיוב\",\n  \"payment_required\": \"נדרש תשלום\",\n  \"no_content\": \"אין תוכן\",\n  \"select_customer_tooltip\": \"בחר לקוח\",\n  \"customers\": \"לקוחות\",\n  \"hour\": \"שעה\",\n  \"minutes\": \"דקות\",\n  \"updated\": \"עודכן\",\n  \"change_bot_picture_title\": \"שנה תמונת בוט\",\n  \"select_customer_label\": \"בחר לקוח\",\n  \"start_typing\": \"התחל להקליד...\",\n  \"choose_set_or_continue\": \"בחר סט או המשך בלעדיו\",\n  \"continue_without_set\": \"המשך ללא סט\",\n  \"select_set\": \"בחר סט\",\n  \"channel_settings\": \"הגדרות\",\n  \"post_needs_content_or_image\": \"הפוסט שלך צריך לכלול לפחות תו אחד או תמונה אחת.\",\n  \"please_fix_your_settings\": \"אנא תקן את ההגדרות שלך\",\n  \"shortlink_urls_question\": \"האם ברצונך לקצר את הקישורים? זה יאפשר לך לקבל סטטיסטיקות על הקלקות\",\n  \"yes_shortlink_it\": \"כן, קצר את זה!\",\n  \"added_successfully\": \"נוסף בהצלחה\",\n  \"updated_successfully\": \"עודכן בהצלחה\",\n  \"create_post_title\": \"צור פוסט\",\n  \"post_preview\": \"תצוגה מקדימה של הפוסט\",\n  \"check_circles_above\": \"סמן את העיגולים למעלה\",\n  \"create_output\": \"צור פלט\",\n  \"assistant_initial_message\": \"היי! אני יכול לעזור לך לשפר את הפוסטים שלך ברשתות החברתיות.\",\n  \"no_longer_global_mode\": \"לא במצב גלובלי יותר\",\n  \"two_days\": \"יומיים\",\n  \"three_days\": \"שלושה ימים\",\n  \"four_days\": \"ארבעה ימים\",\n  \"five_days\": \"חמישה ימים\",\n  \"six_days\": \"שישה ימים\",\n  \"two_weeks\": \"שבועיים\",\n  \"repeat_post_every_label\": \"חזור על הפוסט כל\",\n  \"add_new_tag\": \"הוסף תגית חדשה\",\n  \"tag_name\": \"שם\",\n  \"post_is_too_long\": \"הפוסט ארוך מדי, אנא תקן אותו\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"הפוסט שלך צריך לכלול לפחות תו אחד או תמונה אחת.\",\n  \"internal_edit\": \"עריכה פנימית\",\n  \"are_you_sure_go_back_to_global_mode\": \"פעולה זו אינה ניתנת לביטול. האם אתה בטוח שברצונך לחזור למצב גלובלי?\",\n  \"yes_go_back_to_global_mode\": \"כן, חזור למצב גלובלי\",\n  \"are_you_sure_delete_this_post\": \"האם אתה בטוח שברצונך למחוק את הפוסט הזה?\",\n  \"yes_delete_it\": \"כן, מחק את זה!\",\n  \"cant_edit_networks_when_creating_set\": \"לא ניתן לערוך רשתות בעת יצירת סט\",\n  \"click_to_exit_global_editing\": \"לחץ על כפתור זה כדי לצאת מעריכה גלובלית ולהתאים את הפוסט לערוץ זה\",\n  \"edit_content\": \"ערוך תוכן\",\n  \"editing_a_specific_network\": \"עריכה של רשת מסוימת\",\n  \"back_to_global\": \"חזרה לגלובלי\",\n  \"delete_post_tooltip\": \"מחק פוסט\",\n  \"drop_files_here_to_upload\": \"גרור קבצים לכאן להעלאה\",\n  \"insert_emoji\": \"הוסף אימוג'י\",\n  \"write_something\": \"כתוב משהו…\",\n  \"click_channel_to_add\": \"לחץ על ערוץ כדי להוסיף אותו\",\n  \"connect_your_channels\": \"חבר את הערוצים שלך\",\n  \"connect_social_media_to_start\": \"חבר את חשבונות המדיה החברתית שלך כדי להתחיל לקבוע פוסטים\",\n  \"connected_channels\": \"ערוצים מחוברים\",\n  \"continue\": \"המשך\",\n  \"continue_without_channels\": \"המשך ללא ערוצים\",\n  \"watch_tutorial\": \"צפה במדריך\",\n  \"watch_tutorial_title\": \"למד כיצד להשתמש ב-Postiz\",\n  \"watch_tutorial_description\": \"צפה בסרטון הקצר הזה ולמד כיצד להפיק את המרב מ-Postiz\",\n  \"back\": \"חזרה\",\n  \"get_started\": \"התחלה\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/it/translation.json",
    "content": "{\n  \"calendar\": \"Calendario\",\n  \"webhooks\": \"Webhook\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"I webhook sono un modo per ricevere notifiche quando succede qualcosa in Postiz tramite una richiesta HTTP.\",\n  \"name\": \"Nome\",\n  \"url\": \"URL\",\n  \"edit\": \"Modifica\",\n  \"delete\": \"Elimina\",\n  \"add_a_webhook\": \"Aggiungi un webhook\",\n  \"save\": \"Salva\",\n  \"send_test\": \"Invia test\",\n  \"select_role\": \"Seleziona ruolo\",\n  \"video_made_with_ai\": \"Video realizzato con l'IA\",\n  \"please_add_at_least\": \"Per favore aggiungi almeno 20 caratteri\",\n  \"send_invitation_via_email\": \"Inviare l'invito via email?\",\n  \"global_settings\": \"Impostazioni globali\",\n  \"copy_id\": \"Copia ID canale\",\n  \"team_members\": \"Membri del team\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Invita il tuo assistente o un membro del team a gestire il tuo account\",\n  \"remove\": \"Rimuovi\",\n  \"add_another_member\": \"Aggiungi un altro membro\",\n  \"signatures\": \"Firme\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Puoi aggiungere firme al tuo account da utilizzare nei tuoi post.\",\n  \"content\": \"Contenuto\",\n  \"auto_add\": \"Aggiunta automatica?\",\n  \"delay_comment\": \"Commento sul ritardo\",\n  \"actions\": \"Azioni\",\n  \"use_signature\": \"Usa firma\",\n  \"add_a_signature\": \"Aggiungi una firma\",\n  \"no\": \"No\",\n  \"yes\": \"Sì\",\n  \"your_git_repository\": \"Il tuo repository Git\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Collega il tuo repository GitHub per ricevere aggiornamenti e analisi\",\n  \"connected\": \"Connesso:\",\n  \"disconnect\": \"Disconnetti\",\n  \"connect_your_repository\": \"Collega il tuo repository\",\n  \"cancel\": \"Annulla\",\n  \"connect\": \"Collega\",\n  \"public_api\": \"API pubblica\",\n  \"check_n8n\": \"Scopri il nostro nodo personalizzato N8N per Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Usa l'API di Postiz per integrarla con i tuoi strumenti.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Leggi come usarla nella documentazione.\",\n  \"reveal\": \"Mostra\",\n  \"copy_key\": \"Copia chiave\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Collega il server MCP di Postiz al tuo client (streaming Http) per programmare i tuoi post più velocemente!\",\n  \"share_with_a_client\": \"Condividi con un cliente\",\n  \"post\": \"Post\",\n  \"comments\": \"Commenti\",\n  \"user\": \"Utente\",\n  \"login_register_to_add_comments\": \"Accedi / Registrati per aggiungere commenti\",\n  \"status\": \"Stato:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Non ci sono plug compatibili con i tuoi canali\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Devi aggiungere: X oppure LinkedIn oppure Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Vai al calendario per aggiungere canali\",\n  \"channels\": \"Canali\",\n  \"activate\": \"Attiva\",\n  \"this_channel_needs_to_be_refreshed\": \"Questo canale deve essere aggiornato,\",\n  \"click_here_to_refresh\": \"clicca qui per aggiornare\",\n  \"can_t_show_analytics_yet\": \"Impossibile mostrare le analisi al momento\",\n  \"you_have_to_add_social_media_channels\": \"Devi aggiungere i canali dei social media\",\n  \"supported\": \"Supportato:\",\n  \"step\": \"PASSO\",\n  \"skip_onboarding\": \"Salta onboarding\",\n  \"onboarding\": \"Onboarding\",\n  \"next\": \"Avanti\",\n  \"you_are_done_from_here_you_can\": \"Hai finito, da qui puoi:\",\n  \"view_analytics\": \"Visualizza Analisi\",\n  \"schedule_a_new_post\": \"Programma un nuovo post\",\n  \"to_sell_posts_you_would_have_to\": \"Per vendere post dovresti:\",\n  \"1_connect_at_least_one_channel\": \"1. Collegare almeno un canale\",\n  \"2_connect_you_bank_account\": \"2. Collegare il tuo conto bancario\",\n  \"go_back_to_connect_channels\": \"Torna a collegare i canali\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Vai alla pagina venditore per collegare il tuo conto bancario\",\n  \"connect_channels\": \"Collega Canali\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Collega i tuoi canali social e le piattaforme di pubblicazione per\\n            programmare i post in seguito\",\n  \"social\": \"Social\",\n  \"publishing_platforms\": \"Piattaforme di pubblicazione\",\n  \"no_channels\": \"Nessun canale ancora\",\n  \"connect_your_accounts\": \"Collega i tuoi account social per iniziare a programmare, pubblicare e analizzare — tutto in un unico posto.\",\n  \"notifications\": \"Notifiche\",\n  \"no_notifications\": \"Nessuna notifica\",\n  \"send_message\": \"Invia messaggio\",\n  \"mar_28\": \"28 mar\",\n  \"there_are_no_messages_yet\": \"Non ci sono ancora messaggi.\",\n  \"checkout_the_marketplace\": \"Visita il Marketplace\",\n  \"go_to_marketplace\": \"Vai al marketplace\",\n  \"all_messages\": \"Tutti i messaggi\",\n  \"previous\": \"Precedente\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Seleziona o carica immagini (massimo 5 alla volta)\",\n  \"you_can_also_drag_drop_pictures\": \"Puoi anche trascinare e rilasciare le immagini\",\n  \"you_don_t_have_any_assets_yet\": \"Non hai ancora nessuna risorsa.\",\n  \"click_the_button_below_to_upload_one\": \"Clicca sul pulsante qui sotto per caricarne una\",\n  \"click_the_button_below_to_upload_other\": \"Clicca il pulsante qui sotto per caricare più file\",\n  \"add_selected_media\": \"Aggiungi i media selezionati\",\n  \"insert_media\": \"Inserisci media\",\n  \"design_media\": \"Progetta media\",\n  \"select\": \"Seleziona\",\n  \"editor\": \"Editor\",\n  \"clear\": \"Cancella\",\n  \"order_completed\": \"Ordine completato\",\n  \"the_order_has_been_completed\": \"L'ordine è stato completato\",\n  \"post_has_been_published\": \"Il post è stato pubblicato\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Nuova offerta\",\n  \"platform\": \"Piattaforma\",\n  \"posts\": \"Post\",\n  \"pay_accept_offer\": \"Paga e accetta l'offerta\",\n  \"accepted\": \"Accettato\",\n  \"post_draft\": \"Bozza del post\",\n  \"revision_needed\": \"Revisione necessaria\",\n  \"approve\": \"Approva\",\n  \"preview\": \"Anteprima\",\n  \"revision_requested\": \"Revisione richiesta\",\n  \"accepted_1\": \"ACCETTATO\",\n  \"cancelled_by_the_seller\": \"Annullato dal venditore\",\n  \"please_select_your_country_where_your_business_is\": \"Seleziona il paese in cui si trova la tua attività.\",\n  \"select_country\": \"--SELEZIONA PAESE--\",\n  \"connect_bank_account\": \"Collega conto bancario\",\n  \"seller_mode\": \"Modalità venditore\",\n  \"active\": \"Attivo\",\n  \"details\": \"Dettagli\",\n  \"audience_size\": \"Dimensione pubblico\",\n  \"add_another_platform\": \"Aggiungi un'altra piattaforma\",\n  \"send_an_offer_for\": \"Invia un'offerta per $\",\n  \"complete_order_and_pay_early\": \"Completa l'ordine e paga in anticipo\",\n  \"order_in_progress\": \"Ordine in corso\",\n  \"create_a_new_offer\": \"Crea una nuova offerta\",\n  \"orders\": \"Ordini\",\n  \"price\": \"Prezzo\",\n  \"state\": \"Stato\",\n  \"showing\": \"Visualizzazione\",\n  \"to\": \"a\",\n  \"from\": \"da\",\n  \"results\": \"Risultati\",\n  \"content_writer\": \"Scrittore di contenuti\",\n  \"influencer\": \"Influencer\",\n  \"request_service\": \"Richiedi servizio\",\n  \"the_marketplace_is_not_opened_yet\": \"Il marketplace non è ancora aperto\",\n  \"check_again_soon\": \"Ricontrolla presto!\",\n  \"filter\": \"Filtro\",\n  \"result\": \"Risultato\",\n  \"seller\": \"Venditore\",\n  \"buyer\": \"Acquirente\",\n  \"discord_support\": \"Supporto Discord\",\n  \"teams\": \"Team\",\n  \"webhooks_1\": \"Webhook\",\n  \"auto_post\": \"Pubblicazione automatica\",\n  \"logout_from\": \"Disconnetti da\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Unisciti a oltre 10.000 imprenditori che usano Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Per gestire tutti i tuoi canali social\",\n  \"100_no_risk_trial\": \"Prova senza rischi al 100%\",\n  \"pay_nothing_for_the_first_7_days\": \"Non paghi nulla per i primi 7 giorni\",\n  \"cancel_anytime_hassle_free\": \"Annulla in qualsiasi momento, dalle impostazioni\",\n  \"add_free_subscription\": \"-- AGGIUNGI ABBONAMENTO GRATUITO --\",\n  \"currently_impersonating\": \"Attualmente stai impersonando\",\n  \"user_1\": \"utente:\",\n  \"drag_n_drop_some_files_here\": \"Trascina qui alcuni file\",\n  \"add_time_slot\": \"Aggiungi fascia oraria\",\n  \"add_slot\": \"Aggiungi fascia oraria\",\n  \"cancel_publication\": \"Annulla pubblicazione\",\n  \"statistics\": \"Statistiche\",\n  \"loading\": \"Caricamento in corso\",\n  \"short_link\": \"Link breve\",\n  \"original_link\": \"Link originale\",\n  \"clicks\": \"Click\",\n  \"selected_customer\": \"Cliente selezionato\",\n  \"customer\": \"Cliente:\",\n  \"repeat_post_every\": \"Ripeti post ogni...\",\n  \"use_this_media\": \"Usa questo media\",\n  \"create_new_post\": \"Crea post\",\n  \"update_post\": \"Aggiorna post esistente\",\n  \"merge_comments_into_one_post\": \"Unisci i commenti in un unico post\",\n  \"accounts_that_will_engage\": \"Account che interagiranno:\",\n  \"day\": \"Giorno\",\n  \"week\": \"Settimana\",\n  \"month\": \"Mese\",\n  \"remove_from_customer\": \"Rimuovi dal cliente\",\n  \"show_more\": \"+ Mostra di più\",\n  \"show_less\": \"- Mostra di meno\",\n  \"upload\": \"Carica\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"Aggiungi canale\",\n  \"add_platform\": \"Aggiungi piattaforma\",\n  \"articles\": \"Articoli\",\n  \"add_comment\": \"Aggiungi commento\",\n  \"add_post\": \"Aggiungi post in una discussione\",\n  \"add_comment_or_post\": \"Aggiungi commento / post\",\n  \"you_are_in_global_editing_mode\": \"Sei in modalità di modifica globale\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Il post deve contenere almeno 6 caratteri\",\n  \"are_you_sure_you_want_to_delete_post\": \"Sei sicuro di voler eliminare questo post?\",\n  \"post_deleted_successfully\": \"Post eliminato con successo\",\n  \"delete_post\": \"Elimina post\",\n  \"save_as_draft\": \"Salva come bozza\",\n  \"post_now\": \"Pubblica ora\",\n  \"please_add\": \"Per favore aggiungi\",\n  \"to_your_telegram_group_channel_and_click_here\": \"al tuo\\ngruppo/canale Telegram e clicca qui:\",\n  \"connect_telegram\": \"Collega Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Per favore aggiungi il seguente comando nella tua chat:\",\n  \"copy\": \"Copia\",\n  \"settings\": \"Impostazioni\",\n  \"integrations\": \"Integrazioni\",\n  \"add_integration\": \"Aggiungi integrazione\",\n  \"you_are_now_editing_only\": \"Ora stai modificando solo\",\n  \"tag_a_company\": \"Tagga un'azienda\",\n  \"video_length_is_invalid_must_be_up_to\": \"La durata del video non è valida, deve essere fino a\",\n  \"seconds\": \"secondi\",\n  \"this_feature_available_only_for_photos\": \"Questa funzione è disponibile solo per le foto, verrà aggiunta una musica predefinita che potrai cambiare in seguito.\",\n  \"allow_user_to\": \"Consenti all'utente di:\",\n  \"your_video_will_be_labeled_promotional\": \"Il tuo video sarà etichettato come \\\"Contenuto promozionale\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"Questo non può essere modificato una volta che il video è stato pubblicato.\",\n  \"turn_on_to_disclose_video_promotes\": \"Attiva per dichiarare che questo video promuove beni o servizi in cambio di qualcosa di valore. Il tuo video potrebbe promuovere te stesso, una terza parte o entrambi.\",\n  \"you_are_promoting_yourself\": \"Stai promuovendo te stesso o il tuo marchio.\",\n  \"this_video_will_be_classified_brand_organic\": \"Questo video sarà classificato come Brand Organico.\",\n  \"you_are_promoting_another_brand\": \"Stai promuovendo un altro marchio o una terza parte.\",\n  \"this_video_will_be_classified_branded_content\": \"Questo video sarà classificato come Contenuto Brandizzato.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Pubblicando, accetti le condizioni di TikTok\",\n  \"music_usage_confirmation\": \"Conferma utilizzo musica\",\n  \"branded_content_policy\": \"Politica sui contenuti sponsorizzati\",\n  \"select_1\": \"--Seleziona--\",\n  \"select_flair\": \"--Seleziona Flair--\",\n  \"link\": \"Link\",\n  \"add_subreddit\": \"Aggiungi Subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Per favore aggiungi almeno un Subreddit\",\n  \"add_community\": \"Aggiungi Comunità\",\n  \"select_post_type\": \"Seleziona tipo di post...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Non siamo riusciti a trovare nessuna azienda collegata alla tua pagina LinkedIn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Per favore chiudi questa finestra di dialogo, crea una nuova pagina e aggiungi nuovamente un nuovo canale.\",\n  \"select_linkedin_page\": \"Seleziona pagina LinkedIn:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Non siamo riusciti a trovare nessuna azienda collegata alle pagine selezionate.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Ti consigliamo di collegare tutte le pagine e tutte le aziende.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Per favore chiudi questa finestra di dialogo, elimina la tua integrazione e aggiungi nuovamente un nuovo canale.\",\n  \"select_instagram_account\": \"Seleziona account Instagram:\",\n  \"select_page\": \"Seleziona pagina:\",\n  \"generate_image_with_ai\": \"Genera immagine con IA\",\n  \"reconnect_channel\": \"Riconnetti canale\",\n  \"update_credentials\": \"Aggiorna credenziali\",\n  \"additional_settings\": \"Impostazioni aggiuntive\",\n  \"change_bot\": \"Cambia Bot\",\n  \"move_add_to_customer\": \"Sposta / aggiungi al cliente\",\n  \"edit_time_slots\": \"Modifica fasce orarie\",\n  \"enable_channel\": \"Abilita canale\",\n  \"disable_channel\": \"Disabilita canale\",\n  \"add\": \"Aggiungi\",\n  \"short_post\": \"Post breve\",\n  \"long_post\": \"Post lungo\",\n  \"a_thread_with_short_posts\": \"Una discussione con post brevi\",\n  \"a_thread_with_long_posts\": \"Una discussione con post lunghi\",\n  \"personal_voice_i_am_happy_to_announce\": \"Voce personale (\\\"Sono felice di annunciare\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Voce aziendale (\\\"Siamo felici di annunciare\\\")\",\n  \"generate\": \"Genera\",\n  \"generate_posts\": \"Genera post\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Acquista un account PRO a vita con SOL ($199). Ti informiamo che non è previsto alcun rimborso per questo acquisto.\",\n  \"purchase_now\": \"Acquista ora\",\n  \"pay_today\": \"Paga oggi\",\n  \"we_are_sorry_to_see_you_go\": \"Ci dispiace vederti andare :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Ti andrebbe di dirci brevemente cosa avremmo potuto fare meglio?\",\n  \"cancel_subscription\": \"Annulla abbonamento\",\n  \"plans\": \"Piani\",\n  \"monthly\": \"MENSILE\",\n  \"yearly\": \"ANNUALE\",\n  \"reactivate_subscription\": \"Riattiva abbonamento\",\n  \"update_payment_method_invoices_history\": \"Aggiorna metodo di pagamento / Storico fatture\",\n  \"cancel_subscription_1\": \"Annulla abbonamento\",\n  \"your_subscription_will_be_canceled_at\": \"Il tuo abbonamento sarà annullato il\",\n  \"you_will_never_be_charged_again\": \"Non ti verrà mai più addebitato nulla\",\n  \"current_package\": \"Pacchetto attuale:\",\n  \"next_package\": \"Prossimo pacchetto:\",\n  \"claim\": \"Richiedi\",\n  \"frequently_asked_questions\": \"Domande frequenti\",\n  \"autopost\": \"Autopost\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Autopost può pubblicare automaticamente i nuovi elementi RSS sui social media\",\n  \"title\": \"Titolo\",\n  \"add_an_autopost\": \"Aggiungi un autopost\",\n  \"post_content\": \"Contenuto del post\",\n  \"sign_up\": \"Registrati\",\n  \"or\": \"OPPURE\",\n  \"by_registering_you_agree_to_our\": \"Registrandoti accetti i nostri\",\n  \"and\": \"e\",\n  \"terms_of_service\": \"Termini di servizio\",\n  \"privacy_policy\": \"Informativa sulla privacy\",\n  \"create_account\": \"Crea account\",\n  \"already_have_an_account\": \"Hai già un account?\",\n  \"sign_in\": \"Accedi\",\n  \"sign_in_1\": \"Accedi\",\n  \"don_t_have_an_account\": \"Non hai un account?\",\n  \"forgot_password\": \"Password dimenticata\",\n  \"forgot_password_1\": \"Password dimenticata\",\n  \"send_password_reset_email\": \"Invia email per reimpostare la password\",\n  \"go_back_to_login\": \"Torna al login\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Ti abbiamo inviato un'email con un link per reimpostare la password.\",\n  \"change_password\": \"Cambia password\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Abbiamo reimpostato con successo la tua password. Ora puoi accedere con la tua\",\n  \"click_here_to_go_back_to_login\": \"Clicca qui per tornare al login\",\n  \"activate_your_account\": \"Attiva il tuo account\",\n  \"thank_you_for_registering\": \"Grazie per esserti registrato!\",\n  \"please_check_your_email_to_activate_your_account\": \"Controlla la tua email per attivare il tuo account.\",\n  \"sign_in_with\": \"Accedi con\",\n  \"continue_with_google\": \"Continua con Google\",\n  \"sign_in_with_github\": \"Accedi con GitHub\",\n  \"continue_with_farcaster\": \"Continua con Farcaster\",\n  \"continue_with_your_wallet\": \"Continua con il tuo Wallet\",\n  \"stars_per_day\": \"Stelle al giorno\",\n  \"media\": \"Media\",\n  \"check_launch\": \"Verifica lancio\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Carica il tuo repository GitHub dalle impostazioni per vedere le analisi\",\n  \"stars\": \"Stelle\",\n  \"processing_stars\": \"Elaborazione delle stelle...\",\n  \"forks\": \"Fork\",\n  \"registration_is_disabled\": \"La registrazione è disabilitata\",\n  \"login_instead\": \"Accedi invece\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Seleziona una conversazione e inizia a chattare.\",\n  \"adding_channel_redirecting_you\": \"Aggiunta canale, ti stiamo reindirizzando\",\n  \"could_not_add_provider\": \"Impossibile aggiungere il provider.\",\n  \"you_are_being_redirected_back\": \"Stai venendo reindirizzato indietro\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Stiamo riscontrando alcune difficoltà, prova ad aggiornare la pagina\",\n  \"post_not_found\": \"Post non trovato\",\n  \"publication_date\": \"Data di pubblicazione:\",\n  \"analytics\": \"Analisi\",\n  \"launches\": \"Lanci\",\n  \"plugs\": \"Promozioni\",\n  \"billing\": \"Fatturazione\",\n  \"affiliate\": \"Affiliazione\",\n  \"monday\": \"Lunedì\",\n  \"tuesday\": \"Martedì\",\n  \"wednesday\": \"Mercoledì\",\n  \"thursday\": \"Giovedì\",\n  \"friday\": \"Venerdì\",\n  \"saturday\": \"Sabato\",\n  \"sunday\": \"Domenica\",\n  \"can_t_change_date_remove_post_from_publication\": \"Impossibile cambiare la data, rimuovi il post dalla pubblicazione\",\n  \"predicted_github_trending_change\": \"Variazione prevista delle tendenze di GitHub\",\n  \"duplicate_post\": \"Post duplicato\",\n  \"preview_post\": \"Anteprima post\",\n  \"post_statistics\": \"Statistiche del post\",\n  \"draft\": \"Bozza\",\n  \"week_number\": \"Settimana {{number}}\",\n  \"top_title_edit_webhook\": \"Modifica webhook\",\n  \"top_title_add_webhook\": \"Aggiungi webhook\",\n  \"top_title_oh_no\": \"Oh no\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"Modifica autopost\",\n  \"top_title_add_autopost\": \"Aggiungi autopost\",\n  \"top_title_send_a_new_offer\": \"Invia una nuova offerta\",\n  \"top_title_media_library\": \"Libreria multimediale\",\n  \"top_title_add_signature\": \"Aggiungi firma\",\n  \"top_title_send_a_message_to\": \"Invia un messaggio a {{name}}\",\n  \"top_title_configure_provider\": \"Configura provider\",\n  \"top_title_add_member\": \"Aggiungi membro\",\n  \"top_title_change_bot_picture\": \"Cambia immagine bot\",\n  \"top_title_create_a_new_tag\": \"Crea un nuovo tag\",\n  \"top_title_select_company\": \"Seleziona azienda\",\n  \"top_title_additional_settings\": \"Impostazioni aggiuntive\",\n  \"top_title_time_table_slots\": \"Fasce orarie\",\n  \"top_title_design_media\": \"Progetta media\",\n  \"top_title_edit_post\": \"Modifica post\",\n  \"top_title_create_post\": \"Crea post\",\n  \"top_title_move__add_to_customer\": \"Sposta / Aggiungi al cliente\",\n  \"top_title_add_api_key_for\": \"Aggiungi chiave API per {{name}}\",\n  \"top_title_instance_url\": \"URL istanza\",\n  \"top_title_custom_url\": \"URL personalizzato\",\n  \"top_title_add_channel\": \"Aggiungi canale\",\n  \"top_title_add_telegram\": \"Aggiungi Telegram\",\n  \"top_title_add_wrapcast\": \"Aggiungi Wrapcast\",\n  \"top_title_comments_for\": \"Commenti per {{date}}\",\n  \"top_title_edit_signature\": \"Modifica firma\",\n  \"label_name\": \"Nome\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Titolo\",\n  \"label_subtitle\": \"Sottotitolo\",\n  \"label_email\": \"Email\",\n  \"label_full_name\": \"Nome completo\",\n  \"label_password\": \"Password\",\n  \"label_confirm_password\": \"Conferma password\",\n  \"label_api_key\": \"Chiave API\",\n  \"label_instance_url\": \"URL istanza\",\n  \"label_custom_url\": \"URL personalizzato\",\n  \"label_feedback\": \"Feedback\",\n  \"label_bio\": \"Biografia\",\n  \"label_role\": \"Ruolo\",\n  \"label_country\": \"Paese\",\n  \"label_audience_size\": \"Dimensione del pubblico su tutte le piattaforme\",\n  \"label_pick_time\": \"Scegli l'orario\",\n  \"label_nickname\": \"Soprannome\",\n  \"label_write_anything\": \"Scrivi qualsiasi cosa\",\n  \"label_output_format\": \"Formato di output\",\n  \"label_add_pictures\": \"Aggiungere immagini?\",\n  \"label_hour\": \"Ora\",\n  \"label_minutes\": \"Minuti\",\n  \"label_select_publication\": \"Seleziona pubblicazione\",\n  \"label_canonical_link\": \"Link canonico\",\n  \"label_cover_picture\": \"Immagine di copertina\",\n  \"label_tags\": \"Tag\",\n  \"label_topics\": \"Argomenti\",\n  \"label_tags_maximum_4\": \"Tag (Massimo 4)\",\n  \"label_attachments\": \"Allegati\",\n  \"label_type\": \"Tipo\",\n  \"label_thumbnail\": \"Miniatura\",\n  \"label_who_can_see_this_video\": \"Chi può vedere questo video?\",\n  \"label_content_posting_method\": \"Metodo di pubblicazione del contenuto\",\n  \"label_auto_add_music\": \"Aggiungi musica automaticamente\",\n  \"label_duet\": \"Duetto\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Commenti\",\n  \"label_disclose_video_content\": \"Divulga il contenuto del video\",\n  \"label_your_brand\": \"Il tuo brand\",\n  \"label_branded_content\": \"Contenuto sponsorizzato\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Media\",\n  \"label_search_subreddit\": \"Cerca Subreddit\",\n  \"label_delay\": \"Ritardo\",\n  \"label_post_type\": \"Tipo di post\",\n  \"label_collaborators\": \"Collaboratori (max 3) - gli account non possono essere privati\",\n  \"label_community\": \"Community\",\n  \"label_search_community\": \"Cerca nella community\",\n  \"label_channel\": \"Canale\",\n  \"label_search_channel\": \"Cerca canale\",\n  \"label_select_channel\": \"Seleziona canale\",\n  \"label_new_password\": \"Nuova password\",\n  \"label_repeat_password\": \"Ripeti password\",\n  \"label_platform\": \"Piattaforma\",\n  \"label_price_per_post\": \"Prezzo per post\",\n  \"label_integrations\": \"Integrazioni\",\n  \"label_code\": \"Codice\",\n  \"label_should_sync_last_post\": \"Dobbiamo sincronizzare l'ultimo post attuale?\",\n  \"label_when_post\": \"Quando dobbiamo pubblicarlo?\",\n  \"label_autogenerate_content\": \"Genera automaticamente il contenuto\",\n  \"label_generate_picture\": \"Generare un'immagine?\",\n  \"label_company\": \"Azienda\",\n  \"label_tag_color\": \"Colore etichetta\",\n  \"label_select_board\": \"Seleziona bacheca\",\n  \"label_select_organization\": \"Seleziona organizzazione\",\n  \"label_auto_add_signature\": \"Aggiungere automaticamente la firma?\",\n  \"enable_color_picker\": \"Abilita selettore colore\",\n  \"cancel_the_color_picker\": \"Annulla il selettore colore\",\n  \"no_content_yet\": \"Nessun contenuto ancora\",\n  \"write_your_reply\": \"Scrivi il tuo post...\",\n  \"add_a_tag\": \"Aggiungi un'etichetta\",\n  \"add_to_calendar\": \"Aggiungi al calendario\",\n  \"select_channels_from_circles\": \"Seleziona i canali dai cerchi sopra\",\n  \"not_matching_order\": \"Ordine non corrispondente\",\n  \"submit_for_order\": \"Invia per l'ordine\",\n  \"schedule\": \"Programma\",\n  \"update\": \"Aggiorna\",\n  \"attachments\": \"Allegati\",\n  \"tags\": \"Tag\",\n  \"public_to_everyone\": \"Pubblico per tutti\",\n  \"mutual_follow_friends\": \"Amici con follow reciproco\",\n  \"follower_of_creator\": \"Follower del creatore\",\n  \"self_only\": \"Solo io\",\n  \"post_content_directly_to_tiktok\": \"Pubblica contenuto direttamente su TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Carica contenuto su TikTok senza pubblicarlo\",\n  \"choose_upload_without_posting_description\": \"Scegli 'carica senza pubblicare' se vuoi rivedere e modificare il tuo contenuto nell'app di TikTok prima di pubblicarlo. Questo ti dà accesso agli strumenti di modifica integrati di TikTok e ti permette di fare le ultime modifiche prima della pubblicazione.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Mi verrà addebitato qualcosa da Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Per confermare le informazioni della carta di credito, Postiz tratterrà 2$ e li rilascerà immediatamente. Puoi annullare l'abbonamento in qualsiasi momento dalle impostazioni senza parlare con nessuno.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Posso fidarmi di Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz è orgogliosamente open-source! Crediamo in una cultura etica e trasparente, il che significa che Postiz vivrà per sempre. Puoi consultare tutto il codice o usarlo per progetti personali. Per visualizzare il repository open-source, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">clicca qui</a>.\",\n  \"faq_what_are_channels\": \"Cosa sono i canali?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz ti permette di programmare i tuoi post su diversi canali.\\nUn canale è una piattaforma di pubblicazione dove puoi programmare i tuoi post.\\nAd esempio, puoi programmare i tuoi post su X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads e Pinterest.\",\n  \"faq_what_are_team_members\": \"Chi sono i membri del team?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Se hai un team con più membri, puoi invitarli nel tuo workspace per collaborare ai tuoi post e aggiungere i loro canali personali\",\n  \"faq_what_is_ai_auto_complete\": \"Cos'è il completamento automatico AI?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Automatizziamo ChatGPT per aiutarti a scrivere post social e articoli.\",\n  \"enter_email\": \"Inserisci email\",\n  \"are_you_sure\": \"Sei sicuro?\",\n  \"no_cancel\": \"No, annulla!\",\n  \"are_you_sure_you_want_to_delete\": \"Sei sicuro di voler eliminare {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Sei sicuro di voler eliminare l'immagine?\",\n  \"are_you_sure_you_want_to_logout\": \"Sei sicuro di voler uscire?\",\n  \"yes_logout\": \"Sì, esci\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Sei sicuro di voler eliminare questo slot?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Sei sicuro di voler eliminare questo Subreddit?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Sei sicuro di voler chiudere la finestra?\",\n  \"yes_close\": \"Sì, chiudi\",\n  \"link_copied_to_clipboard\": \"Link copiato negli appunti\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Sei sicuro di voler chiudere questa finestra? (tutti i dati andranno persi)\",\n  \"yes_close_it\": \"Sì, chiudila!\",\n  \"uploading_pictures\": \"Caricamento immagini...\",\n  \"agent_starting\": \"Avvio agente\",\n  \"researching_your_content\": \"Analisi del tuo contenuto...\",\n  \"understanding_the_category\": \"Comprensione della categoria...\",\n  \"finding_the_topic\": \"Ricerca dell'argomento...\",\n  \"finding_popular_posts_to_match_with\": \"Ricerca di post popolari da abbinare...\",\n  \"generating_hook\": \"Generazione hook...\",\n  \"generating_content\": \"Generazione contenuto...\",\n  \"generating_pictures\": \"Generazione immagini...\",\n  \"finding_time_to_post\": \"Ricerca del momento migliore per pubblicare...\",\n  \"write_anything\": \"Scrivi qualsiasi cosa\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Puoi scrivere tutto quello che vuoi e anche aggiungere link, faremo noi la ricerca per te...\",\n  \"output_format\": \"Formato di output\",\n  \"add_pictures\": \"Aggiungere immagini?\",\n  \"7_days\": \"7 giorni\",\n  \"30_days\": \"30 giorni\",\n  \"90_days\": \"90 giorni\",\n  \"start_7_days_free_trial\": \"Inizia la prova gratuita di 7 giorni\",\n  \"change_language\": \"Cambia lingua\",\n  \"that_a_wrap\": \"È tutto!\\n\\nSe ti è piaciuto questo thread:\\n\\n1. Seguimi su @{{username}} per altri contenuti come questo\\n2. Ritwitta il tweet qui sotto per condividere questo thread con il tuo pubblico\\n\",\n  \"post_as_images_carousel\": \"Pubblica come carosello di immagini\",\n  \"save_set\": \"Salva set\",\n  \"separate_post\": \"Separa il post in più post\",\n  \"label_who_can_reply_to_this_post\": \"Chi può rispondere a questo post?\",\n  \"delete_integration\": \"Elimina integrazione\",\n  \"start_writing_your_post\": \"Inizia a scrivere il tuo post per un'anteprima\",\n  \"billing_join_over\": \"Unisciti a oltre\",\n  \"billing_entrepreneurs_count\": \"Oltre 20.000 imprenditori\",\n  \"billing_who_use\": \"che usano\",\n  \"billing_postiz_grow_social\": \"Postiz per far crescere la loro presenza sui social\",\n  \"billing_no_risk_trial\": \"Prova gratuita senza rischi al 100%\",\n  \"billing_pay_nothing_7_days\": \"Non paghi NULLA per i primi 7 giorni\",\n  \"billing_cancel_anytime\": \"Annulla in qualsiasi momento, dalle impostazioni\",\n  \"billing_choose_plan\": \"Scegli un piano\",\n  \"billing_monthly\": \"Mensile\",\n  \"billing_yearly\": \"Annuale\",\n  \"billing_20_percent_off\": \"20% di sconto\",\n  \"billing_features\": \"Funzionalità\",\n  \"billing_channel\": \"canale\",\n  \"billing_channels\": \"canali\",\n  \"billing_unlimited\": \"Illimitato\",\n  \"billing_posts_per_month\": \"post al mese\",\n  \"billing_unlimited_team_members\": \"Membri del team illimitati\",\n  \"billing_ai_auto_complete\": \"Completamento automatico AI\",\n  \"billing_ai_copilots\": \"Copiloti AI\",\n  \"billing_ai_autocomplete\": \"Completamento automatico AI\",\n  \"billing_advanced_picture_editor\": \"Editor di immagini avanzato\",\n  \"billing_ai_images_per_month\": \"Immagini AI al mese\",\n  \"billing_ai_videos_per_month\": \"Video AI al mese\",\n  \"billing_billing_address\": \"Indirizzo di fatturazione\",\n  \"billing_payment\": \"Pagamento\",\n  \"billing_powered_by_stripe\": \"Pagamenti sicuri elaborati da\",\n  \"billing_your_7_day_trial_is\": \"La tua prova di 7 giorni è\",\n  \"billing_100_percent_free\": \"100% gratuita\",\n  \"billing_ending\": \"in scadenza\",\n  \"billing_cancel_anytime_short\": \"Annulla in qualsiasi momento.\",\n  \"billing_pay_0_start_trial\": \"Paga 0€ oggi - Inizia la tua prova gratuita!\",\n  \"billing_pay_now\": \"Paga ora\",\n  \"billing_per_month\": \"/ mese\",\n  \"billing_per_year\": \"/ anno\",\n  \"billing_order_summary\": \"Riepilogo ordine\",\n  \"billing_applied\": \"Applicato\",\n  \"billing_due_today\": \"Dovuto oggi\",\n  \"billing_then\": \"Poi\",\n  \"billing_on\": \"il\",\n  \"billing_discount_applied\": \"applicato\",\n  \"billing_remove\": \"Rimuovi\",\n  \"billing_coupon_expires\": \"Il coupon scade il\",\n  \"billing_invalid_coupon\": \"Codice coupon non valido\",\n  \"billing_coupon_applied\": \"Coupon applicato con successo!\",\n  \"billing_coupon_removed\": \"Coupon rimosso\",\n  \"billing_error_removing_coupon\": \"Errore durante la rimozione del coupon\",\n  \"billing_have_discount_coupon\": \"Hai un coupon sconto?\",\n  \"billing_discount_coupon\": \"Coupon sconto\",\n  \"billing_cancel\": \"Annulla\",\n  \"billing_enter_coupon_code\": \"Inserisci il codice coupon\",\n  \"billing_applying\": \"Applicazione in corso...\",\n  \"billing_apply\": \"Applica\",\n  \"billing_subscription\": \"Abbonamento\",\n  \"billing_cancel_notice\": \"Annulla in qualsiasi momento dalle impostazioni senza parlare con nessuno e non ti verrà mai addebitato nulla.\",\n  \"select_channels\": \"Seleziona canali\",\n  \"start_a_new_chat\": \"Inizia una nuova chat\",\n  \"your_assistant\": \"Il tuo assistente\",\n  \"agent_welcome_message\": \"Ciao, sono il tuo agente Postiz 🙌🏻.\\n\\nPosso programmare uno o più post su più canali e generare immagini e video.\\n\\nPuoi selezionare i canali che vuoi usare dal menu a sinistra.\\n\\nPuoi vedere le tue conversazioni precedenti dal menu a destra.\\n\\nPuoi anche usarmi come server MCP, vai su Impostazioni >> API pubblica\",\n  \"last_github_trending\": \"Ultime tendenze su Github\",\n  \"next_predicted_github_trending\": \"Prossime tendenze previste su GitHub\",\n  \"repository\": \"Repository\",\n  \"date\": \"Data\",\n  \"total_stars\": \"Stelle totali\",\n  \"total_forks\": \"Fork totali\",\n  \"continue_with\": \"Continua con\",\n  \"email_address\": \"Indirizzo email\",\n  \"email_already_exists\": \"L'email esiste già\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Modifica autopost\",\n  \"add_autopost_title\": \"Aggiungi autopost\",\n  \"webhook_deleted_successfully\": \"Webhook eliminato con successo\",\n  \"all_integrations\": \"Tutte le integrazioni\",\n  \"specific_integrations\": \"Integrazioni specifiche\",\n  \"post_on_next_available_slot\": \"Pubblica nel prossimo slot disponibile\",\n  \"post_immediately\": \"Pubblica immediatamente\",\n  \"could_not_use_rss_feed\": \"Impossibile utilizzare questo feed RSS\",\n  \"rss_valid\": \"RSS valido!\",\n  \"autopost_updated_successfully\": \"Autopost aggiornato con successo\",\n  \"autopost_added_successfully\": \"Autopost aggiunto con successo\",\n  \"write_your_post_placeholder\": \"Scrivi il tuo post...\",\n  \"select_or_upload_pictures_max_5\": \"Seleziona o carica immagini (massimo 5 alla volta).\",\n  \"you_can_drag_drop_pictures\": \"Puoi anche trascinare e rilasciare le immagini.\",\n  \"you_dont_have_any_media_yet\": \"Non hai ancora nessun media\",\n  \"media_library\": \"Libreria media\",\n  \"media_settings\": \"Impostazioni media\",\n  \"media_editor\": \"Editor media\",\n  \"close\": \"Chiudi\",\n  \"me\": \"Io\",\n  \"noname\": \"Senza nome\",\n  \"password_reset_link_expired\": \"Il link per il reset della password è scaduto. Per favore riprova.\",\n  \"invalid_api_key\": \"API key non valida\",\n  \"could_not_connect_to_platform\": \"Impossibile connettersi alla piattaforma\",\n  \"web3_provider\": \"Provider Web3\",\n  \"add_provider_title\": \"Aggiungi provider\",\n  \"profile_updated\": \"Profilo aggiornato\",\n  \"sets\": \"Set\",\n  \"invitation_link_sent\": \"Link di invito inviato\",\n  \"send_invitation_link\": \"Invia link di invito\",\n  \"copy_link\": \"Copia link\",\n  \"are_you_sure_remove_team_member\": \"Sei sicuro di voler rimuovere questo membro del team?\",\n  \"admin\": \"Admin\",\n  \"super_admin\": \"Super Admin\",\n  \"update_webhook\": \"Aggiorna webhook\",\n  \"add_webhook\": \"Aggiungi webhook\",\n  \"webhook_updated_successfully\": \"Webhook aggiornato con successo\",\n  \"webhook_added_successfully\": \"Webhook aggiunto con successo\",\n  \"webhook_sent\": \"Webhook inviato\",\n  \"today\": \"Oggi\",\n  \"channel_disconnected_click_to_reconnect\": \"Canale disconnesso, clicca per riconnettere.\",\n  \"channel_disabled_upgrade_plan\": \"Questo canale è disabilitato, aggiorna il tuo piano per abilitarlo.\",\n  \"channel_added\": \"Canale aggiunto\",\n  \"are_you_sure_disable_channel\": \"Sei sicuro di voler disabilitare questo canale?\",\n  \"disable_channel_title\": \"Disabilita Canale\",\n  \"channel_disabled\": \"Canale Disabilitato\",\n  \"are_you_sure_delete_channel\": \"Sei sicuro di voler eliminare questo canale?\",\n  \"delete_channel_title\": \"Elimina Canale\",\n  \"delete_posts_before_channel\": \"Devi eliminare tutti i post associati a questo canale prima di eliminarlo\",\n  \"channel_deleted\": \"Canale Eliminato\",\n  \"channel_enabled\": \"Canale Abilitato\",\n  \"time_table_slots\": \"Fasce orarie\",\n  \"channel_id_copied\": \"ID canale copiato negli appunti\",\n  \"settings_updated\": \"Impostazioni aggiornate\",\n  \"customer_updated\": \"Cliente aggiornato\",\n  \"custom_url\": \"URL personalizzato\",\n  \"picture\": \"Immagine\",\n  \"upgrade_required\": \"Devi eseguire l'upgrade per utilizzare questa funzione\",\n  \"move_to_billing\": \"Vai alla fatturazione\",\n  \"payment_required\": \"Pagamento richiesto\",\n  \"no_content\": \"nessun contenuto\",\n  \"select_customer_tooltip\": \"Seleziona cliente\",\n  \"customers\": \"Clienti\",\n  \"hour\": \"Ora\",\n  \"minutes\": \"Minuti\",\n  \"updated\": \"Aggiornato\",\n  \"change_bot_picture_title\": \"Cambia immagine del bot\",\n  \"select_customer_label\": \"Seleziona cliente\",\n  \"start_typing\": \"Inizia a digitare...\",\n  \"choose_set_or_continue\": \"Scegli un set o continua senza\",\n  \"continue_without_set\": \"Continua senza set\",\n  \"select_set\": \"Seleziona un set\",\n  \"channel_settings\": \"Impostazioni\",\n  \"post_needs_content_or_image\": \"Il tuo post deve contenere almeno un carattere o un'immagine.\",\n  \"please_fix_your_settings\": \"Per favore, correggi le tue impostazioni\",\n  \"shortlink_urls_question\": \"Vuoi accorciare gli URL? Ti permetterà di ottenere statistiche sui clic\",\n  \"yes_shortlink_it\": \"Sì, accorcialo!\",\n  \"added_successfully\": \"Aggiunto con successo\",\n  \"updated_successfully\": \"Aggiornato con successo\",\n  \"create_post_title\": \"Crea post\",\n  \"post_preview\": \"Anteprima post\",\n  \"check_circles_above\": \"Controlla i cerchi sopra\",\n  \"create_output\": \"Crea output\",\n  \"assistant_initial_message\": \"Ciao! Posso aiutarti a perfezionare i tuoi post sui social media.\",\n  \"no_longer_global_mode\": \"Non più in modalità globale\",\n  \"two_days\": \"Due giorni\",\n  \"three_days\": \"Tre giorni\",\n  \"four_days\": \"Quattro giorni\",\n  \"five_days\": \"Cinque giorni\",\n  \"six_days\": \"Sei giorni\",\n  \"two_weeks\": \"Due settimane\",\n  \"repeat_post_every_label\": \"Ripeti post ogni\",\n  \"add_new_tag\": \"Aggiungi nuovo tag\",\n  \"tag_name\": \"Nome\",\n  \"post_is_too_long\": \"il post è troppo lungo, per favore correggilo\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Il tuo post deve contenere almeno un carattere o un'immagine.\",\n  \"internal_edit\": \"Modifica interna\",\n  \"are_you_sure_go_back_to_global_mode\": \"Questa azione è irreversibile. Sei sicuro di voler tornare alla modalità globale?\",\n  \"yes_go_back_to_global_mode\": \"Sì, torna alla modalità globale\",\n  \"are_you_sure_delete_this_post\": \"Sei sicuro di voler eliminare questo post?\",\n  \"yes_delete_it\": \"Sì, eliminalo!\",\n  \"cant_edit_networks_when_creating_set\": \"Non puoi modificare le reti durante la creazione di un set\",\n  \"click_to_exit_global_editing\": \"Clicca su questo pulsante per uscire dalla modifica globale e personalizzare il post per questo canale\",\n  \"edit_content\": \"Modifica contenuto\",\n  \"editing_a_specific_network\": \"Modifica di una rete specifica\",\n  \"back_to_global\": \"Torna alla modalità globale\",\n  \"delete_post_tooltip\": \"Elimina post\",\n  \"drop_files_here_to_upload\": \"Trascina qui i tuoi file per caricarli\",\n  \"insert_emoji\": \"Inserisci emoji\",\n  \"write_something\": \"Scrivi qualcosa…\",\n  \"click_channel_to_add\": \"Clicca su un canale per aggiungerlo\",\n  \"connect_your_channels\": \"Collega i tuoi canali\",\n  \"connect_social_media_to_start\": \"Collega i tuoi account social per iniziare a programmare i post\",\n  \"connected_channels\": \"Canali collegati\",\n  \"continue\": \"Continua\",\n  \"continue_without_channels\": \"Continua senza canali\",\n  \"watch_tutorial\": \"Guarda il tutorial\",\n  \"watch_tutorial_title\": \"Scopri come usare Postiz\",\n  \"watch_tutorial_description\": \"Guarda questo breve video per imparare a sfruttare al meglio Postiz\",\n  \"back\": \"Indietro\",\n  \"get_started\": \"Inizia\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/ja/translation.json",
    "content": "{\n  \"calendar\": \"カレンダー\",\n  \"webhooks\": \"ウェブフック\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"ウェブフックは、Postizで何かが起こったときにHTTPリクエストを通じて通知を受け取る方法です。\",\n  \"name\": \"名前\",\n  \"url\": \"URL\",\n  \"edit\": \"編集\",\n  \"delete\": \"削除\",\n  \"add_a_webhook\": \"ウェブフックを追加\",\n  \"save\": \"保存\",\n  \"send_test\": \"テスト送信\",\n  \"select_role\": \"役割を選択\",\n  \"video_made_with_ai\": \"AIで作成された動画\",\n  \"please_add_at_least\": \"少なくとも20文字を追加してください\",\n  \"send_invitation_via_email\": \"メールで招待を送信しますか？\",\n  \"global_settings\": \"グローバル設定\",\n  \"copy_id\": \"チャンネルIDをコピー\",\n  \"team_members\": \"チームメンバー\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"アシスタントやチームメンバーを招待してアカウントを管理してもらいましょう\",\n  \"remove\": \"削除\",\n  \"add_another_member\": \"メンバーを追加\",\n  \"signatures\": \"署名\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"投稿で使用するための署名をアカウントに追加できます。\",\n  \"content\": \"内容\",\n  \"auto_add\": \"自動追加？\",\n  \"delay_comment\": \"遅延コメント\",\n  \"actions\": \"操作\",\n  \"use_signature\": \"署名を使用\",\n  \"add_a_signature\": \"署名を追加\",\n  \"no\": \"いいえ\",\n  \"yes\": \"はい\",\n  \"your_git_repository\": \"あなたのGitリポジトリ\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"アップデートと分析を受け取るためにGitHubリポジトリを接続してください\",\n  \"connected\": \"接続済み：\",\n  \"disconnect\": \"切断\",\n  \"connect_your_repository\": \"リポジトリを接続\",\n  \"cancel\": \"キャンセル\",\n  \"connect\": \"接続\",\n  \"public_api\": \"パブリックAPI\",\n  \"check_n8n\": \"Postiz用のカスタムN8Nノードをご覧ください。\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Postiz APIを使ってあなたのツールと連携しましょう。\",\n  \"read_how_to_use_it_over_the_documentation\": \"ドキュメントで使い方を確認してください。\",\n  \"reveal\": \"表示\",\n  \"copy_key\": \"キーをコピー\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"投稿をより迅速にスケジュールするために、Postiz MCPサーバーをクライアント（HTTPストリーミング）に接続しましょう！\",\n  \"share_with_a_client\": \"クライアントと共有\",\n  \"post\": \"投稿\",\n  \"comments\": \"コメント\",\n  \"user\": \"ユーザー\",\n  \"login_register_to_add_comments\": \"コメントを追加するにはログイン／登録してください\",\n  \"status\": \"ステータス：\",\n  \"there_are_not_plugs_matching_your_channels\": \"あなたのチャンネルに一致するプラグはありません\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"XまたはLinkedInまたはThreadsを追加する必要があります\",\n  \"go_to_the_calendar_to_add_channels\": \"チャンネルを追加するにはカレンダーに移動してください\",\n  \"channels\": \"チャンネル\",\n  \"activate\": \"有効化\",\n  \"this_channel_needs_to_be_refreshed\": \"このチャンネルは更新が必要です。\",\n  \"click_here_to_refresh\": \"ここをクリックして更新\",\n  \"can_t_show_analytics_yet\": \"まだ分析情報を表示できません\",\n  \"you_have_to_add_social_media_channels\": \"ソーシャルメディアチャンネルを追加する必要があります\",\n  \"supported\": \"対応：\",\n  \"step\": \"ステップ\",\n  \"skip_onboarding\": \"オンボーディングをスキップ\",\n  \"onboarding\": \"オンボーディング\",\n  \"next\": \"次へ\",\n  \"you_are_done_from_here_you_can\": \"完了しました。ここから次のことができます：\",\n  \"view_analytics\": \"分析情報を見る\",\n  \"schedule_a_new_post\": \"新しい投稿を予約する\",\n  \"to_sell_posts_you_would_have_to\": \"投稿を販売するには、次のことが必要です：\",\n  \"1_connect_at_least_one_channel\": \"1. 少なくとも1つのチャンネルを接続する\",\n  \"2_connect_you_bank_account\": \"2. 銀行口座を接続する\",\n  \"go_back_to_connect_channels\": \"チャンネル接続に戻る\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"販売者ページに移動して銀行口座を接続する\",\n  \"connect_channels\": \"チャンネルを接続\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"ソーシャルメディアや出版ウェブサイトのチャンネルを接続して、\\n後で投稿を予約しましょう\",\n  \"social\": \"ソーシャル\",\n  \"publishing_platforms\": \"出版プラットフォーム\",\n  \"no_channels\": \"まだチャンネルがありません\",\n  \"connect_your_accounts\": \"ソーシャルアカウントを連携して、スケジューリング、投稿、分析をすべて一か所で始めましょう。\",\n  \"notifications\": \"通知\",\n  \"no_notifications\": \"通知はありません\",\n  \"send_message\": \"メッセージを送信\",\n  \"mar_28\": \"3月28日\",\n  \"there_are_no_messages_yet\": \"まだメッセージはありません。\",\n  \"checkout_the_marketplace\": \"マーケットプレイスをチェック\",\n  \"go_to_marketplace\": \"マーケットプレイスへ行く\",\n  \"all_messages\": \"すべてのメッセージ\",\n  \"previous\": \"前へ\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"画像を選択またはアップロード（最大5枚まで同時に）\",\n  \"you_can_also_drag_drop_pictures\": \"画像をドラッグ＆ドロップすることもできます\",\n  \"you_don_t_have_any_assets_yet\": \"まだアセットがありません。\",\n  \"click_the_button_below_to_upload_one\": \"下のボタンをクリックしてアップロードしてください\",\n  \"click_the_button_below_to_upload_other\": \"下のボタンをクリックして複数アップロード\",\n  \"add_selected_media\": \"選択したメディアを追加\",\n  \"insert_media\": \"メディアを挿入\",\n  \"design_media\": \"メディアをデザイン\",\n  \"select\": \"選択\",\n  \"editor\": \"エディター\",\n  \"clear\": \"クリア\",\n  \"order_completed\": \"注文完了\",\n  \"the_order_has_been_completed\": \"注文が完了しました\",\n  \"post_has_been_published\": \"投稿が公開されました\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"新しいオファー\",\n  \"platform\": \"プラットフォーム\",\n  \"posts\": \"投稿\",\n  \"pay_accept_offer\": \"支払い＆オファーを承諾\",\n  \"accepted\": \"承諾済み\",\n  \"post_draft\": \"下書き投稿\",\n  \"revision_needed\": \"修正が必要\",\n  \"approve\": \"承認する\",\n  \"preview\": \"プレビュー\",\n  \"revision_requested\": \"修正依頼済み\",\n  \"accepted_1\": \"承認済み\",\n  \"cancelled_by_the_seller\": \"出品者によってキャンセルされました\",\n  \"please_select_your_country_where_your_business_is\": \"ビジネスを行っている国を選択してください。\",\n  \"select_country\": \"--国を選択--\",\n  \"connect_bank_account\": \"銀行口座を接続\",\n  \"seller_mode\": \"出品者モード\",\n  \"active\": \"有効\",\n  \"details\": \"詳細\",\n  \"audience_size\": \"オーディエンス規模\",\n  \"add_another_platform\": \"別のプラットフォームを追加\",\n  \"send_an_offer_for\": \"$でオファーを送信\",\n  \"complete_order_and_pay_early\": \"注文を完了して早期支払い\",\n  \"order_in_progress\": \"注文進行中\",\n  \"create_a_new_offer\": \"新しいオファーを作成\",\n  \"orders\": \"注文\",\n  \"price\": \"価格\",\n  \"state\": \"状態\",\n  \"showing\": \"表示中\",\n  \"to\": \"から\",\n  \"from\": \"まで\",\n  \"results\": \"結果\",\n  \"content_writer\": \"コンテンツライター\",\n  \"influencer\": \"インフルエンサー\",\n  \"request_service\": \"サービスを依頼する\",\n  \"the_marketplace_is_not_opened_yet\": \"マーケットプレイスはまだオープンしていません\",\n  \"check_again_soon\": \"またすぐにご確認ください！\",\n  \"filter\": \"フィルター\",\n  \"result\": \"結果\",\n  \"seller\": \"販売者\",\n  \"buyer\": \"購入者\",\n  \"discord_support\": \"Discordサポート\",\n  \"teams\": \"チーム\",\n  \"webhooks_1\": \"ウェブフック\",\n  \"auto_post\": \"自動投稿\",\n  \"logout_from\": \"ログアウト：\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Postizを利用している10,000人以上の起業家に参加しましょう\",\n  \"to_manage_all_your_social_media_channels\": \"すべてのソーシャルメディアチャンネルを管理するために\",\n  \"100_no_risk_trial\": \"100％リスクなしのトライアル\",\n  \"pay_nothing_for_the_first_7_days\": \"最初の7日間は無料\",\n  \"cancel_anytime_hassle_free\": \"いつでも設定からキャンセルできます\",\n  \"add_free_subscription\": \"-- 無料サブスクリプションを追加 --\",\n  \"currently_impersonating\": \"現在なりすまし中\",\n  \"user_1\": \"ユーザー：\",\n  \"drag_n_drop_some_files_here\": \"ここにファイルをドラッグ＆ドロップしてください\",\n  \"add_time_slot\": \"時間枠を追加\",\n  \"add_slot\": \"スロットを追加\",\n  \"cancel_publication\": \"公開をキャンセル\",\n  \"statistics\": \"統計\",\n  \"loading\": \"読み込み中\",\n  \"short_link\": \"短縮リンク\",\n  \"original_link\": \"元のリンク\",\n  \"clicks\": \"クリック数\",\n  \"selected_customer\": \"選択された顧客\",\n  \"customer\": \"顧客：\",\n  \"repeat_post_every\": \"投稿を繰り返す間隔...\",\n  \"use_this_media\": \"このメディアを使用\",\n  \"create_new_post\": \"投稿を作成\",\n  \"update_post\": \"既存の投稿を更新\",\n  \"merge_comments_into_one_post\": \"コメントを1つの投稿にまとめる\",\n  \"accounts_that_will_engage\": \"エンゲージするアカウント：\",\n  \"day\": \"日\",\n  \"week\": \"週\",\n  \"month\": \"月\",\n  \"remove_from_customer\": \"顧客から削除\",\n  \"show_more\": \"＋もっと表示\",\n  \"show_less\": \"－表示を減らす\",\n  \"upload\": \"アップロード\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"チャンネルを追加\",\n  \"add_platform\": \"プラットフォームを追加\",\n  \"articles\": \"記事\",\n  \"add_comment\": \"コメントを追加\",\n  \"add_post\": \"スレッドに投稿を追加\",\n  \"add_comment_or_post\": \"コメント／投稿を追加\",\n  \"you_are_in_global_editing_mode\": \"グローバル編集モードになっています\",\n  \"the_post_should_be_at_least_6_characters_long\": \"投稿は6文字以上である必要があります\",\n  \"are_you_sure_you_want_to_delete_post\": \"この投稿を本当に削除しますか？\",\n  \"post_deleted_successfully\": \"投稿が正常に削除されました\",\n  \"delete_post\": \"投稿を削除\",\n  \"save_as_draft\": \"下書きとして保存\",\n  \"post_now\": \"今すぐ投稿\",\n  \"please_add\": \"追加してください\",\n  \"to_your_telegram_group_channel_and_click_here\": \"あなたのTelegramグループ／チャンネルに追加し、ここをクリックしてください：\",\n  \"connect_telegram\": \"Telegramを接続\",\n  \"please_add_the_following_command_in_your_chat\": \"チャットに次のコマンドを追加してください：\",\n  \"copy\": \"コピー\",\n  \"settings\": \"設定\",\n  \"integrations\": \"連携\",\n  \"add_integration\": \"連携を追加\",\n  \"you_are_now_editing_only\": \"現在、次のみを編集しています\",\n  \"tag_a_company\": \"会社をタグ付け\",\n  \"video_length_is_invalid_must_be_up_to\": \"動画の長さが無効です。最大\",\n  \"seconds\": \"秒までです\",\n  \"this_feature_available_only_for_photos\": \"この機能は写真のみで利用可能です。デフォルトの音楽が追加され、後で変更できます。\",\n  \"allow_user_to\": \"ユーザーに許可する：\",\n  \"your_video_will_be_labeled_promotional\": \"あなたの動画には「プロモーションコンテンツ」とラベルが付きます。\",\n  \"this_cannot_be_changed_once_posted\": \"一度投稿すると、これは変更できません。\",\n  \"turn_on_to_disclose_video_promotes\": \"この動画が何らかの価値と引き換えに商品やサービスを宣伝していることを開示するにはオンにしてください。動画は自分自身、第三者、またはその両方を宣伝している場合があります。\",\n  \"you_are_promoting_yourself\": \"あなた自身またはあなたのブランドを宣伝しています。\",\n  \"this_video_will_be_classified_brand_organic\": \"この動画はブランドオーガニックとして分類されます。\",\n  \"you_are_promoting_another_brand\": \"他のブランドまたは第三者を宣伝しています。\",\n  \"this_video_will_be_classified_branded_content\": \"この動画はブランデッドコンテンツとして分類されます。\",\n  \"by_posting_you_agree_to_tiktoks\": \"投稿することで、TikTokの規約に同意したことになります\",\n  \"music_usage_confirmation\": \"音楽使用確認\",\n  \"branded_content_policy\": \"ブランドコンテンツポリシー\",\n  \"select_1\": \"--選択--\",\n  \"select_flair\": \"--フレアを選択--\",\n  \"link\": \"リンク\",\n  \"add_subreddit\": \"サブレディットを追加\",\n  \"please_add_at_least_one_subreddit\": \"少なくとも1つのサブレディットを追加してください\",\n  \"add_community\": \"コミュニティを追加\",\n  \"select_post_type\": \"投稿タイプを選択...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"あなたのLinkedInページに接続されているビジネスが見つかりませんでした。\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"このダイアログを閉じて、新しいページを作成し、再度新しいチャンネルを追加してください。\",\n  \"select_linkedin_page\": \"LinkedInページを選択：\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"選択したページに接続されているビジネスが見つかりませんでした。\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"すべてのページとすべてのビジネスを接続することをおすすめします。\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"このダイアログを閉じて、連携を削除し、再度新しいチャンネルを追加してください。\",\n  \"select_instagram_account\": \"Instagramアカウントを選択：\",\n  \"select_page\": \"ページを選択：\",\n  \"generate_image_with_ai\": \"AIで画像を生成\",\n  \"reconnect_channel\": \"チャンネルを再接続\",\n  \"update_credentials\": \"認証情報を更新\",\n  \"additional_settings\": \"追加設定\",\n  \"change_bot\": \"ボットを変更\",\n  \"move_add_to_customer\": \"顧客に移動／追加\",\n  \"edit_time_slots\": \"時間枠を編集\",\n  \"enable_channel\": \"チャンネルを有効にする\",\n  \"disable_channel\": \"チャンネルを無効にする\",\n  \"add\": \"追加\",\n  \"short_post\": \"短い投稿\",\n  \"long_post\": \"長い投稿\",\n  \"a_thread_with_short_posts\": \"短い投稿のスレッド\",\n  \"a_thread_with_long_posts\": \"長い投稿のスレッド\",\n  \"personal_voice_i_am_happy_to_announce\": \"個人の声（「お知らせできて嬉しいです」）\",\n  \"company_voice_we_are_happy_to_announce\": \"会社の声（「お知らせできて嬉しいです」）\",\n  \"generate\": \"生成\",\n  \"generate_posts\": \"投稿を生成\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"SOL（$199）で生涯PROアカウントを購入できます。この購入には返金がないことをご承知おきください。\",\n  \"purchase_now\": \"今すぐ購入\",\n  \"pay_today\": \"今日支払う\",\n  \"we_are_sorry_to_see_you_go\": \"ご利用いただけなくなり残念です :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"どのように改善できたか、簡単に教えていただけますか？\",\n  \"cancel_subscription\": \"サブスクリプションを解約\",\n  \"plans\": \"プラン\",\n  \"monthly\": \"月額\",\n  \"yearly\": \"年額\",\n  \"reactivate_subscription\": \"サブスクリプションを再開\",\n  \"update_payment_method_invoices_history\": \"支払い方法の更新 / 請求履歴\",\n  \"cancel_subscription_1\": \"サブスクリプションを解約\",\n  \"your_subscription_will_be_canceled_at\": \"サブスクリプションは次の日付で解約されます:\",\n  \"you_will_never_be_charged_again\": \"今後、料金が請求されることはありません\",\n  \"current_package\": \"現在のパッケージ：\",\n  \"next_package\": \"次のパッケージ：\",\n  \"claim\": \"請求\",\n  \"frequently_asked_questions\": \"よくある質問\",\n  \"autopost\": \"自動投稿\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"自動投稿は、RSSの新しいアイテムを自動的にソーシャルメディアに投稿できます\",\n  \"title\": \"タイトル\",\n  \"add_an_autopost\": \"自動投稿を追加\",\n  \"post_content\": \"投稿内容\",\n  \"sign_up\": \"サインアップ\",\n  \"or\": \"または\",\n  \"by_registering_you_agree_to_our\": \"登録することで、あなたは当社の\",\n  \"and\": \"および\",\n  \"terms_of_service\": \"利用規約\",\n  \"privacy_policy\": \"プライバシーポリシー\",\n  \"create_account\": \"アカウントを作成\",\n  \"already_have_an_account\": \"すでにアカウントをお持ちですか？\",\n  \"sign_in\": \"サインイン\",\n  \"sign_in_1\": \"サインイン\",\n  \"don_t_have_an_account\": \"アカウントをお持ちでないですか？\",\n  \"forgot_password\": \"パスワードをお忘れですか\",\n  \"forgot_password_1\": \"パスワードをお忘れですか\",\n  \"send_password_reset_email\": \"パスワードリセットメールを送信\",\n  \"go_back_to_login\": \"ログイン画面に戻る\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"パスワードをリセットするためのリンクを記載したメールを送信しました。\",\n  \"change_password\": \"パスワードを変更\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"パスワードのリセットが完了しました。新しいパスワードでログインできます。\",\n  \"click_here_to_go_back_to_login\": \"ログイン画面に戻るにはこちらをクリックしてください\",\n  \"activate_your_account\": \"アカウントを有効化\",\n  \"thank_you_for_registering\": \"ご登録ありがとうございます！\",\n  \"please_check_your_email_to_activate_your_account\": \"アカウントを有効化するためにメールをご確認ください。\",\n  \"sign_in_with\": \"でサインイン\",\n  \"continue_with_google\": \"Googleで続行\",\n  \"sign_in_with_github\": \"GitHubでサインイン\",\n  \"continue_with_farcaster\": \"Farcasterで続行\",\n  \"continue_with_your_wallet\": \"ウォレットで続行\",\n  \"stars_per_day\": \"1日あたりのスター数\",\n  \"media\": \"メディア\",\n  \"check_launch\": \"ローンチを確認\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"設定からGitHubリポジトリを読み込むと分析が表示されます\",\n  \"stars\": \"スター\",\n  \"processing_stars\": \"スターを処理中...\",\n  \"forks\": \"フォーク\",\n  \"registration_is_disabled\": \"登録は無効になっています\",\n  \"login_instead\": \"代わりにログイン\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"会話を選択してチャットを始めましょう。\",\n  \"adding_channel_redirecting_you\": \"チャンネルを追加中、リダイレクトしています\",\n  \"could_not_add_provider\": \"プロバイダーを追加できませんでした。\",\n  \"you_are_being_redirected_back\": \"元のページにリダイレクトしています\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"問題が発生しています。ページをリフレッシュしてみてください。\",\n  \"post_not_found\": \"投稿が見つかりません\",\n  \"publication_date\": \"公開日：\",\n  \"analytics\": \"アナリティクス\",\n  \"launches\": \"ローンチ\",\n  \"plugs\": \"プラグ\",\n  \"billing\": \"請求\",\n  \"affiliate\": \"アフィリエイト\",\n  \"monday\": \"月曜日\",\n  \"tuesday\": \"火曜日\",\n  \"wednesday\": \"水曜日\",\n  \"thursday\": \"木曜日\",\n  \"friday\": \"金曜日\",\n  \"saturday\": \"土曜日\",\n  \"sunday\": \"日曜日\",\n  \"can_t_change_date_remove_post_from_publication\": \"日付を変更できません。投稿を公開から削除してください。\",\n  \"predicted_github_trending_change\": \"GitHubトレンド予測の変化\",\n  \"duplicate_post\": \"重複した投稿\",\n  \"preview_post\": \"投稿をプレビュー\",\n  \"post_statistics\": \"投稿の統計\",\n  \"draft\": \"下書き\",\n  \"week_number\": \"第{{number}}週\",\n  \"top_title_edit_webhook\": \"Webhookを編集\",\n  \"top_title_add_webhook\": \"Webhookを追加\",\n  \"top_title_oh_no\": \"おっと\",\n  \"top_title_auto_plug\": \"自動プラグ：{{title}}\",\n  \"top_title_edit_autopost\": \"自動投稿を編集\",\n  \"top_title_add_autopost\": \"自動投稿を追加\",\n  \"top_title_send_a_new_offer\": \"新しいオファーを送信\",\n  \"top_title_media_library\": \"メディアライブラリ\",\n  \"top_title_add_signature\": \"署名を追加\",\n  \"top_title_send_a_message_to\": \"{{name}}にメッセージを送信\",\n  \"top_title_configure_provider\": \"プロバイダーを設定\",\n  \"top_title_add_member\": \"メンバーを追加\",\n  \"top_title_change_bot_picture\": \"ボットの画像を変更\",\n  \"top_title_create_a_new_tag\": \"新しいタグを作成\",\n  \"top_title_select_company\": \"会社を選択\",\n  \"top_title_additional_settings\": \"追加設定\",\n  \"top_title_time_table_slots\": \"タイムテーブル枠\",\n  \"top_title_design_media\": \"メディアをデザイン\",\n  \"top_title_edit_post\": \"投稿を編集\",\n  \"top_title_create_post\": \"投稿を作成\",\n  \"top_title_move__add_to_customer\": \"顧客に移動／追加\",\n  \"top_title_add_api_key_for\": \"{{name}}のAPIキーを追加\",\n  \"top_title_instance_url\": \"インスタンスURL\",\n  \"top_title_custom_url\": \"カスタムURL\",\n  \"top_title_add_channel\": \"チャンネルを追加\",\n  \"top_title_add_telegram\": \"Telegramを追加\",\n  \"top_title_add_wrapcast\": \"Wrapcastを追加\",\n  \"top_title_comments_for\": \"{{date}}のコメント\",\n  \"top_title_edit_signature\": \"署名を編集\",\n  \"label_name\": \"名前\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"タイトル\",\n  \"label_subtitle\": \"サブタイトル\",\n  \"label_email\": \"メールアドレス\",\n  \"label_full_name\": \"氏名\",\n  \"label_password\": \"パスワード\",\n  \"label_confirm_password\": \"パスワードの確認\",\n  \"label_api_key\": \"APIキー\",\n  \"label_instance_url\": \"インスタンスURL\",\n  \"label_custom_url\": \"カスタムURL\",\n  \"label_feedback\": \"フィードバック\",\n  \"label_bio\": \"自己紹介\",\n  \"label_role\": \"役割\",\n  \"label_country\": \"国\",\n  \"label_audience_size\": \"全プラットフォームでのオーディエンス数\",\n  \"label_pick_time\": \"時間を選択\",\n  \"label_nickname\": \"ニックネーム\",\n  \"label_write_anything\": \"何でも書いてください\",\n  \"label_output_format\": \"出力形式\",\n  \"label_add_pictures\": \"画像を追加しますか？\",\n  \"label_hour\": \"時\",\n  \"label_minutes\": \"分\",\n  \"label_select_publication\": \"公開先を選択\",\n  \"label_canonical_link\": \"正規リンク\",\n  \"label_cover_picture\": \"カバー画像\",\n  \"label_tags\": \"タグ\",\n  \"label_topics\": \"トピック\",\n  \"label_tags_maximum_4\": \"タグ（最大4つ）\",\n  \"label_attachments\": \"添付ファイル\",\n  \"label_type\": \"タイプ\",\n  \"label_thumbnail\": \"サムネイル\",\n  \"label_who_can_see_this_video\": \"この動画を見られる人\",\n  \"label_content_posting_method\": \"コンテンツ投稿方法\",\n  \"label_auto_add_music\": \"自動で音楽を追加\",\n  \"label_duet\": \"デュエット\",\n  \"label_stitch\": \"ステッチ\",\n  \"label_comments\": \"コメント\",\n  \"label_disclose_video_content\": \"動画内容を公開する\",\n  \"label_your_brand\": \"あなたのブランド\",\n  \"label_branded_content\": \"ブランドコンテンツ\",\n  \"label_subreddit\": \"サブレディット\",\n  \"label_flair\": \"フレア\",\n  \"label_media\": \"メディア\",\n  \"label_search_subreddit\": \"サブレディットを検索\",\n  \"label_delay\": \"遅延\",\n  \"label_post_type\": \"投稿タイプ\",\n  \"label_collaborators\": \"共同作成者（最大3名）- アカウントは非公開にできません\",\n  \"label_community\": \"コミュニティ\",\n  \"label_search_community\": \"コミュニティを検索\",\n  \"label_channel\": \"チャンネル\",\n  \"label_search_channel\": \"チャンネルを検索\",\n  \"label_select_channel\": \"チャンネルを選択\",\n  \"label_new_password\": \"新しいパスワード\",\n  \"label_repeat_password\": \"パスワードを再入力\",\n  \"label_platform\": \"プラットフォーム\",\n  \"label_price_per_post\": \"投稿ごとの価格\",\n  \"label_integrations\": \"連携\",\n  \"label_code\": \"コード\",\n  \"label_should_sync_last_post\": \"現在の最新投稿を同期しますか？\",\n  \"label_when_post\": \"いつ投稿しますか？\",\n  \"label_autogenerate_content\": \"コンテンツを自動生成\",\n  \"label_generate_picture\": \"画像を生成しますか？\",\n  \"label_company\": \"会社\",\n  \"label_tag_color\": \"タグの色\",\n  \"label_select_board\": \"ボードを選択\",\n  \"label_select_organization\": \"組織を選択\",\n  \"label_auto_add_signature\": \"署名を自動追加しますか？\",\n  \"enable_color_picker\": \"カラーピッカーを有効にする\",\n  \"cancel_the_color_picker\": \"カラーピッカーをキャンセル\",\n  \"no_content_yet\": \"まだコンテンツがありません\",\n  \"write_your_reply\": \"投稿内容を入力してください...\",\n  \"add_a_tag\": \"タグを追加\",\n  \"add_to_calendar\": \"カレンダーに追加\",\n  \"select_channels_from_circles\": \"上のサークルからチャンネルを選択してください\",\n  \"not_matching_order\": \"順序が一致しません\",\n  \"submit_for_order\": \"注文を送信\",\n  \"schedule\": \"スケジュール\",\n  \"update\": \"更新\",\n  \"attachments\": \"添付ファイル\",\n  \"tags\": \"タグ\",\n  \"public_to_everyone\": \"全員に公開\",\n  \"mutual_follow_friends\": \"相互フォローの友達\",\n  \"follower_of_creator\": \"クリエイターのフォロワー\",\n  \"self_only\": \"自分のみ\",\n  \"post_content_directly_to_tiktok\": \"コンテンツを直接TikTokに投稿する\",\n  \"upload_content_to_tiktok_without_posting\": \"投稿せずにコンテンツをTikTokにアップロードする\",\n  \"choose_upload_without_posting_description\": \"公開前にTikTokアプリ内でコンテンツを確認・編集したい場合は「投稿せずにアップロード」を選択してください。これにより、TikTokの編集ツールを利用でき、投稿前に最終調整が可能です。\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Postizから料金が請求されますか？\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"クレジットカード情報を確認するために、Postizは2ドルを一時的に保持し、すぐに返金します。設定からいつでも人と話すことなくサブスクリプションをキャンセルできます。\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Postizは信頼できますか？\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postizは誇りを持ってオープンソースです！私たちは倫理的で透明性のある文化を信じており、Postizは永遠に存続します。全てのコードを確認したり、個人プロジェクトで利用したりできます。オープンソースリポジトリを見るには、<a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">こちらをクリック</a>してください。\",\n  \"faq_what_are_channels\": \"チャンネルとは何ですか？\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postizでは、さまざまなチャンネル間で投稿のスケジューリングが可能です。\\nチャンネルとは、投稿をスケジュールできる配信プラットフォームのことです。\\n例えば、X、Facebook、Instagram、TikTok、YouTube、Reddit、Linkedin、Dribbble、Threads、Pinterestなどで投稿をスケジュールできます。\",\n  \"faq_what_are_team_members\": \"チームメンバーとは何ですか？\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"複数のメンバーがいるチームの場合、ワークスペースに招待して投稿の共同作業や個人チャンネルの追加ができます\",\n  \"faq_what_is_ai_auto_complete\": \"AI自動補完とは何ですか？\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"ChatGPTを自動化して、SNS投稿や記事の作成をサポートします。\",\n  \"enter_email\": \"メールアドレスを入力\",\n  \"are_you_sure\": \"本当に宜しいですか？\",\n  \"no_cancel\": \"いいえ、キャンセルします\",\n  \"are_you_sure_you_want_to_delete\": \"{{name}} を削除してもよろしいですか？\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"この画像を削除してもよろしいですか？\",\n  \"are_you_sure_you_want_to_logout\": \"本当にログアウトしますか？\",\n  \"yes_logout\": \"はい、ログアウトします\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"このスロットを削除してもよろしいですか？\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"このSubredditを削除してもよろしいですか？\",\n  \"are_you_sure_you_want_to_close_the_window\": \"ウィンドウを閉じてもよろしいですか？\",\n  \"yes_close\": \"はい、閉じます\",\n  \"link_copied_to_clipboard\": \"リンクがクリップボードにコピーされました\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"このモーダルを閉じてもよろしいですか？（すべてのデータが失われます）\",\n  \"yes_close_it\": \"はい、閉じます！\",\n  \"uploading_pictures\": \"画像をアップロード中...\",\n  \"agent_starting\": \"エージェントを起動中\",\n  \"researching_your_content\": \"コンテンツを調査中...\",\n  \"understanding_the_category\": \"カテゴリを理解中...\",\n  \"finding_the_topic\": \"トピックを検索中...\",\n  \"finding_popular_posts_to_match_with\": \"マッチする人気投稿を検索中...\",\n  \"generating_hook\": \"フックを生成中...\",\n  \"generating_content\": \"コンテンツを生成中...\",\n  \"generating_pictures\": \"画像を生成中...\",\n  \"finding_time_to_post\": \"投稿する時間を検索中...\",\n  \"write_anything\": \"何でも書いてください\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"ご自由に何でも書くことができ、リンクも追加できます。リサーチは私たちが行います…\",\n  \"output_format\": \"出力形式\",\n  \"add_pictures\": \"画像を追加しますか？\",\n  \"7_days\": \"7日間\",\n  \"30_days\": \"30日間\",\n  \"90_days\": \"90日間\",\n  \"start_7_days_free_trial\": \"7日間の無料トライアルを開始\",\n  \"change_language\": \"言語を変更\",\n  \"that_a_wrap\": \"以上で終了です！\\n\\nこのスレッドを楽しんでいただけたなら：\\n\\n1. @{{username}} をフォローして、さらに多くの投稿をご覧ください\\n2. 下のツイートをリツイートして、このスレッドをあなたのフォロワーと共有してください\\n\",\n  \"post_as_images_carousel\": \"画像カルーセルとして投稿\",\n  \"save_set\": \"セットを保存\",\n  \"separate_post\": \"投稿を複数に分割\",\n  \"label_who_can_reply_to_this_post\": \"この投稿に返信できる人\",\n  \"delete_integration\": \"連携を削除\",\n  \"start_writing_your_post\": \"プレビュー用に投稿を書き始めてください\",\n  \"billing_join_over\": \"すでに参加している\",\n  \"billing_entrepreneurs_count\": \"20,000人以上の起業家\",\n  \"billing_who_use\": \"が利用している\",\n  \"billing_postiz_grow_social\": \"Postizでソーシャルプレゼンスを成長させる\",\n  \"billing_no_risk_trial\": \"100%リスクなしの無料トライアル\",\n  \"billing_pay_nothing_7_days\": \"最初の7日間は一切料金不要\",\n  \"billing_cancel_anytime\": \"いつでも設定からキャンセルできます\",\n  \"billing_choose_plan\": \"プランを選択\",\n  \"billing_monthly\": \"月額\",\n  \"billing_yearly\": \"年額\",\n  \"billing_20_percent_off\": \"20%オフ\",\n  \"billing_features\": \"機能\",\n  \"billing_channel\": \"チャンネル\",\n  \"billing_channels\": \"チャンネル\",\n  \"billing_unlimited\": \"無制限\",\n  \"billing_posts_per_month\": \"月あたりの投稿数\",\n  \"billing_unlimited_team_members\": \"チームメンバー無制限\",\n  \"billing_ai_auto_complete\": \"AI自動補完\",\n  \"billing_ai_copilots\": \"AIコパイロット\",\n  \"billing_ai_autocomplete\": \"AI自動補完\",\n  \"billing_advanced_picture_editor\": \"高度な画像編集ツール\",\n  \"billing_ai_images_per_month\": \"月あたりのAI画像数\",\n  \"billing_ai_videos_per_month\": \"月あたりのAI動画数\",\n  \"billing_billing_address\": \"請求先住所\",\n  \"billing_payment\": \"支払い\",\n  \"billing_powered_by_stripe\": \"安全な支払いはによって処理されています\",\n  \"billing_your_7_day_trial_is\": \"7日間の無料トライアルは\",\n  \"billing_100_percent_free\": \"完全に無料\",\n  \"billing_ending\": \"終了します\",\n  \"billing_cancel_anytime_short\": \"設定からいつでもキャンセル可能\",\n  \"billing_pay_0_start_trial\": \"本日のお支払いは0円 - 無料トライアルを始めましょう！\",\n  \"billing_pay_now\": \"今すぐ支払う\",\n  \"billing_per_month\": \"／月\",\n  \"billing_per_year\": \"／年\",\n  \"billing_order_summary\": \"ご注文内容\",\n  \"billing_applied\": \"適用済み\",\n  \"billing_due_today\": \"本日のお支払い額\",\n  \"billing_then\": \"その後\",\n  \"billing_on\": \"日付\",\n  \"billing_discount_applied\": \"割引適用済み\",\n  \"billing_remove\": \"削除\",\n  \"billing_coupon_expires\": \"クーポンの有効期限：\",\n  \"billing_invalid_coupon\": \"無効なクーポンコードです\",\n  \"billing_coupon_applied\": \"クーポンが正常に適用されました！\",\n  \"billing_coupon_removed\": \"クーポンが削除されました\",\n  \"billing_error_removing_coupon\": \"クーポンの削除中にエラーが発生しました\",\n  \"billing_have_discount_coupon\": \"割引クーポンをお持ちですか？\",\n  \"billing_discount_coupon\": \"割引クーポン\",\n  \"billing_cancel\": \"キャンセル\",\n  \"billing_enter_coupon_code\": \"クーポンコードを入力してください\",\n  \"billing_applying\": \"適用中...\",\n  \"billing_apply\": \"適用\",\n  \"billing_subscription\": \"サブスクリプション\",\n  \"billing_cancel_notice\": \"設定からいつでも人と話すことなくキャンセルでき、料金は一切発生しません。\",\n  \"select_channels\": \"チャンネルを選択\",\n  \"start_a_new_chat\": \"新しいチャットを開始\",\n  \"your_assistant\": \"あなたのアシスタント\",\n  \"agent_welcome_message\": \"こんにちは、私はあなたのPostizエージェントです🙌🏻。\\n\\n複数のチャンネルに投稿や複数の投稿をスケジュールしたり、画像や動画を生成したりできます。\\n\\n左側のメニューから使用したいチャンネルを選択できます。\\n\\n右側のメニューから過去の会話を見ることができます。\\n\\nまた、私をMCPサーバーとしても利用できます。設定 >> パブリックAPI をご確認ください。\",\n  \"last_github_trending\": \"最新のGitHubトレンド\",\n  \"next_predicted_github_trending\": \"次に予測されるGitHubトレンド\",\n  \"repository\": \"リポジトリ\",\n  \"date\": \"日付\",\n  \"total_stars\": \"総スター数\",\n  \"total_forks\": \"総フォーク数\",\n  \"continue_with\": \"続ける\",\n  \"email_address\": \"メールアドレス\",\n  \"email_already_exists\": \"メールアドレスは既に存在します\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"自動投稿を編集\",\n  \"add_autopost_title\": \"自動投稿を追加\",\n  \"webhook_deleted_successfully\": \"Webhookが正常に削除されました\",\n  \"all_integrations\": \"すべての連携\",\n  \"specific_integrations\": \"特定の連携\",\n  \"post_on_next_available_slot\": \"次の利用可能なスロットで投稿\",\n  \"post_immediately\": \"すぐに投稿\",\n  \"could_not_use_rss_feed\": \"このRSSフィードは使用できませんでした\",\n  \"rss_valid\": \"RSSは有効です！\",\n  \"autopost_updated_successfully\": \"自動投稿が正常に更新されました\",\n  \"autopost_added_successfully\": \"自動投稿が正常に追加されました\",\n  \"write_your_post_placeholder\": \"投稿内容を入力してください...\",\n  \"select_or_upload_pictures_max_5\": \"画像を選択またはアップロード（最大5枚まで同時に可能）\",\n  \"you_can_drag_drop_pictures\": \"画像をドラッグ＆ドロップすることもできます。\",\n  \"you_dont_have_any_media_yet\": \"まだメディアがありません\",\n  \"media_library\": \"メディアライブラリ\",\n  \"media_settings\": \"メディア設定\",\n  \"media_editor\": \"メディアエディター\",\n  \"close\": \"閉じる\",\n  \"me\": \"自分\",\n  \"noname\": \"名前なし\",\n  \"password_reset_link_expired\": \"パスワードリセットリンクの有効期限が切れています。もう一度お試しください。\",\n  \"invalid_api_key\": \"無効なAPIキーです\",\n  \"could_not_connect_to_platform\": \"プラットフォームに接続できませんでした\",\n  \"web3_provider\": \"Web3プロバイダー\",\n  \"add_provider_title\": \"プロバイダーを追加\",\n  \"profile_updated\": \"プロフィールが更新されました\",\n  \"sets\": \"セット\",\n  \"invitation_link_sent\": \"招待リンクが送信されました\",\n  \"send_invitation_link\": \"招待リンクを送信\",\n  \"copy_link\": \"リンクをコピー\",\n  \"are_you_sure_remove_team_member\": \"このチームメンバーを削除してもよろしいですか？\",\n  \"admin\": \"管理者\",\n  \"super_admin\": \"スーパー管理者\",\n  \"update_webhook\": \"Webhookを更新\",\n  \"add_webhook\": \"Webhookを追加\",\n  \"webhook_updated_successfully\": \"Webhookが正常に更新されました\",\n  \"webhook_added_successfully\": \"Webhookが正常に追加されました\",\n  \"webhook_sent\": \"Webhook送信\",\n  \"today\": \"今日\",\n  \"channel_disconnected_click_to_reconnect\": \"チャンネルが切断されました。再接続するにはクリックしてください。\",\n  \"channel_disabled_upgrade_plan\": \"このチャンネルは無効になっています。有効にするにはプランをアップグレードしてください。\",\n  \"channel_added\": \"チャンネルが追加されました\",\n  \"are_you_sure_disable_channel\": \"本当にこのチャンネルを無効にしますか？\",\n  \"disable_channel_title\": \"チャンネルを無効化\",\n  \"channel_disabled\": \"チャンネルが無効になりました\",\n  \"are_you_sure_delete_channel\": \"本当にこのチャンネルを削除しますか？\",\n  \"delete_channel_title\": \"チャンネルを削除\",\n  \"delete_posts_before_channel\": \"このチャンネルを削除する前に、関連するすべての投稿を削除する必要があります\",\n  \"channel_deleted\": \"チャンネルが削除されました\",\n  \"channel_enabled\": \"チャンネルが有効になりました\",\n  \"time_table_slots\": \"タイムテーブル枠\",\n  \"channel_id_copied\": \"チャンネルIDがクリップボードにコピーされました\",\n  \"settings_updated\": \"設定が更新されました\",\n  \"customer_updated\": \"顧客情報が更新されました\",\n  \"custom_url\": \"カスタムURL\",\n  \"picture\": \"画像\",\n  \"upgrade_required\": \"この機能を利用するにはアップグレードが必要です\",\n  \"move_to_billing\": \"請求画面へ移動\",\n  \"payment_required\": \"お支払いが必要です\",\n  \"no_content\": \"内容なし\",\n  \"select_customer_tooltip\": \"顧客を選択\",\n  \"customers\": \"顧客\",\n  \"hour\": \"時間\",\n  \"minutes\": \"分\",\n  \"updated\": \"更新済み\",\n  \"change_bot_picture_title\": \"ボットの画像を変更\",\n  \"select_customer_label\": \"顧客を選択\",\n  \"start_typing\": \"入力を開始...\",\n  \"choose_set_or_continue\": \"セットを選択するか、セットなしで続行してください\",\n  \"continue_without_set\": \"セットなしで続行\",\n  \"select_set\": \"セットを選択\",\n  \"channel_settings\": \"設定\",\n  \"post_needs_content_or_image\": \"投稿には少なくとも1文字または1つの画像が必要です。\",\n  \"please_fix_your_settings\": \"設定を修正してください\",\n  \"shortlink_urls_question\": \"URLを短縮リンクにしますか？クリック数の統計を取得できます。\",\n  \"yes_shortlink_it\": \"はい、短縮リンクにします！\",\n  \"added_successfully\": \"追加に成功しました\",\n  \"updated_successfully\": \"更新に成功しました\",\n  \"create_post_title\": \"投稿を作成\",\n  \"post_preview\": \"投稿プレビュー\",\n  \"check_circles_above\": \"上の円を確認してください\",\n  \"create_output\": \"出力を作成\",\n  \"assistant_initial_message\": \"こんにちは！あなたのSNS投稿をブラッシュアップするお手伝いができます。\",\n  \"no_longer_global_mode\": \"グローバルモードを終了しました\",\n  \"two_days\": \"2日間\",\n  \"three_days\": \"3日間\",\n  \"four_days\": \"4日間\",\n  \"five_days\": \"5日間\",\n  \"six_days\": \"6日間\",\n  \"two_weeks\": \"2週間\",\n  \"repeat_post_every_label\": \"投稿を繰り返す間隔\",\n  \"add_new_tag\": \"新しいタグを追加\",\n  \"tag_name\": \"名前\",\n  \"post_is_too_long\": \"投稿が長すぎます。修正してください\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"投稿には少なくとも1文字または1枚の画像が必要です。\",\n  \"internal_edit\": \"内部編集\",\n  \"are_you_sure_go_back_to_global_mode\": \"この操作は元に戻せません。本当にグローバルモードに戻りますか？\",\n  \"yes_go_back_to_global_mode\": \"はい、グローバルモードに戻ります\",\n  \"are_you_sure_delete_this_post\": \"本当にこの投稿を削除しますか？\",\n  \"yes_delete_it\": \"はい、削除します！\",\n  \"cant_edit_networks_when_creating_set\": \"セット作成中はネットワークを編集できません\",\n  \"click_to_exit_global_editing\": \"このボタンをクリックしてグローバル編集を終了し、このチャンネル用に投稿をカスタマイズします\",\n  \"edit_content\": \"内容を編集\",\n  \"editing_a_specific_network\": \"特定のネットワークを編集中\",\n  \"back_to_global\": \"グローバルに戻る\",\n  \"delete_post_tooltip\": \"投稿を削除\",\n  \"drop_files_here_to_upload\": \"ここにファイルをドロップしてアップロード\",\n  \"insert_emoji\": \"絵文字を挿入\",\n  \"write_something\": \"何かを書いてください…\",\n  \"click_channel_to_add\": \"追加するチャンネルをクリック\",\n  \"connect_your_channels\": \"チャンネルを接続する\",\n  \"connect_social_media_to_start\": \"ソーシャルメディアアカウントを接続して投稿の予約を始めましょう\",\n  \"connected_channels\": \"接続済みチャンネル\",\n  \"continue\": \"続行\",\n  \"continue_without_channels\": \"チャンネルなしで続行\",\n  \"watch_tutorial\": \"チュートリアルを見る\",\n  \"watch_tutorial_title\": \"Postizの使い方を学ぶ\",\n  \"watch_tutorial_description\": \"この短い動画を見て、Postizを最大限に活用する方法を学びましょう\",\n  \"back\": \"戻る\",\n  \"get_started\": \"開始\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/ka_ge/translation.json",
    "content": "{\n    \"calendar\": \"კალენდარი\",\n    \"webhooks\": \"ვებჰუქები\",\n    \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"ვებჰუქები საშუალებას გაძლევთ მიიღოთ შეტყობინება, როცა Postiz-ში რაიმე ხდება HTTP მოთხოვნის მეშვეობით.\",\n    \"name\": \"სახელი\",\n    \"url\": \"ბმული\",\n    \"edit\": \"რედაქტირება\",\n    \"delete\": \"წაშლა\",\n    \"add_a_webhook\": \"ვებჰუქის დამატება\",\n    \"save\": \"შენახვა\",\n    \"send_test\": \"ტესტის გაგზავნა\",\n    \"select_role\": \"როლის არჩევა\",\n    \"video_made_with_ai\": \"ვიდეო შექმნილია AI-ის მეშვეობით\",\n    \"please_add_at_least\": \"Დაამატეთ მინიმუმ 20 სიმბოლო\",\n    \"send_invitation_via_email\": \"მოწვევა გადაიგზავნოს ელფოსტით?\",\n    \"global_settings\": \"გლობალური პარამეტრები\",\n    \"copy_id\": \"არხის ID-ის კოპირება\",\n    \"team_members\": \"გუნდის წევრები\",\n    \"invite_your_assistant_or_team_member_to_manage_your_account\": \"მოიწვიეთ ასისტენტი ან გუნდის წევრი თქვენი ანგარიშის სამართავად\",\n    \"remove\": \"წაშლა\",\n    \"add_another_member\": \"სხვა წევრის დამატება\",\n    \"signatures\": \"ხელმოწერები\",\n    \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"შეგიძლიათ დაამატოთ ხელმოწერები, რომლებიც გამოყენებული იქნება თქვენს პოსტებში.\",\n    \"content\": \"კონტენტი\",\n    \"auto_add\": \"ავტომატურად დამატება?\",\n    \"actions\": \"ქმედებები\",\n    \"use_signature\": \"ხელმოწერის გამოყენება\",\n    \"add_a_signature\": \"ხელმოწერის დამატება\",\n    \"no\": \"არა\",\n    \"yes\": \"კი\",\n    \"your_git_repository\": \"თქვენი Git რეპოზიტორია\",\n    \"connect_your_github_repository_to_receive_updates_and_analytics\": \"დააკავშირეთ თქვენი GitHub რეპოზიტორია განახლებებისა და ანალიტიკის მისაღებად\",\n    \"connected\": \"დაკავშირებულია:\",\n    \"disconnect\": \"გათიშვა\",\n    \"connect_your_repository\": \"რეპოზიტორიის დაკავშირება\",\n    \"cancel\": \"გაუქმება\",\n    \"connect\": \"დაკავშირება\",\n    \"public_api\": \"საჯარო API\",\n    \"check_n8n\": \"იხილეთ ჩვენი N8N მორგებული node Postiz-ისთვის.\",\n    \"use_postiz_api_to_integrate_with_your_tools\": \"გამოიყენეთ Postiz API თქვენი ინსტრუმენტების ინტეგრაციისთვის.\",\n    \"read_how_to_use_it_over_the_documentation\": \"იხილეთ დეტალები დოკუმენტაციაში.\",\n    \"reveal\": \"ჩვენება\",\n    \"copy_key\": \"გასაღების კოპირება\",\n    \"mcp\": \"MCP\",\n    \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"დააკავშირეთ MCP-კლიენტი Postiz-სთან პოსტების უფრო სწრაფად დასაგეგმად!\",\n    \"share_with_a_client\": \"კლიენტთან გაზიარება\",\n    \"post\": \"პოსტი\",\n    \"comments\": \"კომენტარები\",\n    \"user\": \"მომხმარებელი\",\n    \"login_register_to_add_comments\": \"კომენტარის დასამატებლად გაიარეთ ავტორიზაცია ან რეგისტრაცია\",\n    \"status\": \"სტატუსი:\",\n    \"there_are_not_plugs_matching_your_channels\": \"თქვენს არხებთან შესაბამისი მოდულები ვერ მოიძებნა\",\n    \"you_have_to_add_x_or_linkedin_or_threads\": \"უნდა დაამატოთ: X, LinkedIn ან Threads\",\n    \"go_to_the_calendar_to_add_channels\": \"გადადით კალენდარში არხების დასამატებლად\",\n    \"channels\": \"არხები\",\n    \"activate\": \"აქტივაცია\",\n    \"this_channel_needs_to_be_refreshed\": \"ამ არხს განახლება სჭირდება,\",\n    \"click_here_to_refresh\": \"დააჭირეთ აქ განახლებისთვის\",\n    \"can_t_show_analytics_yet\": \"ჯერ ანალიტიკა მიუწვდომელია\",\n    \"you_have_to_add_social_media_channels\": \"დაამატეთ სოციალური მედიის არხები\",\n    \"supported\": \"მხარდაჭერილია:\",\n    \"step\": \"ნაბიჯი\",\n    \"skip_onboarding\": \"გაშვების გამოტოვება\",\n    \"onboarding\": \"გაშვება\",\n    \"next\": \"შემდეგი\",\n    \"you_are_done_from_here_you_can\": \"მზადაა! აქედან შეგიძლიათ:\",\n    \"view_analytics\": \"ანალიტიკის ნახვა\",\n    \"schedule_a_new_post\": \"ახალი პოსტის დაგეგმვა\",\n    \"to_sell_posts_you_would_have_to\": \"პოსტების გასაყიდად საჭიროა:\",\n    \"1_connect_at_least_one_channel\": \"1. მინიმუმ ერთი არხის დაკავშირება\",\n    \"2_connect_you_bank_account\": \"2. საბანკო ანგარიშის დაკავშირება\",\n    \"go_back_to_connect_channels\": \"დაბრუნდით არხების დასაკავშირებლად\",\n    \"move_to_the_seller_page_to_connect_you_bank\": \"გადადით გამყიდველის გვერდზე საბანკო ანგარიშის დასაკავშირებლად\",\n    \"connect_channels\": \"არხების დაკავშირება\",\n    \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"დააკავშირეთ სოციალური მედიისა და გამოქვეყნების არხები, რომ შემდეგ დაგეგმოთ პოსტები\",\n    \"social\": \"სოციალური\",\n    \"publishing_platforms\": \"გამოქვეყნების პლატფორმები\",\n    \"no_channels\": \"არხები ჯერ არ არის\",\n    \"connect_your_accounts\": \"დააბმითეთ თქვენი ანგარიშები — დაგეგმვა, გამოქვეყნება და ანალიზი ერთ სივრცეში.\",\n    \"notifications\": \"შეტყობინებები\",\n    \"no_notifications\": \"შეტყობინებები არ არის\",\n    \"send_message\": \"შეტყობინების გაგზავნა\",\n    \"mar_28\": \"მარტი 28\",\n    \"there_are_no_messages_yet\": \"ჯერ არ არის შეტყობინებები.\",\n    \"checkout_the_marketplace\": \"ეწვიეთ მარკეტპლეისს\",\n    \"go_to_marketplace\": \"გადასვლა მარკეტპლეისში\",\n    \"all_messages\": \"ყველა შეტყობინება\",\n    \"previous\": \"წინა\",\n    \"select_or_upload_pictures_maximum_5_at_a_time\": \"აირჩიეთ ან ატვირთეთ სურათები (მაქს. 5 ერთდროულად)\",\n    \"you_can_also_drag_drop_pictures\": \"ასევე შეგიძლიათ გადაათრიოთ და ჩააგდოთ სურათები\",\n    \"you_don_t_have_any_assets_yet\": \"ჯერ არ გაქვთ ასეტები\",\n    \"click_the_button_below_to_upload_one\": \"დააჭირეთ ქვედა ღილაკს ატვირთვისთვის\",\n    \"click_the_button_below_to_upload_other\": \"Დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე, რომ ატვირთოთ რამდენიმე\",\n    \"add_selected_media\": \"არჩეული მედიის დამატება\",\n    \"insert_media\": \"მედიის ჩასმა\",\n    \"design_media\": \"მედიის დიზაინი\",\n    \"select\": \"არჩევა\",\n    \"editor\": \"რედაქტორი\",\n    \"clear\": \"გასუფთავება\",\n    \"order_completed\": \"შეკვეთა დასრულდა\",\n    \"the_order_has_been_completed\": \"შეკვეთა დასრულებულია\",\n    \"post_has_been_published\": \"პოსტი გამოქვეყნდა\",\n    \"url_1\": \"ბმული:\",\n    \"new_offer\": \"ახალი შეთავაზება\",\n    \"platform\": \"პლატფორმა\",\n    \"posts\": \"პოსტები\",\n    \"pay_accept_offer\": \"გადახდა და შეთავაზების მიღება\",\n    \"accepted\": \"მიღებულია\",\n    \"post_draft\": \"პოსტის მონახაზი\",\n    \"revision_needed\": \"საჭიროა გადახედვა\",\n    \"approve\": \"დამტკიცება\",\n    \"preview\": \"გადახედვა\",\n    \"revision_requested\": \"მოთხოვნილია გადახედვა\",\n    \"accepted_1\": \"მიღებულია\",\n    \"cancelled_by_the_seller\": \"გაუქმდა გამყიდველის მიერ\",\n    \"please_select_your_country_where_your_business_is\": \"აირჩიეთ თქვენი ბიზნესის ქვეყანა\",\n    \"select_country\": \"--აირჩიეთ ქვეყანა--\",\n    \"connect_bank_account\": \"საბანკო ანგარიშის დაკავშირება\",\n    \"seller_mode\": \"გამყიდველის რეჟიმი\",\n    \"active\": \"აქტიური\",\n    \"details\": \"დეტალები\",\n    \"audience_size\": \"აუდიტორიის ზომა\",\n    \"add_another_platform\": \"სხვა პლატფორმის დამატება\",\n    \"send_an_offer_for\": \"შეთავაზების გაგზავნა $\",\n    \"complete_order_and_pay_early\": \"შეკვეთის დასრულება და გადახდა\",\n    \"order_in_progress\": \"შეკვეთა პროცესშია\",\n    \"create_a_new_offer\": \"ახალი შეთავაზების შექმნა\",\n    \"orders\": \"შეკვეთები\",\n    \"price\": \"ფასი\",\n    \"state\": \"სტატუსი\",\n    \"showing\": \"ნაჩვენებია\",\n    \"to\": \"მდე\",\n    \"from\": \"დან\",\n    \"results\": \"შედეგები\",\n    \"content_writer\": \"კონტენტის ავტორი\",\n    \"influencer\": \"ინფლუენსერი\",\n    \"request_service\": \"მომსახურების მოთხოვნა\",\n    \"the_marketplace_is_not_opened_yet\": \"მარკეტპლეისი ჯერ არ არის გახსნილი\",\n    \"check_again_soon\": \"შეამოწმეთ მალე!\",\n    \"filter\": \"ფილტრი\",\n    \"result\": \"შედეგი\",\n    \"seller\": \"გამყიდველი\",\n    \"buyer\": \"მყიდველი\",\n    \"discord_support\": \"Discord მხარდაჭერა\",\n    \"teams\": \"გუნდები\",\n    \"webhooks_1\": \"ვებჰუქები\",\n    \"auto_post\": \"ავტო-პოსტი\",\n    \"logout_from\": \"გასვლა ანგარიშიდან\",\n    \"join_10000_entrepreneurs_who_use_postiz\": \"შეუერთდით 10,000+ მეწარმეს, ვინც იყენებს Postiz-ს\",\n    \"to_manage_all_your_social_media_channels\": \"ყველა სოციალური არხის სამართავად\",\n    \"100_no_risk_trial\": \"100% რისკის გარეშე საცდელი პერიოდი\",\n    \"pay_nothing_for_the_first_7_days\": \"პირველი 7 დღე უფასოა\",\n    \"cancel_anytime_hassle_free\": \"გაუქმება ნებისმიერ დროს, უპრობლემოდ\",\n    \"add_free_subscription\": \"-- უფასო გამოწერის დამატება --\",\n    \"currently_impersonating\": \"ამჟამად იმპერსონაციაა\",\n    \"user_1\": \"მომხმარებელი:\",\n    \"drag_n_drop_some_files_here\": \"გადაათრიეთ ფაილები აქ\",\n    \"add_time_slot\": \"დროის სლოტის დამატება\",\n    \"add_slot\": \"სლოტის დამატება\",\n    \"cancel_publication\": \"გამოქვეყნების გაუქმება\",\n    \"statistics\": \"სტატისტიკა\",\n    \"loading\": \"იტვირთება\",\n    \"short_link\": \"მოკლე ბმული\",\n    \"original_link\": \"ორიგინალი ბმული\",\n    \"clicks\": \"კლიკები\",\n    \"selected_customer\": \"არჩეული კლიენტი\",\n    \"customer\": \"კლიენტი:\",\n    \"repeat_post_every\": \"პოსტის გამეორება ყოველ...\",\n    \"use_this_media\": \"ამ მედიის გამოყენება\",\n    \"create_new_post\": \"პოსტის შექმნა\",\n    \"update_post\": \"არსებული პოსტის განახლება\",\n    \"merge_comments_into_one_post\": \"კომენტარების გაერთიანება ერთ პოსტად\",\n    \"accounts_that_will_engage\": \"ანგარიშები, რომლებიც ჩაერთვებიან:\",\n    \"day\": \"დღე\",\n    \"week\": \"კვირა\",\n    \"month\": \"თვე\",\n    \"remove_from_customer\": \"კლიენტიდან მოხსნა\",\n    \"show_more\": \"+ მეტი\",\n    \"show_less\": \"- ნაკლები\",\n    \"upload\": \"ატვირთვა\",\n    \"ai\": \"AI\",\n    \"add_channel\": \"არხის დამატება\",\n    \"add_platform\": \"პლატფორმის დამატება\",\n    \"articles\": \"სტატიები\",\n    \"add_comment\": \"კომენტარის დამატება\",\n    \"add_post\": \"პოსტის დამატება თრედში\",\n    \"add_comment_or_post\": \"კომენტარის / პოსტის დამატება\",\n    \"you_are_in_global_editing_mode\": \"გლობალური რედაქტირების რეჟიმში ხართ\",\n    \"the_post_should_be_at_least_6_characters_long\": \"პოსტი უნდა შეიცავდეს მინიმუმ 6 სიმბოლოს\",\n    \"are_you_sure_you_want_to_delete_post\": \"დარწმუნებული ხართ, რომ წაშალოთ ეს პოსტი?\",\n    \"post_deleted_successfully\": \"პოსტი წარმატებით წაიშალა\",\n    \"delete_post\": \"პოსტს წაშლა\",\n    \"save_as_draft\": \"მონახაზად შენახვა\",\n    \"post_now\": \"გამოქვეყნება ახლა\",\n    \"please_add\": \"დაამატეთ, გთხოვთ\",\n    \"to_your_telegram_group_channel_and_click_here\": \"თქვენს Telegram ჯგუფში/არხში და შემდეგ დააჭირეთ აქ:\",\n    \"connect_telegram\": \"Telegram-ის დაკავშირება\",\n    \"please_add_the_following_command_in_your_chat\": \"დაამატეთ ჩატში შემდეგი ბრძანება:\",\n    \"copy\": \"კოპირება\",\n    \"settings\": \"პარამეტრები\",\n    \"integrations\": \"ინტეგრაციები\",\n    \"add_integration\": \"ინტეგრაციის დამატება\",\n    \"you_are_now_editing_only\": \"ახლა რედაქტირებ მხოლოდ\",\n    \"tag_a_company\": \"მიანიჭე ტეგი კომპანიას\",\n    \"video_length_is_invalid_must_be_up_to\": \"ვიდეოს სიგრძე არასწორია, უნდა იყოს მაქსიმუმ\",\n    \"seconds\": \"წამი\",\n    \"this_feature_available_only_for_photos\": \"ეს ფუნქცია ხელმისაწვდომია მხოლოდ ფოტოებისთვის — დაემატება ნაგულისხმევი მუსიკა, რომელსაც შემდეგ შეცვლით.\",\n    \"allow_user_to\": \"ნება მიეცით მომხმარებელს:\",\n    \"your_video_will_be_labeled_promotional\": \"თქვენი ვიდეო მოინათლება როგორც \\\"რეკლამიური კონტენტი\\\".\",\n    \"this_cannot_be_changed_once_posted\": \"ეს ვერ შეიცვლება პოსტის გამოქვეყნების შემდეგ.\",\n    \"turn_on_to_disclose_video_promotes\": \"ჩართეთ, რათა მიუთითოთ, რომ ვიდეო ხელს უწყობს საქონელს ან მომსახურებას ღირებულების სანაცვლოდ. ვიდეო შესაძლოა ასახავდეს თქვენ, მესამე პირს ან ორივეს.\",\n    \"you_are_promoting_yourself\": \"აქვეყნებთ თქვენი ბრენდის რეკლამას.\",\n    \"this_video_will_be_classified_brand_organic\": \"ვიდეო კლასიფიცირდება როგორც Brand Organic.\",\n    \"you_are_promoting_another_brand\": \"აქვეყნებთ სხვა ბრენდის/მესამე მხარის რეკლამას.\",\n    \"this_video_will_be_classified_branded_content\": \"ვიდეო კლასიფიცირდება როგორც Branded Content.\",\n    \"by_posting_you_agree_to_tiktoks\": \"პოსტის გამოქვეყნებით ეთანხმებით TikTok-ის\",\n    \"music_usage_confirmation\": \"მუსიკის გამოყენების დადასტურებას\",\n    \"branded_content_policy\": \"ბრენდირებული კონტენტის პოლიტიკას\",\n    \"select_1\": \"--აირჩიეთ--\",\n    \"select_flair\": \"--აირჩიეთ ფლეარი--\",\n    \"link\": \"ბმული\",\n    \"add_subreddit\": \"Subreddit-ის დამატება\",\n    \"please_add_at_least_one_subreddit\": \"დაამატეთ მინიმუმ ერთი Subreddit\",\n    \"add_community\": \"Community-ის დამატება\",\n    \"select_post_type\": \"აირჩიეთ პოსტის ტიპი...\",\n    \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"LinkedIn გვერდს მიერთებული ბიზნესი ვერ ვიპოვეთ.\",\n    \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"დახურეთ ეს ფანჯარა, შექმენით ახალი გვერდი და თავიდან დაამატეთ არხი.\",\n    \"select_linkedin_page\": \"აირჩიეთ LinkedIn გვერდი:\",\n    \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"არჩეულ გვერდებს მიერთებული ბიზნესი ვერ ვიპოვეთ.\",\n    \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"გირჩევთ დააკავშიროთ ყველა გვერდი და ყველა ბიზნესი.\",\n    \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"დახურეთ ეს ფანჯარა, წაშალეთ ინტეგრაცია და თავიდან დაამატეთ არხი.\",\n    \"select_instagram_account\": \"აირჩიეთ Instagram ანგარიში:\",\n    \"select_page\": \"გვერდის არჩევა:\",\n    \"generate_image_with_ai\": \"სურათის გენერირება AI-ით\",\n    \"reconnect_channel\": \"არხის ხელახლა დაკავშირება\",\n    \"update_credentials\": \"მომართვების განახლება\",\n    \"additional_settings\": \"დამატებითი პარამეტრები\",\n    \"change_bot\": \"ბოტის შეცვლა\",\n    \"move_add_to_customer\": \"გადატანა / დამატება კლიენტზე\",\n    \"edit_time_slots\": \"დროის სლოტების რედაქტირება\",\n    \"enable_channel\": \"არხის ჩართვა\",\n    \"disable_channel\": \"არხის გამორთვა\",\n    \"add\": \"დამატება\",\n    \"short_post\": \"მოკლე პოსტი\",\n    \"long_post\": \"გრძელი პოსტი\",\n    \"a_thread_with_short_posts\": \"თრედი მოკლე პოსტებით\",\n    \"a_thread_with_long_posts\": \"თრედი გრძელი პოსტებით\",\n    \"personal_voice_i_am_happy_to_announce\": \"პირადი ხმით (\\\"მიხარია განცხადება...\\\")\",\n    \"company_voice_we_are_happy_to_announce\": \"კომპანიის ხმით (\\\"მოხარულები ვართ, რომ ვაცხადებთ...\\\")\",\n    \"generate\": \"გენერირება\",\n    \"generate_posts\": \"პოსტების გენერირება\",\n    \"purchase_a_life_time_pro_account_with_sol_199\": \"შეიძინეთ სამუდამო PRO ანგარიში SOL-ით ($199). გთხოვთ გაითვალისწინოთ, რომ ანაზღაურება არ ხდება.\",\n    \"purchase_now\": \"ყიდვა ახლა\",\n    \"pay_today\": \"გადახდა დღეს\",\n    \"we_are_sorry_to_see_you_go\": \"ანანთაით ხართ, რომ მიდიხართ :(\",\n    \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"გვისურვებდით მოკლედ გვეცნობრებინათ, რა შეგვეძლო გაგვეკეთებინა უკეთ?\",\n    \"cancel_subscription\": \"გამოწერის გაუქმება\",\n    \"plans\": \"გეგმები\",\n    \"monthly\": \"თვიური\",\n    \"yearly\": \"წლიური\",\n    \"reactivate_subscription\": \"გამოწერის ხელახლა აქტივაცია\",\n    \"update_payment_method_invoices_history\": \"გადახდის მეთოდის განახლება / ინვოისების ისტორია\",\n    \"cancel_subscription_1\": \"გამოწერის გაუქმება\",\n    \"your_subscription_will_be_canceled_at\": \"თქვენი გამოწერა გაუქმდება თარიღზე:\",\n    \"you_will_never_be_charged_again\": \"სხვა აღარ ჩამოგეჭრებათ თანხა\",\n    \"current_package\": \"მიმდინარე პაკეტი:\",\n    \"next_package\": \"შემდეგი პაკეტი:\",\n    \"claim\": \"მოთხოვნა\",\n    \"frequently_asked_questions\": \"ხშირად დასმული კითხვები\",\n    \"autopost\": \"ავტოპოსტი\",\n    \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"ავტოპოსტი ავტომატურად გამოაქვეყნებს თქვენს RSS-ის ახალ ჩანაწერებს სოციალურ მედია არხებზე\",\n    \"title\": \"სათაური\",\n    \"add_an_autopost\": \"ავტოპოსტის დამატება\",\n    \"post_content\": \"პოსტის შინაარსი\",\n    \"sign_up\": \"რეგისტრაცია\",\n    \"or\": \"ან\",\n    \"by_registering_you_agree_to_our\": \"რეგისტრაციით ეთანხმებით ჩვენს\",\n    \"and\": \"და\",\n    \"terms_of_service\": \"გამოყენების პირობებს\",\n    \"privacy_policy\": \"კონფიდენციალურობის პოლიტიკას\",\n    \"create_account\": \"ანგარიშის შექმნა\",\n    \"already_have_an_account\": \"უკვე გაქვთ ანგარიში?\",\n    \"sign_in\": \"შესვლა\",\n    \"sign_in_1\": \"შესვლა\",\n    \"don_t_have_an_account\": \"არ გაქვთ ანგარიში?\",\n    \"forgot_password\": \"დაგავიწყდათ პაროლი\",\n    \"forgot_password_1\": \"პაროლის აღდგენა\",\n    \"send_password_reset_email\": \"პაროლის აღდგენის წერილის გაგზავნა\",\n    \"go_back_to_login\": \"დაბრუნება შესვლაზე\",\n    \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"გაგიგზავნეთ ელფოსტა პაროლის აღდგენის ბმულით.\",\n    \"change_password\": \"პაროლის შეცვლა\",\n    \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"პაროლი წარმატებით აღდგა. შეგიძლიათ შეხვიდეთ თქვენი\",\n    \"click_here_to_go_back_to_login\": \"დააჭირეთ აქ შესვლაზე დასაბრუნებლად\",\n    \"activate_your_account\": \"ანგარიშის გააქტიურება\",\n    \"thank_you_for_registering\": \"გმადლობთ რეგისტრაციისთვის!\",\n    \"please_check_your_email_to_activate_your_account\": \"გთხოვთ, გადაამოწმოთ ელფოსტა ანგარიშის გასააქტიურებლად.\",\n    \"sign_in_with\": \"შესვლა შემდეგით\",\n    \"continue_with_google\": \"გაგრძელება Google-ით\",\n    \"sign_in_with_github\": \"შესვლა GitHub-ით\",\n    \"continue_with_farcaster\": \"გაგრძელება Farcaster-ით\",\n    \"continue_with_your_wallet\": \"გაგრძელება საფულით\",\n    \"stars_per_day\": \"ვარსკვლავები დღეში\",\n    \"media\": \"მედია\",\n    \"check_launch\": \"გაშვების გადამოწმება\",\n    \"load_your_github_repository_from_settings_to_see_analytics\": \"ჩატვირთეთ GitHub რეპოზიტორია პარამეტრებიდან ანალიტიკის სანახავად\",\n    \"stars\": \"ვარსკვლავები\",\n    \"processing_stars\": \"ვარსკვლავების დამუშავება...\",\n    \"forks\": \"ფორკები\",\n    \"registration_is_disabled\": \"რეგისტრაცია გათიშულია\",\n    \"login_instead\": \"შემოსვლა\",\n    \"gitroom\": \"Gitroom\",\n    \"select_a_conversation_and_chat_away\": \"აირჩიეთ საუბარი და დაიწყეთ ჩათი\",\n    \"adding_channel_redirecting_you\": \"არხის დამატება... გადამისამართება\",\n    \"could_not_add_provider\": \"პროვაიდერის დამატება ვერ მოხერხდა.\",\n    \"you_are_being_redirected_back\": \"ბრუნდებით უკან\",\n    \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"ამჟამად ჭირს მუშაობა, სცადეთ გვერდის განახლება\",\n    \"post_not_found\": \"პოსტი ვერ მოიძებნა\",\n    \"publication_date\": \"გამოქვეყნების თარიღი:\",\n    \"analytics\": \"ანალიტიკა\",\n    \"launches\": \"გაშვებები\",\n    \"plugs\": \"მოდულები\",\n    \"billing\": \"ბილინგი\",\n    \"affiliate\": \"აფილியேიტი\",\n    \"monday\": \"ორშაბათი\",\n    \"tuesday\": \"სამშაბათი\",\n    \"wednesday\": \"ოთხშაბათი\",\n    \"thursday\": \"ხუთშაბათი\",\n    \"friday\": \"პარასკევი\",\n    \"saturday\": \"შაბათი\",\n    \"sunday\": \"კვირა\",\n    \"can_t_change_date_remove_post_from_publication\": \"თარიღის შეცვლა შეუძლებელია — მოხსენით პოსტი გამოქვეყნებიდან\",\n    \"predicted_github_trending_change\": \"პროგნოზირებული ცვლილება GitHub Trending-ში\",\n    \"duplicate_post\": \"პოსტის დუბლირება\",\n    \"preview_post\": \"პოსტზე წინასწარი გადახედვა\",\n    \"post_statistics\": \"პოსტის სტატისტიკა\",\n    \"draft\": \"მონახაზი\",\n    \"week_number\": \"კვირა {{number}}\",\n    \"top_title_edit_webhook\": \"ვებჰუქის რედაქტირება\",\n    \"top_title_add_webhook\": \"ვებჰუქის დამატება\",\n    \"top_title_oh_no\": \"ო, არა\",\n    \"top_title_auto_plug\": \"ავტომოდული: {{title}}\",\n    \"top_title_edit_autopost\": \"ავტოპოსტის რედაქტირება\",\n    \"top_title_add_autopost\": \"ავტოპოსტის დამატება\",\n    \"top_title_send_a_new_offer\": \"ახალი შეთავაზების გაგზავნა\",\n    \"top_title_media_library\": \"მედიალაიბრარი\",\n    \"top_title_add_signature\": \"ხელმოწერის დამატება\",\n    \"top_title_send_a_message_to\": \"შეტყობინების გაგზავნა: {{name}}\",\n    \"top_title_configure_provider\": \"პროვაიდერის კონფიგურაცია\",\n    \"top_title_add_member\": \"წევრის დამატება\",\n    \"top_title_change_bot_picture\": \"ბოტის სურათის შეცვლა\",\n    \"top_title_create_a_new_tag\": \"ახალი ტეგის შექმნა\",\n    \"top_title_select_company\": \"კომპანიის არჩევა\",\n    \"top_title_additional_settings\": \"დამატებითი პარამეტრები\",\n    \"top_title_time_table_slots\": \"დროის ცხრილი / სლოტები\",\n    \"top_title_design_media\": \"მედიის დიზაინი\",\n    \"top_title_edit_post\": \"პოსტის რედაქტირება\",\n    \"top_title_create_post\": \"ახალი პოსტის შექმნა\",\n    \"top_title_move__add_to_customer\": \"გადატანა / დამატება კლიენტზე\",\n    \"top_title_add_api_key_for\": \"API გასაღები — {{name}}\",\n    \"top_title_instance_url\": \"ინსტანციის URL\",\n    \"top_title_custom_url\": \"მორგებული URL\",\n    \"top_title_add_channel\": \"არხის დამატება\",\n    \"top_title_add_telegram\": \"Telegram-ის დამატება\",\n    \"top_title_add_wrapcast\": \"Wrapcast-ის დამატება\",\n    \"top_title_comments_for\": \"კომენტარები — {{date}}\",\n    \"top_title_edit_signature\": \"ხელმოწერის რედაქტირება\",\n    \"label_name\": \"სახელი\",\n    \"label_url\": \"ბმული\",\n    \"label_title\": \"სათაური\",\n    \"label_subtitle\": \"ქვესათაური\",\n    \"label_email\": \"ელ.ფოსტა\",\n    \"label_full_name\": \"სრული სახელი\",\n    \"label_password\": \"პაროლი\",\n    \"label_confirm_password\": \"პაროლის დადასტურება\",\n    \"label_api_key\": \"API გასაღები\",\n    \"label_instance_url\": \"ინსტანციის URL\",\n    \"label_custom_url\": \"მორგებული URL\",\n    \"label_feedback\": \"უკუკავშირი\",\n    \"label_bio\": \"ბიო\",\n    \"label_role\": \"როლი\",\n    \"label_country\": \"ქვეყანა\",\n    \"label_audience_size\": \"აუდიტორიის ზომა ყველა პლატფორმაზე\",\n    \"label_pick_time\": \"დროის არჩევა\",\n    \"label_nickname\": \"მეტსახელი\",\n    \"label_write_anything\": \"დაწერეთ რაც გსურთ\",\n    \"label_output_format\": \"გამოტანის ფორმატი\",\n    \"label_add_pictures\": \"დავამატოთ სურათები?\",\n    \"label_hour\": \"საათი\",\n    \"label_minutes\": \"წუთი\",\n    \"label_select_publication\": \"აირჩიეთ პუბლიკაცია\",\n    \"label_canonical_link\": \"კანონიკური ბმული\",\n    \"label_cover_picture\": \"ქოვერის სურათი\",\n    \"label_tags\": \"ტეგები\",\n    \"label_topics\": \"თემები\",\n    \"label_tags_maximum_4\": \"ტეგები (მაქს. 4)\",\n    \"label_attachments\": \"დანართები\",\n    \"label_type\": \"ტიპი\",\n    \"label_thumbnail\": \"თამბნეილი\",\n    \"label_who_can_see_this_video\": \"ვის შეუძლია ამ ვიდეოს ნახვა?\",\n    \"label_content_posting_method\": \"კონტენტის გამოქვეყნების მეთოდი\",\n    \"label_auto_add_music\": \"მუსიკის ავტომატური დამატება\",\n    \"label_duet\": \"დუეტი\",\n    \"label_stitch\": \"Stitch\",\n    \"label_comments\": \"კომენტარები\",\n    \"label_disclose_video_content\": \"ვიდეოს შინაარსის გამჟღავნება\",\n    \"label_your_brand\": \"თქვენი ბრენდი\",\n    \"label_branded_content\": \"ბრენდირებული კონტენტი\",\n    \"label_subreddit\": \"Subreddit\",\n    \"label_flair\": \"Flair\",\n    \"label_media\": \"მედია\",\n    \"label_search_subreddit\": \"Subreddit-ის ძიება\",\n    \"label_delay\": \"დაყოვნება\",\n    \"label_post_type\": \"პოსტის ტიპი\",\n    \"label_collaborators\": \"თანაავტორები (მაქს. 3) — ანგარიშები არ უნდა იყოს პრაივეტი\",\n    \"label_community\": \"Community\",\n    \"label_search_community\": \"Community-ის ძიება\",\n    \"label_channel\": \"არხი\",\n    \"label_search_channel\": \"არხის ძიება\",\n    \"label_select_channel\": \"აირჩიეთ არხი\",\n    \"label_new_password\": \"ახალი პაროლი\",\n    \"label_repeat_password\": \"პაროლის გამეორება\",\n    \"label_platform\": \"პლატფორმა\",\n    \"label_price_per_post\": \"ფასი ერთ პოსტზე\",\n    \"label_integrations\": \"ინტეგრაციები\",\n    \"label_code\": \"კოდი\",\n    \"label_should_sync_last_post\": \"გავათანაბროთ თუ არა მიმდინარე ბოლო პოსტი?\",\n    \"label_when_post\": \"როდის დავპოსტოთ?\",\n    \"label_autogenerate_content\": \"კონტენტის ავტომატური გენერირება\",\n    \"label_generate_picture\": \"სურათის გენერირება?\",\n    \"label_company\": \"კომპანია\",\n    \"label_tag_color\": \"ტეგის ფერი\",\n    \"label_select_board\": \"ბორდის არჩევა\",\n    \"label_select_organization\": \"ორგანიზაციის არჩევა\",\n    \"label_auto_add_signature\": \"ავტომატურად დავამატოთ ხელმოწერა?\",\n    \"enable_color_picker\": \"ფერების ამრჩევის ჩართვა\",\n    \"cancel_the_color_picker\": \"ფერის ამრჩევის გაუქმება\",\n    \"no_content_yet\": \"კონტენტი ჯერჯერობით არ არის\",\n    \"write_your_reply\": \"დაწერეთ თქვენი პოსტი...\",\n    \"add_a_tag\": \"ტეგის დამატება\",\n    \"add_to_calendar\": \"კალენდარში დამატება\",\n    \"select_channels_from_circles\": \"აირჩიეთ არხები ზემოთ არსებულ წრეებიდან\",\n    \"not_matching_order\": \"შეკვეთას არ ემთხვევა\",\n    \"submit_for_order\": \"გაგზავნა შეკვეთაზე\",\n    \"schedule\": \"დაგეგმვა\",\n    \"update\": \"განახლება\",\n    \"attachments\": \"დანართები\",\n    \"tags\": \"ტეგები\",\n    \"public_to_everyone\": \"ხილულია ყველასთვის\",\n    \"mutual_follow_friends\": \"ურთიერთჩამომყოლები\",\n    \"follower_of_creator\": \"შემქმნელის გამომწერები\",\n    \"self_only\": \"მხოლოდ მე\",\n    \"post_content_directly_to_tiktok\": \"კონტენტის პირდაპირ გამოქვეყნება TikTok-ში\",\n    \"upload_content_to_tiktok_without_posting\": \"კონტენტის ატვირთვა TikTok-ში გამოქვეყნების გარეშე\",\n    \"choose_upload_without_posting_description\": \"აირჩიეთ ატვირთვა გამოქვეყნების გარეშე, თუ გსურთ მასალის გადამოწმება/რედაქტირება TikTok-ის აპში გამოქვეყნებამდე.\",\n    \"faq_am_i_going_to_be_charged_by_postiz\": \"Postiz დამაკისრებს თანხას?\",\n    \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"საკრედიტო ბარათის დადასტურებისთვის Postiz დროებით დაბლოკავს $2-ს და მაშინვე გაათავისუფლებს\",\n    \"faq_can_i_trust_postiz\": \"შემიძლია ვენდო Postiz-ს?\",\n    \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz არის ღია კოდის! გამჭვირვალე კულტურა — შეგიძლიათ ნახოთ მთელი კოდი ან გამოიყენოთ პირადი პროექტებისთვის. ღია რეპოზიტორიის სანახავად <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">დააჭირეთ აქ</a>.\",\n    \"faq_what_are_channels\": \"რა არის არხები?\",\n    \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz გაძლევთ პოსტების დაგეგმვის საშუალებას სხვადასხვა არხებზე — მაგალითად X, Facebook, Instagram, TikTok, YouTube, Reddit, LinkedIn, Dribbble, Threads და Pinterest.\",\n    \"faq_what_are_team_members\": \"რა არის გუნდის წევრები?\",\n    \"faq_if_you_have_a_team_with_multiple_members\": \"თუ თქვენ ჰყავთ გუნდი რამდენიმე წევრით, შეგიძლიათ მოიწვიოთ ისინი workspace-ში, ითანამშრომლოთ პოსტებზე და დაამატონ თავიანთი არხები\",\n    \"faq_what_is_ai_auto_complete\": \"რა არის AI auto-complete?\",\n    \"faq_we_automate_chatgpt_to_help_you_write\": \"ვავტომატებთ ChatGPT-ს, რომ დაგეხმაროთ სოციალური პოსტებისა და სტატიათა წერაში.\",\n    \"enter_email\": \"შეიტანეთ ელფოსტა\",\n    \"are_you_sure\": \"დარწმუნებული ხართ?\",\n    \"yes_delete_it\": \"კი, წაშალე!\",\n    \"no_cancel\": \"არა, გაუქმება!\",\n    \"are_you_sure_you_want_to_delete\": \"ნამდვილად გსურთ წაშალოთ {{name}}?\",\n    \"are_you_sure_you_want_to_delete_the_image\": \"ნამდვილად გსურთ სურათის წაშლა?\",\n    \"are_you_sure_you_want_to_logout\": \"ნამდვილად გსურთ გასვლა?\",\n    \"yes_logout\": \"კი, გამოსვლა\",\n    \"are_you_sure_you_want_to_delete_this_slot\": \"წავშალოთ ეს სლოტი?\",\n    \"are_you_sure_you_want_to_delete_this_subreddit\": \"წავშალოთ ეს Subreddit?\",\n    \"are_you_sure_you_want_to_close_the_window\": \"დავხუროთ ფანჯარა?\",\n    \"yes_close\": \"კი, დახურე\",\n    \"link_copied_to_clipboard\": \"ბმული დაკოპირდა\",\n    \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"დავხუროთ ეს ფანჯარა? (ყველა მონაცემი დაიკარგება)\",\n    \"yes_close_it\": \"კი, დახურე!\",\n    \"uploading_pictures\": \"სურათების ატვირთვა...\",\n    \"agent_starting\": \"აგენტის გაშვება\",\n    \"researching_your_content\": \"თქვენი კონტენტის კვლევა...\",\n    \"understanding_the_category\": \"კატეგორიის გააზრება...\",\n    \"finding_the_topic\": \"თემის ძიება...\",\n    \"finding_popular_posts_to_match_with\": \"პოპულარული პოსტების მოპოვება შესატყვისად...\",\n    \"generating_hook\": \"ჰუკის გენერირება...\",\n    \"generating_content\": \"კონტენტის გენერირება...\",\n    \"generating_pictures\": \"სურათების გენერირება...\",\n    \"finding_time_to_post\": \"საუკეთესო დროის პოვნა...\",\n    \"write_anything\": \"დაწერეთ რაც გსურთ\",\n    \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"დაწერეთ რაც გინდათ და დაამატეთ ბმულები — კვლევას ჩვენ მოვახდენთ\",\n    \"output_format\": \"გამოტანის ფორმატი\",\n    \"add_pictures\": \"სურათების დამატება?\",\n    \"7_days\": \"7 დღე\",\n    \"30_days\": \"30 დღე\",\n    \"90_days\": \"90 დღე\",\n    \"start_7_days_free_trial\": \"დაიწყე 7-დღიანი უფასო პერიოდი\",\n    \"change_language\": \"ენის შეცვლა\",\n    \"that_a_wrap\": \"დასრულებულია!\\n\\nთუ მოგეწონა ეს თრედი:\\n\\n1. გამომყევი @{{username}}\\n2. გააზიარეთ ქვემოთ არსებული პოსტი\",\n    \"post_as_images_carousel\": \"გამოქვეყნება სურათების კარუსელად\",\n    \"save_set\": \"სეტის შენახვა\",\n    \"separate_post\": \"თრედის დაყოფა რამდენიმე პოსტად\",\n    \"label_who_can_reply_to_this_post\": \"ვის შეუძლია პასუხის გაცემა ამ პოსტზე?\",\n    \"delete_integration\": \"ინტეგრაციის წაშლა\",\n    \"start_writing_your_post\": \"დაიწყეთ თქვენი პოსტის წერა წინასწარი ხედვისთვის\",\n    \"faq_can_i_trust_postiz_gitroom\": \"Შემიძლია ვენდო Postiz-ს?\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/ko/translation.json",
    "content": "{\n  \"calendar\": \"캘린더\",\n  \"webhooks\": \"웹훅\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"웹훅은 Postiz에서 무언가 발생할 때 HTTP 요청을 통해 알림을 받는 방법입니다.\",\n  \"name\": \"이름\",\n  \"url\": \"URL\",\n  \"edit\": \"편집\",\n  \"delete\": \"삭제\",\n  \"add_a_webhook\": \"웹훅 추가\",\n  \"save\": \"저장\",\n  \"send_test\": \"테스트 전송\",\n  \"select_role\": \"역할 선택\",\n  \"video_made_with_ai\": \"AI로 제작된 영상\",\n  \"please_add_at_least\": \"최소 20자 이상 입력해 주세요\",\n  \"send_invitation_via_email\": \"이메일로 초대장을 보내시겠습니까?\",\n  \"global_settings\": \"글로벌 설정\",\n  \"copy_id\": \"채널 ID 복사\",\n  \"team_members\": \"팀원\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"계정을 관리할 수 있도록 어시스턴트나 팀원을 초대하세요.\",\n  \"remove\": \"제거\",\n  \"add_another_member\": \"다른 멤버 추가\",\n  \"signatures\": \"서명\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"게시물에 사용할 수 있도록 계정에 서명을 추가할 수 있습니다.\",\n  \"content\": \"내용\",\n  \"auto_add\": \"자동 추가?\",\n  \"delay_comment\": \"지연 코멘트\",\n  \"actions\": \"작업\",\n  \"use_signature\": \"서명 사용\",\n  \"add_a_signature\": \"서명 추가\",\n  \"no\": \"아니오\",\n  \"yes\": \"예\",\n  \"your_git_repository\": \"귀하의 Git 저장소\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"업데이트와 분석을 받으려면 GitHub 저장소를 연결하세요\",\n  \"connected\": \"연결됨:\",\n  \"disconnect\": \"연결 해제\",\n  \"connect_your_repository\": \"저장소 연결\",\n  \"cancel\": \"취소\",\n  \"connect\": \"연결\",\n  \"public_api\": \"공개 API\",\n  \"check_n8n\": \"Postiz를 위한 저희의 N8N 커스텀 노드를 확인해보세요.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Postiz API를 사용하여 도구와 통합하세요.\",\n  \"read_how_to_use_it_over_the_documentation\": \"문서를 통해 사용 방법을 확인하세요.\",\n  \"reveal\": \"표시\",\n  \"copy_key\": \"키 복사\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"게시물을 더 빠르게 예약하려면 Postiz MCP 서버를 클라이언트에 연결하세요(Http 스트리밍).\",\n  \"share_with_a_client\": \"클라이언트와 공유\",\n  \"post\": \"게시물\",\n  \"comments\": \"댓글\",\n  \"user\": \"사용자\",\n  \"login_register_to_add_comments\": \"댓글을 추가하려면 로그인/회원가입하세요\",\n  \"status\": \"상태:\",\n  \"there_are_not_plugs_matching_your_channels\": \"채널에 맞는 플러그가 없습니다\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"X 또는 LinkedIn 또는 Threads를 추가해야 합니다\",\n  \"go_to_the_calendar_to_add_channels\": \"채널을 추가하려면 캘린더로 이동하세요\",\n  \"channels\": \"채널\",\n  \"activate\": \"활성화\",\n  \"this_channel_needs_to_be_refreshed\": \"이 채널을 새로 고침해야 합니다.\",\n  \"click_here_to_refresh\": \"여기를 클릭하여 새로 고침\",\n  \"can_t_show_analytics_yet\": \"아직 분석 정보를 표시할 수 없습니다.\",\n  \"you_have_to_add_social_media_channels\": \"소셜 미디어 채널을 추가해야 합니다.\",\n  \"supported\": \"지원됨:\",\n  \"step\": \"단계\",\n  \"skip_onboarding\": \"온보딩 건너뛰기\",\n  \"onboarding\": \"온보딩\",\n  \"next\": \"다음\",\n  \"you_are_done_from_here_you_can\": \"완료되었습니다. 이제 다음을 할 수 있습니다:\",\n  \"view_analytics\": \"분석 보기\",\n  \"schedule_a_new_post\": \"새 게시물 예약\",\n  \"to_sell_posts_you_would_have_to\": \"게시물을 판매하려면 다음을 해야 합니다:\",\n  \"1_connect_at_least_one_channel\": \"1. 최소 한 개의 채널 연결\",\n  \"2_connect_you_bank_account\": \"2. 은행 계좌 연결\",\n  \"go_back_to_connect_channels\": \"채널 연결로 돌아가기\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"판매자 페이지로 이동하여 은행 계좌 연결\",\n  \"connect_channels\": \"채널 연결\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"소셜 미디어 및 퍼블리싱 웹사이트 채널을 연결하여\\n나중에 게시물을 예약하세요.\",\n  \"social\": \"소셜\",\n  \"publishing_platforms\": \"퍼블리싱 플랫폼\",\n  \"no_channels\": \"아직 채널이 없습니다\",\n  \"connect_your_accounts\": \"소셜 계정을 연결하여 한 곳에서 예약, 게시, 분석을 시작하세요.\",\n  \"notifications\": \"알림\",\n  \"no_notifications\": \"알림 없음\",\n  \"send_message\": \"메시지 보내기\",\n  \"mar_28\": \"3월 28일\",\n  \"there_are_no_messages_yet\": \"아직 메시지가 없습니다.\",\n  \"checkout_the_marketplace\": \"마켓플레이스를 확인하세요\",\n  \"go_to_marketplace\": \"마켓플레이스로 이동\",\n  \"all_messages\": \"모든 메시지\",\n  \"previous\": \"이전\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"사진을 선택하거나 업로드하세요 (최대 5장까지 한 번에 가능)\",\n  \"you_can_also_drag_drop_pictures\": \"사진을 드래그 앤 드롭하여 추가할 수도 있습니다\",\n  \"you_don_t_have_any_assets_yet\": \"아직 자산이 없습니다.\",\n  \"click_the_button_below_to_upload_one\": \"아래 버튼을 클릭하여 하나를 업로드하세요\",\n  \"click_the_button_below_to_upload_other\": \"아래 버튼을 클릭하여 여러 개를 업로드하세요\",\n  \"add_selected_media\": \"선택한 미디어 추가\",\n  \"insert_media\": \"미디어 삽입\",\n  \"design_media\": \"디자인 미디어\",\n  \"select\": \"선택\",\n  \"editor\": \"에디터\",\n  \"clear\": \"지우기\",\n  \"order_completed\": \"주문 완료\",\n  \"the_order_has_been_completed\": \"주문이 완료되었습니다\",\n  \"post_has_been_published\": \"게시물이 게시되었습니다\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"새로운 제안\",\n  \"platform\": \"플랫폼\",\n  \"posts\": \"게시물\",\n  \"pay_accept_offer\": \"결제 및 제안 수락\",\n  \"accepted\": \"수락됨\",\n  \"post_draft\": \"게시물 초안\",\n  \"revision_needed\": \"수정 필요\",\n  \"approve\": \"승인\",\n  \"preview\": \"미리보기\",\n  \"revision_requested\": \"수정 요청됨\",\n  \"accepted_1\": \"승인됨\",\n  \"cancelled_by_the_seller\": \"판매자가 취소함\",\n  \"please_select_your_country_where_your_business_is\": \"비즈니스가 위치한 국가를 선택하세요.\",\n  \"select_country\": \"--국가 선택--\",\n  \"connect_bank_account\": \"은행 계좌 연결\",\n  \"seller_mode\": \"판매자 모드\",\n  \"active\": \"활성화됨\",\n  \"details\": \"세부 정보\",\n  \"audience_size\": \"청중 규모\",\n  \"add_another_platform\": \"다른 플랫폼 추가\",\n  \"send_an_offer_for\": \"$에 대한 제안 보내기\",\n  \"complete_order_and_pay_early\": \"주문 완료 및 조기 결제\",\n  \"order_in_progress\": \"주문 진행 중\",\n  \"create_a_new_offer\": \"새 제안 만들기\",\n  \"orders\": \"주문\",\n  \"price\": \"가격\",\n  \"state\": \"상태\",\n  \"showing\": \"표시 중\",\n  \"to\": \"까지\",\n  \"from\": \"부터\",\n  \"results\": \"결과\",\n  \"content_writer\": \"콘텐츠 작가\",\n  \"influencer\": \"인플루언서\",\n  \"request_service\": \"서비스 요청\",\n  \"the_marketplace_is_not_opened_yet\": \"마켓플레이스가 아직 열리지 않았습니다\",\n  \"check_again_soon\": \"곧 다시 확인해 주세요!\",\n  \"filter\": \"필터\",\n  \"result\": \"결과\",\n  \"seller\": \"판매자\",\n  \"buyer\": \"구매자\",\n  \"discord_support\": \"디스코드 지원\",\n  \"teams\": \"팀\",\n  \"webhooks_1\": \"웹훅\",\n  \"auto_post\": \"자동 게시\",\n  \"logout_from\": \"로그아웃\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Postiz를 사용하는 10,000명 이상의 창업가들과 함께하세요\",\n  \"to_manage_all_your_social_media_channels\": \"모든 소셜 미디어 채널을 관리하세요\",\n  \"100_no_risk_trial\": \"100% 무위험 체험\",\n  \"pay_nothing_for_the_first_7_days\": \"처음 7일 동안은 결제되지 않습니다\",\n  \"cancel_anytime_hassle_free\": \"언제든지 설정에서 취소할 수 있습니다\",\n  \"add_free_subscription\": \"-- 무료 구독 추가 --\",\n  \"currently_impersonating\": \"현재 가장 중\",\n  \"user_1\": \"사용자:\",\n  \"drag_n_drop_some_files_here\": \"여기에 파일을 드래그 앤 드롭하세요\",\n  \"add_time_slot\": \"시간대 추가\",\n  \"add_slot\": \"슬롯 추가\",\n  \"cancel_publication\": \"게시 취소\",\n  \"statistics\": \"통계\",\n  \"loading\": \"로딩 중\",\n  \"short_link\": \"단축 링크\",\n  \"original_link\": \"원본 링크\",\n  \"clicks\": \"클릭수\",\n  \"selected_customer\": \"선택된 고객\",\n  \"customer\": \"고객:\",\n  \"repeat_post_every\": \"게시 반복 주기...\",\n  \"use_this_media\": \"이 미디어 사용\",\n  \"create_new_post\": \"게시물 만들기\",\n  \"update_post\": \"기존 게시물 업데이트\",\n  \"merge_comments_into_one_post\": \"댓글을 하나의 게시물로 합치기\",\n  \"accounts_that_will_engage\": \"참여할 계정:\",\n  \"day\": \"일\",\n  \"week\": \"주\",\n  \"month\": \"월\",\n  \"remove_from_customer\": \"고객에서 제거\",\n  \"show_more\": \"+ 더 보기\",\n  \"show_less\": \"- 간략히 보기\",\n  \"upload\": \"업로드\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"채널 추가\",\n  \"add_platform\": \"플랫폼 추가\",\n  \"articles\": \"기사\",\n  \"add_comment\": \"댓글 추가\",\n  \"add_post\": \"스레드에 게시물 추가\",\n  \"add_comment_or_post\": \"댓글 / 게시물 추가\",\n  \"you_are_in_global_editing_mode\": \"현재 전체 편집 모드입니다\",\n  \"the_post_should_be_at_least_6_characters_long\": \"게시글은 최소 6자 이상이어야 합니다\",\n  \"are_you_sure_you_want_to_delete_post\": \"이 게시물을 삭제하시겠습니까?\",\n  \"post_deleted_successfully\": \"게시물이 성공적으로 삭제되었습니다\",\n  \"delete_post\": \"게시글 삭제\",\n  \"save_as_draft\": \"임시 저장\",\n  \"post_now\": \"지금 게시\",\n  \"please_add\": \"추가해 주세요\",\n  \"to_your_telegram_group_channel_and_click_here\": \"텔레그램 그룹/채널에 추가하고 여기를 클릭하세요:\",\n  \"connect_telegram\": \"텔레그램 연결\",\n  \"please_add_the_following_command_in_your_chat\": \"채팅에 다음 명령어를 추가해 주세요:\",\n  \"copy\": \"복사\",\n  \"settings\": \"설정\",\n  \"integrations\": \"통합\",\n  \"add_integration\": \"통합 추가\",\n  \"you_are_now_editing_only\": \"현재 이 항목만 편집 중입니다\",\n  \"tag_a_company\": \"회사 태그하기\",\n  \"video_length_is_invalid_must_be_up_to\": \"영상 길이가 잘못되었습니다. 최대\",\n  \"seconds\": \"초까지 가능합니다\",\n  \"this_feature_available_only_for_photos\": \"이 기능은 사진에만 사용할 수 있으며, 기본 음악이 추가됩니다. 나중에 변경할 수 있습니다.\",\n  \"allow_user_to\": \"사용자에게 허용:\",\n  \"your_video_will_be_labeled_promotional\": \"동영상에 \\\"프로모션 콘텐츠\\\" 라벨이 표시됩니다.\",\n  \"this_cannot_be_changed_once_posted\": \"게시 후에는 변경할 수 없습니다.\",\n  \"turn_on_to_disclose_video_promotes\": \"이 동영상이 대가를 받고 상품 또는 서비스를 홍보함을 공개하려면 켜세요. 이 동영상은 본인, 제3자 또는 둘 다를 홍보할 수 있습니다.\",\n  \"you_are_promoting_yourself\": \"본인 또는 본인 브랜드를 홍보하고 있습니다.\",\n  \"this_video_will_be_classified_brand_organic\": \"이 동영상은 브랜드 오가닉으로 분류됩니다.\",\n  \"you_are_promoting_another_brand\": \"다른 브랜드 또는 제3자를 홍보하고 있습니다.\",\n  \"this_video_will_be_classified_branded_content\": \"이 동영상은 브랜드드 콘텐츠로 분류됩니다.\",\n  \"by_posting_you_agree_to_tiktoks\": \"게시함으로써 TikTok의 약관에 동의하게 됩니다.\",\n  \"music_usage_confirmation\": \"음악 사용 확인\",\n  \"branded_content_policy\": \"브랜드 콘텐츠 정책\",\n  \"select_1\": \"--선택--\",\n  \"select_flair\": \"--플레어 선택--\",\n  \"link\": \"링크\",\n  \"add_subreddit\": \"서브레딧 추가\",\n  \"please_add_at_least_one_subreddit\": \"최소한 하나의 서브레딧을 추가해 주세요.\",\n  \"add_community\": \"커뮤니티 추가\",\n  \"select_post_type\": \"게시물 유형 선택...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"연결된 LinkedIn 페이지에 연결된 비즈니스를 찾을 수 없습니다.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"이 대화 상자를 닫고, 새 페이지를 만든 후 새 채널을 다시 추가해 주세요.\",\n  \"select_linkedin_page\": \"LinkedIn 페이지 선택:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"선택한 페이지에 연결된 비즈니스를 찾을 수 없습니다.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"모든 페이지와 모든 비즈니스를 연결하는 것을 권장합니다.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"이 대화 상자를 닫고, 통합을 삭제한 후 새 채널을 다시 추가해 주세요.\",\n  \"select_instagram_account\": \"Instagram 계정 선택:\",\n  \"select_page\": \"페이지 선택:\",\n  \"generate_image_with_ai\": \"AI로 이미지 생성\",\n  \"reconnect_channel\": \"채널 다시 연결\",\n  \"update_credentials\": \"자격 증명 업데이트\",\n  \"additional_settings\": \"추가 설정\",\n  \"change_bot\": \"봇 변경\",\n  \"move_add_to_customer\": \"고객으로 이동/추가\",\n  \"edit_time_slots\": \"시간대 편집\",\n  \"enable_channel\": \"채널 활성화\",\n  \"disable_channel\": \"채널 비활성화\",\n  \"add\": \"추가\",\n  \"short_post\": \"짧은 게시물\",\n  \"long_post\": \"긴 게시물\",\n  \"a_thread_with_short_posts\": \"짧은 게시물로 이루어진 스레드\",\n  \"a_thread_with_long_posts\": \"긴 게시물로 이루어진 스레드\",\n  \"personal_voice_i_am_happy_to_announce\": \"개인 목소리(\\\"기쁘게 소식을 전합니다\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"회사 목소리(\\\"기쁘게 소식을 전해드립니다\\\")\",\n  \"generate\": \"생성\",\n  \"generate_posts\": \"게시물 생성\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"SOL($199)로 평생 PRO 계정을 구매하세요. 이 구매는 환불이 불가하니 유의해 주세요.\",\n  \"purchase_now\": \"지금 구매하기\",\n  \"pay_today\": \"오늘 결제하기\",\n  \"we_are_sorry_to_see_you_go\": \"떠나셔서 아쉽습니다 :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"저희가 더 잘할 수 있었던 점을 간단히 말씀해주실 수 있나요?\",\n  \"cancel_subscription\": \"구독 취소\",\n  \"plans\": \"요금제\",\n  \"monthly\": \"월간\",\n  \"yearly\": \"연간\",\n  \"reactivate_subscription\": \"구독 재활성화\",\n  \"update_payment_method_invoices_history\": \"결제 수단/청구 내역 업데이트\",\n  \"cancel_subscription_1\": \"구독 취소\",\n  \"your_subscription_will_be_canceled_at\": \"구독이 다음 날짜에 취소됩니다:\",\n  \"you_will_never_be_charged_again\": \"더 이상 결제되지 않습니다\",\n  \"current_package\": \"현재 패키지:\",\n  \"next_package\": \"다음 패키지:\",\n  \"claim\": \"청구\",\n  \"frequently_asked_questions\": \"자주 묻는 질문\",\n  \"autopost\": \"자동 게시\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"자동 게시 기능은 RSS의 새 항목을 소셜 미디어에 자동으로 게시할 수 있습니다.\",\n  \"title\": \"제목\",\n  \"add_an_autopost\": \"자동 게시 추가\",\n  \"post_content\": \"게시물 내용\",\n  \"sign_up\": \"회원가입\",\n  \"or\": \"또는\",\n  \"by_registering_you_agree_to_our\": \"회원가입을 통해 다음에 동의합니다:\",\n  \"and\": \"및\",\n  \"terms_of_service\": \"이용약관\",\n  \"privacy_policy\": \"개인정보 처리방침\",\n  \"create_account\": \"계정 만들기\",\n  \"already_have_an_account\": \"이미 계정이 있으신가요?\",\n  \"sign_in\": \"로그인\",\n  \"sign_in_1\": \"로그인\",\n  \"don_t_have_an_account\": \"계정이 없으신가요?\",\n  \"forgot_password\": \"비밀번호 찾기\",\n  \"forgot_password_1\": \"비밀번호 찾기\",\n  \"send_password_reset_email\": \"비밀번호 재설정 이메일 보내기\",\n  \"go_back_to_login\": \"로그인 화면으로 돌아가기\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"비밀번호를 재설정할 수 있는 링크가 포함된 이메일을 보냈습니다.\",\n  \"change_password\": \"비밀번호 변경\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"비밀번호가 성공적으로 재설정되었습니다. 이제 로그인하실 수 있습니다.\",\n  \"click_here_to_go_back_to_login\": \"로그인 화면으로 돌아가려면 여기를 클릭하세요\",\n  \"activate_your_account\": \"계정 활성화\",\n  \"thank_you_for_registering\": \"회원가입해 주셔서 감사합니다!\",\n  \"please_check_your_email_to_activate_your_account\": \"계정 활성화를 위해 이메일을 확인해 주세요.\",\n  \"sign_in_with\": \"다음으로 로그인\",\n  \"continue_with_google\": \"Google로 계속하기\",\n  \"sign_in_with_github\": \"GitHub로 로그인\",\n  \"continue_with_farcaster\": \"Farcaster로 계속하기\",\n  \"continue_with_your_wallet\": \"지갑으로 계속하기\",\n  \"stars_per_day\": \"일일 별점\",\n  \"media\": \"미디어\",\n  \"check_launch\": \"런치 확인\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"설정에서 GitHub 저장소를 불러와 분석 정보를 확인하세요\",\n  \"stars\": \"별점\",\n  \"processing_stars\": \"별점 처리 중...\",\n  \"forks\": \"포크\",\n  \"registration_is_disabled\": \"회원가입이 비활성화되었습니다\",\n  \"login_instead\": \"대신 로그인하기\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"대화를 선택하고 채팅을 시작하세요.\",\n  \"adding_channel_redirecting_you\": \"채널 추가 중, 리디렉션합니다\",\n  \"could_not_add_provider\": \"공급자를 추가할 수 없습니다.\",\n  \"you_are_being_redirected_back\": \"이전 페이지로 리디렉션됩니다.\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"문제가 발생했습니다. 페이지를 새로고침해 주세요.\",\n  \"post_not_found\": \"게시물을 찾을 수 없습니다.\",\n  \"publication_date\": \"게시일:\",\n  \"analytics\": \"분석\",\n  \"launches\": \"출시\",\n  \"plugs\": \"홍보\",\n  \"billing\": \"결제\",\n  \"affiliate\": \"제휴\",\n  \"monday\": \"월요일\",\n  \"tuesday\": \"화요일\",\n  \"wednesday\": \"수요일\",\n  \"thursday\": \"목요일\",\n  \"friday\": \"금요일\",\n  \"saturday\": \"토요일\",\n  \"sunday\": \"일요일\",\n  \"can_t_change_date_remove_post_from_publication\": \"날짜를 변경할 수 없습니다. 게시물에서 제거해 주세요.\",\n  \"predicted_github_trending_change\": \"예상 GitHub 트렌드 변화\",\n  \"duplicate_post\": \"중복 게시물\",\n  \"preview_post\": \"게시물 미리보기\",\n  \"post_statistics\": \"게시물 통계\",\n  \"draft\": \"임시 저장\",\n  \"week_number\": \"{{number}}주차\",\n  \"top_title_edit_webhook\": \"웹훅 편집\",\n  \"top_title_add_webhook\": \"웹훅 추가\",\n  \"top_title_oh_no\": \"이런\",\n  \"top_title_auto_plug\": \"자동 플러그: {{title}}\",\n  \"top_title_edit_autopost\": \"자동 게시물 편집\",\n  \"top_title_add_autopost\": \"자동 게시물 추가\",\n  \"top_title_send_a_new_offer\": \"새로운 제안 보내기\",\n  \"top_title_media_library\": \"미디어 라이브러리\",\n  \"top_title_add_signature\": \"서명 추가\",\n  \"top_title_send_a_message_to\": \"{{name}}님에게 메시지 보내기\",\n  \"top_title_configure_provider\": \"공급자 구성\",\n  \"top_title_add_member\": \"멤버 추가\",\n  \"top_title_change_bot_picture\": \"봇 사진 변경\",\n  \"top_title_create_a_new_tag\": \"새 태그 만들기\",\n  \"top_title_select_company\": \"회사 선택\",\n  \"top_title_additional_settings\": \"추가 설정\",\n  \"top_title_time_table_slots\": \"시간표 슬롯\",\n  \"top_title_design_media\": \"미디어 디자인\",\n  \"top_title_edit_post\": \"게시물 편집\",\n  \"top_title_create_post\": \"게시물 만들기\",\n  \"top_title_move__add_to_customer\": \"고객에게 이동/추가\",\n  \"top_title_add_api_key_for\": \"{{name}}의 API 키 추가\",\n  \"top_title_instance_url\": \"인스턴스 URL\",\n  \"top_title_custom_url\": \"커스텀 URL\",\n  \"top_title_add_channel\": \"채널 추가\",\n  \"top_title_add_telegram\": \"텔레그램 추가\",\n  \"top_title_add_wrapcast\": \"랩캐스트 추가\",\n  \"top_title_comments_for\": \"{{date}}의 댓글\",\n  \"top_title_edit_signature\": \"서명 편집\",\n  \"label_name\": \"이름\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"직함\",\n  \"label_subtitle\": \"부제목\",\n  \"label_email\": \"이메일\",\n  \"label_full_name\": \"전체 이름\",\n  \"label_password\": \"비밀번호\",\n  \"label_confirm_password\": \"비밀번호 확인\",\n  \"label_api_key\": \"API 키\",\n  \"label_instance_url\": \"인스턴스 URL\",\n  \"label_custom_url\": \"사용자 지정 URL\",\n  \"label_feedback\": \"피드백\",\n  \"label_bio\": \"소개\",\n  \"label_role\": \"역할\",\n  \"label_country\": \"국가\",\n  \"label_audience_size\": \"모든 플랫폼의 청중 규모\",\n  \"label_pick_time\": \"시간 선택\",\n  \"label_nickname\": \"별명\",\n  \"label_write_anything\": \"아무거나 작성하세요\",\n  \"label_output_format\": \"출력 형식\",\n  \"label_add_pictures\": \"사진 추가할까요?\",\n  \"label_hour\": \"시\",\n  \"label_minutes\": \"분\",\n  \"label_select_publication\": \"출판물 선택\",\n  \"label_canonical_link\": \"정식 링크\",\n  \"label_cover_picture\": \"커버 사진\",\n  \"label_tags\": \"태그\",\n  \"label_topics\": \"주제\",\n  \"label_tags_maximum_4\": \"태그 (최대 4개)\",\n  \"label_attachments\": \"첨부파일\",\n  \"label_type\": \"유형\",\n  \"label_thumbnail\": \"썸네일\",\n  \"label_who_can_see_this_video\": \"이 영상을 볼 수 있는 사람\",\n  \"label_content_posting_method\": \"콘텐츠 게시 방법\",\n  \"label_auto_add_music\": \"자동 음악 추가\",\n  \"label_duet\": \"듀엣\",\n  \"label_stitch\": \"스티치\",\n  \"label_comments\": \"댓글\",\n  \"label_disclose_video_content\": \"영상 내용 공개\",\n  \"label_your_brand\": \"당신의 브랜드\",\n  \"label_branded_content\": \"브랜드 콘텐츠\",\n  \"label_subreddit\": \"서브레딧\",\n  \"label_flair\": \"플레어\",\n  \"label_media\": \"미디어\",\n  \"label_search_subreddit\": \"서브레딧 검색\",\n  \"label_delay\": \"지연\",\n  \"label_post_type\": \"게시물 유형\",\n  \"label_collaborators\": \"협업자 (최대 3명) - 계정은 비공개일 수 없음\",\n  \"label_community\": \"커뮤니티\",\n  \"label_search_community\": \"커뮤니티 검색\",\n  \"label_channel\": \"채널\",\n  \"label_search_channel\": \"채널 검색\",\n  \"label_select_channel\": \"채널 선택\",\n  \"label_new_password\": \"새 비밀번호\",\n  \"label_repeat_password\": \"비밀번호 재입력\",\n  \"label_platform\": \"플랫폼\",\n  \"label_price_per_post\": \"게시글당 가격\",\n  \"label_integrations\": \"연동\",\n  \"label_code\": \"코드\",\n  \"label_should_sync_last_post\": \"현재 마지막 게시글을 동기화할까요?\",\n  \"label_when_post\": \"언제 게시할까요?\",\n  \"label_autogenerate_content\": \"콘텐츠 자동 생성\",\n  \"label_generate_picture\": \"이미지 생성할까요?\",\n  \"label_company\": \"회사\",\n  \"label_tag_color\": \"태그 색상\",\n  \"label_select_board\": \"게시판 선택\",\n  \"label_select_organization\": \"조직 선택\",\n  \"label_auto_add_signature\": \"서명 자동 추가할까요?\",\n  \"enable_color_picker\": \"색상 선택기 활성화\",\n  \"cancel_the_color_picker\": \"색상 선택기 취소\",\n  \"no_content_yet\": \"아직 콘텐츠가 없습니다\",\n  \"write_your_reply\": \"게시글을 작성하세요...\",\n  \"add_a_tag\": \"태그 추가\",\n  \"add_to_calendar\": \"캘린더에 추가\",\n  \"select_channels_from_circles\": \"위의 원에서 채널을 선택하세요\",\n  \"not_matching_order\": \"순서가 일치하지 않음\",\n  \"submit_for_order\": \"주문 제출\",\n  \"schedule\": \"일정\",\n  \"update\": \"업데이트\",\n  \"attachments\": \"첨부파일\",\n  \"tags\": \"태그\",\n  \"public_to_everyone\": \"모두에게 공개\",\n  \"mutual_follow_friends\": \"맞팔 친구\",\n  \"follower_of_creator\": \"크리에이터의 팔로워\",\n  \"self_only\": \"나만 보기\",\n  \"post_content_directly_to_tiktok\": \"콘텐츠를 TikTok에 바로 게시하기\",\n  \"upload_content_to_tiktok_without_posting\": \"게시하지 않고 TikTok에 콘텐츠 업로드하기\",\n  \"choose_upload_without_posting_description\": \"게시하지 않고 업로드를 선택하면 TikTok 앱 내에서 콘텐츠를 검토하고 편집한 후 게시할 수 있습니다. TikTok의 내장 편집 도구를 사용할 수 있으며, 게시 전에 최종 수정을 할 수 있습니다.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Postiz에서 요금이 청구되나요?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"신용카드 정보를 확인하기 위해 Postiz는 $2를 보류했다가 즉시 해제합니다. 설정에서 언제든지 사람과 대화하지 않고 구독을 취소할 수 있습니다.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Postiz를 신뢰할 수 있나요?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz는 자랑스럽게 오픈소스입니다! 우리는 윤리적이고 투명한 문화를 믿으며, Postiz는 영원히 존재할 것입니다. 전체 코드를 확인하거나 개인 프로젝트에 사용할 수 있습니다. 오픈소스 저장소를 보려면 <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">여기를 클릭하세요</a>.\",\n  \"faq_what_are_channels\": \"채널이란 무엇인가요?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz를 통해 다양한 채널에 게시물을 예약할 수 있습니다.\\n채널은 게시물을 예약할 수 있는 게시 플랫폼입니다.\\n예를 들어, X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads, Pinterest에 게시물을 예약할 수 있습니다.\",\n  \"faq_what_are_team_members\": \"팀 멤버란 무엇인가요?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"여러 명의 팀이 있다면, 워크스페이스에 초대하여 게시물 협업 및 개인 채널 추가가 가능합니다.\",\n  \"faq_what_is_ai_auto_complete\": \"AI 자동완성이란 무엇인가요?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"ChatGPT를 자동화하여 소셜 게시물과 글쓰기를 도와드립니다.\",\n  \"enter_email\": \"이메일 입력\",\n  \"are_you_sure\": \"정말로 하시겠습니까?\",\n  \"no_cancel\": \"아니요, 취소하세요!\",\n  \"are_you_sure_you_want_to_delete\": \"{{name}}을(를) 삭제하시겠습니까?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"이 이미지를 삭제하시겠습니까?\",\n  \"are_you_sure_you_want_to_logout\": \"로그아웃하시겠습니까?\",\n  \"yes_logout\": \"네, 로그아웃\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"이 슬롯을 삭제하시겠습니까?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"이 서브레딧을 삭제하시겠습니까?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"이 창을 닫으시겠습니까?\",\n  \"yes_close\": \"네, 닫기\",\n  \"link_copied_to_clipboard\": \"링크가 클립보드에 복사되었습니다\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"이 모달을 닫으시겠습니까? (모든 데이터가 사라집니다)\",\n  \"yes_close_it\": \"네, 닫으세요!\",\n  \"uploading_pictures\": \"사진 업로드 중...\",\n  \"agent_starting\": \"에이전트 시작 중\",\n  \"researching_your_content\": \"콘텐츠 조사 중...\",\n  \"understanding_the_category\": \"카테고리 이해 중...\",\n  \"finding_the_topic\": \"주제 찾는 중...\",\n  \"finding_popular_posts_to_match_with\": \"연관 인기 게시글 찾는 중...\",\n  \"generating_hook\": \"후킹 문구 생성 중...\",\n  \"generating_content\": \"콘텐츠 생성 중...\",\n  \"generating_pictures\": \"사진 생성 중...\",\n  \"finding_time_to_post\": \"게시할 시간 찾는 중...\",\n  \"write_anything\": \"아무거나 작성하세요\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"원하는 내용을 자유롭게 작성하고 링크도 추가할 수 있습니다. 저희가 대신 조사해드릴게요...\",\n  \"output_format\": \"출력 형식\",\n  \"add_pictures\": \"사진 추가하기?\",\n  \"7_days\": \"7일\",\n  \"30_days\": \"30일\",\n  \"90_days\": \"90일\",\n  \"start_7_days_free_trial\": \"7일 무료 체험 시작하기\",\n  \"change_language\": \"언어 변경\",\n  \"that_a_wrap\": \"여기까지입니다!\\n\\n이 스레드가 유익하셨다면:\\n\\n1. 더 많은 정보를 원하시면 @{{username}}를 팔로우하세요\\n2. 아래 트윗을 리트윗해서 이 스레드를 여러분의 팔로워들과 공유하세요\\n\",\n  \"post_as_images_carousel\": \"이미지 캐러셀로 게시\",\n  \"save_set\": \"세트 저장\",\n  \"separate_post\": \"게시물을 여러 개로 분리\",\n  \"label_who_can_reply_to_this_post\": \"이 게시물에 누가 답글을 달 수 있나요?\",\n  \"delete_integration\": \"통합 삭제\",\n  \"start_writing_your_post\": \"미리보기를 위해 게시글 작성을 시작하세요\",\n  \"billing_join_over\": \"이미 가입한\",\n  \"billing_entrepreneurs_count\": \"20,000+명의 창업가\",\n  \"billing_who_use\": \"이(가) 사용하는\",\n  \"billing_postiz_grow_social\": \"Postiz로 소셜 존재감을 키우세요\",\n  \"billing_no_risk_trial\": \"100% 무위험 무료 체험\",\n  \"billing_pay_nothing_7_days\": \"처음 7일 동안은 결제 없이 이용하세요\",\n  \"billing_cancel_anytime\": \"언제든지 설정에서 취소할 수 있습니다\",\n  \"billing_choose_plan\": \"요금제 선택\",\n  \"billing_monthly\": \"월간\",\n  \"billing_yearly\": \"연간\",\n  \"billing_20_percent_off\": \"20% 할인\",\n  \"billing_features\": \"기능\",\n  \"billing_channel\": \"채널\",\n  \"billing_channels\": \"채널\",\n  \"billing_unlimited\": \"무제한\",\n  \"billing_posts_per_month\": \"월별 게시물\",\n  \"billing_unlimited_team_members\": \"무제한 팀원\",\n  \"billing_ai_auto_complete\": \"AI 자동완성\",\n  \"billing_ai_copilots\": \"AI 코파일럿\",\n  \"billing_ai_autocomplete\": \"AI 자동완성\",\n  \"billing_advanced_picture_editor\": \"고급 사진 편집기\",\n  \"billing_ai_images_per_month\": \"월별 AI 이미지\",\n  \"billing_ai_videos_per_month\": \"월별 AI 비디오\",\n  \"billing_billing_address\": \"청구 주소\",\n  \"billing_payment\": \"결제\",\n  \"billing_powered_by_stripe\": \"안전한 결제는 Stripe에서 처리됩니다\",\n  \"billing_your_7_day_trial_is\": \"7일 무료 체험이\",\n  \"billing_100_percent_free\": \"100% 무료\",\n  \"billing_ending\": \"종료됩니다\",\n  \"billing_cancel_anytime_short\": \"설정에서 언제든지 취소할 수 있습니다\",\n  \"billing_pay_0_start_trial\": \"오늘 $0 결제 - 무료 체험을 시작하세요!\",\n  \"billing_pay_now\": \"지금 결제\",\n  \"billing_per_month\": \"/월\",\n  \"billing_per_year\": \"/ 연\",\n  \"billing_order_summary\": \"주문 요약\",\n  \"billing_applied\": \"적용됨\",\n  \"billing_due_today\": \"오늘 결제 금액\",\n  \"billing_then\": \"그 다음\",\n  \"billing_on\": \"~에\",\n  \"billing_discount_applied\": \"할인 적용됨\",\n  \"billing_remove\": \"제거\",\n  \"billing_coupon_expires\": \"쿠폰 만료일:\",\n  \"billing_invalid_coupon\": \"유효하지 않은 쿠폰 코드\",\n  \"billing_coupon_applied\": \"쿠폰이 성공적으로 적용되었습니다!\",\n  \"billing_coupon_removed\": \"쿠폰이 제거되었습니다\",\n  \"billing_error_removing_coupon\": \"쿠폰 제거 중 오류가 발생했습니다\",\n  \"billing_have_discount_coupon\": \"할인 쿠폰이 있으신가요?\",\n  \"billing_discount_coupon\": \"할인 쿠폰\",\n  \"billing_cancel\": \"취소\",\n  \"billing_enter_coupon_code\": \"쿠폰 코드를 입력하세요\",\n  \"billing_applying\": \"적용 중...\",\n  \"billing_apply\": \"적용\",\n  \"billing_subscription\": \"구독\",\n  \"billing_cancel_notice\": \"설정에서 언제든지 사람과 대화하지 않고 구독을 취소할 수 있으며, 결제가 청구되지 않습니다.\",\n  \"select_channels\": \"채널 선택\",\n  \"start_a_new_chat\": \"새 채팅 시작\",\n  \"your_assistant\": \"당신의 어시스턴트\",\n  \"agent_welcome_message\": \"안녕하세요, 저는 Postiz 에이전트입니다 🙌🏻.\\n\\n저는 하나 또는 여러 개의 게시물을 여러 채널에 예약 게시할 수 있고, 사진과 동영상을 생성할 수 있습니다.\\n\\n왼쪽 메뉴에서 사용하고 싶은 채널을 선택할 수 있습니다.\\n\\n오른쪽 메뉴에서 이전 대화를 볼 수 있습니다.\\n\\n또한 저를 MCP 서버로 사용할 수 있으며, 설정 >> 공개 API에서 확인할 수 있습니다.\",\n  \"last_github_trending\": \"최근 깃허브 트렌딩\",\n  \"next_predicted_github_trending\": \"다음 예측 깃허브 트렌딩\",\n  \"repository\": \"저장소\",\n  \"date\": \"날짜\",\n  \"total_stars\": \"총 별점\",\n  \"total_forks\": \"총 포크\",\n  \"continue_with\": \"다음으로 계속\",\n  \"email_address\": \"이메일 주소\",\n  \"email_already_exists\": \"이미 존재하는 이메일입니다\",\n  \"google\": \"구글\",\n  \"farcaster\": \"파캐스터\",\n  \"edit_autopost\": \"자동 게시 수정\",\n  \"add_autopost_title\": \"자동 게시 추가\",\n  \"webhook_deleted_successfully\": \"웹훅이 성공적으로 삭제되었습니다\",\n  \"all_integrations\": \"모든 통합\",\n  \"specific_integrations\": \"특정 통합\",\n  \"post_on_next_available_slot\": \"다음 사용 가능한 시간에 게시\",\n  \"post_immediately\": \"즉시 게시\",\n  \"could_not_use_rss_feed\": \"이 RSS 피드를 사용할 수 없습니다\",\n  \"rss_valid\": \"RSS가 유효합니다!\",\n  \"autopost_updated_successfully\": \"자동 게시가 성공적으로 업데이트되었습니다\",\n  \"autopost_added_successfully\": \"자동 게시가 성공적으로 추가되었습니다\",\n  \"write_your_post_placeholder\": \"게시글을 작성하세요...\",\n  \"select_or_upload_pictures_max_5\": \"사진을 선택하거나 업로드하세요 (최대 5장까지 한 번에 가능합니다).\",\n  \"you_can_drag_drop_pictures\": \"사진을 드래그 앤 드롭하여 추가할 수도 있습니다.\",\n  \"you_dont_have_any_media_yet\": \"아직 미디어가 없습니다\",\n  \"media_library\": \"미디어 라이브러리\",\n  \"media_settings\": \"미디어 설정\",\n  \"media_editor\": \"미디어 편집기\",\n  \"close\": \"닫기\",\n  \"me\": \"나\",\n  \"noname\": \"이름 없음\",\n  \"password_reset_link_expired\": \"비밀번호 재설정 링크가 만료되었습니다. 다시 시도해 주세요.\",\n  \"invalid_api_key\": \"잘못된 API 키입니다\",\n  \"could_not_connect_to_platform\": \"플랫폼에 연결할 수 없습니다\",\n  \"web3_provider\": \"Web3 제공자\",\n  \"add_provider_title\": \"제공자 추가\",\n  \"profile_updated\": \"프로필이 업데이트되었습니다\",\n  \"sets\": \"세트\",\n  \"invitation_link_sent\": \"초대 링크가 전송되었습니다\",\n  \"send_invitation_link\": \"초대 링크 보내기\",\n  \"copy_link\": \"링크 복사\",\n  \"are_you_sure_remove_team_member\": \"이 팀 멤버를 삭제하시겠습니까?\",\n  \"admin\": \"관리자\",\n  \"super_admin\": \"슈퍼 관리자\",\n  \"update_webhook\": \"웹훅 업데이트\",\n  \"add_webhook\": \"웹훅 추가\",\n  \"webhook_updated_successfully\": \"웹훅이 성공적으로 업데이트되었습니다\",\n  \"webhook_added_successfully\": \"웹훅이 성공적으로 추가되었습니다\",\n  \"webhook_sent\": \"웹훅 전송됨\",\n  \"today\": \"오늘\",\n  \"channel_disconnected_click_to_reconnect\": \"채널이 연결 해제되었습니다. 다시 연결하려면 클릭하세요.\",\n  \"channel_disabled_upgrade_plan\": \"이 채널은 비활성화되어 있습니다. 활성화하려면 요금제를 업그레이드하세요.\",\n  \"channel_added\": \"채널이 추가되었습니다\",\n  \"are_you_sure_disable_channel\": \"이 채널을 비활성화하시겠습니까?\",\n  \"disable_channel_title\": \"채널 비활성화\",\n  \"channel_disabled\": \"채널이 비활성화되었습니다\",\n  \"are_you_sure_delete_channel\": \"이 채널을 삭제하시겠습니까?\",\n  \"delete_channel_title\": \"채널 삭제\",\n  \"delete_posts_before_channel\": \"이 채널을 삭제하기 전에 관련된 모든 게시물을 삭제해야 합니다\",\n  \"channel_deleted\": \"채널이 삭제되었습니다\",\n  \"channel_enabled\": \"채널이 활성화되었습니다\",\n  \"time_table_slots\": \"시간표 슬롯\",\n  \"channel_id_copied\": \"채널 ID가 클립보드에 복사되었습니다\",\n  \"settings_updated\": \"설정이 업데이트되었습니다\",\n  \"customer_updated\": \"고객 정보가 업데이트되었습니다\",\n  \"custom_url\": \"맞춤 URL\",\n  \"picture\": \"사진\",\n  \"upgrade_required\": \"이 기능을 사용하려면 업그레이드가 필요합니다\",\n  \"move_to_billing\": \"결제 페이지로 이동\",\n  \"payment_required\": \"결제가 필요합니다\",\n  \"no_content\": \"내용 없음\",\n  \"select_customer_tooltip\": \"고객 선택\",\n  \"customers\": \"고객\",\n  \"hour\": \"시간\",\n  \"minutes\": \"분\",\n  \"updated\": \"업데이트됨\",\n  \"change_bot_picture_title\": \"봇 사진 변경\",\n  \"select_customer_label\": \"고객 선택\",\n  \"start_typing\": \"입력 시작...\",\n  \"choose_set_or_continue\": \"세트를 선택하거나 세트 없이 계속하세요\",\n  \"continue_without_set\": \"세트 없이 계속하기\",\n  \"select_set\": \"세트 선택\",\n  \"channel_settings\": \"설정\",\n  \"post_needs_content_or_image\": \"게시물에는 최소 한 글자 또는 이미지가 있어야 합니다.\",\n  \"please_fix_your_settings\": \"설정을 수정해 주세요\",\n  \"shortlink_urls_question\": \"URL을 단축하시겠습니까? 클릭 통계를 확인할 수 있습니다.\",\n  \"yes_shortlink_it\": \"네, 단축하세요!\",\n  \"added_successfully\": \"성공적으로 추가되었습니다\",\n  \"updated_successfully\": \"성공적으로 업데이트되었습니다\",\n  \"create_post_title\": \"게시물 작성\",\n  \"post_preview\": \"게시물 미리보기\",\n  \"check_circles_above\": \"위의 원을 확인하세요\",\n  \"create_output\": \"결과 생성\",\n  \"assistant_initial_message\": \"안녕하세요! 소셜 미디어 게시물을 다듬는 데 도와드릴 수 있습니다.\",\n  \"no_longer_global_mode\": \"글로벌 모드가 해제되었습니다\",\n  \"two_days\": \"이틀\",\n  \"three_days\": \"삼일\",\n  \"four_days\": \"나흘\",\n  \"five_days\": \"닷새\",\n  \"six_days\": \"엿새\",\n  \"two_weeks\": \"2주\",\n  \"repeat_post_every_label\": \"게시 반복 주기\",\n  \"add_new_tag\": \"새 태그 추가\",\n  \"tag_name\": \"이름\",\n  \"post_is_too_long\": \"게시글이 너무 깁니다. 수정해 주세요.\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"게시물에는 최소한 한 글자 또는 한 이미지를 포함해야 합니다.\",\n  \"internal_edit\": \"내부 편집\",\n  \"are_you_sure_go_back_to_global_mode\": \"이 작업은 되돌릴 수 없습니다. 정말로 글로벌 모드로 돌아가시겠습니까?\",\n  \"yes_go_back_to_global_mode\": \"네, 글로벌 모드로 돌아갑니다\",\n  \"are_you_sure_delete_this_post\": \"이 게시물을 삭제하시겠습니까?\",\n  \"yes_delete_it\": \"네, 삭제하세요!\",\n  \"cant_edit_networks_when_creating_set\": \"세트를 생성할 때는 네트워크를 편집할 수 없습니다.\",\n  \"click_to_exit_global_editing\": \"이 버튼을 클릭하면 글로벌 편집을 종료하고 이 채널에 맞게 게시물을 맞춤 설정할 수 있습니다.\",\n  \"edit_content\": \"내용 편집\",\n  \"editing_a_specific_network\": \"특정 네트워크 편집 중\",\n  \"back_to_global\": \"글로벌로 돌아가기\",\n  \"delete_post_tooltip\": \"게시물 삭제\",\n  \"drop_files_here_to_upload\": \"여기에 파일을 끌어다 놓아 업로드하세요\",\n  \"insert_emoji\": \"이모지 삽입\",\n  \"write_something\": \"무엇인가를 작성하세요 …\",\n  \"click_channel_to_add\": \"채널을 추가하려면 클릭하세요\",\n  \"connect_your_channels\": \"채널 연결하기\",\n  \"connect_social_media_to_start\": \"게시물 예약을 시작하려면 소셜 미디어 계정을 연결하세요\",\n  \"connected_channels\": \"연결된 채널\",\n  \"continue\": \"계속하기\",\n  \"continue_without_channels\": \"채널 없이 계속하기\",\n  \"watch_tutorial\": \"튜토리얼 보기\",\n  \"watch_tutorial_title\": \"Postiz 사용법 배우기\",\n  \"watch_tutorial_description\": \"이 짧은 영상을 통해 Postiz를 최대한 활용하는 방법을 알아보세요\",\n  \"back\": \"뒤로가기\",\n  \"get_started\": \"시작하기\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/pt/translation.json",
    "content": "{\n  \"calendar\": \"Calendário\",\n  \"webhooks\": \"Webhooks\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Webhooks são uma forma de ser notificado quando algo acontece no Postiz via uma solicitação HTTP.\",\n  \"name\": \"Nome\",\n  \"url\": \"URL\",\n  \"edit\": \"Editar\",\n  \"delete\": \"Excluir\",\n  \"add_a_webhook\": \"Adicionar um webhook\",\n  \"save\": \"Salvar\",\n  \"send_test\": \"Enviar teste\",\n  \"select_role\": \"Selecionar função\",\n  \"video_made_with_ai\": \"Vídeo feito com IA\",\n  \"please_add_at_least\": \"Por favor, adicione pelo menos 20 caracteres\",\n  \"send_invitation_via_email\": \"Enviar convite por e-mail?\",\n  \"global_settings\": \"Configurações Globais\",\n  \"copy_id\": \"Copiar ID do canal\",\n  \"team_members\": \"Membros da equipe\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Convide seu assistente ou membro da equipe para gerenciar sua conta\",\n  \"remove\": \"Remover\",\n  \"add_another_member\": \"Adicionar outro membro\",\n  \"signatures\": \"Assinaturas\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Você pode adicionar assinaturas à sua conta para serem usadas em suas postagens.\",\n  \"content\": \"Conteúdo\",\n  \"auto_add\": \"Adicionar automaticamente?\",\n  \"delay_comment\": \"Comentário de atraso\",\n  \"actions\": \"Ações\",\n  \"use_signature\": \"Usar assinatura\",\n  \"add_a_signature\": \"Adicionar uma assinatura\",\n  \"no\": \"Não\",\n  \"yes\": \"Sim\",\n  \"your_git_repository\": \"Seu repositório Git\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Conecte seu repositório GitHub para receber atualizações e análises\",\n  \"connected\": \"Conectado:\",\n  \"disconnect\": \"Desconectar\",\n  \"connect_your_repository\": \"Conectar seu repositório\",\n  \"cancel\": \"Cancelar\",\n  \"connect\": \"Conectar\",\n  \"public_api\": \"API Pública\",\n  \"check_n8n\": \"Confira nosso node personalizado do N8N para o Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Use a API do Postiz para integrar com suas ferramentas.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Leia como usá-la na documentação.\",\n  \"reveal\": \"Revelar\",\n  \"copy_key\": \"Copiar chave\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Conecte o servidor MCP do Postiz ao seu cliente (transmissão HTTP) para agendar suas postagens mais rapidamente!\",\n  \"share_with_a_client\": \"Compartilhar com um cliente\",\n  \"post\": \"Postar\",\n  \"comments\": \"Comentários\",\n  \"user\": \"Usuário\",\n  \"login_register_to_add_comments\": \"Faça login / Cadastre-se para adicionar comentários\",\n  \"status\": \"Status:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Não há plugs compatíveis com seus canais\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Você precisa adicionar: X ou LinkedIn ou Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Vá ao calendário para adicionar canais\",\n  \"channels\": \"Canais\",\n  \"activate\": \"Ativar\",\n  \"this_channel_needs_to_be_refreshed\": \"Este canal precisa ser atualizado,\",\n  \"click_here_to_refresh\": \"clique aqui para atualizar\",\n  \"can_t_show_analytics_yet\": \"Ainda não é possível mostrar análises\",\n  \"you_have_to_add_social_media_channels\": \"Você precisa adicionar canais de Mídias Sociais\",\n  \"supported\": \"Suportado:\",\n  \"step\": \"ETAPA\",\n  \"skip_onboarding\": \"Pular introdução\",\n  \"onboarding\": \"Introdução\",\n  \"next\": \"Próximo\",\n  \"you_are_done_from_here_you_can\": \"Pronto, a partir daqui você pode:\",\n  \"view_analytics\": \"Ver análises\",\n  \"schedule_a_new_post\": \"Agendar uma nova publicação\",\n  \"to_sell_posts_you_would_have_to\": \"Para vender publicações, você precisa:\",\n  \"1_connect_at_least_one_channel\": \"1. Conectar pelo menos um canal\",\n  \"2_connect_you_bank_account\": \"2. Conectar sua conta bancária\",\n  \"go_back_to_connect_channels\": \"Voltar para conectar canais\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Ir para a página do vendedor para conectar sua conta bancária\",\n  \"connect_channels\": \"Conectar canais\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Conecte seus canais de mídias sociais e plataformas de publicação para\\n            agendar publicações depois\",\n  \"social\": \"Social\",\n  \"publishing_platforms\": \"Plataformas de Publicação\",\n  \"no_channels\": \"Ainda não há canais\",\n  \"connect_your_accounts\": \"Conecte suas contas sociais para começar a agendar, publicar e analisar — tudo em um só lugar.\",\n  \"notifications\": \"Notificações\",\n  \"no_notifications\": \"Nenhuma notificação\",\n  \"send_message\": \"Enviar mensagem\",\n  \"mar_28\": \"28 de mar\",\n  \"there_are_no_messages_yet\": \"Ainda não há mensagens.\",\n  \"checkout_the_marketplace\": \"Confira o Marketplace\",\n  \"go_to_marketplace\": \"Ir para o marketplace\",\n  \"all_messages\": \"Todas as mensagens\",\n  \"previous\": \"Anterior\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Selecione ou envie fotos (máximo de 5 por vez)\",\n  \"you_can_also_drag_drop_pictures\": \"Você também pode arrastar e soltar fotos\",\n  \"you_don_t_have_any_assets_yet\": \"Você ainda não tem nenhum ativo.\",\n  \"click_the_button_below_to_upload_one\": \"Clique no botão abaixo para enviar um\",\n  \"click_the_button_below_to_upload_other\": \"Clique no botão abaixo para enviar vários\",\n  \"add_selected_media\": \"Adicionar mídia selecionada\",\n  \"insert_media\": \"Inserir mídia\",\n  \"design_media\": \"Mídia de design\",\n  \"select\": \"Selecionar\",\n  \"editor\": \"Editor\",\n  \"clear\": \"Limpar\",\n  \"order_completed\": \"Pedido concluído\",\n  \"the_order_has_been_completed\": \"O pedido foi concluído\",\n  \"post_has_been_published\": \"A publicação foi publicada\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Nova oferta\",\n  \"platform\": \"Plataforma\",\n  \"posts\": \"Publicações\",\n  \"pay_accept_offer\": \"Pagar e aceitar oferta\",\n  \"accepted\": \"Aceito\",\n  \"post_draft\": \"Rascunho de Postagem\",\n  \"revision_needed\": \"Revisão Necessária\",\n  \"approve\": \"Aprovar\",\n  \"preview\": \"Pré-visualizar\",\n  \"revision_requested\": \"Revisão Solicitada\",\n  \"accepted_1\": \"ACEITO\",\n  \"cancelled_by_the_seller\": \"Cancelado pelo vendedor\",\n  \"please_select_your_country_where_your_business_is\": \"Por favor, selecione o país onde está o seu negócio.\",\n  \"select_country\": \"--SELECIONE O PAÍS--\",\n  \"connect_bank_account\": \"Conectar Conta Bancária\",\n  \"seller_mode\": \"Modo Vendedor\",\n  \"active\": \"Ativo\",\n  \"details\": \"Detalhes\",\n  \"audience_size\": \"Tamanho do Público\",\n  \"add_another_platform\": \"Adicionar outra plataforma\",\n  \"send_an_offer_for\": \"Enviar uma oferta por $\",\n  \"complete_order_and_pay_early\": \"Concluir pedido e pagar antecipadamente\",\n  \"order_in_progress\": \"Pedido em andamento\",\n  \"create_a_new_offer\": \"Criar uma nova oferta\",\n  \"orders\": \"Pedidos\",\n  \"price\": \"Preço\",\n  \"state\": \"Estado\",\n  \"showing\": \"Mostrando\",\n  \"to\": \"até\",\n  \"from\": \"de\",\n  \"results\": \"Resultados\",\n  \"content_writer\": \"Redator de Conteúdo\",\n  \"influencer\": \"Influenciador\",\n  \"request_service\": \"Solicitar Serviço\",\n  \"the_marketplace_is_not_opened_yet\": \"O marketplace ainda não foi aberto\",\n  \"check_again_soon\": \"Verifique novamente em breve!\",\n  \"filter\": \"Filtro\",\n  \"result\": \"Resultado\",\n  \"seller\": \"Vendedor\",\n  \"buyer\": \"Comprador\",\n  \"discord_support\": \"Suporte no Discord\",\n  \"teams\": \"Equipes\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Postagem Automática\",\n  \"logout_from\": \"Sair de\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Junte-se a mais de 10.000 empreendedores que usam o Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Para gerenciar todos os seus canais de mídia social\",\n  \"100_no_risk_trial\": \"Teste 100% sem risco\",\n  \"pay_nothing_for_the_first_7_days\": \"Não pague nada nos primeiros 7 dias\",\n  \"cancel_anytime_hassle_free\": \"Cancele a qualquer momento, nas configurações\",\n  \"add_free_subscription\": \"-- ADICIONAR ASSINATURA GRATUITA --\",\n  \"currently_impersonating\": \"Atualmente personificando\",\n  \"user_1\": \"usuário:\",\n  \"drag_n_drop_some_files_here\": \"Arraste e solte alguns arquivos aqui\",\n  \"add_time_slot\": \"Adicionar Horário\",\n  \"add_slot\": \"Adicionar horário\",\n  \"cancel_publication\": \"Cancelar publicação\",\n  \"statistics\": \"Estatísticas\",\n  \"loading\": \"Carregando\",\n  \"short_link\": \"Link curto\",\n  \"original_link\": \"Link original\",\n  \"clicks\": \"Cliques\",\n  \"selected_customer\": \"Cliente selecionado\",\n  \"customer\": \"Cliente:\",\n  \"repeat_post_every\": \"Repetir postagem a cada...\",\n  \"use_this_media\": \"Usar esta mídia\",\n  \"create_new_post\": \"Criar publicação\",\n  \"update_post\": \"Atualizar postagem existente\",\n  \"merge_comments_into_one_post\": \"Mesclar comentários em uma postagem\",\n  \"accounts_that_will_engage\": \"Contas que irão interagir:\",\n  \"day\": \"Dia\",\n  \"week\": \"Semana\",\n  \"month\": \"Mês\",\n  \"remove_from_customer\": \"Remover do cliente\",\n  \"show_more\": \"+ Mostrar mais\",\n  \"show_less\": \"- Mostrar menos\",\n  \"upload\": \"Enviar\",\n  \"ai\": \"IA\",\n  \"add_channel\": \"Adicionar canal\",\n  \"add_platform\": \"Adicionar plataforma\",\n  \"articles\": \"Artigos\",\n  \"add_comment\": \"Adicionar comentário\",\n  \"add_post\": \"Adicionar postagem em um tópico\",\n  \"add_comment_or_post\": \"Adicionar comentário / postagem\",\n  \"you_are_in_global_editing_mode\": \"Você está no modo de edição global\",\n  \"the_post_should_be_at_least_6_characters_long\": \"A publicação deve ter pelo menos 6 caracteres\",\n  \"are_you_sure_you_want_to_delete_post\": \"Tem certeza de que deseja excluir esta postagem?\",\n  \"post_deleted_successfully\": \"Postagem excluída com sucesso\",\n  \"delete_post\": \"Excluir publicação\",\n  \"save_as_draft\": \"Salvar como rascunho\",\n  \"post_now\": \"Publicar agora\",\n  \"please_add\": \"Por favor, adicione\",\n  \"to_your_telegram_group_channel_and_click_here\": \"ao seu grupo/canal do Telegram e clique aqui:\",\n  \"connect_telegram\": \"Conectar Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Por favor, adicione o seguinte comando no seu chat:\",\n  \"copy\": \"Copiar\",\n  \"settings\": \"Configurações\",\n  \"integrations\": \"Integrações\",\n  \"add_integration\": \"Adicionar integração\",\n  \"you_are_now_editing_only\": \"Agora você está editando apenas\",\n  \"tag_a_company\": \"Marcar uma empresa\",\n  \"video_length_is_invalid_must_be_up_to\": \"A duração do vídeo é inválida, deve ser de até\",\n  \"seconds\": \"segundos\",\n  \"this_feature_available_only_for_photos\": \"Este recurso está disponível apenas para fotos, será adicionada uma música padrão que você poderá alterar depois.\",\n  \"allow_user_to\": \"Permitir que o usuário:\",\n  \"your_video_will_be_labeled_promotional\": \"Seu vídeo será rotulado como \\\"Conteúdo Promocional\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"Isso não poderá ser alterado depois que seu vídeo for publicado.\",\n  \"turn_on_to_disclose_video_promotes\": \"Ative para informar que este vídeo promove bens ou serviços em troca de algo de valor. Seu vídeo pode promover você mesmo, um terceiro ou ambos.\",\n  \"you_are_promoting_yourself\": \"Você está promovendo a si mesmo ou à sua própria marca.\",\n  \"this_video_will_be_classified_brand_organic\": \"Este vídeo será classificado como Marca Orgânica.\",\n  \"you_are_promoting_another_brand\": \"Você está promovendo outra marca ou um terceiro.\",\n  \"this_video_will_be_classified_branded_content\": \"Este vídeo será classificado como Conteúdo de Marca.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Ao publicar, você concorda com os Termos do TikTok\",\n  \"music_usage_confirmation\": \"Confirmação de Uso de Música\",\n  \"branded_content_policy\": \"Política de Conteúdo de Marca\",\n  \"select_1\": \"--Selecionar--\",\n  \"select_flair\": \"--Selecionar Flair--\",\n  \"link\": \"Link\",\n  \"add_subreddit\": \"Adicionar Subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Por favor, adicione pelo menos um Subreddit\",\n  \"add_community\": \"Adicionar Comunidade\",\n  \"select_post_type\": \"Selecionar Tipo de Postagem...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Não conseguimos encontrar nenhum negócio conectado à sua página do LinkedIn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Por favor, feche este diálogo, crie uma nova página e adicione um novo canal novamente.\",\n  \"select_linkedin_page\": \"Selecione a Página do Linkedin:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Não conseguimos encontrar nenhum negócio conectado às páginas selecionadas.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Recomendamos que você conecte todas as páginas e todos os negócios.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Por favor, feche este diálogo, exclua sua integração e adicione um novo canal novamente.\",\n  \"select_instagram_account\": \"Selecione a Conta do Instagram:\",\n  \"select_page\": \"Selecione a Página:\",\n  \"generate_image_with_ai\": \"Gerar imagem com IA\",\n  \"reconnect_channel\": \"Reconectar canal\",\n  \"update_credentials\": \"Atualizar Credenciais\",\n  \"additional_settings\": \"Configurações Adicionais\",\n  \"change_bot\": \"Trocar Bot\",\n  \"move_add_to_customer\": \"Mover / adicionar para cliente\",\n  \"edit_time_slots\": \"Editar Faixas de Horário\",\n  \"enable_channel\": \"Ativar canal\",\n  \"disable_channel\": \"Desativar canal\",\n  \"add\": \"Adicionar\",\n  \"short_post\": \"Postagem curta\",\n  \"long_post\": \"Postagem longa\",\n  \"a_thread_with_short_posts\": \"Um tópico com postagens curtas\",\n  \"a_thread_with_long_posts\": \"Um tópico com postagens longas\",\n  \"personal_voice_i_am_happy_to_announce\": \"Voz pessoal (\\\"Estou feliz em anunciar\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Voz da empresa (\\\"Estamos felizes em anunciar\\\")\",\n  \"generate\": \"Gerar\",\n  \"generate_posts\": \"Gerar postagens\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Adquira uma conta PRO vitalícia com SOL (US$199). Por favor, esteja ciente de que não há reembolso para esta compra.\",\n  \"purchase_now\": \"Comprar agora\",\n  \"pay_today\": \"Pagar hoje\",\n  \"we_are_sorry_to_see_you_go\": \"Lamentamos ver você partir :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Você poderia nos dizer brevemente o que poderíamos ter feito melhor?\",\n  \"cancel_subscription\": \"Cancelar assinatura\",\n  \"plans\": \"Planos\",\n  \"monthly\": \"MENSAL\",\n  \"yearly\": \"ANUAL\",\n  \"reactivate_subscription\": \"Reativar assinatura\",\n  \"update_payment_method_invoices_history\": \"Atualizar método de pagamento / Histórico de faturas\",\n  \"cancel_subscription_1\": \"Cancelar assinatura\",\n  \"your_subscription_will_be_canceled_at\": \"Sua assinatura será cancelada em\",\n  \"you_will_never_be_charged_again\": \"Você nunca mais será cobrado\",\n  \"current_package\": \"Pacote Atual:\",\n  \"next_package\": \"Próximo Pacote:\",\n  \"claim\": \"Resgatar\",\n  \"frequently_asked_questions\": \"Perguntas Frequentes\",\n  \"autopost\": \"Autopost\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"O Autopost pode publicar automaticamente seus novos itens RSS nas redes sociais\",\n  \"title\": \"Título\",\n  \"add_an_autopost\": \"Adicionar um autopost\",\n  \"post_content\": \"Conteúdo da postagem\",\n  \"sign_up\": \"Cadastrar-se\",\n  \"or\": \"OU\",\n  \"by_registering_you_agree_to_our\": \"Ao se registrar, você concorda com nossos\",\n  \"and\": \"e\",\n  \"terms_of_service\": \"Termos de Serviço\",\n  \"privacy_policy\": \"Política de Privacidade\",\n  \"create_account\": \"Criar Conta\",\n  \"already_have_an_account\": \"Já tem uma conta?\",\n  \"sign_in\": \"Entrar\",\n  \"sign_in_1\": \"Entrar\",\n  \"don_t_have_an_account\": \"Não tem uma conta?\",\n  \"forgot_password\": \"Esqueceu a senha\",\n  \"forgot_password_1\": \"Esqueceu a Senha\",\n  \"send_password_reset_email\": \"Enviar e-mail de redefinição de senha\",\n  \"go_back_to_login\": \"Voltar para o login\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Enviamos um e-mail para você com um link para redefinir sua senha.\",\n  \"change_password\": \"Alterar senha\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Redefinimos sua senha com sucesso. Agora você pode fazer login com sua\",\n  \"click_here_to_go_back_to_login\": \"Clique aqui para voltar ao login\",\n  \"activate_your_account\": \"Ative sua conta\",\n  \"thank_you_for_registering\": \"Obrigado por se registrar!\",\n  \"please_check_your_email_to_activate_your_account\": \"Por favor, verifique seu e-mail para ativar sua conta.\",\n  \"sign_in_with\": \"Entrar com\",\n  \"continue_with_google\": \"Continuar com o Google\",\n  \"sign_in_with_github\": \"Entrar com o GitHub\",\n  \"continue_with_farcaster\": \"Continuar com o Farcaster\",\n  \"continue_with_your_wallet\": \"Continuar com sua carteira\",\n  \"stars_per_day\": \"Estrelas por dia\",\n  \"media\": \"Mídia\",\n  \"check_launch\": \"Verificar lançamento\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Carregue seu repositório do GitHub nas configurações para ver as análises\",\n  \"stars\": \"Estrelas\",\n  \"processing_stars\": \"Processando estrelas...\",\n  \"forks\": \"Forks\",\n  \"registration_is_disabled\": \"O registro está desativado\",\n  \"login_instead\": \"Faça login em vez disso\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Selecione uma conversa e comece a conversar.\",\n  \"adding_channel_redirecting_you\": \"Adicionando canal, redirecionando você\",\n  \"could_not_add_provider\": \"Não foi possível adicionar o provedor.\",\n  \"you_are_being_redirected_back\": \"Você está sendo redirecionado de volta\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Estamos enfrentando alguma dificuldade, tente atualizar a página\",\n  \"post_not_found\": \"Post não encontrado\",\n  \"publication_date\": \"Data de publicação:\",\n  \"analytics\": \"Análises\",\n  \"launches\": \"Lançamentos\",\n  \"plugs\": \"Divulgações\",\n  \"billing\": \"Cobrança\",\n  \"affiliate\": \"Afiliado\",\n  \"monday\": \"Segunda-feira\",\n  \"tuesday\": \"Terça-feira\",\n  \"wednesday\": \"Quarta-feira\",\n  \"thursday\": \"Quinta-feira\",\n  \"friday\": \"Sexta-feira\",\n  \"saturday\": \"Sábado\",\n  \"sunday\": \"Domingo\",\n  \"can_t_change_date_remove_post_from_publication\": \"Não é possível alterar a data, remova o post da publicação\",\n  \"predicted_github_trending_change\": \"Mudança prevista no Trending do GitHub\",\n  \"duplicate_post\": \"Post duplicado\",\n  \"preview_post\": \"Visualizar post\",\n  \"post_statistics\": \"Estatísticas do post\",\n  \"draft\": \"Rascunho\",\n  \"week_number\": \"Semana {{number}}\",\n  \"top_title_edit_webhook\": \"Editar webhook\",\n  \"top_title_add_webhook\": \"Adicionar webhook\",\n  \"top_title_oh_no\": \"Oh não\",\n  \"top_title_auto_plug\": \"Auto Plug: {{title}}\",\n  \"top_title_edit_autopost\": \"Editar autopost\",\n  \"top_title_add_autopost\": \"Adicionar autopost\",\n  \"top_title_send_a_new_offer\": \"Enviar uma nova oferta\",\n  \"top_title_media_library\": \"Biblioteca de Mídia\",\n  \"top_title_add_signature\": \"Adicionar assinatura\",\n  \"top_title_send_a_message_to\": \"Enviar uma mensagem para {{name}}\",\n  \"top_title_configure_provider\": \"Configurar Provedor\",\n  \"top_title_add_member\": \"Adicionar Membro\",\n  \"top_title_change_bot_picture\": \"Alterar Foto do Bot\",\n  \"top_title_create_a_new_tag\": \"Criar uma nova tag\",\n  \"top_title_select_company\": \"Selecionar Empresa\",\n  \"top_title_additional_settings\": \"Configurações Adicionais\",\n  \"top_title_time_table_slots\": \"Horários da Tabela\",\n  \"top_title_design_media\": \"Design de Mídia\",\n  \"top_title_edit_post\": \"Editar Publicação\",\n  \"top_title_create_post\": \"Criar Publicação\",\n  \"top_title_move__add_to_customer\": \"Mover / Adicionar ao cliente\",\n  \"top_title_add_api_key_for\": \"Adicionar chave de API para {{name}}\",\n  \"top_title_instance_url\": \"URL da Instância\",\n  \"top_title_custom_url\": \"URL Personalizada\",\n  \"top_title_add_channel\": \"Adicionar Canal\",\n  \"top_title_add_telegram\": \"Adicionar Telegram\",\n  \"top_title_add_wrapcast\": \"Adicionar Wrapcast\",\n  \"top_title_comments_for\": \"Comentários para {{date}}\",\n  \"top_title_edit_signature\": \"Editar assinatura\",\n  \"label_name\": \"Nome\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Título\",\n  \"label_subtitle\": \"Subtítulo\",\n  \"label_email\": \"E-mail\",\n  \"label_full_name\": \"Nome completo\",\n  \"label_password\": \"Senha\",\n  \"label_confirm_password\": \"Confirmar senha\",\n  \"label_api_key\": \"Chave de API\",\n  \"label_instance_url\": \"URL da instância\",\n  \"label_custom_url\": \"URL personalizada\",\n  \"label_feedback\": \"Feedback\",\n  \"label_bio\": \"Biografia\",\n  \"label_role\": \"Função\",\n  \"label_country\": \"País\",\n  \"label_audience_size\": \"Tamanho do público em todas as plataformas\",\n  \"label_pick_time\": \"Escolher horário\",\n  \"label_nickname\": \"Apelido\",\n  \"label_write_anything\": \"Escreva qualquer coisa\",\n  \"label_output_format\": \"Formato de saída\",\n  \"label_add_pictures\": \"Adicionar imagens?\",\n  \"label_hour\": \"Hora\",\n  \"label_minutes\": \"Minutos\",\n  \"label_select_publication\": \"Selecionar publicação\",\n  \"label_canonical_link\": \"Link canônico\",\n  \"label_cover_picture\": \"Imagem de capa\",\n  \"label_tags\": \"Tags\",\n  \"label_topics\": \"Tópicos\",\n  \"label_tags_maximum_4\": \"Tags (Máximo 4)\",\n  \"label_attachments\": \"Anexos\",\n  \"label_type\": \"Tipo\",\n  \"label_thumbnail\": \"Miniatura\",\n  \"label_who_can_see_this_video\": \"Quem pode ver este vídeo?\",\n  \"label_content_posting_method\": \"Método de postagem de conteúdo\",\n  \"label_auto_add_music\": \"Adicionar música automaticamente\",\n  \"label_duet\": \"Dueto\",\n  \"label_stitch\": \"Stitch\",\n  \"label_comments\": \"Comentários\",\n  \"label_disclose_video_content\": \"Divulgar conteúdo do vídeo\",\n  \"label_your_brand\": \"Sua marca\",\n  \"label_branded_content\": \"Conteúdo de marca\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Mídia\",\n  \"label_search_subreddit\": \"Buscar Subreddit\",\n  \"label_delay\": \"Atraso\",\n  \"label_post_type\": \"Tipo de postagem\",\n  \"label_collaborators\": \"Colaboradores (máx. 3) - contas não podem ser privadas\",\n  \"label_community\": \"Comunidade\",\n  \"label_search_community\": \"Pesquisar comunidade\",\n  \"label_channel\": \"Canal\",\n  \"label_search_channel\": \"Pesquisar canal\",\n  \"label_select_channel\": \"Selecionar canal\",\n  \"label_new_password\": \"Nova senha\",\n  \"label_repeat_password\": \"Repetir senha\",\n  \"label_platform\": \"Plataforma\",\n  \"label_price_per_post\": \"Preço por postagem\",\n  \"label_integrations\": \"Integrações\",\n  \"label_code\": \"Código\",\n  \"label_should_sync_last_post\": \"Devemos sincronizar a última postagem atual?\",\n  \"label_when_post\": \"Quando devemos postar?\",\n  \"label_autogenerate_content\": \"Gerar conteúdo automaticamente\",\n  \"label_generate_picture\": \"Gerar imagem?\",\n  \"label_company\": \"Empresa\",\n  \"label_tag_color\": \"Cor da tag\",\n  \"label_select_board\": \"Selecionar quadro\",\n  \"label_select_organization\": \"Selecionar organização\",\n  \"label_auto_add_signature\": \"Adicionar assinatura automaticamente?\",\n  \"enable_color_picker\": \"Ativar seletor de cores\",\n  \"cancel_the_color_picker\": \"Cancelar o seletor de cores\",\n  \"no_content_yet\": \"Ainda não há conteúdo\",\n  \"write_your_reply\": \"Escreva sua postagem...\",\n  \"add_a_tag\": \"Adicionar uma tag\",\n  \"add_to_calendar\": \"Adicionar ao calendário\",\n  \"select_channels_from_circles\": \"Selecione os canais dos círculos acima\",\n  \"not_matching_order\": \"Ordem não correspondente\",\n  \"submit_for_order\": \"Enviar para pedido\",\n  \"schedule\": \"Agendar\",\n  \"update\": \"Atualizar\",\n  \"attachments\": \"Anexos\",\n  \"tags\": \"Tags\",\n  \"public_to_everyone\": \"Público para todos\",\n  \"mutual_follow_friends\": \"Amigos com seguimento mútuo\",\n  \"follower_of_creator\": \"Seguidor do criador\",\n  \"self_only\": \"Apenas eu\",\n  \"post_content_directly_to_tiktok\": \"Publicar conteúdo diretamente no TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Enviar conteúdo para o TikTok sem publicar\",\n  \"choose_upload_without_posting_description\": \"Escolha enviar sem publicar se quiser revisar e editar seu conteúdo no aplicativo do TikTok antes de publicar. Isso lhe dá acesso às ferramentas de edição do próprio TikTok e permite fazer ajustes finais antes de postar.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Vou ser cobrado pelo Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Para confirmar as informações do cartão de crédito, a Postiz irá reter R$2 e liberá-lo imediatamente. Você pode cancelar sua assinatura a qualquer momento nas configurações, sem precisar falar com ninguém.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Posso confiar no Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"O Postiz é orgulhosamente open-source! Acreditamos em uma cultura ética e transparente, o que significa que o Postiz viverá para sempre. Você pode conferir todo o código ou usá-lo em projetos pessoais. Para ver o repositório open-source, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">clique aqui</a>.\",\n  \"faq_what_are_channels\": \"O que são canais?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"O Postiz permite que você agende suas publicações entre diferentes canais.\\nUm canal é uma plataforma de publicação onde você pode agendar seus posts.\\nPor exemplo, você pode agendar suas publicações no X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads e Pinterest.\",\n  \"faq_what_are_team_members\": \"O que são membros da equipe?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Se você tem uma equipe com vários membros, pode convidá-los para o seu workspace para colaborar nas postagens e adicionar seus canais pessoais\",\n  \"faq_what_is_ai_auto_complete\": \"O que é autocompletar por IA?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Automatizamos o ChatGPT para ajudar você a escrever postagens e artigos para redes sociais.\",\n  \"enter_email\": \"Digite o e-mail\",\n  \"are_you_sure\": \"Tem certeza?\",\n  \"no_cancel\": \"Não, cancelar!\",\n  \"are_you_sure_you_want_to_delete\": \"Tem certeza de que deseja excluir {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Tem certeza de que deseja excluir a imagem?\",\n  \"are_you_sure_you_want_to_logout\": \"Tem certeza de que deseja sair?\",\n  \"yes_logout\": \"Sim, sair\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Tem certeza de que deseja excluir este slot?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Tem certeza de que deseja excluir este Subreddit?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Tem certeza de que deseja fechar a janela?\",\n  \"yes_close\": \"Sim, fechar\",\n  \"link_copied_to_clipboard\": \"Link copiado para a área de transferência\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Tem certeza de que deseja fechar este modal? (todos os dados serão perdidos)\",\n  \"yes_close_it\": \"Sim, feche!\",\n  \"uploading_pictures\": \"Enviando imagens...\",\n  \"agent_starting\": \"Agente iniciando\",\n  \"researching_your_content\": \"Pesquisando seu conteúdo...\",\n  \"understanding_the_category\": \"Entendendo a categoria...\",\n  \"finding_the_topic\": \"Encontrando o tópico...\",\n  \"finding_popular_posts_to_match_with\": \"Encontrando posts populares para combinar...\",\n  \"generating_hook\": \"Gerando gancho...\",\n  \"generating_content\": \"Gerando conteúdo...\",\n  \"generating_pictures\": \"Gerando imagens...\",\n  \"finding_time_to_post\": \"Encontrando o melhor horário para postar...\",\n  \"write_anything\": \"Escreva qualquer coisa\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Você pode escrever o que quiser e também adicionar links, nós faremos a pesquisa para você...\",\n  \"output_format\": \"Formato de saída\",\n  \"add_pictures\": \"Adicionar imagens?\",\n  \"7_days\": \"7 Dias\",\n  \"30_days\": \"30 Dias\",\n  \"90_days\": \"90 Dias\",\n  \"start_7_days_free_trial\": \"Comece o teste gratuito de 7 dias\",\n  \"change_language\": \"Mudar idioma\",\n  \"that_a_wrap\": \"É isso aí!\\n\\nSe você gostou deste fio:\\n\\n1. Siga-me @{{username}} para ver mais conteúdos como este\\n2. Dê RT no tweet abaixo para compartilhar este fio com seu público\\n\",\n  \"post_as_images_carousel\": \"Publicar como carrossel de imagens\",\n  \"save_set\": \"Salvar conjunto\",\n  \"separate_post\": \"Separar postagem em várias postagens\",\n  \"label_who_can_reply_to_this_post\": \"Quem pode responder a esta postagem?\",\n  \"delete_integration\": \"Excluir integração\",\n  \"start_writing_your_post\": \"Comece a escrever sua postagem para visualizar\",\n  \"billing_join_over\": \"Junte-se a mais de\",\n  \"billing_entrepreneurs_count\": \"20.000+ Empreendedores\",\n  \"billing_who_use\": \"que usam\",\n  \"billing_postiz_grow_social\": \"Postiz para aumentar sua presença social\",\n  \"billing_no_risk_trial\": \"Teste gratuito 100% sem risco\",\n  \"billing_pay_nothing_7_days\": \"Não pague NADA nos primeiros 7 dias\",\n  \"billing_cancel_anytime\": \"Cancele a qualquer momento, nas configurações\",\n  \"billing_choose_plan\": \"Escolha um plano\",\n  \"billing_monthly\": \"Mensal\",\n  \"billing_yearly\": \"Anual\",\n  \"billing_20_percent_off\": \"20% de desconto\",\n  \"billing_features\": \"Recursos\",\n  \"billing_channel\": \"canal\",\n  \"billing_channels\": \"canais\",\n  \"billing_unlimited\": \"Ilimitado\",\n  \"billing_posts_per_month\": \"publicações por mês\",\n  \"billing_unlimited_team_members\": \"Membros de equipe ilimitados\",\n  \"billing_ai_auto_complete\": \"Autocompletar com IA\",\n  \"billing_ai_copilots\": \"Copilotos de IA\",\n  \"billing_ai_autocomplete\": \"Autocompletar com IA\",\n  \"billing_advanced_picture_editor\": \"Editor de imagens avançado\",\n  \"billing_ai_images_per_month\": \"Imagens de IA por mês\",\n  \"billing_ai_videos_per_month\": \"Vídeos de IA por mês\",\n  \"billing_billing_address\": \"Endereço de cobrança\",\n  \"billing_payment\": \"Pagamento\",\n  \"billing_powered_by_stripe\": \"Pagamentos seguros processados por\",\n  \"billing_your_7_day_trial_is\": \"Seu teste de 7 dias é\",\n  \"billing_100_percent_free\": \"100% gratuito\",\n  \"billing_ending\": \"terminando\",\n  \"billing_cancel_anytime_short\": \"Cancele a qualquer momento.\",\n  \"billing_pay_0_start_trial\": \"Pague R$0 hoje - Comece seu teste gratuito!\",\n  \"billing_pay_now\": \"Pagar agora\",\n  \"billing_per_month\": \"/ mês\",\n  \"billing_per_year\": \"/ ano\",\n  \"billing_order_summary\": \"Resumo do Pedido\",\n  \"billing_applied\": \"Aplicado\",\n  \"billing_due_today\": \"Vencimento hoje\",\n  \"billing_then\": \"Depois\",\n  \"billing_on\": \"em\",\n  \"billing_discount_applied\": \"aplicado\",\n  \"billing_remove\": \"Remover\",\n  \"billing_coupon_expires\": \"O cupom expira em\",\n  \"billing_invalid_coupon\": \"Código de cupom inválido\",\n  \"billing_coupon_applied\": \"Cupom aplicado com sucesso!\",\n  \"billing_coupon_removed\": \"Cupom removido\",\n  \"billing_error_removing_coupon\": \"Erro ao remover o cupom\",\n  \"billing_have_discount_coupon\": \"Possui um cupom de desconto?\",\n  \"billing_discount_coupon\": \"Cupom de Desconto\",\n  \"billing_cancel\": \"Cancelar\",\n  \"billing_enter_coupon_code\": \"Digite o código do cupom\",\n  \"billing_applying\": \"Aplicando...\",\n  \"billing_apply\": \"Aplicar\",\n  \"billing_subscription\": \"Assinatura\",\n  \"billing_cancel_notice\": \"Cancele a qualquer momento nas configurações sem falar com ninguém e nunca será cobrado.\",\n  \"select_channels\": \"Selecionar canais\",\n  \"start_a_new_chat\": \"Iniciar um novo chat\",\n  \"your_assistant\": \"Seu Assistente\",\n  \"agent_welcome_message\": \"Olá, sou seu agente Postiz 🙌🏻.\\n\\nPosso agendar uma publicação ou várias publicações para múltiplos canais e gerar imagens e vídeos.\\n\\nVocê pode selecionar os canais que deseja usar no menu à esquerda.\\n\\nVocê pode ver suas conversas anteriores no menu à direita.\\n\\nVocê também pode me usar como um Servidor MCP, confira Configurações >> API Pública\",\n  \"last_github_trending\": \"Últimas tendências do Github\",\n  \"next_predicted_github_trending\": \"Próxima tendência prevista do GitHub\",\n  \"repository\": \"Repositório\",\n  \"date\": \"Data\",\n  \"total_stars\": \"Total de estrelas\",\n  \"total_forks\": \"Total de forks\",\n  \"continue_with\": \"Continuar com\",\n  \"email_address\": \"Endereço de e-mail\",\n  \"email_already_exists\": \"O e-mail já existe\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Editar autopost\",\n  \"add_autopost_title\": \"Adicionar autopost\",\n  \"webhook_deleted_successfully\": \"Webhook excluído com sucesso\",\n  \"all_integrations\": \"Todas as integrações\",\n  \"specific_integrations\": \"Integrações específicas\",\n  \"post_on_next_available_slot\": \"Publicar no próximo horário disponível\",\n  \"post_immediately\": \"Publicar imediatamente\",\n  \"could_not_use_rss_feed\": \"Não foi possível usar este feed RSS\",\n  \"rss_valid\": \"RSS válido!\",\n  \"autopost_updated_successfully\": \"Autopost atualizado com sucesso\",\n  \"autopost_added_successfully\": \"Autopost adicionado com sucesso\",\n  \"write_your_post_placeholder\": \"Escreva sua postagem...\",\n  \"select_or_upload_pictures_max_5\": \"Selecione ou envie fotos (máximo de 5 por vez).\",\n  \"you_can_drag_drop_pictures\": \"Você também pode arrastar e soltar fotos.\",\n  \"you_dont_have_any_media_yet\": \"Você ainda não tem nenhuma mídia\",\n  \"media_library\": \"Biblioteca de Mídia\",\n  \"media_settings\": \"Configurações de Mídia\",\n  \"media_editor\": \"Editor de Mídia\",\n  \"close\": \"Fechar\",\n  \"me\": \"Eu\",\n  \"noname\": \"Sem nome\",\n  \"password_reset_link_expired\": \"Seu link de redefinição de senha expirou. Por favor, tente novamente.\",\n  \"invalid_api_key\": \"Chave de API inválida\",\n  \"could_not_connect_to_platform\": \"Não foi possível conectar à plataforma\",\n  \"web3_provider\": \"Provedor Web3\",\n  \"add_provider_title\": \"Adicionar Provedor\",\n  \"profile_updated\": \"Perfil atualizado\",\n  \"sets\": \"Conjuntos\",\n  \"invitation_link_sent\": \"Link de convite enviado\",\n  \"send_invitation_link\": \"Enviar link de convite\",\n  \"copy_link\": \"Copiar link\",\n  \"are_you_sure_remove_team_member\": \"Tem certeza de que deseja remover este membro da equipe?\",\n  \"admin\": \"Administrador\",\n  \"super_admin\": \"Super Administrador\",\n  \"update_webhook\": \"Atualizar webhook\",\n  \"add_webhook\": \"Adicionar webhook\",\n  \"webhook_updated_successfully\": \"Webhook atualizado com sucesso\",\n  \"webhook_added_successfully\": \"Webhook adicionado com sucesso\",\n  \"webhook_sent\": \"Webhook enviado\",\n  \"today\": \"Hoje\",\n  \"channel_disconnected_click_to_reconnect\": \"Canal desconectado, clique para reconectar.\",\n  \"channel_disabled_upgrade_plan\": \"Este canal está desativado, por favor faça upgrade do seu plano para habilitá-lo.\",\n  \"channel_added\": \"Canal adicionado\",\n  \"are_you_sure_disable_channel\": \"Tem certeza de que deseja desativar este canal?\",\n  \"disable_channel_title\": \"Desativar Canal\",\n  \"channel_disabled\": \"Canal Desativado\",\n  \"are_you_sure_delete_channel\": \"Tem certeza de que deseja excluir este canal?\",\n  \"delete_channel_title\": \"Excluir Canal\",\n  \"delete_posts_before_channel\": \"Você precisa excluir todas as postagens associadas a este canal antes de excluí-lo\",\n  \"channel_deleted\": \"Canal Excluído\",\n  \"channel_enabled\": \"Canal Ativado\",\n  \"time_table_slots\": \"Horários da Tabela\",\n  \"channel_id_copied\": \"ID do canal copiado para a área de transferência\",\n  \"settings_updated\": \"Configurações Atualizadas\",\n  \"customer_updated\": \"Cliente Atualizado\",\n  \"custom_url\": \"URL personalizada\",\n  \"picture\": \"Imagem\",\n  \"upgrade_required\": \"Você precisa fazer upgrade para usar este recurso\",\n  \"move_to_billing\": \"Ir para cobrança\",\n  \"payment_required\": \"Pagamento Necessário\",\n  \"no_content\": \"sem conteúdo\",\n  \"select_customer_tooltip\": \"Selecionar cliente\",\n  \"customers\": \"Clientes\",\n  \"hour\": \"Hora\",\n  \"minutes\": \"Minutos\",\n  \"updated\": \"Atualizado\",\n  \"change_bot_picture_title\": \"Alterar foto do bot\",\n  \"select_customer_label\": \"Selecionar cliente\",\n  \"start_typing\": \"Comece a digitar...\",\n  \"choose_set_or_continue\": \"Escolha um conjunto ou continue sem um\",\n  \"continue_without_set\": \"Continuar sem conjunto\",\n  \"select_set\": \"Selecionar um conjunto\",\n  \"channel_settings\": \"Configurações\",\n  \"post_needs_content_or_image\": \"Sua publicação deve ter pelo menos um caractere ou uma imagem.\",\n  \"please_fix_your_settings\": \"Por favor, corrija suas configurações\",\n  \"shortlink_urls_question\": \"Você quer encurtar as URLs? Isso permitirá obter estatísticas sobre os cliques\",\n  \"yes_shortlink_it\": \"Sim, encurte!\",\n  \"added_successfully\": \"Adicionado com sucesso\",\n  \"updated_successfully\": \"Atualizado com sucesso\",\n  \"create_post_title\": \"Criar publicação\",\n  \"post_preview\": \"Prévia da publicação\",\n  \"check_circles_above\": \"Verifique os círculos acima\",\n  \"create_output\": \"Criar saída\",\n  \"assistant_initial_message\": \"Olá! Posso ajudar você a aprimorar suas postagens nas redes sociais.\",\n  \"no_longer_global_mode\": \"Não está mais no modo global\",\n  \"two_days\": \"Dois dias\",\n  \"three_days\": \"Três dias\",\n  \"four_days\": \"Quatro dias\",\n  \"five_days\": \"Cinco dias\",\n  \"six_days\": \"Seis dias\",\n  \"two_weeks\": \"Duas semanas\",\n  \"repeat_post_every_label\": \"Repetir postagem a cada\",\n  \"add_new_tag\": \"Adicionar nova tag\",\n  \"tag_name\": \"Nome\",\n  \"post_is_too_long\": \"a postagem está muito longa, por favor corrija\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Sua postagem deve ter pelo menos um caractere ou uma imagem.\",\n  \"internal_edit\": \"Edição interna\",\n  \"are_you_sure_go_back_to_global_mode\": \"Esta ação é irreversível. Tem certeza de que deseja voltar para o modo global?\",\n  \"yes_go_back_to_global_mode\": \"Sim, voltar para o modo global\",\n  \"are_you_sure_delete_this_post\": \"Tem certeza de que deseja excluir esta postagem?\",\n  \"yes_delete_it\": \"Sim, exclua!\",\n  \"cant_edit_networks_when_creating_set\": \"Você não pode editar redes ao criar um conjunto\",\n  \"click_to_exit_global_editing\": \"Clique neste botão para sair da edição global e personalizar a postagem para este canal\",\n  \"edit_content\": \"Editar conteúdo\",\n  \"editing_a_specific_network\": \"Editando uma Rede Específica\",\n  \"back_to_global\": \"Voltar para o global\",\n  \"delete_post_tooltip\": \"Excluir postagem\",\n  \"drop_files_here_to_upload\": \"Solte seus arquivos aqui para fazer upload\",\n  \"insert_emoji\": \"Inserir emoji\",\n  \"write_something\": \"Escreva algo…\",\n  \"click_channel_to_add\": \"Clique em um canal para adicionar\",\n  \"connect_your_channels\": \"Conecte seus canais\",\n  \"connect_social_media_to_start\": \"Conecte suas contas de redes sociais para começar a agendar posts\",\n  \"connected_channels\": \"Canais conectados\",\n  \"continue\": \"Continuar\",\n  \"continue_without_channels\": \"Continuar sem canais\",\n  \"watch_tutorial\": \"Assistir ao tutorial\",\n  \"watch_tutorial_title\": \"Aprenda a usar o Postiz\",\n  \"watch_tutorial_description\": \"Assista a este vídeo curto para aprender a tirar o máximo proveito do Postiz\",\n  \"back\": \"Voltar\",\n  \"get_started\": \"Começar\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/ru/translation.json",
    "content": "{\n  \"calendar\": \"Календарь\",\n  \"webhooks\": \"Вебхуки\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Вебхуки — это способ получать уведомления о событиях в Postiz через HTTP-запрос.\",\n  \"name\": \"Имя\",\n  \"url\": \"URL\",\n  \"edit\": \"Редактировать\",\n  \"delete\": \"Удалить\",\n  \"add_a_webhook\": \"Добавить вебхук\",\n  \"save\": \"Сохранить\",\n  \"send_test\": \"Отправить тест\",\n  \"select_role\": \"Выбрать роль\",\n  \"video_made_with_ai\": \"Видео создано с помощью ИИ\",\n  \"please_add_at_least\": \"Пожалуйста, добавьте не менее 20 символов\",\n  \"send_invitation_via_email\": \"Отправить приглашение по электронной почте?\",\n  \"global_settings\": \"Глобальные настройки\",\n  \"copy_id\": \"Скопировать ID канала\",\n  \"team_members\": \"Члены команды\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Пригласите помощника или члена команды для управления вашим аккаунтом\",\n  \"remove\": \"Удалить\",\n  \"add_another_member\": \"Добавить еще одного участника\",\n  \"signatures\": \"Подписи\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Вы можете добавить подписи к своему аккаунту для использования в ваших публикациях.\",\n  \"content\": \"Содержание\",\n  \"auto_add\": \"Добавлять автоматически?\",\n  \"delay_comment\": \"Комментарий к задержке\",\n  \"actions\": \"Действия\",\n  \"use_signature\": \"Использовать подпись\",\n  \"add_a_signature\": \"Добавить подпись\",\n  \"no\": \"Нет\",\n  \"yes\": \"Да\",\n  \"your_git_repository\": \"Ваш Git-репозиторий\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Подключите ваш репозиторий GitHub, чтобы получать обновления и аналитику\",\n  \"connected\": \"Подключено:\",\n  \"disconnect\": \"Отключить\",\n  \"connect_your_repository\": \"Подключить репозиторий\",\n  \"cancel\": \"Отмена\",\n  \"connect\": \"Подключить\",\n  \"public_api\": \"Публичный API\",\n  \"check_n8n\": \"Ознакомьтесь с нашим пользовательским узлом N8N для Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Используйте API Postiz для интеграции с вашими инструментами.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Прочитайте, как использовать его, в документации.\",\n  \"reveal\": \"Показать\",\n  \"copy_key\": \"Скопировать ключ\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Подключите сервер Postiz MCP к вашему клиенту (HTTP-поток), чтобы быстрее планировать ваши публикации!\",\n  \"share_with_a_client\": \"Поделиться с клиентом\",\n  \"post\": \"Пост\",\n  \"comments\": \"Комментарии\",\n  \"user\": \"Пользователь\",\n  \"login_register_to_add_comments\": \"Войдите или зарегистрируйтесь, чтобы добавить комментарии\",\n  \"status\": \"Статус:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Нет плагинов, соответствующих вашим каналам\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Вам нужно добавить: X, LinkedIn или Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Перейдите в календарь, чтобы добавить каналы\",\n  \"channels\": \"Каналы\",\n  \"activate\": \"Активировать\",\n  \"this_channel_needs_to_be_refreshed\": \"Этот канал нужно обновить,\",\n  \"click_here_to_refresh\": \"нажмите здесь, чтобы обновить\",\n  \"can_t_show_analytics_yet\": \"Пока нельзя показать аналитику\",\n  \"you_have_to_add_social_media_channels\": \"Вам нужно добавить каналы социальных сетей\",\n  \"supported\": \"Поддерживается:\",\n  \"step\": \"ШАГ\",\n  \"skip_onboarding\": \"Пропустить вводный курс\",\n  \"onboarding\": \"Вводный курс\",\n  \"next\": \"Далее\",\n  \"you_are_done_from_here_you_can\": \"Готово, отсюда вы можете:\",\n  \"view_analytics\": \"Просмотреть аналитику\",\n  \"schedule_a_new_post\": \"Запланировать новую публикацию\",\n  \"to_sell_posts_you_would_have_to\": \"Чтобы продавать публикации, вам нужно:\",\n  \"1_connect_at_least_one_channel\": \"1. Подключить хотя бы один канал\",\n  \"2_connect_you_bank_account\": \"2. Подключить свой банковский счет\",\n  \"go_back_to_connect_channels\": \"Вернуться к подключению каналов\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Перейти на страницу продавца для подключения банковского счета\",\n  \"connect_channels\": \"Подключить каналы\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Подключите свои каналы социальных сетей и платформ публикаций,\\n            чтобы позже планировать публикации\",\n  \"social\": \"Социальные сети\",\n  \"publishing_platforms\": \"Платформы публикаций\",\n  \"no_channels\": \"Каналов пока нет\",\n  \"connect_your_accounts\": \"Подключите свои социальные аккаунты, чтобы начать планировать, публиковать и анализировать — всё в одном месте.\",\n  \"notifications\": \"Уведомления\",\n  \"no_notifications\": \"Нет уведомлений\",\n  \"send_message\": \"Отправить сообщение\",\n  \"mar_28\": \"28 мар\",\n  \"there_are_no_messages_yet\": \"Пока нет сообщений.\",\n  \"checkout_the_marketplace\": \"Посетите маркетплейс\",\n  \"go_to_marketplace\": \"Перейти в маркетплейс\",\n  \"all_messages\": \"Все сообщения\",\n  \"previous\": \"Назад\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Выберите или загрузите изображения (максимум 5 за раз)\",\n  \"you_can_also_drag_drop_pictures\": \"Вы также можете перетащить изображения мышью\",\n  \"you_don_t_have_any_assets_yet\": \"У вас пока нет ресурсов.\",\n  \"click_the_button_below_to_upload_one\": \"Нажмите кнопку ниже, чтобы загрузить\",\n  \"click_the_button_below_to_upload_other\": \"Нажмите кнопку ниже, чтобы загрузить несколько файлов\",\n  \"add_selected_media\": \"Добавить выбранные медиа\",\n  \"insert_media\": \"Вставить медиа\",\n  \"design_media\": \"Оформить медиа\",\n  \"select\": \"Выбрать\",\n  \"editor\": \"Редактор\",\n  \"clear\": \"Очистить\",\n  \"order_completed\": \"Заказ выполнен\",\n  \"the_order_has_been_completed\": \"Заказ был выполнен\",\n  \"post_has_been_published\": \"Пост опубликован\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Новое предложение\",\n  \"platform\": \"Платформа\",\n  \"posts\": \"Посты\",\n  \"pay_accept_offer\": \"Оплатить и принять предложение\",\n  \"accepted\": \"Принято\",\n  \"post_draft\": \"Черновик поста\",\n  \"revision_needed\": \"Требуется доработка\",\n  \"approve\": \"Одобрить\",\n  \"preview\": \"Предпросмотр\",\n  \"revision_requested\": \"Запрошена доработка\",\n  \"accepted_1\": \"ПРИНЯТО\",\n  \"cancelled_by_the_seller\": \"Отменено продавцом\",\n  \"please_select_your_country_where_your_business_is\": \"Пожалуйста, выберите страну, в которой находится ваш бизнес.\",\n  \"select_country\": \"--ВЫБЕРИТЕ СТРАНУ--\",\n  \"connect_bank_account\": \"Подключить банковский счет\",\n  \"seller_mode\": \"Режим продавца\",\n  \"active\": \"Активен\",\n  \"details\": \"Детали\",\n  \"audience_size\": \"Размер аудитории\",\n  \"add_another_platform\": \"Добавить другую платформу\",\n  \"send_an_offer_for\": \"Отправить предложение на $\",\n  \"complete_order_and_pay_early\": \"Завершить заказ и оплатить заранее\",\n  \"order_in_progress\": \"Заказ в процессе\",\n  \"create_a_new_offer\": \"Создать новое предложение\",\n  \"orders\": \"Заказы\",\n  \"price\": \"Цена\",\n  \"state\": \"Статус\",\n  \"showing\": \"Показано\",\n  \"to\": \"до\",\n  \"from\": \"от\",\n  \"results\": \"Результаты\",\n  \"content_writer\": \"Копирайтер\",\n  \"influencer\": \"Инфлюенсер\",\n  \"request_service\": \"Запросить услугу\",\n  \"the_marketplace_is_not_opened_yet\": \"Маркетплейс еще не открыт\",\n  \"check_again_soon\": \"Проверьте снова скоро!\",\n  \"filter\": \"Фильтр\",\n  \"result\": \"Результат\",\n  \"seller\": \"Продавец\",\n  \"buyer\": \"Покупатель\",\n  \"discord_support\": \"Поддержка Discord\",\n  \"teams\": \"Команды\",\n  \"webhooks_1\": \"Вебхуки\",\n  \"auto_post\": \"Автопостинг\",\n  \"logout_from\": \"Выйти из\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Присоединяйтесь к 10 000+ предпринимателей, которые используют Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Для управления всеми вашими социальными сетями\",\n  \"100_no_risk_trial\": \"100% безрисковый пробный период\",\n  \"pay_nothing_for_the_first_7_days\": \"Не платите ничего первые 7 дней\",\n  \"cancel_anytime_hassle_free\": \"Отменяйте в любое время через настройки\",\n  \"add_free_subscription\": \"-- ДОБАВИТЬ БЕСПЛАТНУЮ ПОДПИСКУ --\",\n  \"currently_impersonating\": \"В данный момент имитируется\",\n  \"user_1\": \"пользователь:\",\n  \"drag_n_drop_some_files_here\": \"Перетащите сюда файлы\",\n  \"add_time_slot\": \"Добавить временной слот\",\n  \"add_slot\": \"Добавить слот\",\n  \"cancel_publication\": \"Отменить публикацию\",\n  \"statistics\": \"Статистика\",\n  \"loading\": \"Загрузка\",\n  \"short_link\": \"Короткая ссылка\",\n  \"original_link\": \"Оригинальная ссылка\",\n  \"clicks\": \"Клики\",\n  \"selected_customer\": \"Выбранный клиент\",\n  \"customer\": \"Клиент:\",\n  \"repeat_post_every\": \"Повторять публикацию каждые...\",\n  \"use_this_media\": \"Использовать этот медиафайл\",\n  \"create_new_post\": \"Создать пост\",\n  \"update_post\": \"Обновить существующий пост\",\n  \"merge_comments_into_one_post\": \"Объединить комментарии в один пост\",\n  \"accounts_that_will_engage\": \"Аккаунты, которые будут взаимодействовать:\",\n  \"day\": \"День\",\n  \"week\": \"Неделя\",\n  \"month\": \"Месяц\",\n  \"remove_from_customer\": \"Удалить от клиента\",\n  \"show_more\": \"+ Показать больше\",\n  \"show_less\": \"- Показать меньше\",\n  \"upload\": \"Загрузить\",\n  \"ai\": \"ИИ\",\n  \"add_channel\": \"Добавить канал\",\n  \"add_platform\": \"Добавить платформу\",\n  \"articles\": \"Статьи\",\n  \"add_comment\": \"Добавить комментарий\",\n  \"add_post\": \"Добавить сообщение в теме\",\n  \"add_comment_or_post\": \"Добавить комментарий / сообщение\",\n  \"you_are_in_global_editing_mode\": \"Вы находитесь в режиме глобального редактирования\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Пост должен содержать не менее 6 символов\",\n  \"are_you_sure_you_want_to_delete_post\": \"Вы уверены, что хотите удалить этот пост?\",\n  \"post_deleted_successfully\": \"Пост успешно удалён\",\n  \"delete_post\": \"Удалить пост\",\n  \"save_as_draft\": \"Сохранить как черновик\",\n  \"post_now\": \"Опубликовать сейчас\",\n  \"please_add\": \"Пожалуйста, добавьте\",\n  \"to_your_telegram_group_channel_and_click_here\": \"в вашу группу/канал Telegram и нажмите здесь:\",\n  \"connect_telegram\": \"Подключить Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Пожалуйста, добавьте следующую команду в ваш чат:\",\n  \"copy\": \"Копировать\",\n  \"settings\": \"Настройки\",\n  \"integrations\": \"Интеграции\",\n  \"add_integration\": \"Добавить интеграцию\",\n  \"you_are_now_editing_only\": \"Сейчас вы редактируете только\",\n  \"tag_a_company\": \"Отметить компанию\",\n  \"video_length_is_invalid_must_be_up_to\": \"Недопустимая длина видео, должно быть не более\",\n  \"seconds\": \"секунд\",\n  \"this_feature_available_only_for_photos\": \"Эта функция доступна только для фотографий, будет добавлена стандартная музыка, которую вы сможете изменить позже.\",\n  \"allow_user_to\": \"Разрешить пользователю:\",\n  \"your_video_will_be_labeled_promotional\": \"Ваше видео будет помечено как «Рекламный контент».\",\n  \"this_cannot_be_changed_once_posted\": \"Это нельзя будет изменить после публикации видео.\",\n  \"turn_on_to_disclose_video_promotes\": \"Включите, чтобы указать, что это видео продвигает товары или услуги в обмен на что-либо ценное. Ваше видео может продвигать вас, третью сторону или обоих.\",\n  \"you_are_promoting_yourself\": \"Вы продвигаете себя или свой бренд.\",\n  \"this_video_will_be_classified_brand_organic\": \"Это видео будет классифицировано как органический брендовый контент.\",\n  \"you_are_promoting_another_brand\": \"Вы продвигаете другой бренд или третью сторону.\",\n  \"this_video_will_be_classified_branded_content\": \"Это видео будет классифицировано как брендированный контент.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Публикуя, вы соглашаетесь с условиями TikTok\",\n  \"music_usage_confirmation\": \"Подтверждение использования музыки\",\n  \"branded_content_policy\": \"Политика брендированного контента\",\n  \"select_1\": \"--Выбрать--\",\n  \"select_flair\": \"--Выбрать метку--\",\n  \"link\": \"Ссылка\",\n  \"add_subreddit\": \"Добавить сабреддит\",\n  \"please_add_at_least_one_subreddit\": \"Пожалуйста, добавьте хотя бы один сабреддит\",\n  \"add_community\": \"Добавить сообщество\",\n  \"select_post_type\": \"Выберите тип поста...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Мы не смогли найти ни одного бизнеса, связанного с вашей страницей LinkedIn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Пожалуйста, закройте это окно, создайте новую страницу и снова добавьте новый канал.\",\n  \"select_linkedin_page\": \"Выберите страницу LinkedIn:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Мы не смогли найти ни одного бизнеса, связанного с выбранными страницами.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Рекомендуем подключить все страницы и все бизнесы.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Пожалуйста, закройте это окно, удалите интеграцию и снова добавьте новый канал.\",\n  \"select_instagram_account\": \"Выберите аккаунт Instagram:\",\n  \"select_page\": \"Выберите страницу:\",\n  \"generate_image_with_ai\": \"Сгенерировать изображение с помощью ИИ\",\n  \"reconnect_channel\": \"Переподключить канал\",\n  \"update_credentials\": \"Обновить учетные данные\",\n  \"additional_settings\": \"Дополнительные настройки\",\n  \"change_bot\": \"Сменить бота\",\n  \"move_add_to_customer\": \"Переместить / добавить к клиенту\",\n  \"edit_time_slots\": \"Редактировать временные интервалы\",\n  \"enable_channel\": \"Включить канал\",\n  \"disable_channel\": \"Отключить канал\",\n  \"add\": \"Добавить\",\n  \"short_post\": \"Короткий пост\",\n  \"long_post\": \"Длинный пост\",\n  \"a_thread_with_short_posts\": \"Тема с короткими постами\",\n  \"a_thread_with_long_posts\": \"Тема с длинными постами\",\n  \"personal_voice_i_am_happy_to_announce\": \"Личный стиль (\\\"Рад сообщить\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"От лица компании (\\\"Рады сообщить\\\")\",\n  \"generate\": \"Сгенерировать\",\n  \"generate_posts\": \"Сгенерировать посты\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Приобретите пожизненный PRO-аккаунт за SOL ($199). Обратите внимание, что возврат средств за эту покупку не предусмотрен.\",\n  \"purchase_now\": \"Купить сейчас\",\n  \"pay_today\": \"Оплатить сегодня\",\n  \"we_are_sorry_to_see_you_go\": \"Жаль, что вы уходите :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Не могли бы вы коротко рассказать, что мы могли бы улучшить?\",\n  \"cancel_subscription\": \"Отменить подписку\",\n  \"plans\": \"Тарифы\",\n  \"monthly\": \"МЕСЯЧНО\",\n  \"yearly\": \"ГОДОВОЙ\",\n  \"reactivate_subscription\": \"Возобновить подписку\",\n  \"update_payment_method_invoices_history\": \"Обновить способ оплаты / История счетов\",\n  \"cancel_subscription_1\": \"Отменить подписку\",\n  \"your_subscription_will_be_canceled_at\": \"Ваша подписка будет отменена\",\n  \"you_will_never_be_charged_again\": \"С вас больше не будет взиматься плата\",\n  \"current_package\": \"Текущий пакет:\",\n  \"next_package\": \"Следующий пакет:\",\n  \"claim\": \"Получить\",\n  \"frequently_asked_questions\": \"Часто задаваемые вопросы\",\n  \"autopost\": \"Автопостинг\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Автопостинг может автоматически публиковать новые элементы вашего RSS в социальных сетях\",\n  \"title\": \"Заголовок\",\n  \"add_an_autopost\": \"Добавить автопостинг\",\n  \"post_content\": \"Содержимое поста\",\n  \"sign_up\": \"Зарегистрироваться\",\n  \"or\": \"ИЛИ\",\n  \"by_registering_you_agree_to_our\": \"Регистрируясь, вы соглашаетесь с нашими\",\n  \"and\": \"и\",\n  \"terms_of_service\": \"Условиями обслуживания\",\n  \"privacy_policy\": \"Политикой конфиденциальности\",\n  \"create_account\": \"Создать аккаунт\",\n  \"already_have_an_account\": \"Уже есть аккаунт?\",\n  \"sign_in\": \"Войти\",\n  \"sign_in_1\": \"Войти\",\n  \"don_t_have_an_account\": \"Нет аккаунта?\",\n  \"forgot_password\": \"Забыли пароль\",\n  \"forgot_password_1\": \"Забыли пароль\",\n  \"send_password_reset_email\": \"Отправить письмо для сброса пароля\",\n  \"go_back_to_login\": \"Вернуться к входу\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Мы отправили вам письмо со ссылкой для сброса пароля.\",\n  \"change_password\": \"Сменить пароль\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Ваш пароль успешно сброшен. Теперь вы можете войти, используя новый пароль.\",\n  \"click_here_to_go_back_to_login\": \"Нажмите здесь, чтобы вернуться к входу\",\n  \"activate_your_account\": \"Активируйте свой аккаунт\",\n  \"thank_you_for_registering\": \"Спасибо за регистрацию!\",\n  \"please_check_your_email_to_activate_your_account\": \"Пожалуйста, проверьте свою электронную почту для активации аккаунта.\",\n  \"sign_in_with\": \"Войти с помощью\",\n  \"continue_with_google\": \"Продолжить через Google\",\n  \"sign_in_with_github\": \"Войти через GitHub\",\n  \"continue_with_farcaster\": \"Продолжить через Farcaster\",\n  \"continue_with_your_wallet\": \"Продолжить через кошелёк\",\n  \"stars_per_day\": \"Звёзд в день\",\n  \"media\": \"Медиа\",\n  \"check_launch\": \"Проверить запуск\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Загрузите свой репозиторий GitHub в настройках, чтобы увидеть аналитику\",\n  \"stars\": \"Звёзды\",\n  \"processing_stars\": \"Обработка звёзд...\",\n  \"forks\": \"Форки\",\n  \"registration_is_disabled\": \"Регистрация отключена\",\n  \"login_instead\": \"Войти вместо этого\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Выберите беседу и начните общение.\",\n  \"adding_channel_redirecting_you\": \"Добавление канала, перенаправляем вас\",\n  \"could_not_add_provider\": \"Не удалось добавить провайдера.\",\n  \"you_are_being_redirected_back\": \"Вы перенаправляетесь обратно\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Возникли некоторые трудности, попробуйте обновить страницу\",\n  \"post_not_found\": \"Пост не найден\",\n  \"publication_date\": \"Дата публикации:\",\n  \"analytics\": \"Аналитика\",\n  \"launches\": \"Запуски\",\n  \"plugs\": \"Плагины\",\n  \"billing\": \"Оплата\",\n  \"affiliate\": \"Партнёрская программа\",\n  \"monday\": \"Понедельник\",\n  \"tuesday\": \"Вторник\",\n  \"wednesday\": \"Среда\",\n  \"thursday\": \"Четверг\",\n  \"friday\": \"Пятница\",\n  \"saturday\": \"Суббота\",\n  \"sunday\": \"Воскресенье\",\n  \"can_t_change_date_remove_post_from_publication\": \"Нельзя изменить дату, удалите пост из публикации\",\n  \"predicted_github_trending_change\": \"Прогнозируемое изменение трендов GitHub\",\n  \"duplicate_post\": \"Дублировать пост\",\n  \"preview_post\": \"Предпросмотр поста\",\n  \"post_statistics\": \"Статистика поста\",\n  \"draft\": \"Черновик\",\n  \"week_number\": \"Неделя {{number}}\",\n  \"top_title_edit_webhook\": \"Редактировать вебхук\",\n  \"top_title_add_webhook\": \"Добавить вебхук\",\n  \"top_title_oh_no\": \"О нет\",\n  \"top_title_auto_plug\": \"Авто-плагин: {{title}}\",\n  \"top_title_edit_autopost\": \"Редактировать автопост\",\n  \"top_title_add_autopost\": \"Добавить автопост\",\n  \"top_title_send_a_new_offer\": \"Отправить новое предложение\",\n  \"top_title_media_library\": \"Медиатека\",\n  \"top_title_add_signature\": \"Добавить подпись\",\n  \"top_title_send_a_message_to\": \"Отправить сообщение для {{name}}\",\n  \"top_title_configure_provider\": \"Настроить провайдера\",\n  \"top_title_add_member\": \"Добавить участника\",\n  \"top_title_change_bot_picture\": \"Изменить изображение бота\",\n  \"top_title_create_a_new_tag\": \"Создать новый тег\",\n  \"top_title_select_company\": \"Выбрать компанию\",\n  \"top_title_additional_settings\": \"Дополнительные настройки\",\n  \"top_title_time_table_slots\": \"Слоты расписания\",\n  \"top_title_design_media\": \"Дизайн медиа\",\n  \"top_title_edit_post\": \"Редактировать пост\",\n  \"top_title_create_post\": \"Создать пост\",\n  \"top_title_move__add_to_customer\": \"Переместить / Добавить к клиенту\",\n  \"top_title_add_api_key_for\": \"Добавить API-ключ для {{name}}\",\n  \"top_title_instance_url\": \"URL экземпляра\",\n  \"top_title_custom_url\": \"Пользовательский URL\",\n  \"top_title_add_channel\": \"Добавить канал\",\n  \"top_title_add_telegram\": \"Добавить Telegram\",\n  \"top_title_add_wrapcast\": \"Добавить Wrapcast\",\n  \"top_title_comments_for\": \"Комментарии за {{date}}\",\n  \"top_title_edit_signature\": \"Редактировать подпись\",\n  \"label_name\": \"Имя\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Заголовок\",\n  \"label_subtitle\": \"Подзаголовок\",\n  \"label_email\": \"Электронная почта\",\n  \"label_full_name\": \"Полное имя\",\n  \"label_password\": \"Пароль\",\n  \"label_confirm_password\": \"Подтвердите пароль\",\n  \"label_api_key\": \"API-ключ\",\n  \"label_instance_url\": \"URL экземпляра\",\n  \"label_custom_url\": \"Пользовательский URL\",\n  \"label_feedback\": \"Обратная связь\",\n  \"label_bio\": \"Биография\",\n  \"label_role\": \"Роль\",\n  \"label_country\": \"Страна\",\n  \"label_audience_size\": \"Размер аудитории на всех платформах\",\n  \"label_pick_time\": \"Выберите время\",\n  \"label_nickname\": \"Никнейм\",\n  \"label_write_anything\": \"Напишите что угодно\",\n  \"label_output_format\": \"Формат вывода\",\n  \"label_add_pictures\": \"Добавить изображения?\",\n  \"label_hour\": \"Час\",\n  \"label_minutes\": \"Минуты\",\n  \"label_select_publication\": \"Выберите публикацию\",\n  \"label_canonical_link\": \"Каноническая ссылка\",\n  \"label_cover_picture\": \"Обложка\",\n  \"label_tags\": \"Теги\",\n  \"label_topics\": \"Темы\",\n  \"label_tags_maximum_4\": \"Теги (максимум 4)\",\n  \"label_attachments\": \"Вложения\",\n  \"label_type\": \"Тип\",\n  \"label_thumbnail\": \"Миниатюра\",\n  \"label_who_can_see_this_video\": \"Кто может видеть это видео?\",\n  \"label_content_posting_method\": \"Способ публикации контента\",\n  \"label_auto_add_music\": \"Автоматически добавить музыку\",\n  \"label_duet\": \"Дуэт\",\n  \"label_stitch\": \"Сшивание\",\n  \"label_comments\": \"Комментарии\",\n  \"label_disclose_video_content\": \"Раскрыть содержание видео\",\n  \"label_your_brand\": \"Ваш бренд\",\n  \"label_branded_content\": \"Брендированный контент\",\n  \"label_subreddit\": \"Сабреддит\",\n  \"label_flair\": \"Флэр\",\n  \"label_media\": \"Медиа\",\n  \"label_search_subreddit\": \"Поиск сабреддита\",\n  \"label_delay\": \"Задержка\",\n  \"label_post_type\": \"Тип поста\",\n  \"label_collaborators\": \"Соавторы (макс. 3) — аккаунты не могут быть приватными\",\n  \"label_community\": \"Сообщество\",\n  \"label_search_community\": \"Поиск сообщества\",\n  \"label_channel\": \"Канал\",\n  \"label_search_channel\": \"Поиск канала\",\n  \"label_select_channel\": \"Выбрать канал\",\n  \"label_new_password\": \"Новый пароль\",\n  \"label_repeat_password\": \"Повторите пароль\",\n  \"label_platform\": \"Платформа\",\n  \"label_price_per_post\": \"Цена за пост\",\n  \"label_integrations\": \"Интеграции\",\n  \"label_code\": \"Код\",\n  \"label_should_sync_last_post\": \"Синхронизировать последний пост?\",\n  \"label_when_post\": \"Когда опубликовать?\",\n  \"label_autogenerate_content\": \"Автоматически сгенерировать контент\",\n  \"label_generate_picture\": \"Сгенерировать изображение?\",\n  \"label_company\": \"Компания\",\n  \"label_tag_color\": \"Цвет тега\",\n  \"label_select_board\": \"Выбрать доску\",\n  \"label_select_organization\": \"Выбрать организацию\",\n  \"label_auto_add_signature\": \"Автоматически добавлять подпись?\",\n  \"enable_color_picker\": \"Включить выбор цвета\",\n  \"cancel_the_color_picker\": \"Отменить выбор цвета\",\n  \"no_content_yet\": \"Пока нет контента\",\n  \"write_your_reply\": \"Напишите свой пост...\",\n  \"add_a_tag\": \"Добавить тег\",\n  \"add_to_calendar\": \"Добавить в календарь\",\n  \"select_channels_from_circles\": \"Выберите каналы из кругов выше\",\n  \"not_matching_order\": \"Порядок не совпадает\",\n  \"submit_for_order\": \"Отправить для заказа\",\n  \"schedule\": \"Расписание\",\n  \"update\": \"Обновить\",\n  \"attachments\": \"Вложения\",\n  \"tags\": \"Теги\",\n  \"public_to_everyone\": \"Доступно всем\",\n  \"mutual_follow_friends\": \"Взаимные друзья\",\n  \"follower_of_creator\": \"Подписчик автора\",\n  \"self_only\": \"Только для себя\",\n  \"post_content_directly_to_tiktok\": \"Публиковать контент напрямую в TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Загрузить контент в TikTok без публикации\",\n  \"choose_upload_without_posting_description\": \"Выберите загрузку без публикации, если хотите просмотреть и отредактировать свой контент в приложении TikTok перед публикацией. Это даст вам доступ к встроенным инструментам редактирования TikTok и позволит внести финальные изменения перед публикацией.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Будет ли с меня взиматься плата Postiz?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Для подтверждения информации о кредитной карте Postiz временно удержит $2 и сразу же их вернет. Вы можете отменить подписку в любое время через настройки без необходимости разговаривать с оператором.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Можно ли доверять Postiz?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz — это полностью open-source! Мы верим в этичную и прозрачную культуру, а значит, Postiz будет существовать всегда. Вы можете ознакомиться с полным кодом или использовать его для личных проектов. Чтобы просмотреть open-source репозиторий, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">нажмите здесь</a>.\",\n  \"faq_what_are_channels\": \"Что такое каналы?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz позволяет планировать публикации между разными каналами.\\nКанал — это платформа для публикаций, где вы можете планировать свои посты.\\nНапример, вы можете планировать публикации в X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads и Pinterest.\",\n  \"faq_what_are_team_members\": \"Кто такие участники команды?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Если у вас есть команда с несколькими участниками, вы можете пригласить их в рабочее пространство для совместной работы над публикациями и добавления их личных каналов\",\n  \"faq_what_is_ai_auto_complete\": \"Что такое автозаполнение на базе ИИ?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Мы автоматизируем ChatGPT, чтобы помочь вам писать посты и статьи для соцсетей.\",\n  \"enter_email\": \"Введите email\",\n  \"are_you_sure\": \"Вы уверены?\",\n  \"no_cancel\": \"Нет, отмена!\",\n  \"are_you_sure_you_want_to_delete\": \"Вы уверены, что хотите удалить {{name}}?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Вы уверены, что хотите удалить изображение?\",\n  \"are_you_sure_you_want_to_logout\": \"Вы уверены, что хотите выйти?\",\n  \"yes_logout\": \"Да, выйти\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Вы уверены, что хотите удалить этот слот?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Вы уверены, что хотите удалить этот сабреддит?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Вы уверены, что хотите закрыть окно?\",\n  \"yes_close\": \"Да, закрыть\",\n  \"link_copied_to_clipboard\": \"Ссылка скопирована в буфер обмена\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Вы уверены, что хотите закрыть это окно? (все данные будут потеряны)\",\n  \"yes_close_it\": \"Да, закрыть!\",\n  \"uploading_pictures\": \"Загрузка изображений...\",\n  \"agent_starting\": \"Запуск агента\",\n  \"researching_your_content\": \"Анализируем ваш контент...\",\n  \"understanding_the_category\": \"Определяем категорию...\",\n  \"finding_the_topic\": \"Определяем тему...\",\n  \"finding_popular_posts_to_match_with\": \"Ищем популярные посты для сопоставления...\",\n  \"generating_hook\": \"Генерируем зацепку...\",\n  \"generating_content\": \"Генерируем контент...\",\n  \"generating_pictures\": \"Генерируем изображения...\",\n  \"finding_time_to_post\": \"Определяем время для публикации...\",\n  \"write_anything\": \"Напишите что угодно\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Вы можете написать всё, что хотите, и также добавить ссылки — мы проведём исследование за вас...\",\n  \"output_format\": \"Формат вывода\",\n  \"add_pictures\": \"Добавить изображения?\",\n  \"7_days\": \"7 дней\",\n  \"30_days\": \"30 дней\",\n  \"90_days\": \"90 дней\",\n  \"start_7_days_free_trial\": \"Начать 7-дневную бесплатную пробную версию\",\n  \"change_language\": \"Сменить язык\",\n  \"that_a_wrap\": \"На этом всё!\\n\\nЕсли вам понравилась эта серия:\\n\\n1. Подпишитесь на меня @{{username}}, чтобы не пропустить новые посты\\n2. Ретвитните твит ниже, чтобы поделиться этой серией со своей аудиторией\\n\",\n  \"post_as_images_carousel\": \"Опубликовать как карусель изображений\",\n  \"save_set\": \"Сохранить набор\",\n  \"separate_post\": \"Разделить пост на несколько постов\",\n  \"label_who_can_reply_to_this_post\": \"Кто может отвечать на этот пост?\",\n  \"delete_integration\": \"Удалить интеграцию\",\n  \"start_writing_your_post\": \"Начните писать свой пост для предварительного просмотра\",\n  \"billing_join_over\": \"Присоединяйтесь к\",\n  \"billing_entrepreneurs_count\": \"20 000+ предпринимателей\",\n  \"billing_who_use\": \"которые используют\",\n  \"billing_postiz_grow_social\": \"Postiz для роста своей социальной активности\",\n  \"billing_no_risk_trial\": \"100% бесплатный пробный период без риска\",\n  \"billing_pay_nothing_7_days\": \"Платите НИЧЕГО первые 7 дней\",\n  \"billing_cancel_anytime\": \"Отменяйте в любое время через настройки\",\n  \"billing_choose_plan\": \"Выберите тариф\",\n  \"billing_monthly\": \"Ежемесячно\",\n  \"billing_yearly\": \"Ежегодно\",\n  \"billing_20_percent_off\": \"Скидка 20%\",\n  \"billing_features\": \"Функции\",\n  \"billing_channel\": \"канал\",\n  \"billing_channels\": \"каналы\",\n  \"billing_unlimited\": \"Безлимитно\",\n  \"billing_posts_per_month\": \"постов в месяц\",\n  \"billing_unlimited_team_members\": \"Неограниченное количество участников команды\",\n  \"billing_ai_auto_complete\": \"Автозаполнение на базе ИИ\",\n  \"billing_ai_copilots\": \"ИИ-ассистенты\",\n  \"billing_ai_autocomplete\": \"Автозаполнение на базе ИИ\",\n  \"billing_advanced_picture_editor\": \"Продвинутый редактор изображений\",\n  \"billing_ai_images_per_month\": \"ИИ-изображений в месяц\",\n  \"billing_ai_videos_per_month\": \"ИИ-видео в месяц\",\n  \"billing_billing_address\": \"Платёжный адрес\",\n  \"billing_payment\": \"Платёж\",\n  \"billing_powered_by_stripe\": \"Безопасные платежи обрабатываются с помощью\",\n  \"billing_your_7_day_trial_is\": \"Ваш 7-дневный пробный период\",\n  \"billing_100_percent_free\": \"100% бесплатно\",\n  \"billing_ending\": \"заканчивается\",\n  \"billing_cancel_anytime_short\": \"Отменить можно в любое время.\",\n  \"billing_pay_0_start_trial\": \"Заплатите $0 сегодня — начните бесплатный пробный период!\",\n  \"billing_pay_now\": \"Оплатить сейчас\",\n  \"billing_per_month\": \"/ месяц\",\n  \"billing_per_year\": \"/ год\",\n  \"billing_order_summary\": \"Сводка заказа\",\n  \"billing_applied\": \"Применено\",\n  \"billing_due_today\": \"К оплате сегодня\",\n  \"billing_then\": \"Затем\",\n  \"billing_on\": \"в\",\n  \"billing_discount_applied\": \"скидка применена\",\n  \"billing_remove\": \"Удалить\",\n  \"billing_coupon_expires\": \"Купон истекает\",\n  \"billing_invalid_coupon\": \"Недействительный промокод\",\n  \"billing_coupon_applied\": \"Промокод успешно применён!\",\n  \"billing_coupon_removed\": \"Промокод удалён\",\n  \"billing_error_removing_coupon\": \"Ошибка при удалении промокода\",\n  \"billing_have_discount_coupon\": \"Есть промокод на скидку?\",\n  \"billing_discount_coupon\": \"Промокод на скидку\",\n  \"billing_cancel\": \"Отмена\",\n  \"billing_enter_coupon_code\": \"Введите промокод\",\n  \"billing_applying\": \"Применение...\",\n  \"billing_apply\": \"Применить\",\n  \"billing_subscription\": \"Подписка\",\n  \"billing_cancel_notice\": \"Вы можете отменить подписку в любое время через настройки без необходимости разговаривать с оператором и больше не будете платить.\",\n  \"select_channels\": \"Выберите каналы\",\n  \"start_a_new_chat\": \"Начать новый чат\",\n  \"your_assistant\": \"Ваш помощник\",\n  \"agent_welcome_message\": \"Здравствуйте, я ваш агент Postiz 🙌🏻.\\n\\nЯ могу запланировать публикацию одного или нескольких постов в нескольких каналах, а также сгенерировать изображения и видео.\\n\\nВы можете выбрать нужные каналы в левом меню.\\n\\nВаши предыдущие разговоры доступны в правом меню.\\n\\nТакже вы можете использовать меня как MCP Server, проверьте Настройки >> Публичный API\",\n  \"last_github_trending\": \"Последние тренды на Github\",\n  \"next_predicted_github_trending\": \"Следующий прогнозируемый тренд на GitHub\",\n  \"repository\": \"Репозиторий\",\n  \"date\": \"Дата\",\n  \"total_stars\": \"Всего звёзд\",\n  \"total_forks\": \"Всего форков\",\n  \"continue_with\": \"Продолжить с\",\n  \"email_address\": \"Адрес электронной почты\",\n  \"email_already_exists\": \"Электронная почта уже существует\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Редактировать автопост\",\n  \"add_autopost_title\": \"Добавить автопост\",\n  \"webhook_deleted_successfully\": \"Вебхук успешно удалён\",\n  \"all_integrations\": \"Все интеграции\",\n  \"specific_integrations\": \"Конкретные интеграции\",\n  \"post_on_next_available_slot\": \"Опубликовать в следующем доступном слоте\",\n  \"post_immediately\": \"Опубликовать немедленно\",\n  \"could_not_use_rss_feed\": \"Не удалось использовать этот RSS-канал\",\n  \"rss_valid\": \"RSS-канал действителен!\",\n  \"autopost_updated_successfully\": \"Автопост успешно обновлён\",\n  \"autopost_added_successfully\": \"Автопост успешно добавлен\",\n  \"write_your_post_placeholder\": \"Напишите свой пост...\",\n  \"select_or_upload_pictures_max_5\": \"Выберите или загрузите изображения (максимум 5 за раз).\",\n  \"you_can_drag_drop_pictures\": \"Вы также можете перетащить изображения.\",\n  \"you_dont_have_any_media_yet\": \"У вас ещё нет медиафайлов\",\n  \"media_library\": \"Медиатека\",\n  \"media_settings\": \"Настройки медиа\",\n  \"media_editor\": \"Редактор медиа\",\n  \"close\": \"Закрыть\",\n  \"me\": \"Я\",\n  \"noname\": \"Без имени\",\n  \"password_reset_link_expired\": \"Ссылка для сброса пароля истекла. Пожалуйста, попробуйте снова.\",\n  \"invalid_api_key\": \"Недействительный API-ключ\",\n  \"could_not_connect_to_platform\": \"Не удалось подключиться к платформе\",\n  \"web3_provider\": \"Web3-провайдер\",\n  \"add_provider_title\": \"Добавить провайдера\",\n  \"profile_updated\": \"Профиль обновлён\",\n  \"sets\": \"Наборы\",\n  \"invitation_link_sent\": \"Ссылка-приглашение отправлена\",\n  \"send_invitation_link\": \"Отправить ссылку-приглашение\",\n  \"copy_link\": \"Скопировать ссылку\",\n  \"are_you_sure_remove_team_member\": \"Вы уверены, что хотите удалить этого участника команды?\",\n  \"admin\": \"Администратор\",\n  \"super_admin\": \"Супер администратор\",\n  \"update_webhook\": \"Обновить вебхук\",\n  \"add_webhook\": \"Добавить вебхук\",\n  \"webhook_updated_successfully\": \"Вебхук успешно обновлён\",\n  \"webhook_added_successfully\": \"Вебхук успешно добавлен\",\n  \"webhook_sent\": \"Вебхук отправлен\",\n  \"today\": \"Сегодня\",\n  \"channel_disconnected_click_to_reconnect\": \"Канал отключён, нажмите для повторного подключения.\",\n  \"channel_disabled_upgrade_plan\": \"Этот канал отключён, пожалуйста, обновите ваш тариф для его активации.\",\n  \"channel_added\": \"Канал добавлен\",\n  \"are_you_sure_disable_channel\": \"Вы уверены, что хотите отключить этот канал?\",\n  \"disable_channel_title\": \"Отключить канал\",\n  \"channel_disabled\": \"Канал отключён\",\n  \"are_you_sure_delete_channel\": \"Вы уверены, что хотите удалить этот канал?\",\n  \"delete_channel_title\": \"Удалить канал\",\n  \"delete_posts_before_channel\": \"Вам нужно удалить все публикации, связанные с этим каналом, прежде чем его удалить\",\n  \"channel_deleted\": \"Канал удалён\",\n  \"channel_enabled\": \"Канал включён\",\n  \"time_table_slots\": \"Слоты расписания\",\n  \"channel_id_copied\": \"ID канала скопирован в буфер обмена\",\n  \"settings_updated\": \"Настройки обновлены\",\n  \"customer_updated\": \"Клиент обновлён\",\n  \"custom_url\": \"Пользовательский URL\",\n  \"picture\": \"Изображение\",\n  \"upgrade_required\": \"Вам необходимо обновить тариф для использования этой функции\",\n  \"move_to_billing\": \"Перейти к оплате\",\n  \"payment_required\": \"Требуется оплата\",\n  \"no_content\": \"нет содержимого\",\n  \"select_customer_tooltip\": \"Выберите клиента\",\n  \"customers\": \"Клиенты\",\n  \"hour\": \"Час\",\n  \"minutes\": \"Минуты\",\n  \"updated\": \"Обновлено\",\n  \"change_bot_picture_title\": \"Изменить изображение бота\",\n  \"select_customer_label\": \"Выберите клиента\",\n  \"start_typing\": \"Начните вводить...\",\n  \"choose_set_or_continue\": \"Выберите набор или продолжите без него\",\n  \"continue_without_set\": \"Продолжить без набора\",\n  \"select_set\": \"Выберите набор\",\n  \"channel_settings\": \"Настройки\",\n  \"post_needs_content_or_image\": \"Ваш пост должен содержать хотя бы один символ или одно изображение.\",\n  \"please_fix_your_settings\": \"Пожалуйста, исправьте ваши настройки\",\n  \"shortlink_urls_question\": \"Хотите сократить ссылки? Это позволит получать статистику по кликам.\",\n  \"yes_shortlink_it\": \"Да, сократить!\",\n  \"added_successfully\": \"Успешно добавлено\",\n  \"updated_successfully\": \"Успешно обновлено\",\n  \"create_post_title\": \"Создать пост\",\n  \"post_preview\": \"Предпросмотр поста\",\n  \"check_circles_above\": \"Проверьте круги выше\",\n  \"create_output\": \"Создать результат\",\n  \"assistant_initial_message\": \"Привет! Я могу помочь вам улучшить ваши посты для социальных сетей.\",\n  \"no_longer_global_mode\": \"Глобальный режим отключён\",\n  \"two_days\": \"Два дня\",\n  \"three_days\": \"Три дня\",\n  \"four_days\": \"Четыре дня\",\n  \"five_days\": \"Пять дней\",\n  \"six_days\": \"Шесть дней\",\n  \"two_weeks\": \"Две недели\",\n  \"repeat_post_every_label\": \"Повторять публикацию каждые\",\n  \"add_new_tag\": \"Добавить новый тег\",\n  \"tag_name\": \"Название\",\n  \"post_is_too_long\": \"пост слишком длинный, пожалуйста, исправьте его\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Ваша публикация должна содержать хотя бы один символ или одно изображение.\",\n  \"internal_edit\": \"Внутреннее редактирование\",\n  \"are_you_sure_go_back_to_global_mode\": \"Это действие необратимо. Вы уверены, что хотите вернуться в глобальный режим?\",\n  \"yes_go_back_to_global_mode\": \"Да, вернуться в глобальный режим\",\n  \"are_you_sure_delete_this_post\": \"Вы уверены, что хотите удалить эту публикацию?\",\n  \"yes_delete_it\": \"Да, удалить!\",\n  \"cant_edit_networks_when_creating_set\": \"Вы не можете редактировать сети при создании набора\",\n  \"click_to_exit_global_editing\": \"Нажмите эту кнопку, чтобы выйти из глобального редактирования и настроить публикацию для этого канала\",\n  \"edit_content\": \"Редактировать содержимое\",\n  \"editing_a_specific_network\": \"Редактирование определённой сети\",\n  \"back_to_global\": \"Вернуться к глобальному\",\n  \"delete_post_tooltip\": \"Удалить публикацию\",\n  \"drop_files_here_to_upload\": \"Перетащите файлы сюда для загрузки\",\n  \"insert_emoji\": \"Вставить эмодзи\",\n  \"write_something\": \"Напишите что-нибудь …\",\n  \"click_channel_to_add\": \"Нажмите на канал, чтобы добавить его\",\n  \"connect_your_channels\": \"Подключите ваши каналы\",\n  \"connect_social_media_to_start\": \"Подключите свои аккаунты в социальных сетях, чтобы начать планировать публикации\",\n  \"connected_channels\": \"Подключенные каналы\",\n  \"continue\": \"Продолжить\",\n  \"continue_without_channels\": \"Продолжить без каналов\",\n  \"watch_tutorial\": \"Смотреть обучение\",\n  \"watch_tutorial_title\": \"Узнайте, как пользоваться Postiz\",\n  \"watch_tutorial_description\": \"Посмотрите это короткое видео, чтобы узнать, как получить максимум от Postiz\",\n  \"back\": \"Назад\",\n  \"get_started\": \"Начать\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/tr/translation.json",
    "content": "{\n  \"calendar\": \"Takvim\",\n  \"webhooks\": \"Web kancaları\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Web kancaları, Postiz'de bir şey olduğunda bir HTTP isteği aracılığıyla bildirim almanın bir yoludur.\",\n  \"name\": \"İsim\",\n  \"url\": \"URL\",\n  \"edit\": \"Düzenle\",\n  \"delete\": \"Sil\",\n  \"add_a_webhook\": \"Bir web kancası ekle\",\n  \"save\": \"Kaydet\",\n  \"send_test\": \"Test Gönder\",\n  \"select_role\": \"Rol Seç\",\n  \"video_made_with_ai\": \"Video yapay zeka ile oluşturuldu\",\n  \"please_add_at_least\": \"Lütfen en az 20 karakter ekleyin\",\n  \"send_invitation_via_email\": \"Davetiyeyi e-posta ile gönderilsin mi?\",\n  \"global_settings\": \"Genel Ayarlar\",\n  \"copy_id\": \"Kanal Kimliğini Kopyala\",\n  \"team_members\": \"Takım Üyeleri\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Hesabınızı yönetmesi için asistanınızı veya takım üyenizi davet edin\",\n  \"remove\": \"Kaldır\",\n  \"add_another_member\": \"Başka bir üye ekle\",\n  \"signatures\": \"İmzalar\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Gönderilerinizde kullanılmak üzere hesabınıza imzalar ekleyebilirsiniz.\",\n  \"content\": \"İçerik\",\n  \"auto_add\": \"Otomatik Ekle?\",\n  \"delay_comment\": \"Gecikme yorumu\",\n  \"actions\": \"Eylemler\",\n  \"use_signature\": \"İmzayı Kullan\",\n  \"add_a_signature\": \"Bir imza ekle\",\n  \"no\": \"Hayır\",\n  \"yes\": \"Evet\",\n  \"your_git_repository\": \"Git Depo'nuz\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Güncellemeler ve analizler almak için GitHub deponuzu bağlayın\",\n  \"connected\": \"Bağlandı:\",\n  \"disconnect\": \"Bağlantıyı Kes\",\n  \"connect_your_repository\": \"Depoyu Bağla\",\n  \"cancel\": \"İptal\",\n  \"connect\": \"Bağla\",\n  \"public_api\": \"Genel API\",\n  \"check_n8n\": \"Postiz için özel N8N düğümümüzü inceleyin.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Araçlarınızla entegre olmak için Postiz API'sini kullanın.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Nasıl kullanılacağını dokümantasyondan okuyun.\",\n  \"reveal\": \"Göster\",\n  \"copy_key\": \"Anahtarı Kopyala\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Gönderilerinizi daha hızlı planlamak için Postiz MCP sunucusunu istemcinize (Http akışı) bağlayın!\",\n  \"share_with_a_client\": \"Bir müşteriyle paylaş\",\n  \"post\": \"Gönderi\",\n  \"comments\": \"Yorumlar\",\n  \"user\": \"Kullanıcı\",\n  \"login_register_to_add_comments\": \"Yorum eklemek için Giriş Yap / Kayıt Ol\",\n  \"status\": \"Durum:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Kanallarınızla eşleşen eklenti yok\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Şunlardan birini eklemelisiniz: X veya LinkedIn veya Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Kanalları eklemek için takvime gidin\",\n  \"channels\": \"Kanallar\",\n  \"activate\": \"Aktifleştir\",\n  \"this_channel_needs_to_be_refreshed\": \"Bu kanalın yenilenmesi gerekiyor,\",\n  \"click_here_to_refresh\": \"yenilemek için buraya tıklayın\",\n  \"can_t_show_analytics_yet\": \"Analitikler henüz gösterilemiyor\",\n  \"you_have_to_add_social_media_channels\": \"Sosyal medya kanalları eklemelisiniz\",\n  \"supported\": \"Desteklenen:\",\n  \"step\": \"ADIM\",\n  \"skip_onboarding\": \"Başlangıç rehberini atla\",\n  \"onboarding\": \"Başlangıç rehberi\",\n  \"next\": \"Sonraki\",\n  \"you_are_done_from_here_you_can\": \"Hazırsınız, buradan şunları yapabilirsiniz:\",\n  \"view_analytics\": \"Analitikleri Görüntüle\",\n  \"schedule_a_new_post\": \"Yeni bir gönderi planla\",\n  \"to_sell_posts_you_would_have_to\": \"Gönderi satmak için şunları yapmalısınız:\",\n  \"1_connect_at_least_one_channel\": \"1. En az bir kanal bağlayın\",\n  \"2_connect_you_bank_account\": \"2. Banka hesabınızı bağlayın\",\n  \"go_back_to_connect_channels\": \"Kanalları bağlamaya geri dön\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Banka hesabınızı bağlamak için satıcı sayfasına gidin\",\n  \"connect_channels\": \"Kanalları Bağla\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Sosyal medya ve yayın platformu kanallarınızı bağlayarak\\n            gönderileri daha sonra planlayabilirsiniz\",\n  \"social\": \"Sosyal\",\n  \"publishing_platforms\": \"Yayın Platformları\",\n  \"no_channels\": \"Henüz kanal yok\",\n  \"connect_your_accounts\": \"Zamanlama, paylaşım ve analiz işlemlerine başlamak için sosyal hesaplarınızı bağlayın — hepsi tek bir yerde.\",\n  \"notifications\": \"Bildirimler\",\n  \"no_notifications\": \"Bildirim yok\",\n  \"send_message\": \"Mesaj Gönder\",\n  \"mar_28\": \"28 Mar\",\n  \"there_are_no_messages_yet\": \"Henüz mesaj yok.\",\n  \"checkout_the_marketplace\": \"Pazaryerini incele\",\n  \"go_to_marketplace\": \"Pazaryerine git\",\n  \"all_messages\": \"Tüm Mesajlar\",\n  \"previous\": \"Önceki\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Resim seçin veya yükleyin (en fazla 5 adet aynı anda)\",\n  \"you_can_also_drag_drop_pictures\": \"Ayrıca resimleri sürükleyip bırakabilirsiniz\",\n  \"you_don_t_have_any_assets_yet\": \"Henüz herhangi bir varlığınız yok.\",\n  \"click_the_button_below_to_upload_one\": \"Bir tane yüklemek için aşağıdaki butona tıklayın\",\n  \"click_the_button_below_to_upload_other\": \"Birden fazla yüklemek için aşağıdaki butona tıklayın\",\n  \"add_selected_media\": \"Seçili medyayı ekle\",\n  \"insert_media\": \"Medya Ekle\",\n  \"design_media\": \"Medya Tasarla\",\n  \"select\": \"Seç\",\n  \"editor\": \"Editör\",\n  \"clear\": \"Temizle\",\n  \"order_completed\": \"Sipariş tamamlandı\",\n  \"the_order_has_been_completed\": \"Sipariş tamamlandı\",\n  \"post_has_been_published\": \"Gönderi yayınlandı\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Yeni Teklif\",\n  \"platform\": \"Platform\",\n  \"posts\": \"Gönderiler\",\n  \"pay_accept_offer\": \"Öde ve Teklifi Kabul Et\",\n  \"accepted\": \"Kabul Edildi\",\n  \"post_draft\": \"Taslak Gönderi\",\n  \"revision_needed\": \"Düzeltme Gerekli\",\n  \"approve\": \"Onayla\",\n  \"preview\": \"Önizleme\",\n  \"revision_requested\": \"Düzeltme Talep Edildi\",\n  \"accepted_1\": \"KABUL EDİLDİ\",\n  \"cancelled_by_the_seller\": \"Satıcı tarafından iptal edildi\",\n  \"please_select_your_country_where_your_business_is\": \"Lütfen işletmenizin bulunduğu ülkeyi seçin.\",\n  \"select_country\": \"--ÜLKE SEÇİN--\",\n  \"connect_bank_account\": \"Banka Hesabını Bağla\",\n  \"seller_mode\": \"Satıcı Modu\",\n  \"active\": \"Aktif\",\n  \"details\": \"Detaylar\",\n  \"audience_size\": \"Hedef Kitle Büyüklüğü\",\n  \"add_another_platform\": \"Başka bir platform ekle\",\n  \"send_an_offer_for\": \"Şu fiyatla teklif gönder: $\",\n  \"complete_order_and_pay_early\": \"Siparişi tamamla ve erken öde\",\n  \"order_in_progress\": \"Sipariş devam ediyor\",\n  \"create_a_new_offer\": \"Yeni bir teklif oluştur\",\n  \"orders\": \"Siparişler\",\n  \"price\": \"Fiyat\",\n  \"state\": \"Durum\",\n  \"showing\": \"Gösteriliyor\",\n  \"to\": \"-e\",\n  \"from\": \"-den\",\n  \"results\": \"Sonuçlar\",\n  \"content_writer\": \"İçerik Yazarı\",\n  \"influencer\": \"Influencer\",\n  \"request_service\": \"Hizmet Talep Et\",\n  \"the_marketplace_is_not_opened_yet\": \"Pazar yeri henüz açılmadı\",\n  \"check_again_soon\": \"Yakında tekrar kontrol et!\",\n  \"filter\": \"Filtrele\",\n  \"result\": \"Sonuç\",\n  \"seller\": \"Satıcı\",\n  \"buyer\": \"Alıcı\",\n  \"discord_support\": \"Discord Desteği\",\n  \"teams\": \"Takımlar\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Otomatik Paylaşım\",\n  \"logout_from\": \"Çıkış yap\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Postiz kullanan 10.000+ girişimciye katılın\",\n  \"to_manage_all_your_social_media_channels\": \"Tüm sosyal medya kanallarınızı yönetmek için\",\n  \"100_no_risk_trial\": \"%100 risksiz deneme\",\n  \"pay_nothing_for_the_first_7_days\": \"İlk 7 gün hiçbir ücret ödeme\",\n  \"cancel_anytime_hassle_free\": \"İstediğiniz zaman, ayarlardan iptal edin\",\n  \"add_free_subscription\": \"-- ÜCRETSİZ ABONELİK EKLE --\",\n  \"currently_impersonating\": \"Şu anda taklit ediliyor\",\n  \"user_1\": \"kullanıcı:\",\n  \"drag_n_drop_some_files_here\": \"Dosyaları buraya sürükleyip bırakın\",\n  \"add_time_slot\": \"Zaman Dilimi Ekle\",\n  \"add_slot\": \"Zaman Dilimi Ekle\",\n  \"cancel_publication\": \"Yayını İptal Et\",\n  \"statistics\": \"İstatistikler\",\n  \"loading\": \"Yükleniyor\",\n  \"short_link\": \"Kısa Link\",\n  \"original_link\": \"Orijinal Link\",\n  \"clicks\": \"Tıklamalar\",\n  \"selected_customer\": \"Seçili Müşteri\",\n  \"customer\": \"Müşteri:\",\n  \"repeat_post_every\": \"Gönderiyi Her ... Tekrarla\",\n  \"use_this_media\": \"Bu medyayı kullan\",\n  \"create_new_post\": \"Gönderi Oluştur\",\n  \"update_post\": \"Mevcut Gönderiyi Güncelle\",\n  \"merge_comments_into_one_post\": \"Yorumları tek gönderide birleştir\",\n  \"accounts_that_will_engage\": \"Etkileşime Geçecek Hesaplar:\",\n  \"day\": \"Gün\",\n  \"week\": \"Hafta\",\n  \"month\": \"Ay\",\n  \"remove_from_customer\": \"Müşteriden kaldır\",\n  \"show_more\": \"+ Daha fazla göster\",\n  \"show_less\": \"- Daha az göster\",\n  \"upload\": \"Yükle\",\n  \"ai\": \"Yapay Zeka\",\n  \"add_channel\": \"Kanal Ekle\",\n  \"add_platform\": \"Platform ekle\",\n  \"articles\": \"Makaleler\",\n  \"add_comment\": \"Yorum ekle\",\n  \"add_post\": \"Bir başlıkta gönderi ekle\",\n  \"add_comment_or_post\": \"Yorum / gönderi ekle\",\n  \"you_are_in_global_editing_mode\": \"Genel düzenleme modundasınız\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Gönderi en az 6 karakter uzunluğunda olmalıdır\",\n  \"are_you_sure_you_want_to_delete_post\": \"Bu gönderiyi silmek istediğinizden emin misiniz?\",\n  \"post_deleted_successfully\": \"Gönderi başarıyla silindi\",\n  \"delete_post\": \"Gönderiyi sil\",\n  \"save_as_draft\": \"Taslak olarak kaydet\",\n  \"post_now\": \"Şimdi paylaş\",\n  \"please_add\": \"Lütfen ekleyin\",\n  \"to_your_telegram_group_channel_and_click_here\": \"Telegram grup/kanalınıza ekleyin ve buraya tıklayın:\",\n  \"connect_telegram\": \"Telegram'ı bağla\",\n  \"please_add_the_following_command_in_your_chat\": \"Lütfen sohbetinize aşağıdaki komutu ekleyin:\",\n  \"copy\": \"Kopyala\",\n  \"settings\": \"Ayarlar\",\n  \"integrations\": \"Entegrasyonlar\",\n  \"add_integration\": \"Entegrasyon Ekle\",\n  \"you_are_now_editing_only\": \"Şu anda yalnızca düzenliyorsunuz\",\n  \"tag_a_company\": \"Bir şirket etiketle\",\n  \"video_length_is_invalid_must_be_up_to\": \"Video uzunluğu geçersiz, en fazla\",\n  \"seconds\": \"saniye olmalıdır\",\n  \"this_feature_available_only_for_photos\": \"Bu özellik yalnızca fotoğraflar için geçerlidir, daha sonra değiştirebileceğiniz varsayılan bir müzik ekleyecektir.\",\n  \"allow_user_to\": \"Kullanıcıya izin ver:\",\n  \"your_video_will_be_labeled_promotional\": \"Videonuz \\\"Tanıtım İçeriği\\\" olarak etiketlenecek.\",\n  \"this_cannot_be_changed_once_posted\": \"Videonuz paylaşıldıktan sonra bu değiştirilemez.\",\n  \"turn_on_to_disclose_video_promotes\": \"Bu videonun bir değer karşılığında mal veya hizmet tanıttığını belirtmek için açın. Videonuz kendinizi, üçüncü bir tarafı veya her ikisini tanıtıyor olabilir.\",\n  \"you_are_promoting_yourself\": \"Kendinizi veya kendi markanızı tanıtıyorsunuz.\",\n  \"this_video_will_be_classified_brand_organic\": \"Bu video Marka Organik olarak sınıflandırılacak.\",\n  \"you_are_promoting_another_brand\": \"Başka bir markayı veya üçüncü bir tarafı tanıtıyorsunuz.\",\n  \"this_video_will_be_classified_branded_content\": \"Bu video Markalı İçerik olarak sınıflandırılacak.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Paylaşım yaparak TikTok'un\",\n  \"music_usage_confirmation\": \"Müzik Kullanım Onayı\",\n  \"branded_content_policy\": \"Markalı İçerik Politikası\",\n  \"select_1\": \"--Seç--\",\n  \"select_flair\": \"--Flair Seç--\",\n  \"link\": \"Bağlantı\",\n  \"add_subreddit\": \"Subreddit Ekle\",\n  \"please_add_at_least_one_subreddit\": \"Lütfen en az bir Subreddit ekleyin\",\n  \"add_community\": \"Topluluk Ekle\",\n  \"select_post_type\": \"Gönderi Türü Seç...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"LinkedIn Sayfanıza bağlı herhangi bir işletme bulamadık.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Lütfen bu pencereyi kapatın, yeni bir sayfa oluşturun ve tekrar yeni bir kanal ekleyin.\",\n  \"select_linkedin_page\": \"LinkedIn Sayfası Seç:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Seçilen sayfalara bağlı herhangi bir işletme bulamadık.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Tüm sayfaları ve tüm işletmeleri bağlamanızı öneririz.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Lütfen bu pencereyi kapatın, entegrasyonunuzu silin ve tekrar yeni bir kanal ekleyin.\",\n  \"select_instagram_account\": \"Instagram Hesabı Seç:\",\n  \"select_page\": \"Sayfa Seç:\",\n  \"generate_image_with_ai\": \"Yapay Zeka ile görsel oluştur\",\n  \"reconnect_channel\": \"Kanalı yeniden bağla\",\n  \"update_credentials\": \"Kimlik Bilgilerini Güncelle\",\n  \"additional_settings\": \"Ek Ayarlar\",\n  \"change_bot\": \"Botu Değiştir\",\n  \"move_add_to_customer\": \"Müşteriye taşı / ekle\",\n  \"edit_time_slots\": \"Zaman Dilimlerini Düzenle\",\n  \"enable_channel\": \"Kanalı Etkinleştir\",\n  \"disable_channel\": \"Kanalı Devre Dışı Bırak\",\n  \"add\": \"Ekle\",\n  \"short_post\": \"Kısa gönderi\",\n  \"long_post\": \"Uzun gönderi\",\n  \"a_thread_with_short_posts\": \"Kısa gönderilerden oluşan bir konu\",\n  \"a_thread_with_long_posts\": \"Uzun gönderilerden oluşan bir konu\",\n  \"personal_voice_i_am_happy_to_announce\": \"Kişisel ses (\\\"Duyurmaktan mutluyum\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Şirket sesi (\\\"Duyurmaktan mutluyuz\\\")\",\n  \"generate\": \"Oluştur\",\n  \"generate_posts\": \"Gönderi Oluştur\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"SOL ($199) ile ömür boyu PRO hesap satın alın. Lütfen bu satın alma için iade yapılmadığını unutmayın.\",\n  \"purchase_now\": \"Şimdi satın al\",\n  \"pay_today\": \"Bugün öde\",\n  \"we_are_sorry_to_see_you_go\": \"Gitmene üzüldük :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Kısaca neyi daha iyi yapabileceğimizi söyler misin?\",\n  \"cancel_subscription\": \"Aboneliği İptal Et\",\n  \"plans\": \"Planlar\",\n  \"monthly\": \"AYLIK\",\n  \"yearly\": \"YILLIK\",\n  \"reactivate_subscription\": \"Aboneliği Yeniden Aktifleştir\",\n  \"update_payment_method_invoices_history\": \"Ödeme Yöntemini Güncelle / Fatura Geçmişi\",\n  \"cancel_subscription_1\": \"Aboneliği iptal et\",\n  \"your_subscription_will_be_canceled_at\": \"Aboneliğiniz şu tarihte iptal edilecek:\",\n  \"you_will_never_be_charged_again\": \"Bir daha asla ücretlendirilmeyeceksiniz\",\n  \"current_package\": \"Mevcut Paket:\",\n  \"next_package\": \"Sonraki Paket:\",\n  \"claim\": \"Talep Et\",\n  \"frequently_asked_questions\": \"Sıkça Sorulan Sorular\",\n  \"autopost\": \"Otomatik Paylaşım\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Otomatik paylaşım, RSS'inizdeki yeni öğeleri sosyal medyaya otomatik olarak gönderebilir\",\n  \"title\": \"Başlık\",\n  \"add_an_autopost\": \"Otomatik paylaşım ekle\",\n  \"post_content\": \"Paylaşım içeriği\",\n  \"sign_up\": \"Kayıt Ol\",\n  \"or\": \"VEYA\",\n  \"by_registering_you_agree_to_our\": \"Kayıt olarak şunları kabul etmiş olursunuz:\",\n  \"and\": \"ve\",\n  \"terms_of_service\": \"Hizmet Şartları\",\n  \"privacy_policy\": \"Gizlilik Politikası\",\n  \"create_account\": \"Hesap Oluştur\",\n  \"already_have_an_account\": \"Zaten bir hesabınız var mı?\",\n  \"sign_in\": \"Giriş Yap\",\n  \"sign_in_1\": \"Giriş yap\",\n  \"don_t_have_an_account\": \"Hesabınız yok mu?\",\n  \"forgot_password\": \"Şifremi unuttum\",\n  \"forgot_password_1\": \"Şifremi Unuttum\",\n  \"send_password_reset_email\": \"Şifre Sıfırlama E-postası Gönder\",\n  \"go_back_to_login\": \"Girişe geri dön\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Şifrenizi sıfırlamanız için size bir e-posta gönderdik.\",\n  \"change_password\": \"Şifreyi Değiştir\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Şifrenizi başarıyla sıfırladık. Artık giriş yapabilirsiniz.\",\n  \"click_here_to_go_back_to_login\": \"Giriş ekranına dönmek için buraya tıklayın\",\n  \"activate_your_account\": \"Hesabınızı Aktifleştirin\",\n  \"thank_you_for_registering\": \"Kayıt olduğunuz için teşekkürler!\",\n  \"please_check_your_email_to_activate_your_account\": \"Hesabınızı aktifleştirmek için lütfen e-postanızı kontrol edin.\",\n  \"sign_in_with\": \"İle giriş yap\",\n  \"continue_with_google\": \"Google ile devam et\",\n  \"sign_in_with_github\": \"GitHub ile giriş yap\",\n  \"continue_with_farcaster\": \"Farcaster ile devam et\",\n  \"continue_with_your_wallet\": \"Cüzdanınız ile devam edin\",\n  \"stars_per_day\": \"Günlük yıldızlar\",\n  \"media\": \"Medya\",\n  \"check_launch\": \"Lansmanı Kontrol Et\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Analitikleri görmek için GitHub deposunu ayarlardan yükleyin\",\n  \"stars\": \"Yıldızlar\",\n  \"processing_stars\": \"Yıldızlar işleniyor...\",\n  \"forks\": \"Çatallar\",\n  \"registration_is_disabled\": \"Kayıt olma devre dışı bırakıldı\",\n  \"login_instead\": \"Bunun yerine giriş yapın\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Bir sohbet seçin ve konuşmaya başlayın.\",\n  \"adding_channel_redirecting_you\": \"Kanal ekleniyor, yönlendiriliyorsunuz\",\n  \"could_not_add_provider\": \"Sağlayıcı eklenemedi.\",\n  \"you_are_being_redirected_back\": \"Geri yönlendiriliyorsunuz\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Bazı zorluklar yaşıyoruz, sayfayı yenilemeyi deneyin\",\n  \"post_not_found\": \"Gönderi bulunamadı\",\n  \"publication_date\": \"Yayınlanma Tarihi:\",\n  \"analytics\": \"Analitik\",\n  \"launches\": \"Lansmanlar\",\n  \"plugs\": \"Duyurular\",\n  \"billing\": \"Faturalandırma\",\n  \"affiliate\": \"Ortaklık\",\n  \"monday\": \"Pazartesi\",\n  \"tuesday\": \"Salı\",\n  \"wednesday\": \"Çarşamba\",\n  \"thursday\": \"Perşembe\",\n  \"friday\": \"Cuma\",\n  \"saturday\": \"Cumartesi\",\n  \"sunday\": \"Pazar\",\n  \"can_t_change_date_remove_post_from_publication\": \"Tarihi değiştiremiyorsunuz, gönderiyi yayından kaldırın\",\n  \"predicted_github_trending_change\": \"Tahmini GitHub Trend Değişimi\",\n  \"duplicate_post\": \"Yinelenen Gönderi\",\n  \"preview_post\": \"Gönderiyi Önizle\",\n  \"post_statistics\": \"Gönderi İstatistikleri\",\n  \"draft\": \"Taslak\",\n  \"week_number\": \"{{number}}. Hafta\",\n  \"top_title_edit_webhook\": \"Webhook'u düzenle\",\n  \"top_title_add_webhook\": \"Webhook ekle\",\n  \"top_title_oh_no\": \"Ah hayır\",\n  \"top_title_auto_plug\": \"Otomatik Eklenti: {{title}}\",\n  \"top_title_edit_autopost\": \"Otomatik gönderiyi düzenle\",\n  \"top_title_add_autopost\": \"Otomatik gönderi ekle\",\n  \"top_title_send_a_new_offer\": \"Yeni bir teklif gönder\",\n  \"top_title_media_library\": \"Medya Kütüphanesi\",\n  \"top_title_add_signature\": \"İmza ekle\",\n  \"top_title_send_a_message_to\": \"{{name}} kişisine mesaj gönder\",\n  \"top_title_configure_provider\": \"Sağlayıcıyı Yapılandır\",\n  \"top_title_add_member\": \"Üye Ekle\",\n  \"top_title_change_bot_picture\": \"Bot Resmini Değiştir\",\n  \"top_title_create_a_new_tag\": \"Yeni bir etiket oluştur\",\n  \"top_title_select_company\": \"Şirket Seç\",\n  \"top_title_additional_settings\": \"Ek Ayarlar\",\n  \"top_title_time_table_slots\": \"Zaman Tablosu Dilimleri\",\n  \"top_title_design_media\": \"Medya Tasarla\",\n  \"top_title_edit_post\": \"Gönderiyi Düzenle\",\n  \"top_title_create_post\": \"Gönderi Oluştur\",\n  \"top_title_move__add_to_customer\": \"Taşı / Müşteriye Ekle\",\n  \"top_title_add_api_key_for\": \"{{name}} için API anahtarı ekle\",\n  \"top_title_instance_url\": \"Instance URL\",\n  \"top_title_custom_url\": \"Özel URL\",\n  \"top_title_add_channel\": \"Kanal Ekle\",\n  \"top_title_add_telegram\": \"Telegram Ekle\",\n  \"top_title_add_wrapcast\": \"Wrapcast Ekle\",\n  \"top_title_comments_for\": \"{{date}} için yorumlar\",\n  \"top_title_edit_signature\": \"İmza Düzenle\",\n  \"label_name\": \"Ad\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Başlık\",\n  \"label_subtitle\": \"Alt Başlık\",\n  \"label_email\": \"E-posta\",\n  \"label_full_name\": \"Tam Adı\",\n  \"label_password\": \"Şifre\",\n  \"label_confirm_password\": \"Şifreyi Onayla\",\n  \"label_api_key\": \"API Anahtarı\",\n  \"label_instance_url\": \"Instance URL\",\n  \"label_custom_url\": \"Özel URL\",\n  \"label_feedback\": \"Geri Bildirim\",\n  \"label_bio\": \"Biyografi\",\n  \"label_role\": \"Rol\",\n  \"label_country\": \"Ülke\",\n  \"label_audience_size\": \"Tüm platformlardaki takipçi sayısı\",\n  \"label_pick_time\": \"Zaman Seç\",\n  \"label_nickname\": \"Takma Ad\",\n  \"label_write_anything\": \"Herhangi bir şey yazın\",\n  \"label_output_format\": \"Çıktı formatı\",\n  \"label_add_pictures\": \"Resim ekle?\",\n  \"label_hour\": \"Saat\",\n  \"label_minutes\": \"Dakika\",\n  \"label_select_publication\": \"Yayın Seç\",\n  \"label_canonical_link\": \"Kanonik Bağlantı\",\n  \"label_cover_picture\": \"Kapak resmi\",\n  \"label_tags\": \"Etiketler\",\n  \"label_topics\": \"Konular\",\n  \"label_tags_maximum_4\": \"Etiketler (En fazla 4)\",\n  \"label_attachments\": \"Ekler\",\n  \"label_type\": \"Tür\",\n  \"label_thumbnail\": \"Küçük resim\",\n  \"label_who_can_see_this_video\": \"Bu videoyu kimler görebilir?\",\n  \"label_content_posting_method\": \"İçerik paylaşım yöntemi\",\n  \"label_auto_add_music\": \"Otomatik müzik ekle\",\n  \"label_duet\": \"Düet\",\n  \"label_stitch\": \"Birleştir\",\n  \"label_comments\": \"Yorumlar\",\n  \"label_disclose_video_content\": \"Video İçeriğini Açıkla\",\n  \"label_your_brand\": \"Markanız\",\n  \"label_branded_content\": \"Markalı içerik\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Rozet\",\n  \"label_media\": \"Medya\",\n  \"label_search_subreddit\": \"Subreddit Ara\",\n  \"label_delay\": \"Gecikme\",\n  \"label_post_type\": \"Gönderi Türü\",\n  \"label_collaborators\": \"İşbirlikçiler (en fazla 3) - hesaplar gizli olamaz\",\n  \"label_community\": \"Topluluk\",\n  \"label_search_community\": \"Toplulukta Ara\",\n  \"label_channel\": \"Kanal\",\n  \"label_search_channel\": \"Kanal Ara\",\n  \"label_select_channel\": \"Kanal Seç\",\n  \"label_new_password\": \"Yeni Şifre\",\n  \"label_repeat_password\": \"Şifreyi Tekrarla\",\n  \"label_platform\": \"Platform\",\n  \"label_price_per_post\": \"Gönderi başına fiyat\",\n  \"label_integrations\": \"Entegrasyonlar\",\n  \"label_code\": \"Kod\",\n  \"label_should_sync_last_post\": \"Mevcut son gönderiyi senkronize edelim mi?\",\n  \"label_when_post\": \"Ne zaman paylaşalım?\",\n  \"label_autogenerate_content\": \"İçeriği otomatik oluştur\",\n  \"label_generate_picture\": \"Resim oluşturulsun mu?\",\n  \"label_company\": \"Şirket\",\n  \"label_tag_color\": \"Etiket Rengi\",\n  \"label_select_board\": \"Pano Seç\",\n  \"label_select_organization\": \"Organizasyon Seç\",\n  \"label_auto_add_signature\": \"Otomatik imza eklensin mi?\",\n  \"enable_color_picker\": \"Renk seçiciyi etkinleştir\",\n  \"cancel_the_color_picker\": \"Renk seçiciyi iptal et\",\n  \"no_content_yet\": \"Henüz İçerik Yok\",\n  \"write_your_reply\": \"Gönderinizi yazın...\",\n  \"add_a_tag\": \"Etiket ekle\",\n  \"add_to_calendar\": \"Takvime Ekle\",\n  \"select_channels_from_circles\": \"Yukarıdaki dairelerden kanalları seçin\",\n  \"not_matching_order\": \"Sıra eşleşmiyor\",\n  \"submit_for_order\": \"Sipariş için gönder\",\n  \"schedule\": \"Zamanla\",\n  \"update\": \"Güncelle\",\n  \"attachments\": \"Ekler\",\n  \"tags\": \"Etiketler\",\n  \"public_to_everyone\": \"Herkese açık\",\n  \"mutual_follow_friends\": \"Karşılıklı takip edilen arkadaşlar\",\n  \"follower_of_creator\": \"Oluşturucunun takipçisi\",\n  \"self_only\": \"Sadece kendim\",\n  \"post_content_directly_to_tiktok\": \"İçeriği doğrudan TikTok'a gönder\",\n  \"upload_content_to_tiktok_without_posting\": \"İçeriği TikTok'a paylaşmadan yükle\",\n  \"choose_upload_without_posting_description\": \"İçeriğinizi yayınlamadan önce TikTok uygulamasında gözden geçirmek ve düzenlemek istiyorsanız, paylaşmadan yüklemeyi seçin. Bu, TikTok'un yerleşik düzenleme araçlarına erişmenizi sağlar ve paylaşmadan önce son düzenlemeleri yapmanıza olanak tanır.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Postiz tarafından ücretlendirilecek miyim?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Kredi kartı bilgilerini doğrulamak için Postiz $2 tutacak ve hemen serbest bırakacaktır, ayarlarından herhangi bir kişiyle konuşmadan aboneliğinizi istediğiniz zaman iptal edebilirsiniz.\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Postiz'e güvenebilir miyim?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz gururla açık kaynaklıdır! Etik ve şeffaf bir kültüre inanıyoruz, bu da Postiz'in sonsuza dek yaşayacağı anlamına gelir. Tüm kodu inceleyebilir veya kişisel projelerinizde kullanabilirsiniz. Açık kaynak deposunu görüntülemek için <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">buraya tıklayın</a>.\",\n  \"faq_what_are_channels\": \"Kanallar nedir?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz, gönderilerinizi farklı kanallar arasında zamanlamanızı sağlar.\\nKanal, gönderilerinizi zamanlayabileceğiniz bir paylaşım platformudur.\\nÖrneğin, gönderilerinizi X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads ve Pinterest'te zamanlayabilirsiniz.\",\n  \"faq_what_are_team_members\": \"Takım üyeleri nedir?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Birden fazla üyesi olan bir ekibiniz varsa, onları çalışma alanınıza davet ederek gönderileriniz üzerinde iş birliği yapabilir ve kendi kişisel kanallarını ekleyebilirler.\",\n  \"faq_what_is_ai_auto_complete\": \"Yapay zeka otomatik tamamlama nedir?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Sosyal medya gönderileri ve makaleler yazmanıza yardımcı olmak için ChatGPT'yi otomatikleştiriyoruz.\",\n  \"enter_email\": \"E-posta girin\",\n  \"are_you_sure\": \"Emin misiniz?\",\n  \"no_cancel\": \"Hayır, iptal et!\",\n  \"are_you_sure_you_want_to_delete\": \"{{name}}'i silmek istediğinizden emin misiniz?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Görseli silmek istediğinizden emin misiniz?\",\n  \"are_you_sure_you_want_to_logout\": \"Çıkış yapmak istediğinizden emin misiniz?\",\n  \"yes_logout\": \"Evet, çıkış yap\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Bu slotu silmek istediğinizden emin misiniz?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Bu Subreddit'i silmek istediğinizden emin misiniz?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Pencereyi kapatmak istediğinizden emin misiniz?\",\n  \"yes_close\": \"Evet, kapat\",\n  \"link_copied_to_clipboard\": \"Bağlantı panoya kopyalandı\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Bu modali kapatmak istediğinizden emin misiniz? (tüm veriler kaybolacak)\",\n  \"yes_close_it\": \"Evet, kapat!\",\n  \"uploading_pictures\": \"Resimler yükleniyor...\",\n  \"agent_starting\": \"Aracı başlatılıyor\",\n  \"researching_your_content\": \"İçeriğiniz araştırılıyor...\",\n  \"understanding_the_category\": \"Kategori anlaşılıyor...\",\n  \"finding_the_topic\": \"Konu bulunuyor...\",\n  \"finding_popular_posts_to_match_with\": \"Eşleşecek popüler gönderiler bulunuyor...\",\n  \"generating_hook\": \"Kanca oluşturuluyor...\",\n  \"generating_content\": \"İçerik oluşturuluyor...\",\n  \"generating_pictures\": \"Resimler oluşturuluyor...\",\n  \"finding_time_to_post\": \"Paylaşım için zaman bulunuyor...\",\n  \"write_anything\": \"Herhangi bir şey yazın\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"İstediğiniz her şeyi yazabilir, ayrıca bağlantılar ekleyebilirsiniz, araştırmayı sizin için biz yapacağız...\",\n  \"output_format\": \"Çıktı formatı\",\n  \"add_pictures\": \"Resim ekle?\",\n  \"7_days\": \"7 Gün\",\n  \"30_days\": \"30 Gün\",\n  \"90_days\": \"90 Gün\",\n  \"start_7_days_free_trial\": \"7 gün ücretsiz denemeyi başlat\",\n  \"change_language\": \"Dili Değiştir\",\n  \"that_a_wrap\": \"Bu iş burada bitti!\\n\\nEğer bu diziyi beğendiyseniz:\\n\\n1. Daha fazlası için beni @{{username}} hesabından takip edin\\n2. Aşağıdaki tweet'i RT'leyerek bu diziyi kendi kitlenizle paylaşın\\n\",\n  \"post_as_images_carousel\": \"Görselleri kaydırmalı gönder olarak paylaş\",\n  \"save_set\": \"Seti Kaydet\",\n  \"separate_post\": \"Gönderiyi birden fazla gönderiye ayır\",\n  \"label_who_can_reply_to_this_post\": \"Bu gönderiye kim yanıt verebilir?\",\n  \"delete_integration\": \"Entegrasyonu Sil\",\n  \"start_writing_your_post\": \"Önizleme için gönderinizi yazmaya başlayın\",\n  \"billing_join_over\": \"Katılan\",\n  \"billing_entrepreneurs_count\": \"20.000+ Girişimci\",\n  \"billing_who_use\": \"kullanan\",\n  \"billing_postiz_grow_social\": \"Sosyal Varlıklarını Büyütmek İçin Postiz\",\n  \"billing_no_risk_trial\": \"%100 Risksiz Ücretsiz Deneme\",\n  \"billing_pay_nothing_7_days\": \"İlk 7 gün boyunca HİÇBİR ŞEY ödemeyin\",\n  \"billing_cancel_anytime\": \"İstediğiniz zaman, ayarlardan iptal edin\",\n  \"billing_choose_plan\": \"Bir Plan Seçin\",\n  \"billing_monthly\": \"Aylık\",\n  \"billing_yearly\": \"Yıllık\",\n  \"billing_20_percent_off\": \"%20 İndirim\",\n  \"billing_features\": \"Özellikler\",\n  \"billing_channel\": \"kanal\",\n  \"billing_channels\": \"kanallar\",\n  \"billing_unlimited\": \"Sınırsız\",\n  \"billing_posts_per_month\": \"aylık gönderi\",\n  \"billing_unlimited_team_members\": \"Sınırsız ekip üyesi\",\n  \"billing_ai_auto_complete\": \"Yapay Zeka otomatik tamamlama\",\n  \"billing_ai_copilots\": \"Yapay Zeka yardımcıları\",\n  \"billing_ai_autocomplete\": \"Yapay Zeka Otomatik Tamamlama\",\n  \"billing_advanced_picture_editor\": \"Gelişmiş Resim Editörü\",\n  \"billing_ai_images_per_month\": \"Aylık Yapay Zeka Görselleri\",\n  \"billing_ai_videos_per_month\": \"Aylık Yapay Zeka Videoları\",\n  \"billing_billing_address\": \"Fatura Adresi\",\n  \"billing_payment\": \"Ödeme\",\n  \"billing_powered_by_stripe\": \"Güvenli ödemeler tarafından işlenir\",\n  \"billing_your_7_day_trial_is\": \"7 günlük deneme süreniz\",\n  \"billing_100_percent_free\": \"%100 ücretsiz\",\n  \"billing_ending\": \"sona eriyor\",\n  \"billing_cancel_anytime_short\": \"İstediğiniz zaman ayarlardan iptal edin\",\n  \"billing_pay_0_start_trial\": \"Bugün 0$ ödeyin - Ücretsiz denemenizi başlatın!\",\n  \"billing_pay_now\": \"Şimdi Öde\",\n  \"billing_per_month\": \"/ ay\",\n  \"billing_per_year\": \"/ yıl\",\n  \"billing_order_summary\": \"Sipariş Özeti\",\n  \"billing_applied\": \"Uygulandı\",\n  \"billing_due_today\": \"Bugün ödenecek\",\n  \"billing_then\": \"Sonra\",\n  \"billing_on\": \"tarihinde\",\n  \"billing_discount_applied\": \"uygulandı\",\n  \"billing_remove\": \"Kaldır\",\n  \"billing_coupon_expires\": \"Kuponun son kullanma tarihi\",\n  \"billing_invalid_coupon\": \"Geçersiz kupon kodu\",\n  \"billing_coupon_applied\": \"Kupon başarıyla uygulandı!\",\n  \"billing_coupon_removed\": \"Kupon kaldırıldı\",\n  \"billing_error_removing_coupon\": \"Kupon kaldırılırken hata oluştu\",\n  \"billing_have_discount_coupon\": \"İndirim kuponunuz mu var?\",\n  \"billing_discount_coupon\": \"İndirim Kuponu\",\n  \"billing_cancel\": \"İptal Et\",\n  \"billing_enter_coupon_code\": \"Kupon kodunu girin\",\n  \"billing_applying\": \"Uygulanıyor...\",\n  \"billing_apply\": \"Uygula\",\n  \"billing_subscription\": \"Abonelik\",\n  \"billing_cancel_notice\": \"Ayarlar kısmından herhangi bir kişiyle konuşmadan istediğiniz zaman iptal edebilir ve asla ücretlendirilmezsiniz.\",\n  \"select_channels\": \"Kanalları Seç\",\n  \"start_a_new_chat\": \"Yeni bir sohbet başlat\",\n  \"your_assistant\": \"Asistanınız\",\n  \"agent_welcome_message\": \"Merhaba, ben Postiz ajanınızım 🙌🏻.\\n\\nBir veya birden fazla gönderiyi birden fazla kanala zamanlayabilir, resim ve video oluşturabilirim.\\n\\nKullanmak istediğiniz kanalları sol menüden seçebilirsiniz.\\n\\nÖnceki konuşmalarınızı sağ menüden görebilirsiniz.\\n\\nAyrıca beni bir MCP Sunucusu olarak da kullanabilirsiniz, Ayarlar >> Genel API bölümünü kontrol edin.\",\n  \"last_github_trending\": \"Son Github Trendleri\",\n  \"next_predicted_github_trending\": \"Sonraki Tahmini Github Trendleri\",\n  \"repository\": \"Depo\",\n  \"date\": \"Tarih\",\n  \"total_stars\": \"Toplam Yıldız\",\n  \"total_forks\": \"Toplam Çatallanma\",\n  \"continue_with\": \"Şununla Devam Et\",\n  \"email_address\": \"E-posta Adresi\",\n  \"email_already_exists\": \"E-posta zaten mevcut\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Otomatik Gönderiyi Düzenle\",\n  \"add_autopost_title\": \"Otomatik Gönderi Ekle\",\n  \"webhook_deleted_successfully\": \"Webhook başarıyla silindi\",\n  \"all_integrations\": \"Tüm entegrasyonlar\",\n  \"specific_integrations\": \"Belirli entegrasyonlar\",\n  \"post_on_next_available_slot\": \"Bir sonraki uygun zamanda paylaş\",\n  \"post_immediately\": \"Hemen Paylaş\",\n  \"could_not_use_rss_feed\": \"Bu RSS beslemesi kullanılamadı\",\n  \"rss_valid\": \"RSS geçerli!\",\n  \"autopost_updated_successfully\": \"Otomatik gönderi başarıyla güncellendi\",\n  \"autopost_added_successfully\": \"Otomatik gönderi başarıyla eklendi\",\n  \"write_your_post_placeholder\": \"Gönderinizi yazın...\",\n  \"select_or_upload_pictures_max_5\": \"Resim seçin veya yükleyin (aynı anda en fazla 5).\",\n  \"you_can_drag_drop_pictures\": \"Ayrıca resimleri sürükleyip bırakabilirsiniz.\",\n  \"you_dont_have_any_media_yet\": \"Henüz herhangi bir medyanız yok\",\n  \"media_library\": \"Medya Kütüphanesi\",\n  \"media_settings\": \"Medya Ayarları\",\n  \"media_editor\": \"Medya Editörü\",\n  \"close\": \"Kapat\",\n  \"me\": \"Ben\",\n  \"noname\": \"İsimsiz\",\n  \"password_reset_link_expired\": \"Şifre sıfırlama bağlantınızın süresi doldu. Lütfen tekrar deneyin.\",\n  \"invalid_api_key\": \"Geçersiz API anahtarı\",\n  \"could_not_connect_to_platform\": \"Platforma bağlanılamadı\",\n  \"web3_provider\": \"Web3 sağlayıcı\",\n  \"add_provider_title\": \"Sağlayıcı Ekle\",\n  \"profile_updated\": \"Profil güncellendi\",\n  \"sets\": \"Setler\",\n  \"invitation_link_sent\": \"Davet bağlantısı gönderildi\",\n  \"send_invitation_link\": \"Davet Bağlantısı Gönder\",\n  \"copy_link\": \"Bağlantıyı Kopyala\",\n  \"are_you_sure_remove_team_member\": \"Bu ekip üyesini kaldırmak istediğinizden emin misiniz?\",\n  \"admin\": \"Yönetici\",\n  \"super_admin\": \"Süper Yönetici\",\n  \"update_webhook\": \"Webhook'u güncelle\",\n  \"add_webhook\": \"Webhook ekle\",\n  \"webhook_updated_successfully\": \"Webhook başarıyla güncellendi\",\n  \"webhook_added_successfully\": \"Webhook başarıyla eklendi\",\n  \"webhook_sent\": \"Webhook gönderildi\",\n  \"today\": \"Bugün\",\n  \"channel_disconnected_click_to_reconnect\": \"Kanal bağlantısı kesildi, yeniden bağlanmak için tıklayın.\",\n  \"channel_disabled_upgrade_plan\": \"Bu kanal devre dışı, etkinleştirmek için lütfen planınızı yükseltin.\",\n  \"channel_added\": \"Kanal eklendi\",\n  \"are_you_sure_disable_channel\": \"Bu kanalı devre dışı bırakmak istediğinizden emin misiniz?\",\n  \"disable_channel_title\": \"Kanalı Devre Dışı Bırak\",\n  \"channel_disabled\": \"Kanal Devre Dışı\",\n  \"are_you_sure_delete_channel\": \"Bu kanalı silmek istediğinizden emin misiniz?\",\n  \"delete_channel_title\": \"Kanalı Sil\",\n  \"delete_posts_before_channel\": \"Bu kanalı silmeden önce bu kanala ait tüm gönderileri silmelisiniz\",\n  \"channel_deleted\": \"Kanal Silindi\",\n  \"channel_enabled\": \"Kanal Etkinleştirildi\",\n  \"time_table_slots\": \"Zaman Tablosu Dilimleri\",\n  \"channel_id_copied\": \"Kanal kimliği panoya kopyalandı\",\n  \"settings_updated\": \"Ayarlar Güncellendi\",\n  \"customer_updated\": \"Müşteri Güncellendi\",\n  \"custom_url\": \"Özel URL\",\n  \"picture\": \"Resim\",\n  \"upgrade_required\": \"Bu özelliği kullanmak için yükseltme yapmanız gerekiyor\",\n  \"move_to_billing\": \"Faturalandırmaya geç\",\n  \"payment_required\": \"Ödeme Gerekli\",\n  \"no_content\": \"içerik yok\",\n  \"select_customer_tooltip\": \"Müşteri Seç\",\n  \"customers\": \"Müşteriler\",\n  \"hour\": \"Saat\",\n  \"minutes\": \"Dakika\",\n  \"updated\": \"Güncellendi\",\n  \"change_bot_picture_title\": \"Bot Resmini Değiştir\",\n  \"select_customer_label\": \"Müşteri Seç\",\n  \"start_typing\": \"Yazmaya başlayın...\",\n  \"choose_set_or_continue\": \"Bir set seçin veya setsiz devam edin\",\n  \"continue_without_set\": \"Setsiz devam et\",\n  \"select_set\": \"Bir Set Seçin\",\n  \"channel_settings\": \"Ayarlar\",\n  \"post_needs_content_or_image\": \"Gönderinizde en az bir karakter veya bir görsel olmalıdır.\",\n  \"please_fix_your_settings\": \"Lütfen ayarlarınızı düzeltin\",\n  \"shortlink_urls_question\": \"URL'leri kısaltmak ister misiniz? Bu, tıklamalar üzerinde istatistik almanızı sağlar.\",\n  \"yes_shortlink_it\": \"Evet, kısalt!\",\n  \"added_successfully\": \"Başarıyla eklendi\",\n  \"updated_successfully\": \"Başarıyla güncellendi\",\n  \"create_post_title\": \"Gönderi Oluştur\",\n  \"post_preview\": \"Gönderi Önizlemesi\",\n  \"check_circles_above\": \"Yukarıdaki daireleri kontrol edin\",\n  \"create_output\": \"Çıktı oluştur\",\n  \"assistant_initial_message\": \"Merhaba! Sosyal medya gönderilerinizi düzenlemenize yardımcı olabilirim.\",\n  \"no_longer_global_mode\": \"Artık global modda değil\",\n  \"two_days\": \"İki Gün\",\n  \"three_days\": \"Üç Gün\",\n  \"four_days\": \"Dört Gün\",\n  \"five_days\": \"Beş Gün\",\n  \"six_days\": \"Altı Gün\",\n  \"two_weeks\": \"İki Hafta\",\n  \"repeat_post_every_label\": \"Gönderiyi Her Tekrarla\",\n  \"add_new_tag\": \"Yeni Etiket Ekle\",\n  \"tag_name\": \"Adı\",\n  \"post_is_too_long\": \"gönderi çok uzun, lütfen düzeltin\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Gönderinizde en az bir karakter veya bir görsel olmalıdır.\",\n  \"internal_edit\": \"Dahili Düzenleme\",\n  \"are_you_sure_go_back_to_global_mode\": \"Bu işlem geri alınamaz. Global moda dönmek istediğinizden emin misiniz?\",\n  \"yes_go_back_to_global_mode\": \"Evet, global moda dön\",\n  \"are_you_sure_delete_this_post\": \"Bu gönderiyi silmek istediğinizden emin misiniz?\",\n  \"yes_delete_it\": \"Evet, sil!\",\n  \"cant_edit_networks_when_creating_set\": \"Bir set oluştururken ağları düzenleyemezsiniz\",\n  \"click_to_exit_global_editing\": \"Global düzenlemeden çıkmak ve bu kanal için gönderiyi özelleştirmek için bu butona tıklayın\",\n  \"edit_content\": \"İçeriği düzenle\",\n  \"editing_a_specific_network\": \"Belirli Bir Ağı Düzenliyorsunuz\",\n  \"back_to_global\": \"Globale dön\",\n  \"delete_post_tooltip\": \"Gönderiyi Sil\",\n  \"drop_files_here_to_upload\": \"Yüklemek için dosyalarınızı buraya bırakın\",\n  \"insert_emoji\": \"Emoji Ekle\",\n  \"write_something\": \"Bir şeyler yazın …\",\n  \"click_channel_to_add\": \"Eklemek için bir kanal seçin\",\n  \"connect_your_channels\": \"Kanallarınızı Bağlayın\",\n  \"connect_social_media_to_start\": \"Gönderi planlamaya başlamak için sosyal medya hesaplarınızı bağlayın\",\n  \"connected_channels\": \"Bağlı Kanallar\",\n  \"continue\": \"Devam Et\",\n  \"continue_without_channels\": \"Kanallar olmadan devam et\",\n  \"watch_tutorial\": \"Eğitimi İzle\",\n  \"watch_tutorial_title\": \"Postiz'i Nasıl Kullanacağınızı Öğrenin\",\n  \"watch_tutorial_description\": \"Postiz'den en iyi şekilde yararlanmayı öğrenmek için bu kısa videoyu izleyin\",\n  \"back\": \"Geri\",\n  \"get_started\": \"Başlayın\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/vi/translation.json",
    "content": "{\n  \"calendar\": \"Lịch\",\n  \"webhooks\": \"Webhook\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Webhook là một cách để nhận thông báo khi có sự kiện xảy ra trong Postiz thông qua một yêu cầu HTTP.\",\n  \"name\": \"Tên\",\n  \"url\": \"URL\",\n  \"edit\": \"Chỉnh sửa\",\n  \"delete\": \"Xóa\",\n  \"add_a_webhook\": \"Thêm webhook\",\n  \"save\": \"Lưu\",\n  \"send_test\": \"Gửi thử\",\n  \"select_role\": \"Chọn vai trò\",\n  \"video_made_with_ai\": \"Video được tạo bằng AI\",\n  \"please_add_at_least\": \"Vui lòng thêm ít nhất 20 ký tự\",\n  \"send_invitation_via_email\": \"Gửi lời mời qua email?\",\n  \"global_settings\": \"Cài đặt toàn cục\",\n  \"copy_id\": \"Sao chép ID kênh\",\n  \"team_members\": \"Thành viên nhóm\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"Mời trợ lý hoặc thành viên nhóm của bạn để quản lý tài khoản của bạn\",\n  \"remove\": \"Xóa\",\n  \"add_another_member\": \"Thêm thành viên khác\",\n  \"signatures\": \"Chữ ký\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"Bạn có thể thêm chữ ký vào tài khoản của mình để sử dụng trong các bài đăng.\",\n  \"content\": \"Nội dung\",\n  \"auto_add\": \"Tự động thêm?\",\n  \"delay_comment\": \"Bình luận bị trì hoãn\",\n  \"actions\": \"Hành động\",\n  \"use_signature\": \"Sử dụng chữ ký\",\n  \"add_a_signature\": \"Thêm chữ ký\",\n  \"no\": \"Không\",\n  \"yes\": \"Có\",\n  \"your_git_repository\": \"Kho lưu trữ Git của bạn\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"Kết nối kho lưu trữ GitHub của bạn để nhận cập nhật và phân tích\",\n  \"connected\": \"Đã kết nối:\",\n  \"disconnect\": \"Ngắt kết nối\",\n  \"connect_your_repository\": \"Kết nối kho lưu trữ của bạn\",\n  \"cancel\": \"Hủy\",\n  \"connect\": \"Kết nối\",\n  \"public_api\": \"API công khai\",\n  \"check_n8n\": \"Hãy xem node tùy chỉnh N8N của chúng tôi cho Postiz.\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"Sử dụng API của Postiz để tích hợp với các công cụ của bạn.\",\n  \"read_how_to_use_it_over_the_documentation\": \"Đọc cách sử dụng trong tài liệu hướng dẫn.\",\n  \"reveal\": \"Hiển thị\",\n  \"copy_key\": \"Sao chép khóa\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"Kết nối máy chủ MCP của Postiz với máy khách của bạn (Http streaming) để lên lịch bài đăng nhanh hơn!\",\n  \"share_with_a_client\": \"Chia sẻ với khách hàng\",\n  \"post\": \"Bài đăng\",\n  \"comments\": \"Bình luận\",\n  \"user\": \"Người dùng\",\n  \"login_register_to_add_comments\": \"Đăng nhập / Đăng ký để thêm bình luận\",\n  \"status\": \"Trạng thái:\",\n  \"there_are_not_plugs_matching_your_channels\": \"Không có plug nào phù hợp với kênh của bạn\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"Bạn cần thêm: X hoặc LinkedIn hoặc Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"Đi tới lịch để thêm kênh\",\n  \"channels\": \"Kênh\",\n  \"activate\": \"Kích hoạt\",\n  \"this_channel_needs_to_be_refreshed\": \"Kênh này cần được làm mới,\",\n  \"click_here_to_refresh\": \"bấm vào đây để làm mới\",\n  \"can_t_show_analytics_yet\": \"Chưa thể hiển thị phân tích\",\n  \"you_have_to_add_social_media_channels\": \"Bạn cần thêm các kênh mạng xã hội\",\n  \"supported\": \"Hỗ trợ:\",\n  \"step\": \"BƯỚC\",\n  \"skip_onboarding\": \"Bỏ qua hướng dẫn\",\n  \"onboarding\": \"Hướng dẫn bắt đầu\",\n  \"next\": \"Tiếp theo\",\n  \"you_are_done_from_here_you_can\": \"Bạn đã hoàn thành, từ đây bạn có thể:\",\n  \"view_analytics\": \"Xem phân tích\",\n  \"schedule_a_new_post\": \"Lên lịch bài đăng mới\",\n  \"to_sell_posts_you_would_have_to\": \"Để bán bài đăng, bạn cần:\",\n  \"1_connect_at_least_one_channel\": \"1. Kết nối ít nhất một kênh\",\n  \"2_connect_you_bank_account\": \"2. Kết nối tài khoản ngân hàng của bạn\",\n  \"go_back_to_connect_channels\": \"Quay lại để kết nối các kênh\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"Chuyển đến trang người bán để kết nối tài khoản ngân hàng của bạn\",\n  \"connect_channels\": \"Kết nối kênh\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"Kết nối các kênh mạng xã hội và trang xuất bản của bạn để\\n            lên lịch bài đăng sau\",\n  \"social\": \"Mạng xã hội\",\n  \"publishing_platforms\": \"Nền tảng xuất bản\",\n  \"no_channels\": \"Chưa có kênh nào\",\n  \"connect_your_accounts\": \"Kết nối các tài khoản mạng xã hội của bạn để bắt đầu lên lịch, đăng bài và phân tích — tất cả trong một nơi.\",\n  \"notifications\": \"Thông báo\",\n  \"no_notifications\": \"Không có thông báo nào\",\n  \"send_message\": \"Gửi tin nhắn\",\n  \"mar_28\": \"28 Thg 3\",\n  \"there_are_no_messages_yet\": \"Chưa có tin nhắn nào.\",\n  \"checkout_the_marketplace\": \"Khám phá Marketplace\",\n  \"go_to_marketplace\": \"Đi tới Marketplace\",\n  \"all_messages\": \"Tất cả tin nhắn\",\n  \"previous\": \"Trước\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"Chọn hoặc tải lên hình ảnh (tối đa 5 hình mỗi lần)\",\n  \"you_can_also_drag_drop_pictures\": \"Bạn cũng có thể kéo & thả hình ảnh\",\n  \"you_don_t_have_any_assets_yet\": \"Bạn chưa có tài sản nào.\",\n  \"click_the_button_below_to_upload_one\": \"Nhấn nút bên dưới để tải lên một tệp\",\n  \"click_the_button_below_to_upload_other\": \"Nhấn nút bên dưới để tải lên nhiều tệp\",\n  \"add_selected_media\": \"Thêm phương tiện đã chọn\",\n  \"insert_media\": \"Chèn phương tiện\",\n  \"design_media\": \"Thiết kế phương tiện\",\n  \"select\": \"Chọn\",\n  \"editor\": \"Trình chỉnh sửa\",\n  \"clear\": \"Xóa\",\n  \"order_completed\": \"Đơn hàng đã hoàn thành\",\n  \"the_order_has_been_completed\": \"Đơn hàng đã được hoàn thành\",\n  \"post_has_been_published\": \"Bài đăng đã được xuất bản\",\n  \"url_1\": \"URL:\",\n  \"new_offer\": \"Ưu đãi mới\",\n  \"platform\": \"Nền tảng\",\n  \"posts\": \"Bài đăng\",\n  \"pay_accept_offer\": \"Thanh toán & Chấp nhận ưu đãi\",\n  \"accepted\": \"Đã chấp nhận\",\n  \"post_draft\": \"Bản nháp bài đăng\",\n  \"revision_needed\": \"Cần chỉnh sửa\",\n  \"approve\": \"Phê duyệt\",\n  \"preview\": \"Xem trước\",\n  \"revision_requested\": \"Yêu cầu chỉnh sửa\",\n  \"accepted_1\": \"ĐÃ CHẤP NHẬN\",\n  \"cancelled_by_the_seller\": \"Đã bị hủy bởi người bán\",\n  \"please_select_your_country_where_your_business_is\": \"Vui lòng chọn quốc gia nơi doanh nghiệp của bạn đặt trụ sở.\",\n  \"select_country\": \"--CHỌN QUỐC GIA--\",\n  \"connect_bank_account\": \"Kết nối tài khoản ngân hàng\",\n  \"seller_mode\": \"Chế độ người bán\",\n  \"active\": \"Đang hoạt động\",\n  \"details\": \"Chi tiết\",\n  \"audience_size\": \"Quy mô khán giả\",\n  \"add_another_platform\": \"Thêm nền tảng khác\",\n  \"send_an_offer_for\": \"Gửi một đề nghị với giá $\",\n  \"complete_order_and_pay_early\": \"Hoàn thành đơn hàng và thanh toán sớm\",\n  \"order_in_progress\": \"Đơn hàng đang được xử lý\",\n  \"create_a_new_offer\": \"Tạo đề nghị mới\",\n  \"orders\": \"Đơn hàng\",\n  \"price\": \"Giá\",\n  \"state\": \"Trạng thái\",\n  \"showing\": \"Hiển thị\",\n  \"to\": \"đến\",\n  \"from\": \"từ\",\n  \"results\": \"Kết quả\",\n  \"content_writer\": \"Người viết nội dung\",\n  \"influencer\": \"Người ảnh hưởng\",\n  \"request_service\": \"Yêu cầu dịch vụ\",\n  \"the_marketplace_is_not_opened_yet\": \"Chợ chưa được mở\",\n  \"check_again_soon\": \"Hãy kiểm tra lại sau!\",\n  \"filter\": \"Bộ lọc\",\n  \"result\": \"Kết quả\",\n  \"seller\": \"Người bán\",\n  \"buyer\": \"Người mua\",\n  \"discord_support\": \"Hỗ trợ Discord\",\n  \"teams\": \"Nhóm\",\n  \"webhooks_1\": \"Webhooks\",\n  \"auto_post\": \"Tự động đăng\",\n  \"logout_from\": \"Đăng xuất khỏi\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"Tham gia cùng hơn 10.000 doanh nhân đang sử dụng Postiz\",\n  \"to_manage_all_your_social_media_channels\": \"Để quản lý tất cả các kênh mạng xã hội của bạn\",\n  \"100_no_risk_trial\": \"Dùng thử 100% không rủi ro\",\n  \"pay_nothing_for_the_first_7_days\": \"Không mất phí trong 7 ngày đầu tiên\",\n  \"cancel_anytime_hassle_free\": \"Hủy bất cứ lúc nào trong phần cài đặt\",\n  \"add_free_subscription\": \"-- THÊM GÓI MIỄN PHÍ --\",\n  \"currently_impersonating\": \"Đang giả lập tài khoản\",\n  \"user_1\": \"người dùng:\",\n  \"drag_n_drop_some_files_here\": \"Kéo và thả một số tệp vào đây\",\n  \"add_time_slot\": \"Thêm khung giờ\",\n  \"add_slot\": \"Thêm khung giờ\",\n  \"cancel_publication\": \"Hủy xuất bản\",\n  \"statistics\": \"Thống kê\",\n  \"loading\": \"Đang tải\",\n  \"short_link\": \"Liên kết rút gọn\",\n  \"original_link\": \"Liên kết gốc\",\n  \"clicks\": \"Lượt nhấp\",\n  \"selected_customer\": \"Khách hàng đã chọn\",\n  \"customer\": \"Khách hàng:\",\n  \"repeat_post_every\": \"Lặp lại bài đăng mỗi...\",\n  \"use_this_media\": \"Sử dụng phương tiện này\",\n  \"create_new_post\": \"Tạo bài viết\",\n  \"update_post\": \"Cập nhật bài viết hiện có\",\n  \"merge_comments_into_one_post\": \"Gộp bình luận vào một bài đăng\",\n  \"accounts_that_will_engage\": \"Các tài khoản sẽ tương tác:\",\n  \"day\": \"Ngày\",\n  \"week\": \"Tuần\",\n  \"month\": \"Tháng\",\n  \"remove_from_customer\": \"Xóa khỏi khách hàng\",\n  \"show_more\": \"+ Xem thêm\",\n  \"show_less\": \"- Thu gọn\",\n  \"upload\": \"Tải lên\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"Thêm kênh\",\n  \"add_platform\": \"Thêm nền tảng\",\n  \"articles\": \"Bài viết\",\n  \"add_comment\": \"Thêm bình luận\",\n  \"add_post\": \"Thêm bài viết vào một chuỗi\",\n  \"add_comment_or_post\": \"Thêm bình luận / bài viết\",\n  \"you_are_in_global_editing_mode\": \"Bạn đang ở chế độ chỉnh sửa toàn cục\",\n  \"the_post_should_be_at_least_6_characters_long\": \"Bài đăng phải có ít nhất 6 ký tự\",\n  \"are_you_sure_you_want_to_delete_post\": \"Bạn có chắc chắn muốn xóa bài viết này không?\",\n  \"post_deleted_successfully\": \"Xóa bài viết thành công\",\n  \"delete_post\": \"Xóa bài đăng\",\n  \"save_as_draft\": \"Lưu dưới dạng bản nháp\",\n  \"post_now\": \"Đăng ngay\",\n  \"please_add\": \"Vui lòng thêm\",\n  \"to_your_telegram_group_channel_and_click_here\": \"vào nhóm/kênh Telegram của bạn và nhấn vào đây:\",\n  \"connect_telegram\": \"Kết nối Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"Vui lòng thêm lệnh sau vào cuộc trò chuyện của bạn:\",\n  \"copy\": \"Sao chép\",\n  \"settings\": \"Cài đặt\",\n  \"integrations\": \"Tích hợp\",\n  \"add_integration\": \"Thêm tích hợp\",\n  \"you_are_now_editing_only\": \"Bạn hiện chỉ đang chỉnh sửa\",\n  \"tag_a_company\": \"Gắn thẻ công ty\",\n  \"video_length_is_invalid_must_be_up_to\": \"Độ dài video không hợp lệ, phải tối đa\",\n  \"seconds\": \"giây\",\n  \"this_feature_available_only_for_photos\": \"Tính năng này chỉ áp dụng cho ảnh, sẽ thêm nhạc mặc định mà bạn có thể thay đổi sau.\",\n  \"allow_user_to\": \"Cho phép người dùng:\",\n  \"your_video_will_be_labeled_promotional\": \"Video của bạn sẽ được dán nhãn \\\"Nội dung quảng cáo\\\".\",\n  \"this_cannot_be_changed_once_posted\": \"Điều này không thể thay đổi sau khi video đã được đăng.\",\n  \"turn_on_to_disclose_video_promotes\": \"Bật để tiết lộ rằng video này quảng bá hàng hóa hoặc dịch vụ để đổi lấy một giá trị nào đó. Video của bạn có thể quảng bá cho chính bạn, bên thứ ba hoặc cả hai.\",\n  \"you_are_promoting_yourself\": \"Bạn đang quảng bá cho chính mình hoặc thương hiệu của bạn.\",\n  \"this_video_will_be_classified_brand_organic\": \"Video này sẽ được phân loại là Thương hiệu hữu cơ.\",\n  \"you_are_promoting_another_brand\": \"Bạn đang quảng bá cho thương hiệu khác hoặc bên thứ ba.\",\n  \"this_video_will_be_classified_branded_content\": \"Video này sẽ được phân loại là Nội dung thương hiệu.\",\n  \"by_posting_you_agree_to_tiktoks\": \"Bằng cách đăng bài, bạn đồng ý với\",\n  \"music_usage_confirmation\": \"Xác nhận sử dụng nhạc\",\n  \"branded_content_policy\": \"Chính sách nội dung thương hiệu\",\n  \"select_1\": \"--Chọn--\",\n  \"select_flair\": \"--Chọn nhãn--\",\n  \"link\": \"Liên kết\",\n  \"add_subreddit\": \"Thêm Subreddit\",\n  \"please_add_at_least_one_subreddit\": \"Vui lòng thêm ít nhất một Subreddit\",\n  \"add_community\": \"Thêm cộng đồng\",\n  \"select_post_type\": \"Chọn loại bài đăng...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"Chúng tôi không tìm thấy doanh nghiệp nào liên kết với Trang LinkedIn của bạn.\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"Vui lòng đóng hộp thoại này, tạo một trang mới và thêm kênh mới lại.\",\n  \"select_linkedin_page\": \"Chọn Trang Linkedin:\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"Chúng tôi không tìm thấy doanh nghiệp nào liên kết với các trang đã chọn.\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"Chúng tôi khuyên bạn nên kết nối tất cả các trang và tất cả các doanh nghiệp.\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"Vui lòng đóng hộp thoại này, xóa tích hợp của bạn và thêm kênh mới lại.\",\n  \"select_instagram_account\": \"Chọn tài khoản Instagram:\",\n  \"select_page\": \"Chọn Trang:\",\n  \"generate_image_with_ai\": \"Tạo hình ảnh bằng AI\",\n  \"reconnect_channel\": \"Kết nối lại kênh\",\n  \"update_credentials\": \"Cập nhật thông tin đăng nhập\",\n  \"additional_settings\": \"Cài đặt bổ sung\",\n  \"change_bot\": \"Thay đổi bot\",\n  \"move_add_to_customer\": \"Chuyển / thêm vào khách hàng\",\n  \"edit_time_slots\": \"Chỉnh sửa khung giờ\",\n  \"enable_channel\": \"Bật kênh\",\n  \"disable_channel\": \"Tắt kênh\",\n  \"add\": \"Thêm\",\n  \"short_post\": \"Bài đăng ngắn\",\n  \"long_post\": \"Bài đăng dài\",\n  \"a_thread_with_short_posts\": \"Chuỗi bài đăng ngắn\",\n  \"a_thread_with_long_posts\": \"Chuỗi bài đăng dài\",\n  \"personal_voice_i_am_happy_to_announce\": \"Giọng cá nhân (\\\"Tôi vui mừng thông báo\\\")\",\n  \"company_voice_we_are_happy_to_announce\": \"Giọng công ty (\\\"Chúng tôi vui mừng thông báo\\\")\",\n  \"generate\": \"Tạo\",\n  \"generate_posts\": \"Tạo bài đăng\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"Mua tài khoản PRO trọn đời với SOL ($199). Lưu ý: giao dịch này không được hoàn tiền.\",\n  \"purchase_now\": \"Mua ngay\",\n  \"pay_today\": \"Thanh toán hôm nay\",\n  \"we_are_sorry_to_see_you_go\": \"Chúng tôi rất tiếc khi thấy bạn rời đi :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"Bạn có thể cho chúng tôi biết ngắn gọn điều gì chúng tôi có thể làm tốt hơn không?\",\n  \"cancel_subscription\": \"Hủy đăng ký\",\n  \"plans\": \"Gói\",\n  \"monthly\": \"HÀNG THÁNG\",\n  \"yearly\": \"HÀNG NĂM\",\n  \"reactivate_subscription\": \"Kích hoạt lại đăng ký\",\n  \"update_payment_method_invoices_history\": \"Cập nhật phương thức thanh toán / Lịch sử hóa đơn\",\n  \"cancel_subscription_1\": \"Hủy đăng ký\",\n  \"your_subscription_will_be_canceled_at\": \"Đăng ký của bạn sẽ bị hủy vào\",\n  \"you_will_never_be_charged_again\": \"Bạn sẽ không bao giờ bị tính phí nữa\",\n  \"current_package\": \"Gói hiện tại:\",\n  \"next_package\": \"Gói tiếp theo:\",\n  \"claim\": \"Yêu cầu\",\n  \"frequently_asked_questions\": \"Câu hỏi thường gặp\",\n  \"autopost\": \"Tự động đăng\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"Tự động đăng có thể tự động đăng các mục mới từ RSS của bạn lên mạng xã hội\",\n  \"title\": \"Tiêu đề\",\n  \"add_an_autopost\": \"Thêm tự động đăng\",\n  \"post_content\": \"Nội dung bài đăng\",\n  \"sign_up\": \"Đăng ký\",\n  \"or\": \"HOẶC\",\n  \"by_registering_you_agree_to_our\": \"Bằng việc đăng ký, bạn đồng ý với\",\n  \"and\": \"và\",\n  \"terms_of_service\": \"Điều khoản dịch vụ\",\n  \"privacy_policy\": \"Chính sách bảo mật\",\n  \"create_account\": \"Tạo tài khoản\",\n  \"already_have_an_account\": \"Đã có tài khoản?\",\n  \"sign_in\": \"Đăng nhập\",\n  \"sign_in_1\": \"Đăng nhập\",\n  \"don_t_have_an_account\": \"Chưa có tài khoản?\",\n  \"forgot_password\": \"Quên mật khẩu\",\n  \"forgot_password_1\": \"Quên mật khẩu\",\n  \"send_password_reset_email\": \"Gửi email đặt lại mật khẩu\",\n  \"go_back_to_login\": \"Quay lại đăng nhập\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"Chúng tôi đã gửi cho bạn một email với liên kết để đặt lại mật khẩu.\",\n  \"change_password\": \"Đổi mật khẩu\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"Chúng tôi đã đặt lại mật khẩu của bạn thành công. Bây giờ bạn có thể đăng nhập bằng\",\n  \"click_here_to_go_back_to_login\": \"Bấm vào đây để quay lại đăng nhập\",\n  \"activate_your_account\": \"Kích hoạt tài khoản của bạn\",\n  \"thank_you_for_registering\": \"Cảm ơn bạn đã đăng ký!\",\n  \"please_check_your_email_to_activate_your_account\": \"Vui lòng kiểm tra email của bạn để kích hoạt tài khoản.\",\n  \"sign_in_with\": \"Đăng nhập với\",\n  \"continue_with_google\": \"Tiếp tục với Google\",\n  \"sign_in_with_github\": \"Đăng nhập với GitHub\",\n  \"continue_with_farcaster\": \"Tiếp tục với Farcaster\",\n  \"continue_with_your_wallet\": \"Tiếp tục với Ví của bạn\",\n  \"stars_per_day\": \"Số sao mỗi ngày\",\n  \"media\": \"Phương tiện\",\n  \"check_launch\": \"Kiểm tra khởi chạy\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"Tải kho GitHub của bạn từ cài đặt để xem phân tích\",\n  \"stars\": \"Sao\",\n  \"processing_stars\": \"Đang xử lý số sao...\",\n  \"forks\": \"Nhánh\",\n  \"registration_is_disabled\": \"Đăng ký đã bị vô hiệu hóa\",\n  \"login_instead\": \"Đăng nhập thay thế\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"Chọn một cuộc trò chuyện và bắt đầu chat.\",\n  \"adding_channel_redirecting_you\": \"Đang thêm kênh, đang chuyển hướng bạn\",\n  \"could_not_add_provider\": \"Không thể thêm nhà cung cấp.\",\n  \"you_are_being_redirected_back\": \"Bạn đang được chuyển hướng trở lại\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"Chúng tôi đang gặp một số sự cố, hãy thử làm mới trang\",\n  \"post_not_found\": \"Không tìm thấy bài viết\",\n  \"publication_date\": \"Ngày xuất bản:\",\n  \"analytics\": \"Phân tích\",\n  \"launches\": \"Ra mắt\",\n  \"plugs\": \"Quảng bá\",\n  \"billing\": \"Thanh toán\",\n  \"affiliate\": \"Liên kết\",\n  \"monday\": \"Thứ Hai\",\n  \"tuesday\": \"Thứ Ba\",\n  \"wednesday\": \"Thứ Tư\",\n  \"thursday\": \"Thứ Năm\",\n  \"friday\": \"Thứ Sáu\",\n  \"saturday\": \"Thứ Bảy\",\n  \"sunday\": \"Chủ Nhật\",\n  \"can_t_change_date_remove_post_from_publication\": \"Không thể thay đổi ngày, hãy xóa bài viết khỏi xuất bản\",\n  \"predicted_github_trending_change\": \"Dự đoán thay đổi xu hướng GitHub\",\n  \"duplicate_post\": \"Bài viết trùng lặp\",\n  \"preview_post\": \"Xem trước bài viết\",\n  \"post_statistics\": \"Thống kê bài viết\",\n  \"draft\": \"Bản nháp\",\n  \"week_number\": \"Tuần {{number}}\",\n  \"top_title_edit_webhook\": \"Chỉnh sửa webhook\",\n  \"top_title_add_webhook\": \"Thêm webhook\",\n  \"top_title_oh_no\": \"Ôi không\",\n  \"top_title_auto_plug\": \"Tự động cắm: {{title}}\",\n  \"top_title_edit_autopost\": \"Chỉnh sửa tự động đăng\",\n  \"top_title_add_autopost\": \"Thêm tự động đăng\",\n  \"top_title_send_a_new_offer\": \"Gửi ưu đãi mới\",\n  \"top_title_media_library\": \"Thư viện phương tiện\",\n  \"top_title_add_signature\": \"Thêm chữ ký\",\n  \"top_title_send_a_message_to\": \"Gửi tin nhắn cho {{name}}\",\n  \"top_title_configure_provider\": \"Cấu hình nhà cung cấp\",\n  \"top_title_add_member\": \"Thêm thành viên\",\n  \"top_title_change_bot_picture\": \"Thay đổi ảnh bot\",\n  \"top_title_create_a_new_tag\": \"Tạo thẻ mới\",\n  \"top_title_select_company\": \"Chọn công ty\",\n  \"top_title_additional_settings\": \"Cài đặt bổ sung\",\n  \"top_title_time_table_slots\": \"Khung giờ thời gian biểu\",\n  \"top_title_design_media\": \"Thiết kế phương tiện\",\n  \"top_title_edit_post\": \"Chỉnh sửa bài đăng\",\n  \"top_title_create_post\": \"Tạo bài đăng\",\n  \"top_title_move__add_to_customer\": \"Chuyển / Thêm vào khách hàng\",\n  \"top_title_add_api_key_for\": \"Thêm API key cho {{name}}\",\n  \"top_title_instance_url\": \"URL phiên bản\",\n  \"top_title_custom_url\": \"URL tùy chỉnh\",\n  \"top_title_add_channel\": \"Thêm kênh\",\n  \"top_title_add_telegram\": \"Thêm Telegram\",\n  \"top_title_add_wrapcast\": \"Thêm Wrapcast\",\n  \"top_title_comments_for\": \"Bình luận cho {{date}}\",\n  \"top_title_edit_signature\": \"Chỉnh sửa chữ ký\",\n  \"label_name\": \"Tên\",\n  \"label_url\": \"URL\",\n  \"label_title\": \"Tiêu đề\",\n  \"label_subtitle\": \"Phụ đề\",\n  \"label_email\": \"Email\",\n  \"label_full_name\": \"Họ và tên\",\n  \"label_password\": \"Mật khẩu\",\n  \"label_confirm_password\": \"Xác nhận mật khẩu\",\n  \"label_api_key\": \"API Key\",\n  \"label_instance_url\": \"URL phiên bản\",\n  \"label_custom_url\": \"URL tùy chỉnh\",\n  \"label_feedback\": \"Phản hồi\",\n  \"label_bio\": \"Tiểu sử\",\n  \"label_role\": \"Vai trò\",\n  \"label_country\": \"Quốc gia\",\n  \"label_audience_size\": \"Quy mô khán giả trên tất cả các nền tảng\",\n  \"label_pick_time\": \"Chọn thời gian\",\n  \"label_nickname\": \"Biệt danh\",\n  \"label_write_anything\": \"Viết bất cứ điều gì\",\n  \"label_output_format\": \"Định dạng đầu ra\",\n  \"label_add_pictures\": \"Thêm hình ảnh?\",\n  \"label_hour\": \"Giờ\",\n  \"label_minutes\": \"Phút\",\n  \"label_select_publication\": \"Chọn ấn phẩm\",\n  \"label_canonical_link\": \"Liên kết chuẩn\",\n  \"label_cover_picture\": \"Ảnh bìa\",\n  \"label_tags\": \"Thẻ\",\n  \"label_topics\": \"Chủ đề\",\n  \"label_tags_maximum_4\": \"Thẻ (Tối đa 4)\",\n  \"label_attachments\": \"Tệp đính kèm\",\n  \"label_type\": \"Loại\",\n  \"label_thumbnail\": \"Ảnh thu nhỏ\",\n  \"label_who_can_see_this_video\": \"Ai có thể xem video này?\",\n  \"label_content_posting_method\": \"Phương thức đăng nội dung\",\n  \"label_auto_add_music\": \"Tự động thêm nhạc\",\n  \"label_duet\": \"Song ca\",\n  \"label_stitch\": \"Ghép\",\n  \"label_comments\": \"Bình luận\",\n  \"label_disclose_video_content\": \"Công khai nội dung video\",\n  \"label_your_brand\": \"Thương hiệu của bạn\",\n  \"label_branded_content\": \"Nội dung thương hiệu\",\n  \"label_subreddit\": \"Subreddit\",\n  \"label_flair\": \"Flair\",\n  \"label_media\": \"Phương tiện\",\n  \"label_search_subreddit\": \"Tìm kiếm Subreddit\",\n  \"label_delay\": \"Trì hoãn\",\n  \"label_post_type\": \"Loại bài đăng\",\n  \"label_collaborators\": \"Cộng tác viên (tối đa 3) - tài khoản không được để riêng tư\",\n  \"label_community\": \"Cộng đồng\",\n  \"label_search_community\": \"Tìm kiếm cộng đồng\",\n  \"label_channel\": \"Kênh\",\n  \"label_search_channel\": \"Tìm kiếm kênh\",\n  \"label_select_channel\": \"Chọn kênh\",\n  \"label_new_password\": \"Mật khẩu mới\",\n  \"label_repeat_password\": \"Nhập lại mật khẩu\",\n  \"label_platform\": \"Nền tảng\",\n  \"label_price_per_post\": \"Giá mỗi bài đăng\",\n  \"label_integrations\": \"Tích hợp\",\n  \"label_code\": \"Mã\",\n  \"label_should_sync_last_post\": \"Chúng ta có nên đồng bộ bài đăng cuối hiện tại không?\",\n  \"label_when_post\": \"Khi nào chúng ta nên đăng?\",\n  \"label_autogenerate_content\": \"Tự động tạo nội dung\",\n  \"label_generate_picture\": \"Tạo hình ảnh?\",\n  \"label_company\": \"Công ty\",\n  \"label_tag_color\": \"Màu thẻ\",\n  \"label_select_board\": \"Chọn bảng\",\n  \"label_select_organization\": \"Chọn tổ chức\",\n  \"label_auto_add_signature\": \"Tự động thêm chữ ký?\",\n  \"enable_color_picker\": \"Bật bộ chọn màu\",\n  \"cancel_the_color_picker\": \"Hủy bộ chọn màu\",\n  \"no_content_yet\": \"Chưa có nội dung\",\n  \"write_your_reply\": \"Viết bài của bạn...\",\n  \"add_a_tag\": \"Thêm thẻ\",\n  \"add_to_calendar\": \"Thêm vào lịch\",\n  \"select_channels_from_circles\": \"Chọn kênh từ các vòng tròn phía trên\",\n  \"not_matching_order\": \"Không đúng thứ tự\",\n  \"submit_for_order\": \"Gửi để đặt hàng\",\n  \"schedule\": \"Lên lịch\",\n  \"update\": \"Cập nhật\",\n  \"attachments\": \"Tệp đính kèm\",\n  \"tags\": \"Thẻ\",\n  \"public_to_everyone\": \"Công khai với mọi người\",\n  \"mutual_follow_friends\": \"Bạn bè theo dõi lẫn nhau\",\n  \"follower_of_creator\": \"Người theo dõi của người tạo\",\n  \"self_only\": \"Chỉ mình tôi\",\n  \"post_content_directly_to_tiktok\": \"Đăng nội dung trực tiếp lên TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"Tải nội dung lên TikTok mà không đăng\",\n  \"choose_upload_without_posting_description\": \"Chọn tải lên mà không đăng nếu bạn muốn xem lại và chỉnh sửa nội dung trong ứng dụng TikTok trước khi xuất bản. Điều này giúp bạn sử dụng các công cụ chỉnh sửa tích hợp của TikTok và thực hiện các điều chỉnh cuối cùng trước khi đăng.\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"Tôi có bị Postiz tính phí không?\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"Để xác nhận thông tin thẻ tín dụng, Postiz sẽ giữ $2 và hoàn trả ngay lập tức, bạn có thể hủy đăng ký bất cứ lúc nào trong phần cài đặt mà không cần nói chuyện với ai\",\n  \"faq_can_i_trust_postiz_gitroom\": \"Tôi có thể tin tưởng Postiz không?\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz tự hào là mã nguồn mở! Chúng tôi tin vào một văn hóa đạo đức và minh bạch, nghĩa là Postiz sẽ tồn tại mãi mãi. Bạn có thể xem toàn bộ mã nguồn hoặc sử dụng cho các dự án cá nhân. Để xem kho mã nguồn mở, <a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">bấm vào đây</a>.\",\n  \"faq_what_are_channels\": \"Kênh là gì?\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz cho phép bạn lên lịch đăng bài trên các kênh khác nhau.\\nMột kênh là một nền tảng xuất bản nơi bạn có thể lên lịch đăng bài.\\nVí dụ, bạn có thể lên lịch đăng bài trên X, Facebook, Instagram, TikTok, YouTube, Reddit, Linkedin, Dribbble, Threads và Pinterest.\",\n  \"faq_what_are_team_members\": \"Thành viên nhóm là gì?\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"Nếu bạn có một nhóm với nhiều thành viên, bạn có thể mời họ vào không gian làm việc để cùng cộng tác đăng bài và thêm các kênh cá nhân của họ\",\n  \"faq_what_is_ai_auto_complete\": \"Tự động hoàn thành bằng AI là gì?\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"Chúng tôi tự động hóa ChatGPT để giúp bạn viết bài đăng mạng xã hội và bài viết.\",\n  \"enter_email\": \"Nhập email\",\n  \"are_you_sure\": \"Bạn có chắc không?\",\n  \"no_cancel\": \"Không, hủy bỏ!\",\n  \"are_you_sure_you_want_to_delete\": \"Bạn có chắc muốn xóa {{name}} không?\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"Bạn có chắc muốn xóa hình ảnh này không?\",\n  \"are_you_sure_you_want_to_logout\": \"Bạn có chắc muốn đăng xuất không?\",\n  \"yes_logout\": \"Vâng, đăng xuất\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"Bạn có chắc muốn xóa khung này không?\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"Bạn có chắc muốn xóa Subreddit này không?\",\n  \"are_you_sure_you_want_to_close_the_window\": \"Bạn có chắc muốn đóng cửa sổ này không?\",\n  \"yes_close\": \"Vâng, đóng lại\",\n  \"link_copied_to_clipboard\": \"Đã sao chép liên kết vào bộ nhớ tạm\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"Bạn có chắc muốn đóng cửa sổ này không? (tất cả dữ liệu sẽ bị mất)\",\n  \"yes_close_it\": \"Vâng, đóng lại!\",\n  \"uploading_pictures\": \"Đang tải ảnh lên...\",\n  \"agent_starting\": \"Đang khởi động tác vụ\",\n  \"researching_your_content\": \"Đang nghiên cứu nội dung của bạn...\",\n  \"understanding_the_category\": \"Đang tìm hiểu danh mục...\",\n  \"finding_the_topic\": \"Đang tìm chủ đề...\",\n  \"finding_popular_posts_to_match_with\": \"Đang tìm các bài viết phổ biến để ghép nối...\",\n  \"generating_hook\": \"Đang tạo tiêu đề hấp dẫn...\",\n  \"generating_content\": \"Đang tạo nội dung...\",\n  \"generating_pictures\": \"Đang tạo hình ảnh...\",\n  \"finding_time_to_post\": \"Đang tìm thời gian đăng bài...\",\n  \"write_anything\": \"Viết bất cứ điều gì\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"Bạn có thể viết bất cứ điều gì bạn muốn, và cũng có thể thêm liên kết, chúng tôi sẽ nghiên cứu cho bạn...\",\n  \"output_format\": \"Định dạng đầu ra\",\n  \"add_pictures\": \"Thêm hình ảnh?\",\n  \"7_days\": \"7 ngày\",\n  \"30_days\": \"30 ngày\",\n  \"90_days\": \"90 ngày\",\n  \"start_7_days_free_trial\": \"Bắt đầu dùng thử miễn phí 7 ngày\",\n  \"change_language\": \"Thay đổi ngôn ngữ\",\n  \"that_a_wrap\": \"Kết thúc rồi!\\n\\nNếu bạn thích chuỗi bài này:\\n\\n1. Hãy theo dõi tôi @{{username}} để xem thêm nhiều nội dung như vậy\\n2. Retweet bài bên dưới để chia sẻ chuỗi này với mọi người\\n\",\n  \"post_as_images_carousel\": \"Đăng dưới dạng băng chuyền hình ảnh\",\n  \"save_set\": \"Lưu bộ\",\n  \"separate_post\": \"Tách bài viết thành nhiều bài\",\n  \"label_who_can_reply_to_this_post\": \"Ai có thể trả lời bài viết này?\",\n  \"delete_integration\": \"Xóa tích hợp\",\n  \"start_writing_your_post\": \"Bắt đầu viết bài của bạn để xem trước\",\n  \"billing_join_over\": \"Tham gia cùng hơn\",\n  \"billing_entrepreneurs_count\": \"Hơn 20.000 doanh nhân\",\n  \"billing_who_use\": \"đang sử dụng\",\n  \"billing_postiz_grow_social\": \"Postiz để phát triển sự hiện diện trên mạng xã hội của họ\",\n  \"billing_no_risk_trial\": \"Dùng thử miễn phí 100% không rủi ro\",\n  \"billing_pay_nothing_7_days\": \"Không phải trả gì trong 7 ngày đầu tiên\",\n  \"billing_cancel_anytime\": \"Hủy bất cứ lúc nào trong phần cài đặt\",\n  \"billing_choose_plan\": \"Chọn gói\",\n  \"billing_monthly\": \"Hàng tháng\",\n  \"billing_yearly\": \"Hàng năm\",\n  \"billing_20_percent_off\": \"Giảm 20%\",\n  \"billing_features\": \"Tính năng\",\n  \"billing_channel\": \"kênh\",\n  \"billing_channels\": \"các kênh\",\n  \"billing_unlimited\": \"Không giới hạn\",\n  \"billing_posts_per_month\": \"bài đăng mỗi tháng\",\n  \"billing_unlimited_team_members\": \"Không giới hạn thành viên nhóm\",\n  \"billing_ai_auto_complete\": \"AI tự động hoàn thành\",\n  \"billing_ai_copilots\": \"AI hỗ trợ\",\n  \"billing_ai_autocomplete\": \"AI Tự động hoàn thành\",\n  \"billing_advanced_picture_editor\": \"Trình chỉnh sửa ảnh nâng cao\",\n  \"billing_ai_images_per_month\": \"Hình ảnh AI mỗi tháng\",\n  \"billing_ai_videos_per_month\": \"Video AI mỗi tháng\",\n  \"billing_billing_address\": \"Địa chỉ thanh toán\",\n  \"billing_payment\": \"Thanh toán\",\n  \"billing_powered_by_stripe\": \"Thanh toán an toàn được xử lý bởi\",\n  \"billing_your_7_day_trial_is\": \"Dùng thử 7 ngày của bạn là\",\n  \"billing_100_percent_free\": \"Miễn phí 100%\",\n  \"billing_ending\": \"kết thúc\",\n  \"billing_cancel_anytime_short\": \"Hủy bất cứ lúc nào trong phần cài đặt\",\n  \"billing_pay_0_start_trial\": \"Thanh toán $0 hôm nay - Bắt đầu dùng thử miễn phí!\",\n  \"billing_pay_now\": \"Thanh toán ngay\",\n  \"billing_per_month\": \"/ tháng\",\n  \"billing_per_year\": \"/ năm\",\n  \"billing_order_summary\": \"Tóm tắt đơn hàng\",\n  \"billing_applied\": \"Đã áp dụng\",\n  \"billing_due_today\": \"Thanh toán hôm nay\",\n  \"billing_then\": \"Sau đó\",\n  \"billing_on\": \"vào ngày\",\n  \"billing_discount_applied\": \"đã áp dụng\",\n  \"billing_remove\": \"Xóa\",\n  \"billing_coupon_expires\": \"Mã giảm giá hết hạn vào\",\n  \"billing_invalid_coupon\": \"Mã giảm giá không hợp lệ\",\n  \"billing_coupon_applied\": \"Áp dụng mã giảm giá thành công!\",\n  \"billing_coupon_removed\": \"Đã xóa mã giảm giá\",\n  \"billing_error_removing_coupon\": \"Lỗi khi xóa mã giảm giá\",\n  \"billing_have_discount_coupon\": \"Bạn có mã giảm giá?\",\n  \"billing_discount_coupon\": \"Mã giảm giá\",\n  \"billing_cancel\": \"Hủy\",\n  \"billing_enter_coupon_code\": \"Nhập mã giảm giá\",\n  \"billing_applying\": \"Đang áp dụng...\",\n  \"billing_apply\": \"Áp dụng\",\n  \"billing_subscription\": \"Đăng ký\",\n  \"billing_cancel_notice\": \"Bạn có thể hủy bất cứ lúc nào trong phần cài đặt mà không cần nói chuyện với ai và sẽ không bị tính phí.\",\n  \"select_channels\": \"Chọn kênh\",\n  \"start_a_new_chat\": \"Bắt đầu cuộc trò chuyện mới\",\n  \"your_assistant\": \"Trợ lý của bạn\",\n  \"agent_welcome_message\": \"Xin chào, tôi là trợ lý Postiz của bạn 🙌🏻.\\n\\nTôi có thể lên lịch đăng một hoặc nhiều bài lên nhiều kênh khác nhau và tạo hình ảnh, video.\\n\\nBạn có thể chọn các kênh muốn sử dụng từ menu bên trái.\\n\\nBạn có thể xem các cuộc trò chuyện trước đó từ menu bên phải.\\n\\nBạn cũng có thể sử dụng tôi như một MCP Server, kiểm tra Cài đặt >> Public API\",\n  \"last_github_trending\": \"Xu hướng Github gần đây\",\n  \"next_predicted_github_trending\": \"Dự đoán xu hướng Github tiếp theo\",\n  \"repository\": \"Kho lưu trữ\",\n  \"date\": \"Ngày\",\n  \"total_stars\": \"Tổng số sao\",\n  \"total_forks\": \"Tổng số nhánh\",\n  \"continue_with\": \"Tiếp tục với\",\n  \"email_address\": \"Địa chỉ email\",\n  \"email_already_exists\": \"Email đã tồn tại\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"Chỉnh sửa tự động đăng\",\n  \"add_autopost_title\": \"Thêm tự động đăng\",\n  \"webhook_deleted_successfully\": \"Đã xóa webhook thành công\",\n  \"all_integrations\": \"Tất cả tích hợp\",\n  \"specific_integrations\": \"Tích hợp cụ thể\",\n  \"post_on_next_available_slot\": \"Đăng vào khung giờ tiếp theo có sẵn\",\n  \"post_immediately\": \"Đăng ngay lập tức\",\n  \"could_not_use_rss_feed\": \"Không thể sử dụng nguồn cấp RSS này\",\n  \"rss_valid\": \"RSS hợp lệ!\",\n  \"autopost_updated_successfully\": \"Cập nhật tự động đăng thành công\",\n  \"autopost_added_successfully\": \"Tự động đăng bài đã được thêm thành công\",\n  \"write_your_post_placeholder\": \"Viết bài đăng của bạn...\",\n  \"select_or_upload_pictures_max_5\": \"Chọn hoặc tải lên hình ảnh (tối đa 5 hình cùng lúc).\",\n  \"you_can_drag_drop_pictures\": \"Bạn cũng có thể kéo và thả hình ảnh.\",\n  \"you_dont_have_any_media_yet\": \"Bạn chưa có phương tiện nào\",\n  \"media_library\": \"Thư viện phương tiện\",\n  \"media_settings\": \"Cài đặt phương tiện\",\n  \"media_editor\": \"Trình chỉnh sửa phương tiện\",\n  \"close\": \"Đóng\",\n  \"me\": \"Tôi\",\n  \"noname\": \"Không tên\",\n  \"password_reset_link_expired\": \"Liên kết đặt lại mật khẩu của bạn đã hết hạn. Vui lòng thử lại.\",\n  \"invalid_api_key\": \"API key không hợp lệ\",\n  \"could_not_connect_to_platform\": \"Không thể kết nối với nền tảng\",\n  \"web3_provider\": \"Nhà cung cấp Web3\",\n  \"add_provider_title\": \"Thêm nhà cung cấp\",\n  \"profile_updated\": \"Cập nhật hồ sơ thành công\",\n  \"sets\": \"Bộ\",\n  \"invitation_link_sent\": \"Đã gửi liên kết mời\",\n  \"send_invitation_link\": \"Gửi liên kết mời\",\n  \"copy_link\": \"Sao chép liên kết\",\n  \"are_you_sure_remove_team_member\": \"Bạn có chắc chắn muốn xóa thành viên này khỏi nhóm không?\",\n  \"admin\": \"Quản trị viên\",\n  \"super_admin\": \"Siêu quản trị viên\",\n  \"update_webhook\": \"Cập nhật webhook\",\n  \"add_webhook\": \"Thêm webhook\",\n  \"webhook_updated_successfully\": \"Cập nhật webhook thành công\",\n  \"webhook_added_successfully\": \"Thêm webhook thành công\",\n  \"webhook_sent\": \"Đã gửi webhook\",\n  \"today\": \"Hôm nay\",\n  \"channel_disconnected_click_to_reconnect\": \"Kênh đã bị ngắt kết nối, nhấn để kết nối lại.\",\n  \"channel_disabled_upgrade_plan\": \"Kênh này đã bị vô hiệu hóa, vui lòng nâng cấp gói của bạn để kích hoạt.\",\n  \"channel_added\": \"Đã thêm kênh\",\n  \"are_you_sure_disable_channel\": \"Bạn có chắc chắn muốn vô hiệu hóa kênh này không?\",\n  \"disable_channel_title\": \"Vô hiệu hóa kênh\",\n  \"channel_disabled\": \"Kênh đã bị vô hiệu hóa\",\n  \"are_you_sure_delete_channel\": \"Bạn có chắc chắn muốn xóa kênh này không?\",\n  \"delete_channel_title\": \"Xóa kênh\",\n  \"delete_posts_before_channel\": \"Bạn phải xóa tất cả các bài đăng liên kết với kênh này trước khi xóa nó\",\n  \"channel_deleted\": \"Đã xóa kênh\",\n  \"channel_enabled\": \"Kênh đã được kích hoạt\",\n  \"time_table_slots\": \"Khung giờ thời gian biểu\",\n  \"channel_id_copied\": \"Đã sao chép ID kênh vào bộ nhớ tạm\",\n  \"settings_updated\": \"Đã cập nhật cài đặt\",\n  \"customer_updated\": \"Đã cập nhật khách hàng\",\n  \"custom_url\": \"URL tùy chỉnh\",\n  \"picture\": \"Hình ảnh\",\n  \"upgrade_required\": \"Bạn cần nâng cấp để sử dụng tính năng này\",\n  \"move_to_billing\": \"Chuyển đến thanh toán\",\n  \"payment_required\": \"Yêu cầu thanh toán\",\n  \"no_content\": \"không có nội dung\",\n  \"select_customer_tooltip\": \"Chọn khách hàng\",\n  \"customers\": \"Khách hàng\",\n  \"hour\": \"Giờ\",\n  \"minutes\": \"Phút\",\n  \"updated\": \"Đã cập nhật\",\n  \"change_bot_picture_title\": \"Thay đổi ảnh Bot\",\n  \"select_customer_label\": \"Chọn khách hàng\",\n  \"start_typing\": \"Bắt đầu nhập...\",\n  \"choose_set_or_continue\": \"Chọn một bộ hoặc tiếp tục mà không có bộ nào\",\n  \"continue_without_set\": \"Tiếp tục mà không có bộ\",\n  \"select_set\": \"Chọn một bộ\",\n  \"channel_settings\": \"Cài đặt\",\n  \"post_needs_content_or_image\": \"Bài đăng của bạn cần có ít nhất một ký tự hoặc một hình ảnh.\",\n  \"please_fix_your_settings\": \"Vui lòng sửa cài đặt của bạn\",\n  \"shortlink_urls_question\": \"Bạn có muốn rút gọn các URL không? Điều này sẽ giúp bạn thống kê số lần nhấp chuột\",\n  \"yes_shortlink_it\": \"Vâng, hãy rút gọn!\",\n  \"added_successfully\": \"Thêm thành công\",\n  \"updated_successfully\": \"Cập nhật thành công\",\n  \"create_post_title\": \"Tạo bài đăng\",\n  \"post_preview\": \"Xem trước bài đăng\",\n  \"check_circles_above\": \"Kiểm tra các vòng tròn phía trên\",\n  \"create_output\": \"Tạo kết quả\",\n  \"assistant_initial_message\": \"Chào bạn! Tôi có thể giúp bạn chỉnh sửa các bài đăng trên mạng xã hội.\",\n  \"no_longer_global_mode\": \"Không còn ở chế độ toàn cục\",\n  \"two_days\": \"Hai ngày\",\n  \"three_days\": \"Ba ngày\",\n  \"four_days\": \"Bốn ngày\",\n  \"five_days\": \"Năm ngày\",\n  \"six_days\": \"Sáu ngày\",\n  \"two_weeks\": \"Hai tuần\",\n  \"repeat_post_every_label\": \"Lặp lại bài đăng mỗi\",\n  \"add_new_tag\": \"Thêm thẻ mới\",\n  \"tag_name\": \"Tên\",\n  \"post_is_too_long\": \"bài đăng quá dài, vui lòng chỉnh sửa\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"Bài đăng của bạn phải có ít nhất một ký tự hoặc một hình ảnh.\",\n  \"internal_edit\": \"Chỉnh sửa nội bộ\",\n  \"are_you_sure_go_back_to_global_mode\": \"Hành động này không thể hoàn tác. Bạn có chắc chắn muốn quay lại chế độ toàn cục không?\",\n  \"yes_go_back_to_global_mode\": \"Vâng, quay lại chế độ toàn cục\",\n  \"are_you_sure_delete_this_post\": \"Bạn có chắc chắn muốn xóa bài đăng này không?\",\n  \"yes_delete_it\": \"Vâng, xóa nó đi!\",\n  \"cant_edit_networks_when_creating_set\": \"Bạn không thể chỉnh sửa các mạng khi đang tạo một bộ\",\n  \"click_to_exit_global_editing\": \"Nhấn vào nút này để thoát chỉnh sửa toàn cục và tùy chỉnh bài đăng cho kênh này\",\n  \"edit_content\": \"Chỉnh sửa nội dung\",\n  \"editing_a_specific_network\": \"Đang chỉnh sửa một mạng cụ thể\",\n  \"back_to_global\": \"Quay lại toàn cục\",\n  \"delete_post_tooltip\": \"Xóa bài đăng\",\n  \"drop_files_here_to_upload\": \"Kéo thả tệp vào đây để tải lên\",\n  \"insert_emoji\": \"Chèn biểu tượng cảm xúc\",\n  \"write_something\": \"Viết gì đó …\",\n  \"click_channel_to_add\": \"Nhấp vào kênh để thêm\",\n  \"connect_your_channels\": \"Kết nối các kênh của bạn\",\n  \"connect_social_media_to_start\": \"Kết nối tài khoản mạng xã hội của bạn để bắt đầu lên lịch đăng bài\",\n  \"connected_channels\": \"Các kênh đã kết nối\",\n  \"continue\": \"Tiếp tục\",\n  \"continue_without_channels\": \"Tiếp tục mà không có kênh\",\n  \"watch_tutorial\": \"Xem hướng dẫn\",\n  \"watch_tutorial_title\": \"Tìm hiểu cách sử dụng Postiz\",\n  \"watch_tutorial_description\": \"Xem video ngắn này để biết cách tận dụng tối đa Postiz\",\n  \"back\": \"Quay lại\",\n  \"get_started\": \"Bắt đầu\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/locales/zh/translation.json",
    "content": "{\n  \"calendar\": \"日历\",\n  \"webhooks\": \"Webhook（网络钩子）\",\n  \"webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request\": \"Webhook 是一种通过 HTTP 请求在 Postiz 中有事件发生时获得通知的方式。\",\n  \"name\": \"名称\",\n  \"url\": \"URL\",\n  \"edit\": \"编辑\",\n  \"delete\": \"删除\",\n  \"add_a_webhook\": \"添加 Webhook\",\n  \"save\": \"保存\",\n  \"send_test\": \"发送测试\",\n  \"select_role\": \"选择角色\",\n  \"video_made_with_ai\": \"视频由AI制作\",\n  \"please_add_at_least\": \"请至少添加20个字符\",\n  \"send_invitation_via_email\": \"通过电子邮件发送邀请？\",\n  \"global_settings\": \"全局设置\",\n  \"copy_id\": \"复制频道ID\",\n  \"team_members\": \"团队成员\",\n  \"invite_your_assistant_or_team_member_to_manage_your_account\": \"邀请你的助理或团队成员来管理你的账户\",\n  \"remove\": \"移除\",\n  \"add_another_member\": \"添加其他成员\",\n  \"signatures\": \"签名\",\n  \"you_can_add_signatures_to_your_account_to_be_used_in_your_posts\": \"你可以为你的账户添加签名，以便在你的帖子中使用。\",\n  \"content\": \"内容\",\n  \"auto_add\": \"自动添加？\",\n  \"delay_comment\": \"延迟评论\",\n  \"actions\": \"操作\",\n  \"use_signature\": \"使用签名\",\n  \"add_a_signature\": \"添加签名\",\n  \"no\": \"否\",\n  \"yes\": \"是\",\n  \"your_git_repository\": \"你的Git仓库\",\n  \"connect_your_github_repository_to_receive_updates_and_analytics\": \"连接你的GitHub仓库以接收更新和分析\",\n  \"connected\": \"已连接：\",\n  \"disconnect\": \"断开连接\",\n  \"connect_your_repository\": \"连接你的仓库\",\n  \"cancel\": \"取消\",\n  \"connect\": \"连接\",\n  \"public_api\": \"公共API\",\n  \"check_n8n\": \"查看我们为Postiz定制的N8N节点。\",\n  \"use_postiz_api_to_integrate_with_your_tools\": \"使用Postiz API与您的工具集成。\",\n  \"read_how_to_use_it_over_the_documentation\": \"请阅读文档了解如何使用。\",\n  \"reveal\": \"显示\",\n  \"copy_key\": \"复制密钥\",\n  \"mcp\": \"MCP\",\n  \"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster\": \"将 Postiz MCP 服务器连接到您的客户端（Http 流式传输），以更快地安排您的帖子！\",\n  \"share_with_a_client\": \"与客户分享\",\n  \"post\": \"帖子\",\n  \"comments\": \"评论\",\n  \"user\": \"用户\",\n  \"login_register_to_add_comments\": \"登录/注册以添加评论\",\n  \"status\": \"状态：\",\n  \"there_are_not_plugs_matching_your_channels\": \"没有与您的频道匹配的插件\",\n  \"you_have_to_add_x_or_linkedin_or_threads\": \"你需要添加：X或LinkedIn或Threads\",\n  \"go_to_the_calendar_to_add_channels\": \"前往日历添加频道\",\n  \"channels\": \"频道\",\n  \"activate\": \"激活\",\n  \"this_channel_needs_to_be_refreshed\": \"该频道需要刷新，\",\n  \"click_here_to_refresh\": \"点击这里刷新\",\n  \"can_t_show_analytics_yet\": \"暂时无法显示分析数据\",\n  \"you_have_to_add_social_media_channels\": \"你需要添加社交媒体频道\",\n  \"supported\": \"支持：\",\n  \"step\": \"步骤\",\n  \"skip_onboarding\": \"跳过引导\",\n  \"onboarding\": \"新手引导\",\n  \"next\": \"下一步\",\n  \"you_are_done_from_here_you_can\": \"你已完成，从这里你可以：\",\n  \"view_analytics\": \"查看分析数据\",\n  \"schedule_a_new_post\": \"安排新帖子\",\n  \"to_sell_posts_you_would_have_to\": \"要出售帖子，你需要：\",\n  \"1_connect_at_least_one_channel\": \"1. 至少连接一个频道\",\n  \"2_connect_you_bank_account\": \"2. 连接你的银行账户\",\n  \"go_back_to_connect_channels\": \"返回连接频道\",\n  \"move_to_the_seller_page_to_connect_you_bank\": \"前往卖家页面连接你的银行账户\",\n  \"connect_channels\": \"连接频道\",\n  \"connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later\": \"连接你的社交媒体和发布网站频道，以便稍后安排帖子\",\n  \"social\": \"社交\",\n  \"publishing_platforms\": \"发布平台\",\n  \"no_channels\": \"还没有频道\",\n  \"connect_your_accounts\": \"连接您的社交账号，即可在一个平台上开始安排、发布和分析内容。\",\n  \"notifications\": \"通知\",\n  \"no_notifications\": \"暂无通知\",\n  \"send_message\": \"发送消息\",\n  \"mar_28\": \"3月28日\",\n  \"there_are_no_messages_yet\": \"还没有消息。\",\n  \"checkout_the_marketplace\": \"查看市场\",\n  \"go_to_marketplace\": \"前往市场\",\n  \"all_messages\": \"所有消息\",\n  \"previous\": \"上一页\",\n  \"select_or_upload_pictures_maximum_5_at_a_time\": \"选择或上传图片（每次最多5张）\",\n  \"you_can_also_drag_drop_pictures\": \"你也可以拖放图片\",\n  \"you_don_t_have_any_assets_yet\": \"你还没有任何资产。\",\n  \"click_the_button_below_to_upload_one\": \"点击下方按钮上传一个\",\n  \"click_the_button_below_to_upload_other\": \"点击下方按钮上传多个\",\n  \"add_selected_media\": \"添加所选媒体\",\n  \"insert_media\": \"插入媒体\",\n  \"design_media\": \"设计媒体\",\n  \"select\": \"选择\",\n  \"editor\": \"编辑器\",\n  \"clear\": \"清除\",\n  \"order_completed\": \"订单已完成\",\n  \"the_order_has_been_completed\": \"订单已完成\",\n  \"post_has_been_published\": \"帖子已发布\",\n  \"url_1\": \"网址：\",\n  \"new_offer\": \"新报价\",\n  \"platform\": \"平台\",\n  \"posts\": \"帖子\",\n  \"pay_accept_offer\": \"支付并接受报价\",\n  \"accepted\": \"已接受\",\n  \"post_draft\": \"草稿\",\n  \"revision_needed\": \"需要修改\",\n  \"approve\": \"批准\",\n  \"preview\": \"预览\",\n  \"revision_requested\": \"已请求修改\",\n  \"accepted_1\": \"已接受\",\n  \"cancelled_by_the_seller\": \"卖家已取消\",\n  \"please_select_your_country_where_your_business_is\": \"请选择您的企业所在国家。\",\n  \"select_country\": \"--选择国家--\",\n  \"connect_bank_account\": \"连接银行账户\",\n  \"seller_mode\": \"卖家模式\",\n  \"active\": \"活跃\",\n  \"details\": \"详情\",\n  \"audience_size\": \"受众规模\",\n  \"add_another_platform\": \"添加另一个平台\",\n  \"send_an_offer_for\": \"发送报价 $\",\n  \"complete_order_and_pay_early\": \"完成订单并提前付款\",\n  \"order_in_progress\": \"订单进行中\",\n  \"create_a_new_offer\": \"创建新报价\",\n  \"orders\": \"订单\",\n  \"price\": \"价格\",\n  \"state\": \"状态\",\n  \"showing\": \"显示\",\n  \"to\": \"到\",\n  \"from\": \"从\",\n  \"results\": \"结果\",\n  \"content_writer\": \"内容创作者\",\n  \"influencer\": \"影响者\",\n  \"request_service\": \"请求服务\",\n  \"the_marketplace_is_not_opened_yet\": \"市场尚未开放\",\n  \"check_again_soon\": \"请稍后再查！\",\n  \"filter\": \"筛选\",\n  \"result\": \"结果\",\n  \"seller\": \"卖家\",\n  \"buyer\": \"买家\",\n  \"discord_support\": \"Discord 支持\",\n  \"teams\": \"团队\",\n  \"webhooks_1\": \"Webhooks（网络钩子）\",\n  \"auto_post\": \"自动发布\",\n  \"logout_from\": \"登出\",\n  \"join_10000_entrepreneurs_who_use_postiz\": \"加入超过10,000名使用Postiz的创业者\",\n  \"to_manage_all_your_social_media_channels\": \"管理你所有的社交媒体渠道\",\n  \"100_no_risk_trial\": \"100% 无风险试用\",\n  \"pay_nothing_for_the_first_7_days\": \"前 7 天免费\",\n  \"cancel_anytime_hassle_free\": \"随时可在设置中取消\",\n  \"add_free_subscription\": \"-- 添加免费订阅 --\",\n  \"currently_impersonating\": \"当前模拟身份\",\n  \"user_1\": \"用户：\",\n  \"drag_n_drop_some_files_here\": \"将文件拖放到此处\",\n  \"add_time_slot\": \"添加时间段\",\n  \"add_slot\": \"添加时段\",\n  \"cancel_publication\": \"取消发布\",\n  \"statistics\": \"统计\",\n  \"loading\": \"加载中\",\n  \"short_link\": \"短链接\",\n  \"original_link\": \"原始链接\",\n  \"clicks\": \"点击量\",\n  \"selected_customer\": \"已选客户\",\n  \"customer\": \"客户：\",\n  \"repeat_post_every\": \"每隔...重复发布\",\n  \"use_this_media\": \"使用此媒体\",\n  \"create_new_post\": \"创建帖子\",\n  \"update_post\": \"更新已有帖子\",\n  \"merge_comments_into_one_post\": \"将评论合并为一条帖子\",\n  \"accounts_that_will_engage\": \"将参与的账号：\",\n  \"day\": \"天\",\n  \"week\": \"周\",\n  \"month\": \"月\",\n  \"remove_from_customer\": \"从客户中移除\",\n  \"show_more\": \"+ 显示更多\",\n  \"show_less\": \"- 显示更少\",\n  \"upload\": \"上传\",\n  \"ai\": \"AI\",\n  \"add_channel\": \"添加频道\",\n  \"add_platform\": \"添加平台\",\n  \"articles\": \"文章\",\n  \"add_comment\": \"添加评论\",\n  \"add_post\": \"在主题中添加帖子\",\n  \"add_comment_or_post\": \"添加评论/帖子\",\n  \"you_are_in_global_editing_mode\": \"您当前处于全局编辑模式\",\n  \"the_post_should_be_at_least_6_characters_long\": \"帖子内容至少需要6个字符\",\n  \"are_you_sure_you_want_to_delete_post\": \"您确定要删除这条帖子吗？\",\n  \"post_deleted_successfully\": \"帖子删除成功\",\n  \"delete_post\": \"删除帖子\",\n  \"save_as_draft\": \"保存为草稿\",\n  \"post_now\": \"立即发布\",\n  \"please_add\": \"请添加\",\n  \"to_your_telegram_group_channel_and_click_here\": \"到您的Telegram群组/频道并点击这里：\",\n  \"connect_telegram\": \"连接Telegram\",\n  \"please_add_the_following_command_in_your_chat\": \"请在您的聊天中添加以下命令：\",\n  \"copy\": \"复制\",\n  \"settings\": \"设置\",\n  \"integrations\": \"集成\",\n  \"add_integration\": \"添加集成\",\n  \"you_are_now_editing_only\": \"您现在仅在编辑\",\n  \"tag_a_company\": \"标记公司\",\n  \"video_length_is_invalid_must_be_up_to\": \"视频时长无效，最长不得超过\",\n  \"seconds\": \"秒\",\n  \"this_feature_available_only_for_photos\": \"此功能仅适用于照片，将添加默认音乐，您之后可以更改。\",\n  \"allow_user_to\": \"允许用户：\",\n  \"your_video_will_be_labeled_promotional\": \"您的视频将被标记为“推广内容”。\",\n  \"this_cannot_be_changed_once_posted\": \"视频发布后此项不可更改。\",\n  \"turn_on_to_disclose_video_promotes\": \"开启后将披露该视频为推广商品或服务以换取有价物。您的视频可以推广自己、第三方或两者。\",\n  \"you_are_promoting_yourself\": \"您正在推广自己或自己的品牌。\",\n  \"this_video_will_be_classified_brand_organic\": \"该视频将被归类为品牌原生内容。\",\n  \"you_are_promoting_another_brand\": \"您正在推广其他品牌或第三方。\",\n  \"this_video_will_be_classified_branded_content\": \"该视频将被归类为品牌内容。\",\n  \"by_posting_you_agree_to_tiktoks\": \"发布即表示您同意 TikTok 的\",\n  \"music_usage_confirmation\": \"音乐使用确认\",\n  \"branded_content_policy\": \"品牌内容政策\",\n  \"select_1\": \"--请选择--\",\n  \"select_flair\": \"--选择标识--\",\n  \"link\": \"链接\",\n  \"add_subreddit\": \"添加子版块\",\n  \"please_add_at_least_one_subreddit\": \"请至少添加一个子版块\",\n  \"add_community\": \"添加社区\",\n  \"select_post_type\": \"选择帖子类型...\",\n  \"we_couldn_t_find_any_business_connected_to_your_linkedin_page\": \"我们未能找到与您的 LinkedIn 页面关联的任何企业。\",\n  \"please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again\": \"请关闭此对话框，创建新页面并再次添加新频道。\",\n  \"select_linkedin_page\": \"选择 Linkedin 页面：\",\n  \"we_couldn_t_find_any_business_connected_to_the_selected_pages\": \"我们未能找到与所选页面关联的任何企业。\",\n  \"we_recommend_you_to_connect_all_the_pages_and_all_the_businesses\": \"我们建议您连接所有页面和所有企业。\",\n  \"please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again\": \"请关闭此对话框，删除您的集成并再次添加新频道。\",\n  \"select_instagram_account\": \"选择 Instagram 账号：\",\n  \"select_page\": \"选择页面：\",\n  \"generate_image_with_ai\": \"用 AI 生成图片\",\n  \"reconnect_channel\": \"重新连接频道\",\n  \"update_credentials\": \"更新凭证\",\n  \"additional_settings\": \"附加设置\",\n  \"change_bot\": \"更换机器人\",\n  \"move_add_to_customer\": \"移动/添加到客户\",\n  \"edit_time_slots\": \"编辑时间段\",\n  \"enable_channel\": \"启用频道\",\n  \"disable_channel\": \"禁用频道\",\n  \"add\": \"添加\",\n  \"short_post\": \"短帖子\",\n  \"long_post\": \"长帖子\",\n  \"a_thread_with_short_posts\": \"包含短帖的主题\",\n  \"a_thread_with_long_posts\": \"包含长帖的主题\",\n  \"personal_voice_i_am_happy_to_announce\": \"个人语气（“我很高兴地宣布”）\",\n  \"company_voice_we_are_happy_to_announce\": \"公司语气（“我们很高兴地宣布”）\",\n  \"generate\": \"生成\",\n  \"generate_posts\": \"生成帖子\",\n  \"purchase_a_life_time_pro_account_with_sol_199\": \"购买终身PRO账户，价格为SOL（$199）。请注意，此购买不支持退款。\",\n  \"purchase_now\": \"立即购买\",\n  \"pay_today\": \"今日付款\",\n  \"we_are_sorry_to_see_you_go\": \"很遗憾看到你离开 :(\",\n  \"would_you_mind_shortly_tell_us_what_we_could_have_done_better\": \"请简要告诉我们可以改进的地方好吗？\",\n  \"cancel_subscription\": \"取消订阅\",\n  \"plans\": \"套餐\",\n  \"monthly\": \"月付\",\n  \"yearly\": \"年付\",\n  \"reactivate_subscription\": \"重新激活订阅\",\n  \"update_payment_method_invoices_history\": \"更新支付方式 / 发票历史\",\n  \"cancel_subscription_1\": \"取消订阅\",\n  \"your_subscription_will_be_canceled_at\": \"你的订阅将在以下时间被取消\",\n  \"you_will_never_be_charged_again\": \"你将不会再被扣费\",\n  \"current_package\": \"当前套餐：\",\n  \"next_package\": \"下一个套餐：\",\n  \"claim\": \"领取\",\n  \"frequently_asked_questions\": \"常见问题\",\n  \"autopost\": \"自动发布\",\n  \"autopost_can_automatically_posts_your_rss_new_items_to_social_media\": \"自动发布可以将您的RSS新内容自动发布到社交媒体\",\n  \"title\": \"标题\",\n  \"add_an_autopost\": \"添加自动发布\",\n  \"post_content\": \"发布内容\",\n  \"sign_up\": \"注册\",\n  \"or\": \"或\",\n  \"by_registering_you_agree_to_our\": \"注册即表示您同意我们的\",\n  \"and\": \"和\",\n  \"terms_of_service\": \"服务条款\",\n  \"privacy_policy\": \"隐私政策\",\n  \"create_account\": \"创建账户\",\n  \"already_have_an_account\": \"已有账户？\",\n  \"sign_in\": \"登录\",\n  \"sign_in_1\": \"登录\",\n  \"don_t_have_an_account\": \"还没有账户？\",\n  \"forgot_password\": \"忘记密码\",\n  \"forgot_password_1\": \"忘记密码\",\n  \"send_password_reset_email\": \"发送密码重置邮件\",\n  \"go_back_to_login\": \"返回登录\",\n  \"we_have_send_you_an_email_with_a_link_to_reset_your_password\": \"我们已向您的邮箱发送了一封包含重置密码链接的邮件。\",\n  \"change_password\": \"更改密码\",\n  \"we_successfully_reset_your_password_you_can_now_login_with_your\": \"我们已成功重置您的密码。您现在可以使用新密码登录。\",\n  \"click_here_to_go_back_to_login\": \"点击这里返回登录\",\n  \"activate_your_account\": \"激活您的账户\",\n  \"thank_you_for_registering\": \"感谢您的注册！\",\n  \"please_check_your_email_to_activate_your_account\": \"请检查您的邮箱以激活账户。\",\n  \"sign_in_with\": \"使用以下方式登录\",\n  \"continue_with_google\": \"使用 Google 继续\",\n  \"sign_in_with_github\": \"使用 GitHub 登录\",\n  \"continue_with_farcaster\": \"使用 Farcaster 继续\",\n  \"continue_with_your_wallet\": \"使用钱包继续\",\n  \"stars_per_day\": \"每日星标\",\n  \"media\": \"媒体\",\n  \"check_launch\": \"检查启动\",\n  \"load_your_github_repository_from_settings_to_see_analytics\": \"请在设置中加载您的 GitHub 仓库以查看分析数据\",\n  \"stars\": \"星标\",\n  \"processing_stars\": \"正在处理星标...\",\n  \"forks\": \"分支\",\n  \"registration_is_disabled\": \"注册已被禁用\",\n  \"login_instead\": \"请登录\",\n  \"gitroom\": \"Gitroom\",\n  \"select_a_conversation_and_chat_away\": \"请选择一个会话并开始聊天。\",\n  \"adding_channel_redirecting_you\": \"正在添加频道，正在为您跳转\",\n  \"could_not_add_provider\": \"无法添加提供者。\",\n  \"you_are_being_redirected_back\": \"您正在被重定向返回\",\n  \"we_are_experiencing_some_difficulty_try_to_refresh_the_page\": \"我们遇到了一些问题，请尝试刷新页面\",\n  \"post_not_found\": \"未找到帖子\",\n  \"publication_date\": \"发布日期：\",\n  \"analytics\": \"分析\",\n  \"launches\": \"发布\",\n  \"plugs\": \"推荐\",\n  \"billing\": \"账单\",\n  \"affiliate\": \"联盟\",\n  \"monday\": \"星期一\",\n  \"tuesday\": \"星期二\",\n  \"wednesday\": \"星期三\",\n  \"thursday\": \"星期四\",\n  \"friday\": \"星期五\",\n  \"saturday\": \"星期六\",\n  \"sunday\": \"星期日\",\n  \"can_t_change_date_remove_post_from_publication\": \"无法更改日期，请先将帖子从发布中移除\",\n  \"predicted_github_trending_change\": \"预测 GitHub 趋势变化\",\n  \"duplicate_post\": \"重复帖子\",\n  \"preview_post\": \"预览帖子\",\n  \"post_statistics\": \"帖子统计\",\n  \"draft\": \"草稿\",\n  \"week_number\": \"第 {{number}} 周\",\n  \"top_title_edit_webhook\": \"编辑 webhook\",\n  \"top_title_add_webhook\": \"添加 webhook\",\n  \"top_title_oh_no\": \"哦不\",\n  \"top_title_auto_plug\": \"自动插件：{{title}}\",\n  \"top_title_edit_autopost\": \"编辑自动发布\",\n  \"top_title_add_autopost\": \"添加自动发布\",\n  \"top_title_send_a_new_offer\": \"发送新报价\",\n  \"top_title_media_library\": \"媒体库\",\n  \"top_title_add_signature\": \"添加签名\",\n  \"top_title_send_a_message_to\": \"发送消息给{{name}}\",\n  \"top_title_configure_provider\": \"配置服务商\",\n  \"top_title_add_member\": \"添加成员\",\n  \"top_title_change_bot_picture\": \"更改机器人头像\",\n  \"top_title_create_a_new_tag\": \"创建新标签\",\n  \"top_title_select_company\": \"选择公司\",\n  \"top_title_additional_settings\": \"附加设置\",\n  \"top_title_time_table_slots\": \"时间表时段\",\n  \"top_title_design_media\": \"设计媒体\",\n  \"top_title_edit_post\": \"编辑帖子\",\n  \"top_title_create_post\": \"创建帖子\",\n  \"top_title_move__add_to_customer\": \"移动/添加到客户\",\n  \"top_title_add_api_key_for\": \"为{{name}}添加API密钥\",\n  \"top_title_instance_url\": \"实例URL\",\n  \"top_title_custom_url\": \"自定义URL\",\n  \"top_title_add_channel\": \"添加频道\",\n  \"top_title_add_telegram\": \"添加Telegram\",\n  \"top_title_add_wrapcast\": \"添加Wrapcast\",\n  \"top_title_comments_for\": \"{{date}}的评论\",\n  \"top_title_edit_signature\": \"编辑签名\",\n  \"label_name\": \"姓名\",\n  \"label_url\": \"网址\",\n  \"label_title\": \"标题\",\n  \"label_subtitle\": \"副标题\",\n  \"label_email\": \"电子邮件\",\n  \"label_full_name\": \"全名\",\n  \"label_password\": \"密码\",\n  \"label_confirm_password\": \"确认密码\",\n  \"label_api_key\": \"API 密钥\",\n  \"label_instance_url\": \"实例网址\",\n  \"label_custom_url\": \"自定义网址\",\n  \"label_feedback\": \"反馈\",\n  \"label_bio\": \"个人简介\",\n  \"label_role\": \"角色\",\n  \"label_country\": \"国家\",\n  \"label_audience_size\": \"所有平台的受众规模\",\n  \"label_pick_time\": \"选择时间\",\n  \"label_nickname\": \"昵称\",\n  \"label_write_anything\": \"随便写点什么\",\n  \"label_output_format\": \"输出格式\",\n  \"label_add_pictures\": \"添加图片？\",\n  \"label_hour\": \"小时\",\n  \"label_minutes\": \"分钟\",\n  \"label_select_publication\": \"选择出版物\",\n  \"label_canonical_link\": \"规范链接\",\n  \"label_cover_picture\": \"封面图片\",\n  \"label_tags\": \"标签\",\n  \"label_topics\": \"话题\",\n  \"label_tags_maximum_4\": \"标签（最多4个）\",\n  \"label_attachments\": \"附件\",\n  \"label_type\": \"类型\",\n  \"label_thumbnail\": \"缩略图\",\n  \"label_who_can_see_this_video\": \"谁可以观看此视频？\",\n  \"label_content_posting_method\": \"内容发布方式\",\n  \"label_auto_add_music\": \"自动添加音乐\",\n  \"label_duet\": \"合拍\",\n  \"label_stitch\": \"拼接\",\n  \"label_comments\": \"评论\",\n  \"label_disclose_video_content\": \"公开视频内容\",\n  \"label_your_brand\": \"你的品牌\",\n  \"label_branded_content\": \"品牌内容\",\n  \"label_subreddit\": \"子版块\",\n  \"label_flair\": \"标记\",\n  \"label_media\": \"媒体\",\n  \"label_search_subreddit\": \"搜索子版块\",\n  \"label_delay\": \"延迟\",\n  \"label_post_type\": \"帖子类型\",\n  \"label_collaborators\": \"协作者（最多3人）- 账号不能为私密\",\n  \"label_community\": \"社区\",\n  \"label_search_community\": \"搜索社区\",\n  \"label_channel\": \"频道\",\n  \"label_search_channel\": \"搜索频道\",\n  \"label_select_channel\": \"选择频道\",\n  \"label_new_password\": \"新密码\",\n  \"label_repeat_password\": \"重复密码\",\n  \"label_platform\": \"平台\",\n  \"label_price_per_post\": \"每帖价格\",\n  \"label_integrations\": \"集成\",\n  \"label_code\": \"代码\",\n  \"label_should_sync_last_post\": \"是否同步当前最新帖子？\",\n  \"label_when_post\": \"我们应该何时发布？\",\n  \"label_autogenerate_content\": \"自动生成内容\",\n  \"label_generate_picture\": \"生成图片？\",\n  \"label_company\": \"公司\",\n  \"label_tag_color\": \"标签颜色\",\n  \"label_select_board\": \"选择看板\",\n  \"label_select_organization\": \"选择组织\",\n  \"label_auto_add_signature\": \"自动添加签名？\",\n  \"enable_color_picker\": \"启用取色器\",\n  \"cancel_the_color_picker\": \"取消取色器\",\n  \"no_content_yet\": \"暂无内容\",\n  \"write_your_reply\": \"写下你的帖子...\",\n  \"add_a_tag\": \"添加标签\",\n  \"add_to_calendar\": \"添加到日历\",\n  \"select_channels_from_circles\": \"从上方的圈子中选择频道\",\n  \"not_matching_order\": \"顺序不匹配\",\n  \"submit_for_order\": \"提交订单\",\n  \"schedule\": \"日程安排\",\n  \"update\": \"更新\",\n  \"attachments\": \"附件\",\n  \"tags\": \"标签\",\n  \"public_to_everyone\": \"对所有人公开\",\n  \"mutual_follow_friends\": \"互相关注的好友\",\n  \"follower_of_creator\": \"创作者的粉丝\",\n  \"self_only\": \"仅自己可见\",\n  \"post_content_directly_to_tiktok\": \"直接将内容发布到 TikTok\",\n  \"upload_content_to_tiktok_without_posting\": \"上传内容到 TikTok 但不发布\",\n  \"choose_upload_without_posting_description\": \"如果你希望在 TikTok 应用内审核和编辑内容后再发布，请选择“上传但不发布”。这样你可以使用 TikTok 内置的编辑工具，并在发布前进行最终调整。\",\n  \"faq_am_i_going_to_be_charged_by_postiz\": \"我会被 Postiz 收费吗？\",\n  \"faq_to_confirm_credit_card_information_postiz_will_hold\": \"为确认信用卡信息，Postiz将暂时预授权2美元并立即释放，您可以随时在设置中取消订阅，无需与任何人交流。\",\n  \"faq_can_i_trust_postiz_gitroom\": \"我可以信任Postiz吗？\",\n  \"faq_postiz_gitroom_is_proudly_open_source\": \"Postiz自豪地开源！我们相信道德和透明的文化，这意味着Postiz将永远存在。您可以查看全部代码或将其用于个人项目。要查看开源仓库，<a href=\\\"https://github.com/gitroomhq/postiz-app\\\" target=\\\"_blank\\\" style=\\\"text-decoration: underline;\\\">请点击这里</a>。\",\n  \"faq_what_are_channels\": \"什么是频道？\",\n  \"faq_postiz_gitroom_allows_you_to_schedule_posts\": \"Postiz允许您在不同频道之间安排您的帖子。\\n频道是一个发布平台，您可以在上面安排您的帖子。\\n例如，您可以在X、Facebook、Instagram、TikTok、YouTube、Reddit、Linkedin、Dribbble、Threads和Pinterest上安排您的帖子。\",\n  \"faq_what_are_team_members\": \"什么是团队成员？\",\n  \"faq_if_you_have_a_team_with_multiple_members\": \"如果你有一个包含多名成员的团队，你可以邀请他们加入你的工作区，共同协作发布内容，并添加他们的个人频道。\",\n  \"faq_what_is_ai_auto_complete\": \"什么是 AI 自动补全？\",\n  \"faq_we_automate_chatgpt_to_help_you_write\": \"我们自动化集成了 ChatGPT，帮助你撰写社交帖子和文章。\",\n  \"enter_email\": \"请输入邮箱\",\n  \"are_you_sure\": \"你确定吗？\",\n  \"no_cancel\": \"不，取消！\",\n  \"are_you_sure_you_want_to_delete\": \"你确定要删除{{name}}吗？\",\n  \"are_you_sure_you_want_to_delete_the_image\": \"你确定要删除这张图片吗？\",\n  \"are_you_sure_you_want_to_logout\": \"你确定要登出吗？\",\n  \"yes_logout\": \"是的，登出\",\n  \"are_you_sure_you_want_to_delete_this_slot\": \"你确定要删除这个槽位吗？\",\n  \"are_you_sure_you_want_to_delete_this_subreddit\": \"你确定要删除这个Subreddit吗？\",\n  \"are_you_sure_you_want_to_close_the_window\": \"你确定要关闭窗口吗？\",\n  \"yes_close\": \"是的，关闭\",\n  \"link_copied_to_clipboard\": \"链接已复制到剪贴板\",\n  \"are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost\": \"你确定要关闭此弹窗吗？（所有数据将丢失）\",\n  \"yes_close_it\": \"是的，关闭它！\",\n  \"uploading_pictures\": \"正在上传图片...\",\n  \"agent_starting\": \"代理启动中\",\n  \"researching_your_content\": \"正在研究你的内容...\",\n  \"understanding_the_category\": \"正在理解类别...\",\n  \"finding_the_topic\": \"正在查找主题...\",\n  \"finding_popular_posts_to_match_with\": \"正在查找匹配的热门帖子...\",\n  \"generating_hook\": \"正在生成引子...\",\n  \"generating_content\": \"正在生成内容...\",\n  \"generating_pictures\": \"正在生成图片...\",\n  \"finding_time_to_post\": \"正在查找发布时间...\",\n  \"write_anything\": \"随便写点什么\",\n  \"you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you\": \"你可以写任何你想写的内容，也可以添加链接，我们会为你做研究……\",\n  \"output_format\": \"输出格式\",\n  \"add_pictures\": \"添加图片？\",\n  \"7_days\": \"7天\",\n  \"30_days\": \"30天\",\n  \"90_days\": \"90天\",\n  \"start_7_days_free_trial\": \"开始7天免费试用\",\n  \"change_language\": \"切换语言\",\n  \"that_a_wrap\": \"本帖到此结束！\\n\\n如果你喜欢这个话题：\\n\\n1. 关注我 @{{username}}，获取更多类似内容\\n2. 转发下方推文，与更多人分享本帖\\n\",\n  \"post_as_images_carousel\": \"以图片轮播的形式发布\",\n  \"save_set\": \"保存设置\",\n  \"separate_post\": \"将帖子拆分为多个帖子\",\n  \"label_who_can_reply_to_this_post\": \"谁可以回复此帖子？\",\n  \"delete_integration\": \"删除集成\",\n  \"start_writing_your_post\": \"开始写你的帖子以预览\",\n  \"billing_join_over\": \"已有超过\",\n  \"billing_entrepreneurs_count\": \"20,000+ 位创业者\",\n  \"billing_who_use\": \"正在使用\",\n  \"billing_postiz_grow_social\": \"Postiz来提升他们的社交影响力\",\n  \"billing_no_risk_trial\": \"100%无风险免费试用\",\n  \"billing_pay_nothing_7_days\": \"前7天完全免费\",\n  \"billing_cancel_anytime\": \"随时可在设置中取消\",\n  \"billing_choose_plan\": \"选择一个方案\",\n  \"billing_monthly\": \"按月\",\n  \"billing_yearly\": \"按年\",\n  \"billing_20_percent_off\": \"立减20%\",\n  \"billing_features\": \"功能\",\n  \"billing_channel\": \"频道\",\n  \"billing_channels\": \"频道\",\n  \"billing_unlimited\": \"无限\",\n  \"billing_posts_per_month\": \"每月发布数\",\n  \"billing_unlimited_team_members\": \"无限团队成员\",\n  \"billing_ai_auto_complete\": \"AI自动补全\",\n  \"billing_ai_copilots\": \"AI协作助手\",\n  \"billing_ai_autocomplete\": \"AI自动补全\",\n  \"billing_advanced_picture_editor\": \"高级图片编辑器\",\n  \"billing_ai_images_per_month\": \"每月AI图片生成数\",\n  \"billing_ai_videos_per_month\": \"每月AI视频数\",\n  \"billing_billing_address\": \"账单地址\",\n  \"billing_payment\": \"付款\",\n  \"billing_powered_by_stripe\": \"安全支付由 Stripe 处理\",\n  \"billing_your_7_day_trial_is\": \"您的7天试用期\",\n  \"billing_100_percent_free\": \"100%免费\",\n  \"billing_ending\": \"即将结束\",\n  \"billing_cancel_anytime_short\": \"随时取消。\",\n  \"billing_pay_0_start_trial\": \"今日支付$0 - 开始您的免费试用！\",\n  \"billing_pay_now\": \"立即支付\",\n  \"billing_per_month\": \"每月\",\n  \"billing_per_year\": \"/ 年\",\n  \"billing_order_summary\": \"订单摘要\",\n  \"billing_applied\": \"已应用\",\n  \"billing_due_today\": \"今日应付\",\n  \"billing_then\": \"之后\",\n  \"billing_on\": \"在\",\n  \"billing_discount_applied\": \"已应用折扣\",\n  \"billing_remove\": \"移除\",\n  \"billing_coupon_expires\": \"优惠券有效期至\",\n  \"billing_invalid_coupon\": \"无效的优惠码\",\n  \"billing_coupon_applied\": \"优惠券应用成功！\",\n  \"billing_coupon_removed\": \"优惠券已移除\",\n  \"billing_error_removing_coupon\": \"移除优惠券时出错\",\n  \"billing_have_discount_coupon\": \"有优惠券吗？\",\n  \"billing_discount_coupon\": \"优惠券\",\n  \"billing_cancel\": \"取消\",\n  \"billing_enter_coupon_code\": \"请输入优惠码\",\n  \"billing_applying\": \"正在应用...\",\n  \"billing_apply\": \"应用\",\n  \"billing_subscription\": \"订阅\",\n  \"billing_cancel_notice\": \"您可以随时在设置中取消订阅，无需与任何人交流，且不会被收费。\",\n  \"select_channels\": \"选择频道\",\n  \"start_a_new_chat\": \"开始新聊天\",\n  \"your_assistant\": \"你的助手\",\n  \"agent_welcome_message\": \"你好，我是你的 Postiz 代理 🙌🏻。\\n\\n我可以为多个频道安排一条或多条帖子，并生成图片和视频。\\n\\n你可以从左侧菜单中选择你想要使用的频道。\\n\\n你可以从右侧菜单中查看你之前的对话。\\n\\n你也可以将我用作 MCP 服务器，查看设置 >> 公共 API。\",\n  \"last_github_trending\": \"最新 Github 趋势\",\n  \"next_predicted_github_trending\": \"下一个预测的 Github 趋势\",\n  \"repository\": \"仓库\",\n  \"date\": \"日期\",\n  \"total_stars\": \"总星标数\",\n  \"total_forks\": \"总分支数\",\n  \"continue_with\": \"继续使用\",\n  \"email_address\": \"电子邮件地址\",\n  \"email_already_exists\": \"电子邮件已存在\",\n  \"google\": \"Google\",\n  \"farcaster\": \"Farcaster\",\n  \"edit_autopost\": \"编辑自动发布\",\n  \"add_autopost_title\": \"添加自动发布\",\n  \"webhook_deleted_successfully\": \"Webhook 删除成功\",\n  \"all_integrations\": \"所有集成\",\n  \"specific_integrations\": \"特定集成\",\n  \"post_on_next_available_slot\": \"在下一个可用时间发布\",\n  \"post_immediately\": \"立即发布\",\n  \"could_not_use_rss_feed\": \"无法使用此 RSS 源\",\n  \"rss_valid\": \"RSS 有效！\",\n  \"autopost_updated_successfully\": \"自动发布更新成功\",\n  \"autopost_added_successfully\": \"自动发布添加成功\",\n  \"write_your_post_placeholder\": \"写下你的帖子...\",\n  \"select_or_upload_pictures_max_5\": \"选择或上传图片（每次最多5张）。\",\n  \"you_can_drag_drop_pictures\": \"你也可以拖放图片。\",\n  \"you_dont_have_any_media_yet\": \"你还没有任何媒体\",\n  \"media_library\": \"媒体库\",\n  \"media_settings\": \"媒体设置\",\n  \"media_editor\": \"媒体编辑器\",\n  \"close\": \"关闭\",\n  \"me\": \"我\",\n  \"noname\": \"无名\",\n  \"password_reset_link_expired\": \"您的密码重置链接已过期，请重试。\",\n  \"invalid_api_key\": \"无效的API密钥\",\n  \"could_not_connect_to_platform\": \"无法连接到平台\",\n  \"web3_provider\": \"Web3提供商\",\n  \"add_provider_title\": \"添加提供商\",\n  \"profile_updated\": \"个人资料已更新\",\n  \"sets\": \"集合\",\n  \"invitation_link_sent\": \"邀请链接已发送\",\n  \"send_invitation_link\": \"发送邀请链接\",\n  \"copy_link\": \"复制链接\",\n  \"are_you_sure_remove_team_member\": \"您确定要移除此团队成员吗？\",\n  \"admin\": \"管理员\",\n  \"super_admin\": \"超级管理员\",\n  \"update_webhook\": \"更新Webhook\",\n  \"add_webhook\": \"添加 webhook\",\n  \"webhook_updated_successfully\": \"Webhook 更新成功\",\n  \"webhook_added_successfully\": \"Webhook 添加成功\",\n  \"webhook_sent\": \"Webhook 已发送\",\n  \"today\": \"今天\",\n  \"channel_disconnected_click_to_reconnect\": \"频道已断开连接，点击重新连接。\",\n  \"channel_disabled_upgrade_plan\": \"该频道已被禁用，请升级您的套餐以启用。\",\n  \"channel_added\": \"频道已添加\",\n  \"are_you_sure_disable_channel\": \"您确定要禁用此频道吗？\",\n  \"disable_channel_title\": \"禁用频道\",\n  \"channel_disabled\": \"频道已禁用\",\n  \"are_you_sure_delete_channel\": \"您确定要删除此频道吗？\",\n  \"delete_channel_title\": \"删除频道\",\n  \"delete_posts_before_channel\": \"您需要先删除与此频道相关的所有帖子，然后才能删除该频道\",\n  \"channel_deleted\": \"频道已删除\",\n  \"channel_enabled\": \"频道已启用\",\n  \"time_table_slots\": \"时间表时段\",\n  \"channel_id_copied\": \"频道 ID 已复制到剪贴板\",\n  \"settings_updated\": \"设置已更新\",\n  \"customer_updated\": \"客户已更新\",\n  \"custom_url\": \"自定义 URL\",\n  \"picture\": \"图片\",\n  \"upgrade_required\": \"您需要升级才能使用此功能\",\n  \"move_to_billing\": \"前往结算\",\n  \"payment_required\": \"需要付款\",\n  \"no_content\": \"无内容\",\n  \"select_customer_tooltip\": \"选择客户\",\n  \"customers\": \"客户\",\n  \"hour\": \"小时\",\n  \"minutes\": \"分钟\",\n  \"updated\": \"已更新\",\n  \"change_bot_picture_title\": \"更换机器人头像\",\n  \"select_customer_label\": \"选择客户\",\n  \"start_typing\": \"开始输入...\",\n  \"choose_set_or_continue\": \"选择一个集合或继续不选择\",\n  \"continue_without_set\": \"不选择集合继续\",\n  \"select_set\": \"选择集合\",\n  \"channel_settings\": \"设置\",\n  \"post_needs_content_or_image\": \"您的帖子至少需要一个字符或一张图片。\",\n  \"please_fix_your_settings\": \"请修正您的设置\",\n  \"shortlink_urls_question\": \"您想要对网址进行短链处理吗？这样可以统计点击次数。\",\n  \"yes_shortlink_it\": \"是的，短链处理！\",\n  \"added_successfully\": \"添加成功\",\n  \"updated_successfully\": \"更新成功\",\n  \"create_post_title\": \"创建帖子\",\n  \"post_preview\": \"帖子预览\",\n  \"check_circles_above\": \"请检查上方的圆圈\",\n  \"create_output\": \"生成内容\",\n  \"assistant_initial_message\": \"您好！我可以帮助您优化社交媒体帖子。\",\n  \"no_longer_global_mode\": \"已退出全局模式\",\n  \"two_days\": \"两天\",\n  \"three_days\": \"三天\",\n  \"four_days\": \"四天\",\n  \"five_days\": \"五天\",\n  \"six_days\": \"六天\",\n  \"two_weeks\": \"两周\",\n  \"repeat_post_every_label\": \"每隔...重复发布\",\n  \"add_new_tag\": \"添加新标签\",\n  \"tag_name\": \"名称\",\n  \"post_is_too_long\": \"帖子太长，请修改\",\n  \"your_post_should_have_at_least_one_character_or_one_image\": \"您的帖子至少应包含一个字符或一张图片。\",\n  \"internal_edit\": \"内部编辑\",\n  \"are_you_sure_go_back_to_global_mode\": \"此操作不可逆。您确定要返回全局模式吗？\",\n  \"yes_go_back_to_global_mode\": \"是的，返回全局模式\",\n  \"are_you_sure_delete_this_post\": \"您确定要删除此帖子吗？\",\n  \"yes_delete_it\": \"是的，删除它！\",\n  \"cant_edit_networks_when_creating_set\": \"创建集合时无法编辑网络\",\n  \"click_to_exit_global_editing\": \"点击此按钮退出全局编辑，并为该频道自定义帖子\",\n  \"edit_content\": \"编辑内容\",\n  \"editing_a_specific_network\": \"正在编辑特定网络\",\n  \"back_to_global\": \"返回全局\",\n  \"delete_post_tooltip\": \"删除帖子\",\n  \"drop_files_here_to_upload\": \"将文件拖到此处以上传\",\n  \"insert_emoji\": \"插入表情\",\n  \"write_something\": \"写点什么…\",\n  \"click_channel_to_add\": \"点击频道以添加\",\n  \"connect_your_channels\": \"连接你的频道\",\n  \"connect_social_media_to_start\": \"连接你的社交媒体账户以开始安排发布\",\n  \"connected_channels\": \"已连接频道\",\n  \"continue\": \"继续\",\n  \"continue_without_channels\": \"继续但不添加频道\",\n  \"watch_tutorial\": \"观看教程\",\n  \"watch_tutorial_title\": \"学习如何使用Postiz\",\n  \"watch_tutorial_description\": \"观看这个简短的视频，学习如何最大程度地利用Postiz\",\n  \"back\": \"返回\",\n  \"get_started\": \"开始使用\"\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/src/translation/translated-label.tsx",
    "content": "import { ReactNode } from 'react';\nimport { useT } from './get.transation.service.client';\n\ninterface TranslatedLabelProps {\n  label: string;\n  translationKey?: string;\n  translationParams?: Record<string, string | number>;\n  children?: ReactNode;\n}\n\n/**\n * TranslatedLabel is a wrapper component that translates labels in form components\n *\n * @param label - The original label text (fallback if no translation found)\n * @param translationKey - Optional custom translation key, defaults to normalized label text\n * @param translationParams - Optional parameters for translation interpolation\n * @param children - Optional children components\n */\nexport function TranslatedLabel({\n  label,\n  translationKey,\n  translationParams = {},\n  children,\n}: TranslatedLabelProps) {\n  const t = useT();\n\n  // If no explicit key is provided, create one from the label\n  const key =\n    translationKey ||\n    `label_${label.toLowerCase().replace(/\\s+/g, '_').replace(/[^\\w]/g, '')}`;\n\n  const translatedLabel = t(key, label, translationParams);\n\n  return (\n    <>\n      {translatedLabel}\n      {children}\n    </>\n  );\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"forceConsistentCasingInFileNames\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.lib.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "libraries/react-shared-libraries/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"declaration\": true,\n    \"esModuleInterop\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"src/**/*.spec.ts\", \"src/**/*.test.ts\"]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"gitroom\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"license\": \"AGPL-3.0\",\n  \"keywords\": [],\n  \"author\": \"\",\n  \"engines\": {\n    \"node\": \">=22.12.0 <23.0.0\"\n  },\n  \"packageManager\": \"pnpm@10.6.1\",\n  \"scripts\": {\n    \"dev\": \"pnpm run --filter ./apps/extension --filter ./apps/orchestrator --filter ./apps/backend --filter ./apps/frontend --parallel dev\",\n    \"dev-backend\": \"pnpm run --filter ./apps/backend --filter ./apps/frontend --parallel dev\",\n    \"pm2\": \"pnpm run pm2-run\",\n    \"publish-sdk\": \"pnpm run --filter ./apps/sdk publish\",\n    \"publish-cli\": \"pnpm run --filter ./apps/cli publish\",\n    \"pm2-run\": \"pm2 delete all || true && pnpm run prisma-db-push && pnpm run --parallel pm2 && pm2 logs\",\n    \"dev:stripe\": \"pnpm dlx concurrently \\\"stripe listen --forward-to localhost:3000/stripe\\\" \\\"pnpm run dev\\\"\",\n    \"build\": \"pnpm -r --workspace-concurrency=1 --filter ./apps/frontend --filter ./apps/backend --filter ./apps/orchestrator run build\",\n    \"build:backend\": \"rm -rf apps/backend/dist && pnpm --filter ./apps/backend run build\",\n    \"build:frontend\": \"rm -rf apps/frontend/dist && pnpm --filter ./apps/frontend run build\",\n    \"build:orchestrator\": \"rm -rf apps/orchestrator/dist && pnpm --filter ./apps/orchestrator run build\",\n    \"build:extension\": \"rm -rf apps/extension/dist && pnpm --filter ./apps/extension run build\",\n    \"build:cli\": \"rm -rf apps/cli/dist && pnpm --filter ./apps/cli run build\",\n    \"dev:backend\": \"rm -rf apps/backend/dist && pnpm --filter ./apps/backend run dev\",\n    \"dev:frontend\": \"rm -rf apps/frontend/dist && pnpm --filter ./apps/frontend run dev\",\n    \"dev:orchestrator\": \"rm -rf apps/orchestrator/dist && pnpm --filter ./apps/orchestrator run dev\",\n    \"start:prod:orchestrator\": \"pnpm --filter ./apps/orchestrator run start\",\n    \"start:prod:backend\": \"pnpm --filter ./apps/backend run start\",\n    \"start:prod:frontend\": \"pnpm --filter ./apps/frontend run start\",\n    \"dev:docker\": \"docker compose -f ./docker-compose.dev.yaml up -d\",\n    \"commands:build:development\": \"pnpm --filter ./apps/commands run build\",\n    \"prisma-generate\": \"pnpm dlx prisma@6.5.0 generate --schema ./libraries/nestjs-libraries/src/database/prisma/schema.prisma\",\n    \"prisma-db-push\": \"pnpm dlx prisma@6.5.0 db push --accept-data-loss --schema ./libraries/nestjs-libraries/src/database/prisma/schema.prisma\",\n    \"prisma-db-pull\": \"pnpm dlx prisma@6.5.0 db pull --schema ./libraries/nestjs-libraries/src/database/prisma/schema.prisma\",\n    \"prisma-reset\": \"cd ./libraries/nestjs-libraries/src/database/prisma && pnpm dlx prisma@6.5.0 db push --force-reset && pnpx prisma@6.5.0 db push\",\n    \"docker-build\": \"./var/docker/docker-build.sh\",\n    \"docker-create\": \"./var/docker/docker-create.sh\",\n    \"postinstall\": \"pnpm run prisma-generate\",\n    \"test\": \"jest --coverage --detectOpenHandles --reporters=default --reporters=jest-junit\"\n  },\n  \"dependencies\": {\n    \"@ag-ui/mastra\": \"0.2.0\",\n    \"@ai-sdk/openai\": \"^2.0.52\",\n    \"@atproto/api\": \"^0.15.15\",\n    \"@aws-sdk/client-s3\": \"^3.787.0\",\n    \"@aws-sdk/s3-request-presigner\": \"^3.787.0\",\n    \"@casl/ability\": \"^6.5.0\",\n    \"@copilotkit/react-core\": \"1.10.6\",\n    \"@copilotkit/react-textarea\": \"1.10.6\",\n    \"@copilotkit/react-ui\": \"1.10.6\",\n    \"@copilotkit/runtime\": \"1.10.6\",\n    \"@dub/analytics\": \"^0.0.32\",\n    \"@hookform/resolvers\": \"^3.3.4\",\n    \"@langchain/community\": \"^0.3.40\",\n    \"@langchain/core\": \"^0.3.44\",\n    \"@langchain/langgraph\": \"^0.2.63\",\n    \"@langchain/openai\": \"^0.5.5\",\n    \"@mantine/core\": \"^5.10.5\",\n    \"@mantine/dates\": \"^5.10.5\",\n    \"@mantine/hooks\": \"^5.10.5\",\n    \"@mantine/modals\": \"^5.10.5\",\n    \"@mastra/core\": \"^0.20.2\",\n    \"@mastra/memory\": \"^0.15.6\",\n    \"@mastra/pg\": \"^0.17.2\",\n    \"@modelcontextprotocol/sdk\": \"^1.22.0\",\n    \"@nest-lab/throttler-storage-redis\": \"^1.2.0\",\n    \"@nestjs/cli\": \"10.0.2\",\n    \"@nestjs/common\": \"^10.0.2\",\n    \"@nestjs/core\": \"^10.0.2\",\n    \"@nestjs/microservices\": \"^10.3.1\",\n    \"@nestjs/platform-express\": \"^10.0.2\",\n    \"@nestjs/schedule\": \"^4.0.0\",\n    \"@nestjs/swagger\": \"^7.3.0\",\n    \"@nestjs/throttler\": \"^6.3.0\",\n    \"@neynar/nodejs-sdk\": \"^3.112.0\",\n    \"@neynar/react\": \"^0.9.7\",\n    \"@pigment-css/react\": \"^0.0.30\",\n    \"@postiz/wallets\": \"^0.0.1\",\n    \"@prisma/client\": \"6.5.0\",\n    \"@sentry/nestjs\": \"^10.26.0\",\n    \"@sentry/nextjs\": \"^10.26.0\",\n    \"@sentry/profiling-node\": \"^10.25.0\",\n    \"@sentry/react\": \"^10.25.0\",\n    \"@solana/wallet-adapter-react\": \"^0.15.35\",\n    \"@solana/wallet-adapter-react-ui\": \"^0.9.35\",\n    \"@stripe/react-stripe-js\": \"^5.4.1\",\n    \"@stripe/stripe-js\": \"^8.6.0\",\n    \"@swc/helpers\": \"0.5.13\",\n    \"@sweetalert2/theme-dark\": \"^5.0.16\",\n    \"@tailwindcss/postcss\": \"^4.1.7\",\n    \"@temporalio/activity\": \"^1.14.0\",\n    \"@temporalio/client\": \"^1.14.0\",\n    \"@temporalio/common\": \"^1.14.0\",\n    \"@temporalio/worker\": \"^1.14.0\",\n    \"@temporalio/workflow\": \"^1.14.0\",\n    \"@tiptap/extension-bold\": \"^3.0.6\",\n    \"@tiptap/extension-document\": \"^3.0.6\",\n    \"@tiptap/extension-heading\": \"^3.0.7\",\n    \"@tiptap/extension-history\": \"^3.0.7\",\n    \"@tiptap/extension-link\": \"^3.0.9\",\n    \"@tiptap/extension-list\": \"^3.0.7\",\n    \"@tiptap/extension-mention\": \"^3.0.7\",\n    \"@tiptap/extension-paragraph\": \"^3.0.6\",\n    \"@tiptap/extension-text\": \"^3.0.6\",\n    \"@tiptap/extension-underline\": \"^3.0.6\",\n    \"@tiptap/pm\": \"^3.0.6\",\n    \"@tiptap/react\": \"^3.0.6\",\n    \"@tiptap/starter-kit\": \"^3.0.6\",\n    \"@tiptap/suggestion\": \"^3.0.7\",\n    \"@types/bcrypt\": \"^5.0.2\",\n    \"@types/concat-stream\": \"^2.0.3\",\n    \"@types/facebook-nodejs-business-sdk\": \"^20.0.2\",\n    \"@types/jsonwebtoken\": \"^9.0.5\",\n    \"@types/lodash\": \"^4.14.202\",\n    \"@types/md5\": \"^2.3.5\",\n    \"@types/mime\": \"^3.0.4\",\n    \"@types/mime-types\": \"^2.1.4\",\n    \"@types/multer\": \"^1.4.11\",\n    \"@types/nodemailer\": \"^6.4.16\",\n    \"@types/parse5\": \"v6.0.1\",\n    \"@types/react-dropzone\": \"^4.2.2\",\n    \"@types/remove-markdown\": \"^0.3.4\",\n    \"@types/sha256\": \"^0.2.2\",\n    \"@types/striptags\": \"^0.0.5\",\n    \"@types/yup\": \"^0.32.0\",\n    \"@uidotdev/usehooks\": \"^2.4.1\",\n    \"@uiw/react-md-editor\": \"^4.0.3\",\n    \"@uppy/aws-s3\": \"^4.1.0\",\n    \"@uppy/compressor\": \"^2.1.1\",\n    \"@uppy/core\": \"^4.4.6\",\n    \"@uppy/dashboard\": \"^4.3.4\",\n    \"@uppy/drag-drop\": \"^4.1.3\",\n    \"@uppy/file-input\": \"^4.1.3\",\n    \"@uppy/progress-bar\": \"^4.2.1\",\n    \"@uppy/react\": \"^4.3.0\",\n    \"@uppy/status-bar\": \"^4.1.3\",\n    \"@uppy/transloadit\": \"^4.2.2\",\n    \"@uppy/xhr-upload\": \"^4.3.3\",\n    \"accept-language\": \"^3.0.20\",\n    \"array-move\": \"^4.0.0\",\n    \"async-mutex\": \"^0.5.0\",\n    \"axios\": \"^1.7.7\",\n    \"bcrypt\": \"^5.1.1\",\n    \"bottleneck\": \"^2.19.5\",\n    \"bs58\": \"^6.0.0\",\n    \"bufferutil\": \"^4.0.8\",\n    \"canvas\": \"^2.11.2\",\n    \"chart.js\": \"^4.4.1\",\n    \"class-transformer\": \"^0.5.1\",\n    \"class-validator\": \"^0.14.1\",\n    \"class-validator-jsonschema\": \"^5.1.0\",\n    \"clsx\": \"^2.1.0\",\n    \"compression\": \"^1.8.1\",\n    \"concat-stream\": \"^2.0.0\",\n    \"cookie-parser\": \"^1.4.7\",\n    \"copy-to-clipboard\": \"^3.3.3\",\n    \"crypto-hash\": \"^3.0.0\",\n    \"dayjs\": \"^1.11.10\",\n    \"dotenv\": \"^16.5.0\",\n    \"dotenv-cli\": \"^8.0.0\",\n    \"emoji-picker-react\": \"^4.12.0\",\n    \"evp_bytestokey\": \"^1.0.3\",\n    \"facebook-nodejs-business-sdk\": \"^21.0.5\",\n    \"fast-xml-parser\": \"^4.5.1\",\n    \"google-auth-library\": \"^9.11.0\",\n    \"googleapis\": \"^137.1.0\",\n    \"hot-reload-extension-vite\": \"^1.0.13\",\n    \"i18n-iso-countries\": \"^7.14.0\",\n    \"i18next\": \"^25.2.1\",\n    \"i18next-browser-languagedetector\": \"^8.1.0\",\n    \"i18next-resources-to-backend\": \"^1.2.1\",\n    \"image-to-pdf\": \"^3.0.2\",\n    \"ioredis\": \"^5.3.2\",\n    \"json-to-graphql-query\": \"^2.2.5\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"lodash\": \"^4.17.21\",\n    \"mastra\": \"^0.13.2\",\n    \"md5\": \"^2.3.0\",\n    \"mime\": \"^3.0.0\",\n    \"mime-types\": \"^2.1.35\",\n    \"multer\": \"^1.4.5-lts.1\",\n    \"music-metadata\": \"^7.14.0\",\n    \"nestjs-command\": \"^3.1.4\",\n    \"nestjs-real-ip\": \"^3.0.1\",\n    \"nestjs-temporal-core\": \"^3.2.0\",\n    \"next\": \"14.2.35\",\n    \"next-plausible\": \"^3.12.0\",\n    \"node-fetch\": \"^3.3.2\",\n    \"node-telegram-bot-api\": \"^0.66.0\",\n    \"nodemailer\": \"^7.0.11\",\n    \"nostr-tools\": \"^2.18.2\",\n    \"openai\": \"^6.2.0\",\n    \"p-limit\": \"^3.1.0\",\n    \"parse5\": \"^6.0.1\",\n    \"polotno\": \"^2.10.5\",\n    \"posthog-js\": \"^1.178.0\",\n    \"react\": \"18.3.1\",\n    \"react-colorful\": \"^5.6.1\",\n    \"react-country-flag\": \"^3.1.0\",\n    \"react-dnd\": \"^16.0.1\",\n    \"react-dnd-html5-backend\": \"^16.0.1\",\n    \"react-dom\": \"18.3.1\",\n    \"react-dropzone\": \"^14.3.5\",\n    \"react-hook-form\": \"^7.58.1\",\n    \"react-hotkeys-hook\": \"^5.1.0\",\n    \"react-i18next\": \"^15.5.2\",\n    \"react-loading\": \"^2.0.3\",\n    \"react-sortablejs\": \"^6.1.4\",\n    \"react-tag-autocomplete\": \"^7.2.0\",\n    \"react-tooltip\": \"^5.26.2\",\n    \"react-use-cookie\": \"^1.6.1\",\n    \"react-use-keypress\": \"^1.3.1\",\n    \"redis\": \"^4.6.12\",\n    \"reflect-metadata\": \"^0.1.13\",\n    \"remove-markdown\": \"^0.5.0\",\n    \"resend\": \"^3.2.0\",\n    \"rss-parser\": \"^3.13.0\",\n    \"rxjs\": \"^7.8.0\",\n    \"sass\": \"^1.89.2\",\n    \"sha256\": \"^0.2.0\",\n    \"sharp\": \"^0.33.4\",\n    \"simple-statistics\": \"^7.8.3\",\n    \"slugify\": \"^1.6.6\",\n    \"stripe\": \"^20.4.0\",\n    \"striptags\": \"^3.2.0\",\n    \"subtitle\": \"4.2.2-alpha.0\",\n    \"sweetalert2\": \"11.4.8\",\n    \"swr\": \"^2.2.5\",\n    \"tailwind-scrollbar\": \"^3.1.0\",\n    \"tailwindcss\": \"3.4.17\",\n    \"tailwindcss-rtl\": \"^0.9.0\",\n    \"timezones-list\": \"^3.1.0\",\n    \"tippy.js\": \"^6.3.7\",\n    \"tldts\": \"^6.1.47\",\n    \"transloadit\": \"^3.0.2\",\n    \"tslib\": \"^2.3.0\",\n    \"tweetnacl\": \"^1.0.3\",\n    \"twitter-api-v2\": \"^1.29.0\",\n    \"twitter-text\": \"^3.1.0\",\n    \"use-debounce\": \"^10.0.0\",\n    \"utf-8-validate\": \"^5.0.10\",\n    \"uuid\": \"^10.0.0\",\n    \"viem\": \"^2.22.9\",\n    \"webextension-polyfill\": \"^0.12.0\",\n    \"ws\": \"^8.18.0\",\n    \"yargs\": \"^17.7.2\",\n    \"yup\": \"^1.4.0\",\n    \"zod\": \"^3.25.76\",\n    \"zustand\": \"^5.0.5\"\n  },\n  \"devDependencies\": {\n    \"@crxjs/vite-plugin\": \"^2.0.0-beta.32\",\n    \"@nestjs/schematics\": \"^10.0.1\",\n    \"@nestjs/testing\": \"^10.0.2\",\n    \"@pmmmwh/react-refresh-webpack-plugin\": \"^0.5.7\",\n    \"@svgr/webpack\": \"^8.0.1\",\n    \"@swc-node/register\": \"1.9.2\",\n    \"@swc/cli\": \"0.3.14\",\n    \"@swc/core\": \"1.5.7\",\n    \"@tailwindcss/vite\": \"^4.0.17\",\n    \"@testing-library/react\": \"15.0.6\",\n    \"@types/cache-manager-redis-store\": \"^2.0.4\",\n    \"@types/chrome\": \"^0.0.319\",\n    \"@types/compression\": \"^1.8.1\",\n    \"@types/cookie-parser\": \"^1.4.6\",\n    \"@types/jest\": \"29.5.12\",\n    \"@types/node\": \"18.16.9\",\n    \"@types/node-telegram-bot-api\": \"^0.64.7\",\n    \"@types/react\": \"18.3.1\",\n    \"@types/react-dom\": \"18.3.0\",\n    \"@types/uuid\": \"^9.0.8\",\n    \"@types/webextension-polyfill\": \"^0.12.3\",\n    \"@types/yargs\": \"^17.0.32\",\n    \"@typescript-eslint/eslint-plugin\": \"7.18.0\",\n    \"@typescript-eslint/parser\": \"7.18.0\",\n    \"@vitejs/plugin-react\": \"^4.2.0\",\n    \"@vitest/coverage-v8\": \"1.6.0\",\n    \"@vitest/ui\": \"1.6.0\",\n    \"autoprefixer\": \"^10.4.17\",\n    \"babel-jest\": \"29.7.0\",\n    \"cross-env\": \"^10.0.0\",\n    \"eslint\": \"8.57.0\",\n    \"eslint-config-next\": \"15.2.1\",\n    \"eslint-config-prettier\": \"^9.0.0\",\n    \"eslint-plugin-import\": \"2.27.5\",\n    \"eslint-plugin-jsx-a11y\": \"6.7.1\",\n    \"eslint-plugin-react\": \"7.32.2\",\n    \"eslint-plugin-react-hooks\": \"4.6.0\",\n    \"fs-extra\": \"^11.3.0\",\n    \"jest\": \"29.7.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"jest-environment-node\": \"^29.4.1\",\n    \"jest-junit\": \"^16.0.0\",\n    \"jest-mock-extended\": \"^4.0.0-beta1\",\n    \"jsdom\": \"~22.1.0\",\n    \"nodemon\": \"^3.1.9\",\n    \"postcss\": \"8.4.38\",\n    \"prettier\": \"^2.6.2\",\n    \"prisma\": \"6.5.0\",\n    \"react-refresh\": \"^0.10.0\",\n    \"ts-jest\": \"^29.1.0\",\n    \"ts-node\": \"10.9.2\",\n    \"tsup\": \"^8.5.0\",\n    \"typescript\": \"5.5.4\",\n    \"vite\": \"^6.3.5\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"vitest\": \"3.1.4\"\n  },\n  \"volta\": {\n    \"node\": \"20.17.0\"\n  },\n  \"jest-junit\": {\n    \"outputDirectory\": \"./reports\",\n    \"outputName\": \"junit.xml\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"next\": \"14.2.35\"\n    },\n    \"onlyBuiltDependencies\": [\n      \"bcrypt\"\n    ]\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - apps/*\n  - libraries/*\n"
  },
  {
    "path": "railway.toml",
    "content": "[phases.setup]\n    nixPkgs = ['nodejs', 'python3']\n    aptPkgs = ['build-essential', 'libudev-dev']"
  },
  {
    "path": "reports/junit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites name=\"jest tests\" tests=\"11\" failures=\"0\" errors=\"0\" time=\"31.615\">\n  <testsuite name=\"PermissionsService\" errors=\"0\" failures=\"0\" skipped=\"0\" timestamp=\"2025-03-24T05:53:33\" time=\"31.496\" tests=\"11\">\n    <testcase classname=\"PermissionsService check() Verification Bypass (64) Bypass for Empty List\" name=\"PermissionsService check() Verification Bypass (64) Bypass for Empty List\" time=\"0.01\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Verification Bypass (64) Bypass for Missing Stripe\" name=\"PermissionsService check() Verification Bypass (64) Bypass for Missing Stripe\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Verification Bypass (64) No Bypass\" name=\"PermissionsService check() Verification Bypass (64) No Bypass\" time=\"0.002\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Channel Permission (82/87) All Conditions True\" name=\"PermissionsService check() Channel Permission (82/87) All Conditions True\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Channel Permission (82/87) Channel With Option Limit\" name=\"PermissionsService check() Channel Permission (82/87) Channel With Option Limit\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Channel Permission (82/87) Channel With Subscription Limit\" name=\"PermissionsService check() Channel Permission (82/87) Channel With Subscription Limit\" time=\"0.002\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Channel Permission (82/87) Channel Without Available Limits\" name=\"PermissionsService check() Channel Permission (82/87) Channel Without Available Limits\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Channel Permission (82/87) Section Different from Channel\" name=\"PermissionsService check() Channel Permission (82/87) Section Different from Channel\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Monthly Posts Permission (97/110) Posts Within Limit\" name=\"PermissionsService check() Monthly Posts Permission (97/110) Posts Within Limit\" time=\"0.008\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Monthly Posts Permission (97/110) Posts Exceed Limit\" name=\"PermissionsService check() Monthly Posts Permission (97/110) Posts Exceed Limit\" time=\"0.003\">\n    </testcase>\n    <testcase classname=\"PermissionsService check() Monthly Posts Permission (97/110) Section Different with Posts Within Limit\" name=\"PermissionsService check() Monthly Posts Permission (97/110) Section Different with Posts Within Limit\" time=\"0.003\">\n    </testcase>\n  </testsuite>\n</testsuites>"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectKey=gitroomhq_postiz-app_bd4cd369-af44-4d19-903b-c4bdb0b66022\nsonar.projectName=Postiz App\nsonar.projectDescription=An Open-Source Social Media Scheduler\n\n# Scan Performace\nsonar.javascript.node.maxspace=24576\nsonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.spec.ts,**/*.test.ts,*.png\n"
  },
  {
    "path": "tsconfig.base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"strictPropertyInitialization\": false,\n    \"experimentalDecorators\": true,\n    \"noPropertyAccessFromIndexSignature\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"importHelpers\": true,\n    \"target\": \"es2015\",\n    \"module\": \"esnext\",\n    \"lib\": [\"es2020\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"incremental\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": true,\n    \"strictBindCallApply\": false,\n    \"noFallthroughCasesInSwitch\": true,\n    \"strict\": true,\n    \"paths\": {\n      \"@gitroom/backend/*\": [\"apps/backend/src/*\"],\n      \"@gitroom/frontend/*\": [\"apps/frontend/src/*\"],\n      \"@gitroom/helpers/*\": [\"libraries/helpers/src/*\"],\n      \"@gitroom/nestjs-libraries/*\": [\"libraries/nestjs-libraries/src/*\"],\n      \"@gitroom/react/*\": [\"libraries/react-shared-libraries/src/*\"],\n      \"@gitroom/plugins/*\": [\"libraries/plugins/src/*\"],\n      \"@gitroom/orchestrator/*\": [\"apps/orchestrator/src/*\"],\n      \"@gitroom/extension/*\": [\"apps/extension/src/*\"]\n    }\n  },\n  \"exclude\": [\"node_modules\", \"tmp\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.base.json\"\n}\n"
  },
  {
    "path": "var/docker/create-namespace-default.sh",
    "content": "#!/bin/sh\n\nsleep 5\n\ntctl namespace create --namespace \"default\" --description 'Default namespace' --rd 1\n\ntini -s -- sleep infinity"
  },
  {
    "path": "var/docker/docker-build.sh",
    "content": "#!/bin/bash\n\nset -o xtrace\n\ndocker rmi localhost/postiz || true\ndocker build --target dist -t localhost/postiz -f Dockerfile.dev .\ndocker build --target devcontainer -t localhost/postiz-devcontainer -f Dockerfile.dev .\n"
  },
  {
    "path": "var/docker/docker-create.sh",
    "content": "#!/usr/bin/env bash\n\ndocker kill postiz || true \ndocker rm postiz || true \ndocker create --name postiz -p 3000:3000 -p 4200:4200 localhost/postiz\n"
  },
  {
    "path": "var/docker/nginx.conf",
    "content": "user                            www;\nworker_processes                auto; # it will be determinate automatically by the number of core\n\nevents {\n    worker_connections          1024;\n}\n\nhttp {\n    include                     /etc/nginx/mime.types;\n    default_type                application/octet-stream;\n    sendfile                    on;\n    access_log                  /var/log/nginx/access.log;\n    client_max_body_size 2G;\n    server {\n        listen 5000;\n        server_name _;\n        gzip on;\n        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\n        location /api/ {\n            proxy_pass http://localhost:3000/;\n            proxy_http_version 1.1;\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Proto $scheme;\n            proxy_set_header Reload $http_reload;\n            proxy_set_header Onboarding $http_onboarding;\n            proxy_set_header Activate $http_activate;\n            proxy_set_header Auth $http_auth;\n            proxy_set_header Showorg $http_showorg;\n            proxy_set_header Impersonate $http_impersonate;\n            proxy_set_header Accept-Language $http_accept_language;\n        }\n\n        location /uploads/ {\n            alias /uploads/;\n        }\n\n        location / {\n            proxy_pass http://localhost:4200/;\n            proxy_http_version 1.1;\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Proto $scheme;\n            proxy_set_header Reload $http_reload;\n            proxy_set_header Onboarding $http_onboarding;\n            proxy_set_header Activate $http_activate;\n            proxy_set_header Auth $http_auth;\n            proxy_set_header Showorg $http_showorg;\n            proxy_set_header Impersonate $http_impersonate;\n            proxy_set_header Accept-Language $http_accept_language;\n            proxy_set_header i18next $http_i18next;\n        }\n    }\n}"
  },
  {
    "path": "version.txt",
    "content": "v1.47.0\n"
  }
]