Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f9b697182 |
@@ -1,58 +0,0 @@
|
||||
---
|
||||
name: pr-description
|
||||
description: Generate a PR description following the project's GitHub PR template. Analyzes the current branch's changes against the base branch to produce a complete, filled-out PR description.
|
||||
user_invocable: true
|
||||
---
|
||||
|
||||
# PR Description Generator
|
||||
|
||||
Generate a pull request description based on the project's PR template at `.github/pull_request_template.md`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Determine the base branch**: Prefer the PR's actual `baseRefName` (via `gh pr view <PR> --json baseRefName`) when a PR exists. Otherwise default by intent — feature PRs target `preview`, release PRs target `master`. If still ambiguous, ask the user.
|
||||
|
||||
2. **Analyze changes**: Run the following to understand what changed:
|
||||
- `git log <base>...HEAD --oneline` to see all commits on this branch
|
||||
- `git diff <base>...HEAD --stat` to see which files changed
|
||||
- `git diff <base>...HEAD` to read the actual diff (use `--no-color`)
|
||||
- If the diff is very large, focus on the most important files first
|
||||
|
||||
3. **Fill out the PR template** with the following sections:
|
||||
|
||||
### Description
|
||||
|
||||
Write a clear, concise summary of what the PR does and why. Focus on the "what" and "why", not line-by-line changes. Mention any important implementation decisions.
|
||||
|
||||
### Type of Change
|
||||
|
||||
Check the appropriate box(es) based on the changes:
|
||||
- Bug fix (non-breaking change which fixes an issue)
|
||||
- Feature (non-breaking change which adds functionality)
|
||||
- Improvement (non-breaking change that improves existing functionality)
|
||||
- Code refactoring
|
||||
- Performance improvements
|
||||
- Documentation update
|
||||
|
||||
### Screenshots and Media
|
||||
|
||||
Leave this section for the user to fill in, preserving the existing placeholder comment from `.github/pull_request_template.md` verbatim rather than introducing different text.
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
Based on the code changes, suggest specific test scenarios that should be verified. Be concrete (e.g., "Navigate to project settings and verify the new toggle works") rather than generic.
|
||||
|
||||
### References
|
||||
- If commit messages or branch name reference a work item identifier (e.g., `WEB-1234`), include it
|
||||
- If the user provides a linked issue, include it
|
||||
- If Sentry issue links or IDs (e.g., `SENTRY-ABC123`, Sentry URLs) were mentioned earlier in the conversation, include them as references
|
||||
|
||||
4. **Output format**: Print the filled-out markdown template so the user can copy it directly. Do NOT wrap it in a code fence — output the raw markdown.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Keep the description concise but informative
|
||||
- Use bullet points for multiple changes
|
||||
- Focus on user-facing impact, not implementation details
|
||||
- If the branch has a Plane work item ID in its name (e.g., `WEB-1234`), reference it
|
||||
- Don't fabricate test scenarios that aren't relevant to the actual changes
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
name: release-notes
|
||||
description: "Generate release notes for a Plane release PR in `makeplane/plane` (semver, e.g. `release: vX.Y.Z`). Reads PR commits, filters out noise, categorizes by conventional-commit type, optionally enriches via Plane MCP, and writes the result as the PR description."
|
||||
user_invocable: true
|
||||
---
|
||||
|
||||
# Release Notes Generator
|
||||
|
||||
Generate structured release notes from a Plane release PR by parsing its commit list, then update the PR description.
|
||||
|
||||
## Versioning
|
||||
|
||||
Plane community uses **semver** (`vX.Y.Z`, major.minor.patch) for releases.
|
||||
|
||||
- PR title format: `release: vX.Y.Z`
|
||||
- Source branch: `canary`
|
||||
- Target branch: `master`
|
||||
|
||||
## When to Use
|
||||
|
||||
- User links/mentions a Plane release PR (e.g. `release: v1.3.0`) and asks for release notes
|
||||
- User asks to "create release notes" / "update PR description" for a release PR in `makeplane/plane`
|
||||
- The branch is named `canary` or `release/x.y.z` and the base is `master`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Fetch commits
|
||||
|
||||
```bash
|
||||
gh pr view <PR_NUM> --json title,body,baseRefName,headRefName,commits \
|
||||
--jq '.commits[] | .messageHeadline + "\n---BODY---\n" + .messageBody + "\n===END==="'
|
||||
```
|
||||
|
||||
For a quick scan first:
|
||||
|
||||
```bash
|
||||
gh pr view <PR_NUM> --json commits \
|
||||
--jq '.commits[] | {oid: .oid[0:10], message: .messageHeadline}'
|
||||
```
|
||||
|
||||
### 2. Filter out noise
|
||||
|
||||
**Always exclude** these commits — mechanical, not user-facing:
|
||||
|
||||
| Pattern | Reason |
|
||||
| -------------------------------------------- | -------------- |
|
||||
| `fix: merge conflicts` | Merge artifact |
|
||||
| `Merge branch '...' of github.com:...` | Merge artifact |
|
||||
| `Revert "..."` (when immediately re-applied) | Internal churn |
|
||||
|
||||
### 3. Parse work item IDs
|
||||
|
||||
Most meaningful commits begin with a Plane work item identifier in brackets:
|
||||
|
||||
- `[WEB-XXXX]` — web/frontend product items
|
||||
- `[SILO-XXXX]` — Silo (integrations: Slack, GitHub, GitLab, Jira/Linear)
|
||||
- `[MOBILE-XXXX]`, `[API-XXXX]`, etc.
|
||||
|
||||
Always preserve these IDs in the release notes — they let readers click through to the source ticket.
|
||||
|
||||
### 4. (Optional) Enrich via Plane MCP
|
||||
|
||||
For larger features where the commit headline is terse, fetch the work item:
|
||||
|
||||
```text
|
||||
mcp__plane__retrieve_work_item_by_identifier(project_identifier="WEB", issue_identifier=6874)
|
||||
```
|
||||
|
||||
Use the returned `name` and `description_stripped` to flesh out the bullet. Skip this for routine fixes — commit body is usually enough. Don't enrich every item (slow + work item descriptions are often empty).
|
||||
|
||||
### 5. Categorize by conventional-commit type
|
||||
|
||||
| Commit prefix | Section |
|
||||
| -------------------------------- | ------------------- |
|
||||
| `feat:`, `feat(scope):` | ✨ New Features |
|
||||
| `fix:`, `fix(scope):` | 🐛 Bug Fixes |
|
||||
| `refactor:` | 🔧 Refactor & Chore |
|
||||
| `chore:`, `chore(scope):` | 🔧 Refactor & Chore |
|
||||
| `chore(deps):`, dependabot bumps | 📦 Dependencies |
|
||||
|
||||
### 6. Format
|
||||
|
||||
```markdown
|
||||
# Release vX.Y.Z
|
||||
|
||||
## ✨ New Features
|
||||
|
||||
- **<Short title>** — [WEB-XXXX] (#PR_NUM)
|
||||
Optional 1–2 sentence elaboration drawn from commit body.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
- **<Short title>** — [WEB-XXXX] (#PR_NUM)
|
||||
|
||||
## 🔧 Refactor & Chore
|
||||
|
||||
- **<Short title>** — [WEB-XXXX] (#PR_NUM)
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
- Bump `<package>` X.Y.Z → A.B.C (#PR_NUM)
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Lead with a bold human-readable title (rewrite the commit subject if cryptic)
|
||||
- Always include the work item ID in brackets and the merge PR number in parens
|
||||
- Add a sub-line elaboration only when the commit body has substance worth surfacing (acceptance criteria, scope notes, gotchas like "behind feature flag", "requires migration", "requires Vercel setting")
|
||||
- Drop empty sections
|
||||
|
||||
### 7. Update the PR description
|
||||
|
||||
```bash
|
||||
gh pr edit <PR_NUM> --body "$(cat <<'EOF'
|
||||
<release notes markdown>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
Always use a HEREDOC with single-quoted `'EOF'` so backticks/dollars in the notes are preserved.
|
||||
|
||||
## Quick Reference: end-to-end
|
||||
|
||||
```bash
|
||||
PR=2498
|
||||
gh pr view $PR --json commits --jq '.commits[] | .messageHeadline + "\n---\n" + .messageBody + "\n==="' > /tmp/commits.txt
|
||||
# read /tmp/commits.txt, filter, categorize, draft notes
|
||||
gh pr edit $PR --body "$(cat <<'EOF'
|
||||
... release notes ...
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
- **Including `fix: merge conflicts`** — merge artifact, no functional content
|
||||
- **Dropping the work item ID** — readers rely on `[WEB-XXXX]` to navigate to the ticket
|
||||
- **Over-enriching with MCP lookups** — work item descriptions are often empty; commit body is usually richer
|
||||
- **Missing the merge PR number** — always include `(#NNNN)` from the commit subject so reviewers can audit the source PR
|
||||
- **Using `--body` without HEREDOC** — backticks/dollar signs get shell-interpreted and corrupt the notes
|
||||
- **Editing the title** — release PR titles are version markers; only edit the body
|
||||
|
||||
## Plane-Specific Conventions
|
||||
|
||||
- Release PRs go from `canary` → `master`
|
||||
- PR title format: `release: vX.Y.Z` semver (major.minor.patch)
|
||||
- Commits coming from feature branches always carry a work item ID; commits without one are usually infra/chores
|
||||
@@ -0,0 +1,7 @@
|
||||
[codespell]
|
||||
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
|
||||
skip = .git*,*.svg,i18n,*-lock.yaml,*.css,.codespellrc,migrations,*.js,*.map,*.mjs
|
||||
check-hidden = true
|
||||
# ignore all CamelCase and camelCase
|
||||
ignore-regex = \b[A-Za-z][a-z]+[A-Z][a-zA-Z]+\b
|
||||
ignore-words-list = tread
|
||||
@@ -1,6 +1,6 @@
|
||||
contact_links:
|
||||
- name: Help and support
|
||||
about: Reach out to us on our Forum or GitHub discussions.
|
||||
about: Reach out to us on our Discord server or GitHub discussions.
|
||||
- name: Dedicated support
|
||||
url: mailto:support@plane.so
|
||||
about: Write to us if you'd like dedicated support using Plane
|
||||
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
- id: checkout_files
|
||||
name: Checkout Files
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
branch_build_push_admin:
|
||||
name: Build-Push Admin Docker Image
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Admin Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Web Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Space Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Live Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Backend Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
needs: [branch_build_setup]
|
||||
steps:
|
||||
- name: Proxy Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.0.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -282,7 +282,7 @@ jobs:
|
||||
- branch_build_push_proxy
|
||||
steps:
|
||||
- name: Checkout Files
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare AIO Assets
|
||||
id: prepare_aio_assets
|
||||
@@ -298,13 +298,13 @@ jobs:
|
||||
echo "AIO_BUILD_VERSION=${aio_version}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload AIO Assets
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ./deployments/aio/community/dist
|
||||
name: aio-assets-dist
|
||||
|
||||
- name: AIO Build and Push
|
||||
uses: makeplane/actions/build-push@v1.4.0
|
||||
uses: makeplane/actions/build-push@v1.1.0
|
||||
with:
|
||||
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
|
||||
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
|
||||
@@ -337,7 +337,7 @@ jobs:
|
||||
- branch_build_push_proxy
|
||||
steps:
|
||||
- name: Checkout Files
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update Assets
|
||||
run: |
|
||||
@@ -352,7 +352,7 @@ jobs:
|
||||
# sed -i 's/APP_RELEASE=stable/APP_RELEASE='${REL_VERSION}'/g' deployments/cli/community/variables.env
|
||||
|
||||
- name: Upload Assets
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: community-assets
|
||||
path: |
|
||||
@@ -381,7 +381,7 @@ jobs:
|
||||
REL_VERSION: ${{ needs.branch_build_setup.outputs.release_version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update Assets
|
||||
run: |
|
||||
@@ -391,13 +391,12 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2.6.1
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ env.REL_VERSION }}
|
||||
name: ${{ env.REL_VERSION }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
draft: false
|
||||
prerelease: ${{ env.IS_PRERELEASE }}
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -10,13 +10,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
- name: Get PR Branch version
|
||||
run: echo "PR_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
|
||||
|
||||
@@ -16,27 +16,47 @@ jobs:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
env:
|
||||
CODEQL_ACTION_FILE_COVERAGE_ON_PRS: "false"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["python", "javascript"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Codespell configuration is within .codespellrc
|
||||
---
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [preview]
|
||||
pull_request:
|
||||
branches: [preview]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@v1
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Copy Right Check
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- "preview"
|
||||
types:
|
||||
- "opened"
|
||||
- "synchronize"
|
||||
- "ready_for_review"
|
||||
- "review_requested"
|
||||
- "reopened"
|
||||
|
||||
jobs:
|
||||
license-check:
|
||||
name: Copy Right Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.22"
|
||||
|
||||
- name: Install addlicense
|
||||
run: |
|
||||
go install github.com/google/addlicense@latest
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Check Copyright For Python Files
|
||||
run: |
|
||||
set -e
|
||||
echo "Running copyright check..."
|
||||
addlicense -check -f COPYRIGHT.txt -ignore "**/migrations/**" $(git ls-files '*.py')
|
||||
echo "Copyright check passed."
|
||||
|
||||
- name: Check Copyright For TypeScript Files
|
||||
run: |
|
||||
set -e
|
||||
echo "Running copyright check..."
|
||||
addlicense -check -f COPYRIGHT.txt -ignore "**/*.config.ts" -ignore "**/*.d.ts" $(git ls-files '*.ts' '*.tsx')
|
||||
echo "Copyright check passed."
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- id: checkout_files
|
||||
name: Checkout Files
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
full_build_push:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -63,23 +63,23 @@ jobs:
|
||||
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
|
||||
steps:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: ${{ env.BUILDX_DRIVER }}
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
endpoint: ${{ env.BUILDX_ENDPOINT }}
|
||||
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v7.0.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
sudo apt-get install -y python3-pip
|
||||
pip3 install awscli
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v4
|
||||
uses: tailscale/github-action@v2
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TAILSCALE_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TAILSCALE_OAUTH_SECRET }}
|
||||
|
||||
@@ -8,6 +8,8 @@ on:
|
||||
types:
|
||||
- "opened"
|
||||
- "synchronize"
|
||||
- "ready_for_review"
|
||||
- "review_requested"
|
||||
- "reopened"
|
||||
|
||||
concurrency:
|
||||
@@ -44,7 +46,7 @@ jobs:
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -87,7 +89,7 @@ jobs:
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -95,7 +97,7 @@ jobs:
|
||||
pnpm-store-${{ runner.os }}-
|
||||
|
||||
- name: Restore Turbo cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ runner.os }}-${{ github.event.pull_request.base.sha }}-${{ github.sha }}
|
||||
@@ -110,59 +112,21 @@ jobs:
|
||||
run: pnpm turbo run build --affected
|
||||
|
||||
- name: Save Turbo cache
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ runner.os }}-${{ github.event.pull_request.base.sha }}-${{ github.sha }}
|
||||
|
||||
# Lint check - no build dependency, OxLint is a standalone Rust binary
|
||||
check-lint:
|
||||
name: check:lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
if: |
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.requested_reviewers != null
|
||||
env:
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 50
|
||||
filter: blob:none
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
- name: Enable Corepack and pnpm
|
||||
run: corepack enable pnpm
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
pnpm-store-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run check:lint
|
||||
run: pnpm turbo run check:lint --affected
|
||||
|
||||
# Type check depends on build artifacts
|
||||
check-types:
|
||||
name: check:types
|
||||
# Lint and type checks depend on build artifacts
|
||||
check:
|
||||
name: ${{ matrix.task }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [check:lint, check:types]
|
||||
env:
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
@@ -185,7 +149,7 @@ jobs:
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -193,7 +157,7 @@ jobs:
|
||||
pnpm-store-${{ runner.os }}-
|
||||
|
||||
- name: Restore Turbo cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: turbo-${{ runner.os }}-${{ github.event.pull_request.base.sha }}-${{ github.sha }}
|
||||
@@ -201,5 +165,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run check:types
|
||||
run: pnpm turbo run check:types --affected
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: pnpm turbo run ${{ matrix.task }} --affected
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Create PR on Sync
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "sync/**"
|
||||
|
||||
env:
|
||||
CURRENT_BRANCH: ${{ github.ref_name }}
|
||||
TARGET_BRANCH: "preview" # The target branch that you would like to merge changes like develop
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # Personal access token required to modify contents and workflows
|
||||
ACCOUNT_USER_NAME: ${{ vars.ACCOUNT_USER_NAME }}
|
||||
ACCOUNT_USER_EMAIL: ${{ vars.ACCOUNT_USER_EMAIL }}
|
||||
|
||||
jobs:
|
||||
create_pull_request:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all branches and tags
|
||||
|
||||
- name: Setup Git
|
||||
run: |
|
||||
git config user.name "$ACCOUNT_USER_NAME"
|
||||
git config user.email "$ACCOUNT_USER_EMAIL"
|
||||
|
||||
- name: Setup GH CLI and Git Config
|
||||
run: |
|
||||
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
||||
sudo apt update
|
||||
sudo apt install gh -y
|
||||
|
||||
- name: Create PR to Target Branch
|
||||
run: |
|
||||
# get all pull requests and check if there is already a PR
|
||||
PR_EXISTS=$(gh pr list --base $TARGET_BRANCH --head $CURRENT_BRANCH --state open --json number | jq '.[] | .number')
|
||||
if [ -n "$PR_EXISTS" ]; then
|
||||
echo "Pull Request already exists: $PR_EXISTS"
|
||||
else
|
||||
echo "Creating new pull request"
|
||||
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $CURRENT_BRANCH --title "${{ vars.SYNC_PR_TITLE }}" --body "")
|
||||
echo "Pull Request created: $PR_URL"
|
||||
fi
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Sync Repositories
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- preview
|
||||
|
||||
env:
|
||||
SOURCE_BRANCH_NAME: ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
sync_changes:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup GH CLI
|
||||
run: |
|
||||
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
||||
sudo apt update
|
||||
sudo apt install gh -y
|
||||
|
||||
- name: Push Changes to Target Repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
run: |
|
||||
TARGET_REPO="${{ vars.SYNC_TARGET_REPO }}"
|
||||
TARGET_BRANCH="${{ vars.SYNC_TARGET_BRANCH_NAME }}"
|
||||
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
|
||||
|
||||
git checkout $SOURCE_BRANCH
|
||||
git remote add target-origin-a "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
|
||||
git push target-origin-a $SOURCE_BRANCH:$TARGET_BRANCH
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["react", "typescript", "jsx-a11y", "import", "promise", "unicorn", "oxc"],
|
||||
"categories": {
|
||||
"correctness": "warn",
|
||||
"suspicious": "warn",
|
||||
"perf": "warn"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es2024": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "18.3"
|
||||
},
|
||||
"jsx-a11y": {
|
||||
"polymorphicPropName": "as"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
".cache/**",
|
||||
".next/**",
|
||||
".react-router/**",
|
||||
".storybook/**",
|
||||
".turbo/**",
|
||||
".vite/**",
|
||||
"*.config.{js,mjs,cjs,ts}",
|
||||
"build/**",
|
||||
"coverage/**",
|
||||
"dist/**",
|
||||
"**/public/**",
|
||||
"storybook-static/**"
|
||||
],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"unicorn/filename-case": "off",
|
||||
"unicorn/no-null": "off",
|
||||
"unicorn/prevent-abbreviations": "off",
|
||||
"no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"sortTailwindcss": {
|
||||
"stylesheet": "packages/tailwind-config/index.css",
|
||||
"functions": ["cn", "clsx", "cva"]
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["packages/codemods/**/*"],
|
||||
@@ -13,5 +7,9 @@
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["@prettier/plugin-oxc"],
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
- `pnpm dev` - Start all dev servers (web:3000, admin:3001)
|
||||
- `pnpm build` - Build all packages and apps
|
||||
- `pnpm check` - Run all checks (format, lint, types)
|
||||
- `pnpm check:lint` - OxLint across all packages
|
||||
- `pnpm check:lint` - ESLint across all packages
|
||||
- `pnpm check:types` - TypeScript type checking
|
||||
- `pnpm fix` - Auto-fix format and lint issues
|
||||
- `pnpm turbo run <command> --filter=<package>` - Target specific package/app
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
- **Imports**: Use `workspace:*` for internal packages, `catalog:` for external deps
|
||||
- **TypeScript**: Strict mode enabled, all files must be typed
|
||||
- **Formatting**: oxfmt, run `pnpm fix:format`
|
||||
- **Linting**: OxLint with shared `.oxlintrc.json` config
|
||||
- **Formatting**: Prettier with Tailwind plugin, run `pnpm fix:format`
|
||||
- **Linting**: ESLint with shared config, max warnings vary by package
|
||||
- **Naming**: camelCase for variables/functions, PascalCase for components/types
|
||||
- **Error Handling**: Use try-catch with proper error types, log errors appropriately
|
||||
- **State Management**: MobX stores in `packages/shared-state`, reactive patterns
|
||||
|
||||
+1
-8
@@ -1,8 +1 @@
|
||||
.oxlintrc.json @sriramveeraghanta @lifeiscontent
|
||||
.oxfmtrc.json @sriramveeraghanta @lifeiscontent
|
||||
apps/api/ @dheeru0198 @pablohashescobar
|
||||
apps/web/ @sriramveeraghanta
|
||||
apps/space/ @sriramveeraghanta
|
||||
apps/admin/ @sriramveeraghanta
|
||||
apps/live/ @Palanikannan1437
|
||||
deployments/ @mguptahub
|
||||
eslint.config.mjs @lifeiscontent
|
||||
+2
-2
@@ -91,7 +91,7 @@ If you would like to _implement_ it, an issue with your proposal must be submitt
|
||||
To ensure consistency throughout the source code, please keep these rules in mind as you are working:
|
||||
|
||||
- All features or bug fixes must be tested by one or more specs (unit-tests).
|
||||
- We lint with [OxLint](https://oxc.rs/docs/guide/usage/linter) using the shared `.oxlintrc.json` and format with [oxfmt](https://oxc.rs/docs/guide/usage/formatter) using `.oxfmtrc.json`.
|
||||
- We lint with [ESLint 9](https://eslint.org/docs/latest/) using the shared `eslint.config.mjs` (type-aware via `typescript-eslint`) and format with [Prettier](https://prettier.io/) using `prettier.config.cjs`.
|
||||
|
||||
## Ways to contribute
|
||||
|
||||
@@ -244,4 +244,4 @@ Happy translating! 🌍✨
|
||||
|
||||
## Need help? Questions and suggestions
|
||||
|
||||
Questions, suggestions, and thoughts are most welcome. We can also be reached in our [Forum](https://forum.plane.so).
|
||||
Questions, suggestions, and thoughts are most welcome. We can also be reached in our [Discord Server](https://discord.com/invite/A92xrEGCge).
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
See the LICENSE file for details.
|
||||
@@ -1,34 +0,0 @@
|
||||
## Copyright check
|
||||
|
||||
To verify that all tracked Python files contain the correct copyright header for **Plane Software Inc.** for the year **2023**, run this command from the repository root:
|
||||
|
||||
```bash
|
||||
addlicense --check -f COPYRIGHT.txt -ignore "**/migrations/**" $(git ls-files '*.py')
|
||||
```
|
||||
|
||||
#### To Apply Changes
|
||||
|
||||
python files
|
||||
|
||||
```bash
|
||||
addlicense -v -f COPYRIGHT.txt -ignore "**/migrations/**" $(git ls-files '*.py')
|
||||
```
|
||||
|
||||
ts and tsx files in a specific app
|
||||
|
||||
```bash
|
||||
addlicense -v -f COPYRIGHT.txt \
|
||||
-ignore "**/*.config.ts" \
|
||||
-ignore "**/*.d.ts" \
|
||||
$(git ls-files 'packages/*.ts')
|
||||
```
|
||||
|
||||
Note: Please make sure ts command is running on specific folder, running it for the whole mono repo is crashing os processes.
|
||||
|
||||
#### Other Options
|
||||
|
||||
- **`addlicense -check`**: runs in check-only mode and fails if any file is missing or has an incorrect header.
|
||||
- **`-c "Plane Software Inc."`**: sets the copyright holder.
|
||||
- **`-f LICENSE.txt`**: uses the contents and format defined in `LICENSE.txt` as the header template.
|
||||
- **`-y 2023`**: sets the year in the header.
|
||||
- **`$(git ls-files '*.py')`**: restricts the check to Python files tracked in git.
|
||||
@@ -7,10 +7,17 @@
|
||||
</p>
|
||||
<p align="center"><b>Modern project management for all teams</b></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.com/invite/A92xrEGCge">
|
||||
<img alt="Discord online members" src="https://img.shields.io/discord/1031547764020084846?color=5865F2&label=Discord&style=for-the-badge" />
|
||||
</a>
|
||||
<img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://plane.so/"><b>Website</b></a> •
|
||||
<a href="https://forum.plane.so"><b>Forum</b></a> •
|
||||
<a href="https://x.com/planepowers"><b>X</b></a> •
|
||||
<a href="https://github.com/makeplane/plane/releases"><b>Releases</b></a> •
|
||||
<a href="https://twitter.com/planepowers"><b>Twitter</b></a> •
|
||||
<a href="https://docs.plane.so/"><b>Documentation</b></a>
|
||||
</p>
|
||||
|
||||
@@ -26,7 +33,7 @@
|
||||
|
||||
Meet [Plane](https://plane.so/), an open-source project management tool to track issues, run ~sprints~ cycles, and manage product roadmaps without the chaos of managing the tool itself. 🧘♀️
|
||||
|
||||
> Plane is evolving every day. Your suggestions, ideas, and reported bugs help us immensely. Do not hesitate to join in the conversation on [Forum](https://forum.plane.so) or raise a GitHub issue. We read everything and respond to most.
|
||||
> Plane is evolving every day. Your suggestions, ideas, and reported bugs help us immensely. Do not hesitate to join in the conversation on [Discord](https://discord.com/invite/A92xrEGCge) or raise a GitHub issue. We read everything and respond to most.
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
@@ -129,7 +136,7 @@ Explore Plane's [product documentation](https://docs.plane.so/) and [developer d
|
||||
|
||||
## ❤️ Community
|
||||
|
||||
Join the Plane community on [GitHub Discussions](https://github.com/orgs/makeplane/discussions) and our [Forum](https://forum.plane.so). We follow a [Code of conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) in all our community channels.
|
||||
Join the Plane community on [GitHub Discussions](https://github.com/orgs/makeplane/discussions) and our [Discord server](https://discord.com/invite/A92xrEGCge). We follow a [Code of conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) in all our community channels.
|
||||
|
||||
Feel free to ask questions, report bugs, participate in discussions, share ideas, request features, or showcase your projects. We’d love to hear from you!
|
||||
|
||||
@@ -145,7 +152,7 @@ There are many ways you can contribute to Plane:
|
||||
|
||||
- Report [bugs](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%F0%9F%90%9Bbug&projects=&template=--bug-report.yaml&title=%5Bbug%5D%3A+) or submit [feature requests](https://github.com/makeplane/plane/issues/new?assignees=srinivaspendem%2Cpushya22&labels=%E2%9C%A8feature&projects=&template=--feature-request.yaml&title=%5Bfeature%5D%3A+).
|
||||
- Review the [documentation](https://docs.plane.so/) and submit [pull requests](https://github.com/makeplane/docs) to improve it—whether it's fixing typos or adding new content.
|
||||
- Talk or write about Plane or any other ecosystem integration and [let us know](https://forum.plane.so)!
|
||||
- Talk or write about Plane or any other ecosystem integration and [let us know](https://discord.com/invite/A92xrEGCge)!
|
||||
- Show your support by upvoting [popular feature requests](https://github.com/makeplane/plane/issues).
|
||||
|
||||
Please read [CONTRIBUTING.md](https://github.com/makeplane/plane/blob/master/CONTRIBUTING.md) for details on the process for submitting pull requests to us.
|
||||
|
||||
@@ -4,7 +4,7 @@ WORKDIR /app
|
||||
|
||||
ENV TURBO_TELEMETRY_DISABLED=1
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PNPM_HOME/bin:$PATH"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
ENV CI=1
|
||||
|
||||
RUN corepack enable pnpm
|
||||
@@ -13,7 +13,7 @@ RUN corepack enable pnpm
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN pnpm add -g turbo@2.9.4
|
||||
RUN pnpm add -g turbo@2.6.3
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
import { Button } from "@plane/propel/button";
|
||||
@@ -120,16 +114,16 @@ export function InstanceAIForm(props: IInstanceAIForm) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
<Button variant="primary" size="lg" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||
{isSubmitting ? "Saving" : "Save changes"}
|
||||
</Button>
|
||||
|
||||
<div className="relative inline-flex items-center gap-1.5 rounded-sm border border-accent-subtle bg-accent-subtle px-4 py-2 text-caption-sm-regular text-accent-secondary">
|
||||
<div className="relative inline-flex items-center gap-1.5 rounded-sm border border-accent-subtle bg-accent-subtle px-4 py-2 text-caption-sm-regular text-accent-secondary ">
|
||||
<Lightbulb className="size-4" />
|
||||
<div>
|
||||
If you have a preferred AI models vendor, please get in{" "}
|
||||
<a className="font-medium underline" href="https://plane.so/contact">
|
||||
<a className="underline font-medium" href="https://plane.so/contact">
|
||||
touch with us.
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Loader } from "@plane/ui";
|
||||
@@ -34,7 +28,7 @@ const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentP
|
||||
) : (
|
||||
<Loader className="space-y-8">
|
||||
<Loader.Item height="50px" width="40%" />
|
||||
<div className="grid w-2/3 grid-cols-2 gap-x-8 gap-y-4">
|
||||
<div className="w-2/3 grid grid-cols-2 gap-x-8 gap-y-4">
|
||||
<Loader.Item height="50px" />
|
||||
<Loader.Item height="50px" />
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -176,8 +170,8 @@ export function InstanceGiteaConfigForm(props: Props) {
|
||||
handleClose={() => setIsDiscardChangesModalOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid w-full grid-cols-2 gap-x-12 gap-y-8">
|
||||
<div className="col-span-2 flex flex-col gap-y-4 pt-1 md:col-span-1">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
|
||||
<div className="pt-2.5 text-18 font-medium">Gitea-provided details for Plane</div>
|
||||
{GITEA_FORM_FIELDS.map((field) => (
|
||||
<ControllerInput
|
||||
@@ -211,7 +205,7 @@ export function InstanceGiteaConfigForm(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="flex flex-col gap-y-4 rounded-lg bg-layer-1 px-6 pt-1.5 pb-4">
|
||||
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-layer-1 rounded-lg">
|
||||
<div className="pt-2 text-18 font-medium">Plane-provided details for Gitea</div>
|
||||
{GITEA_SERVICE_FIELD.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -197,8 +191,8 @@ export function InstanceGithubConfigForm(props: Props) {
|
||||
handleClose={() => setIsDiscardChangesModalOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid w-full grid-cols-2 gap-x-12 gap-y-8">
|
||||
<div className="col-span-2 flex flex-col gap-y-4 pt-1 md:col-span-1">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
|
||||
<div className="pt-2.5 text-18 font-medium">GitHub-provided details for Plane</div>
|
||||
{GITHUB_FORM_FIELDS.map((field) => (
|
||||
<ControllerInput
|
||||
@@ -231,24 +225,24 @@ export function InstanceGithubConfigForm(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-y-6 md:col-span-1">
|
||||
<div className="col-span-2 md:col-span-1 flex flex-col gap-y-6">
|
||||
<div className="pt-2 text-18 font-medium">Plane-provided details for GitHub</div>
|
||||
|
||||
<div className="flex flex-col gap-y-4">
|
||||
{/* common service details */}
|
||||
<div className="flex flex-col gap-y-4 rounded-lg bg-layer-1 px-6 py-4">
|
||||
<div className="flex flex-col gap-y-4 px-6 py-4 bg-layer-1 rounded-lg">
|
||||
{GITHUB_COMMON_SERVICE_DETAILS.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* web service details */}
|
||||
<div className="flex flex-col overflow-hidden rounded-lg">
|
||||
<div className="flex items-center gap-x-3 bg-layer-3 px-6 py-3 text-11 font-medium text-secondary uppercase">
|
||||
<Monitor className="h-3 w-3" />
|
||||
<div className="flex flex-col rounded-lg overflow-hidden">
|
||||
<div className="px-6 py-3 bg-layer-3 font-medium text-11 uppercase flex items-center gap-x-3 text-secondary">
|
||||
<Monitor className="w-3 h-3" />
|
||||
Web
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4 bg-layer-1 px-6 py-4">
|
||||
<div className="px-6 py-4 flex flex-col gap-y-4 bg-layer-1">
|
||||
{GITHUB_SERVICE_DETAILS.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -180,8 +174,8 @@ export function InstanceGitlabConfigForm(props: Props) {
|
||||
handleClose={() => setIsDiscardChangesModalOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid w-full grid-cols-2 gap-x-12 gap-y-8">
|
||||
<div className="col-span-2 flex flex-col gap-y-4 pt-1 md:col-span-1">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
|
||||
<div className="pt-2.5 text-18 font-medium">GitLab-provided details for Plane</div>
|
||||
{GITLAB_FORM_FIELDS.map((field) => (
|
||||
<ControllerInput
|
||||
@@ -215,7 +209,7 @@ export function InstanceGitlabConfigForm(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<div className="flex flex-col gap-y-4 rounded-lg bg-layer-3 px-6 pt-1.5 pb-4">
|
||||
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-layer-3 rounded-lg">
|
||||
<div className="pt-2 text-18 font-medium">Plane-provided details for GitLab</div>
|
||||
{GITLAB_SERVICE_FIELD.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -185,8 +179,8 @@ export function InstanceGoogleConfigForm(props: Props) {
|
||||
handleClose={() => setIsDiscardChangesModalOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid w-full grid-cols-2 gap-x-12 gap-y-8">
|
||||
<div className="col-span-2 flex flex-col gap-y-4 pt-1 md:col-span-1">
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
|
||||
<div className="pt-2.5 text-18 font-medium">Google-provided details for Plane</div>
|
||||
{GOOGLE_FORM_FIELDS.map((field) => (
|
||||
<ControllerInput
|
||||
@@ -219,24 +213,24 @@ export function InstanceGoogleConfigForm(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-y-6 md:col-span-1">
|
||||
<div className="col-span-2 md:col-span-1 flex flex-col gap-y-6">
|
||||
<div className="pt-2 text-18 font-medium">Plane-provided details for Google</div>
|
||||
|
||||
<div className="flex flex-col gap-y-4">
|
||||
{/* common service details */}
|
||||
<div className="flex flex-col gap-y-4 rounded-lg bg-layer-1 px-6 py-4">
|
||||
<div className="flex flex-col gap-y-4 px-6 py-4 bg-layer-1 rounded-lg">
|
||||
{GOOGLE_COMMON_SERVICE_DETAILS.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* web service details */}
|
||||
<div className="flex flex-col overflow-hidden rounded-lg">
|
||||
<div className="flex items-center gap-x-3 bg-layer-3 px-6 py-3 text-11 font-medium text-secondary uppercase">
|
||||
<Monitor className="h-3 w-3" />
|
||||
<div className="flex flex-col rounded-lg overflow-hidden">
|
||||
<div className="px-6 py-3 bg-layer-3 font-medium text-11 uppercase flex items-center gap-x-3 text-secondary">
|
||||
<Monitor className="w-3 h-3" />
|
||||
Web
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4 bg-layer-1 px-6 py-4">
|
||||
<div className="px-6 py-4 flex flex-col gap-y-4 bg-layer-1">
|
||||
{GOOGLE_SERVICE_DETAILS.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||
import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import type { TInstanceConfigurationKeys } from "@plane/types";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { cn, resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
// helpers
|
||||
import { canDisableAuthMethod } from "@/helpers/authentication";
|
||||
// hooks
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { useAuthenticationModes } from "@/hooks/oauth";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// types
|
||||
@@ -27,87 +19,48 @@ import type { Route } from "./+types/page";
|
||||
const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) {
|
||||
// theme
|
||||
const { resolvedTheme: resolvedThemeAdmin } = useTheme();
|
||||
const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin);
|
||||
// Ref to store authentication modes for validation (avoids circular dependency)
|
||||
const authenticationModesRef = useRef<TInstanceAuthenticationModes[]>([]);
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
// store hooks
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// derived values
|
||||
const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? "";
|
||||
const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin);
|
||||
|
||||
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
||||
|
||||
// Create updateConfig with validation - uses authenticationModesRef for current modes
|
||||
const updateConfig = useCallback(
|
||||
(key: TInstanceConfigurationKeys, value: string): void => {
|
||||
// Check if trying to disable (value === "0")
|
||||
if (value === "0") {
|
||||
// Check if this key is an authentication method key
|
||||
const currentAuthModes = authenticationModesRef.current;
|
||||
const isAuthMethodKey = currentAuthModes.some((method) => method.enabledConfigKey === key);
|
||||
const updateConfig = async (key: TInstanceConfigurationKeys, value: string) => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Only validate if this is an authentication method key
|
||||
if (isAuthMethodKey) {
|
||||
const canDisable = canDisableAuthMethod(key, currentAuthModes, formattedConfig);
|
||||
const payload = {
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
if (!canDisable) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Cannot disable authentication",
|
||||
message:
|
||||
"At least one authentication method must remain enabled. Please enable another method before disabling this one.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const updateConfigPromise = updateInstanceConfigurations(payload);
|
||||
|
||||
// Proceed with the update
|
||||
setIsSubmitting(true);
|
||||
setPromiseToast(updateConfigPromise, {
|
||||
loading: "Saving configuration",
|
||||
success: {
|
||||
title: "Success",
|
||||
message: () => "Configuration saved successfully",
|
||||
},
|
||||
error: {
|
||||
title: "Error",
|
||||
message: () => "Failed to save configuration",
|
||||
},
|
||||
});
|
||||
|
||||
const payload = {
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const updateConfigPromise = updateInstanceConfigurations(payload);
|
||||
|
||||
setPromiseToast(updateConfigPromise, {
|
||||
loading: "Saving configuration",
|
||||
success: {
|
||||
title: "Success",
|
||||
message: () => "Configuration saved successfully",
|
||||
},
|
||||
error: {
|
||||
title: "Error",
|
||||
message: () => "Failed to save configuration",
|
||||
},
|
||||
await updateConfigPromise
|
||||
.then(() => {
|
||||
setIsSubmitting(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setIsSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
void updateConfigPromise
|
||||
.then(() => {
|
||||
setIsSubmitting(false);
|
||||
return undefined;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setIsSubmitting(false);
|
||||
});
|
||||
},
|
||||
[formattedConfig, updateInstanceConfigurations]
|
||||
);
|
||||
|
||||
// Get authentication modes - this will use updateConfig which includes validation
|
||||
const authenticationModes = useAuthenticationModes({
|
||||
disabled: isSubmitting,
|
||||
updateConfig,
|
||||
resolvedTheme,
|
||||
});
|
||||
|
||||
// Update ref with latest authentication modes
|
||||
authenticationModesRef.current = authenticationModes;
|
||||
|
||||
const authenticationModes = useAuthenticationModes({ disabled: isSubmitting, updateConfig, resolvedTheme });
|
||||
return (
|
||||
<PageWrapper
|
||||
header={{
|
||||
@@ -117,11 +70,11 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
|
||||
>
|
||||
{formattedConfig ? (
|
||||
<div className="space-y-3">
|
||||
<div className={cn("flex w-full items-center gap-14 rounded-sm")}>
|
||||
<div className={cn("w-full flex items-center gap-14 rounded-sm")}>
|
||||
<div className="flex grow items-center gap-4">
|
||||
<div className="grow">
|
||||
<div className="pb-1 text-16 font-medium">Allow anyone to sign up even without an invite</div>
|
||||
<div className={cn("text-11 leading-5 font-regular text-tertiary")}>
|
||||
<div className="text-16 font-medium pb-1">Allow anyone to sign up even without an invite</div>
|
||||
<div className={cn("font-regular leading-5 text-tertiary text-11")}>
|
||||
Toggling this off will only let users sign up when they are invited.
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +96,7 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-lg pt-6 font-medium">Available authentication modes</div>
|
||||
<div className="text-lg font-medium pt-6">Available authentication modes</div>
|
||||
{authenticationModes.map((method) => (
|
||||
<AuthenticationMethodCard
|
||||
key={method.key}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
@@ -179,7 +173,7 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
|
||||
</CustomSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6 flex flex-col gap-6 border-t border-subtle pt-4">
|
||||
<div className="flex flex-col gap-6 my-6 pt-4 border-t border-subtle">
|
||||
<div className="flex w-full max-w-xl flex-col gap-y-10 px-1">
|
||||
<div className="mr-8 flex items-center gap-10 pt-4">
|
||||
<div className="grow">
|
||||
@@ -207,7 +201,7 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-4xl items-center gap-4 py-1">
|
||||
<div className="flex max-w-4xl items-center py-1 gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState, Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
@@ -91,8 +85,8 @@ export function SendTestEmailModal(props: Props) {
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative w-full transform rounded-lg bg-surface-1 p-5 px-4 text-left shadow-raised-200 transition-all sm:max-w-xl">
|
||||
<h3 className="text-16 leading-6 font-medium text-primary">
|
||||
<Dialog.Panel className="relative transform rounded-lg bg-surface-1 p-5 px-4 text-left shadow-raised-200 transition-all w-full sm:max-w-xl">
|
||||
<h3 className="text-16 font-medium leading-6 text-primary">
|
||||
{sendEmailStep === ESendEmailSteps.SEND_EMAIL
|
||||
? "Send test email"
|
||||
: sendEmailStep === ESendEmailSteps.SUCCESS
|
||||
@@ -121,7 +115,7 @@ export function SendTestEmailModal(props: Props) {
|
||||
</div>
|
||||
)}
|
||||
{sendEmailStep === ESendEmailSteps.FAILED && <div className="text-13">{error}</div>}
|
||||
<div className="mt-5 flex items-center justify-end gap-2">
|
||||
<div className="flex items-center gap-2 justify-end mt-5">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={2}>
|
||||
{sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"}
|
||||
</Button>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Telescope } from "lucide-react";
|
||||
@@ -16,6 +10,8 @@ import { Input, ToggleSwitch } from "@plane/ui";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import { IntercomConfig } from "./intercom";
|
||||
|
||||
export interface IGeneralConfigurationForm {
|
||||
instance: IInstance;
|
||||
@@ -25,13 +21,14 @@ export interface IGeneralConfigurationForm {
|
||||
export const GeneralConfigurationForm = observer(function GeneralConfigurationForm(props: IGeneralConfigurationForm) {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// hooks
|
||||
const { updateInstanceInfo } = useInstance();
|
||||
const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance();
|
||||
|
||||
// form data
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
watch,
|
||||
} = useForm<Partial<IInstance>>({
|
||||
defaultValues: {
|
||||
instance_name: instance?.instance_name,
|
||||
@@ -42,6 +39,17 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
|
||||
const onSubmit = async (formData: Partial<IInstance>) => {
|
||||
const payload: Partial<IInstance> = { ...formData };
|
||||
|
||||
// update the intercom configuration
|
||||
const isIntercomEnabled =
|
||||
instanceConfigurations?.find((config) => config.key === "IS_INTERCOM_ENABLED")?.value === "1";
|
||||
if (!payload.is_telemetry_enabled && isIntercomEnabled) {
|
||||
try {
|
||||
await updateInstanceConfigurations({ IS_INTERCOM_ENABLED: "0" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
await updateInstanceInfo(payload)
|
||||
.then(() =>
|
||||
setToast({
|
||||
@@ -98,17 +106,18 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-subtle pb-1.5 text-16 font-medium text-primary">Telemetry</div>
|
||||
<div className="text-16 font-medium text-primary pb-1.5 border-b border-subtle">Chat + telemetry</div>
|
||||
<IntercomConfig isTelemetryEnabled={watch("is_telemetry_enabled") ?? false} />
|
||||
<div className="flex items-center gap-14">
|
||||
<div className="flex grow items-center gap-4">
|
||||
<div className="grow flex items-center gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="flex size-11 items-center justify-center rounded-lg bg-layer-1">
|
||||
<div className="flex items-center justify-center size-11 bg-layer-1 rounded-lg">
|
||||
<Telescope className="size-5 text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<div className="text-13 leading-5 font-medium text-primary">Let Plane collect anonymous usage data</div>
|
||||
<div className="text-11 leading-5 font-regular text-tertiary">
|
||||
<div className="text-13 font-medium text-primary leading-5">Let Plane collect anonymous usage data</div>
|
||||
<div className="text-11 font-regular text-tertiary leading-5">
|
||||
No PII is collected.This anonymized data is used to understand how you use Plane and build new features
|
||||
in line with{" "}
|
||||
<a
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { MessageSquare } from "lucide-react";
|
||||
import type { IFormattedInstanceConfiguration } from "@plane/types";
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
type TIntercomConfig = {
|
||||
isTelemetryEnabled: boolean;
|
||||
};
|
||||
|
||||
export const IntercomConfig = observer(function IntercomConfig(props: TIntercomConfig) {
|
||||
const { isTelemetryEnabled } = props;
|
||||
// hooks
|
||||
const { instanceConfigurations, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance();
|
||||
// states
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
// derived values
|
||||
const isIntercomEnabled = isTelemetryEnabled
|
||||
? instanceConfigurations
|
||||
? instanceConfigurations?.find((config) => config.key === "IS_INTERCOM_ENABLED")?.value === "1"
|
||||
? true
|
||||
: false
|
||||
: undefined
|
||||
: false;
|
||||
|
||||
const { isLoading } = useSWR(isTelemetryEnabled ? "INSTANCE_CONFIGURATIONS" : null, () =>
|
||||
isTelemetryEnabled ? fetchInstanceConfigurations() : null
|
||||
);
|
||||
|
||||
const initialLoader = isLoading && isIntercomEnabled === undefined;
|
||||
|
||||
const submitInstanceConfigurations = async (payload: Partial<IFormattedInstanceConfiguration>) => {
|
||||
try {
|
||||
await updateInstanceConfigurations(payload);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const enableIntercomConfig = () => {
|
||||
void submitInstanceConfigurations({ IS_INTERCOM_ENABLED: isIntercomEnabled ? "0" : "1" });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-14">
|
||||
<div className="grow flex items-center gap-4">
|
||||
<div className="shrink-0">
|
||||
<div className="flex items-center justify-center size-11 bg-layer-1 rounded-lg">
|
||||
<MessageSquare className="size-5 text-tertiary p-0.5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grow">
|
||||
<div className="text-13 font-medium text-primary leading-5">Chat with us</div>
|
||||
<div className="text-11 font-regular text-tertiary leading-5">
|
||||
Let your users chat with us via Intercom or another service. Toggling Telemetry off turns this off
|
||||
automatically.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto">
|
||||
<ToggleSwitch
|
||||
value={isIntercomEnabled ? true : false}
|
||||
onChange={enableIntercomConfig}
|
||||
size="sm"
|
||||
disabled={!isTelemetryEnabled || isSubmitting || initialLoader}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Loader } from "@plane/ui";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -11,7 +5,7 @@ import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AdminHeader } from "@/components/common/header";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { NewUserPopup } from "@/components/common/new-user-popup";
|
||||
import { NewUserPopup } from "@/components/new-user-popup";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
// local components
|
||||
@@ -42,7 +36,7 @@ function AdminLayout(_props: Route.ComponentProps) {
|
||||
<AdminSidebar />
|
||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-surface-1">
|
||||
<AdminHeader />
|
||||
<div className="vertical-scrollbar scrollbar-md h-full w-full overflow-hidden overflow-y-scroll">
|
||||
<div className="h-full w-full overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
@@ -39,14 +33,14 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
|
||||
const getSidebarMenuItems = () => (
|
||||
<Menu.Items
|
||||
className={cn(
|
||||
"shadow-lg absolute left-0 z-20 mt-1.5 flex w-52 flex-col divide-y divide-subtle rounded-md border border-subtle bg-surface-1 px-1 py-2 text-11 outline-none",
|
||||
"absolute left-0 z-20 mt-1.5 flex w-52 flex-col divide-y divide-subtle rounded-md border border-subtle bg-surface-1 px-1 py-2 text-11 shadow-lg outline-none",
|
||||
{
|
||||
"left-4": isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2.5 pb-2">
|
||||
<span className="truncate px-2 text-secondary">{currentUser?.email}</span>
|
||||
<span className="px-2 text-secondary truncate">{currentUser?.email}</span>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<Menu.Item
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { HelpCircle, MessageSquare, MoveLeft } from "lucide-react";
|
||||
import { HelpCircle, MoveLeft } from "lucide-react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { WEB_BASE_URL } from "@plane/constants";
|
||||
// plane internal packages
|
||||
import { GithubIcon, NewTabIcon, PageIcon } from "@plane/propel/icons";
|
||||
import { DiscordIcon, GithubIcon, NewTabIcon, PageIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -25,9 +19,9 @@ const helpOptions = [
|
||||
Icon: PageIcon,
|
||||
},
|
||||
{
|
||||
name: "Join our Forum",
|
||||
href: "https://forum.plane.so",
|
||||
Icon: MessageSquare,
|
||||
name: "Join our Discord",
|
||||
href: "https://discord.com/invite/A92xrEGCge",
|
||||
Icon: DiscordIcon,
|
||||
},
|
||||
{
|
||||
name: "Report a bug",
|
||||
@@ -50,9 +44,9 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-14 w-full flex-shrink-0 items-center justify-between gap-1 self-baseline border-t border-subtle bg-surface-1 px-4",
|
||||
"flex w-full items-center justify-between gap-1 self-baseline border-t border-subtle bg-surface-1 px-4 h-14 flex-shrink-0",
|
||||
{
|
||||
"h-auto flex-col py-1.5": isSidebarCollapsed,
|
||||
"flex-col h-auto py-1.5": isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
>
|
||||
@@ -60,7 +54,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
<Tooltip tooltipContent="Redirect to Plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}>
|
||||
<a
|
||||
href={redirectionLink}
|
||||
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
|
||||
className={`relative px-2 py-1 flex items-center gap-1 rounded-sm bg-layer-1 text-body-xs-medium text-secondary whitespace-nowrap`}
|
||||
>
|
||||
<NewTabIcon width={14} height={14} />
|
||||
{!isSidebarCollapsed && "Redirect to Plane"}
|
||||
@@ -101,9 +95,9 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<div
|
||||
className={`absolute bottom-2 z-[15] min-w-[10rem] ${
|
||||
className={`absolute bottom-2 min-w-[10rem] z-[15] ${
|
||||
isSidebarCollapsed ? "left-full" : "-left-[75px]"
|
||||
} divide-y divide-subtle-1 rounded-sm bg-surface-1 p-1 whitespace-nowrap shadow-raised-100`}
|
||||
} divide-y divide-subtle-1 whitespace-nowrap rounded-sm bg-surface-1 p-1 shadow-raised-100`}
|
||||
ref={helpOptionsRef}
|
||||
>
|
||||
<div className="space-y-1 pb-2">
|
||||
@@ -134,7 +128,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="px-2 pt-2 pb-1 text-10">Version: v{instance?.current_version}</div>
|
||||
<div className="px-2 pb-1 pt-2 text-10">Version: v{instance?.current_version}</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -29,7 +23,7 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vertical-scrollbar flex scrollbar-sm h-full w-full flex-col gap-2.5 overflow-y-scroll px-4 py-4">
|
||||
<div className="flex h-full w-full flex-col gap-2.5 overflow-y-scroll vertical-scrollbar scrollbar-sm px-4 py-4">
|
||||
{sidebarMenu.map((item, index) => {
|
||||
const isActive = item.href === pathName || pathName?.includes(item.href);
|
||||
return (
|
||||
@@ -38,9 +32,9 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
|
||||
<Tooltip tooltipContent={item.name} position="right" className="ml-2" disabled={!isSidebarCollapsed}>
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
|
||||
"group flex w-full items-center gap-3 rounded-md px-3 py-2 outline-none transition-colors",
|
||||
{
|
||||
"!bg-layer-transparent-active text-primary": isActive,
|
||||
"text-primary !bg-layer-transparent-active": isActive,
|
||||
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
|
||||
},
|
||||
isSidebarCollapsed ? "justify-center" : "w-[260px]"
|
||||
@@ -48,7 +42,7 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
|
||||
>
|
||||
{<item.Icon className="h-4 w-4 flex-shrink-0" />}
|
||||
{!isSidebarCollapsed && (
|
||||
<div className="w-full">
|
||||
<div className="w-full ">
|
||||
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
|
||||
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane helpers
|
||||
@@ -44,7 +38,13 @@ export const AdminSidebar = observer(function AdminSidebar() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-subtle bg-surface-1 duration-300 md:relative ${isSidebarCollapsed ? "-ml-[290px]" : ""} sm:${isSidebarCollapsed ? "-ml-[290px]" : ""} md:ml-0 ${isSidebarCollapsed ? "w-[70px]" : "w-[290px]"} lg:ml-0 ${isSidebarCollapsed ? "w-[70px]" : "w-[290px]"} `}
|
||||
className={`inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-subtle bg-surface-1 duration-300
|
||||
fixed md:relative
|
||||
${isSidebarCollapsed ? "-ml-[290px]" : ""}
|
||||
sm:${isSidebarCollapsed ? "-ml-[290px]" : ""}
|
||||
md:ml-0 ${isSidebarCollapsed ? "w-[70px]" : "w-[290px]"}
|
||||
lg:ml-0 ${isSidebarCollapsed ? "w-[70px]" : "w-[290px]"}
|
||||
`}
|
||||
>
|
||||
<div ref={ref} className="flex h-full w-full flex-1 flex-col">
|
||||
<AdminSidebarDropdown />
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -14,7 +8,6 @@ import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { InstanceWorkspaceService } from "@plane/services";
|
||||
import type { IWorkspace } from "@plane/types";
|
||||
import { validateSlug, validateWorkspaceName } from "@plane/utils";
|
||||
// components
|
||||
import { CustomSelect, Input } from "@plane/ui";
|
||||
// hooks
|
||||
@@ -97,7 +90,14 @@ export function WorkspaceCreateForm() {
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
validate: (value) => validateWorkspaceName(value, true),
|
||||
required: "This is a required field.",
|
||||
validate: (value) =>
|
||||
/^[\w\s-]*$/.test(value) ||
|
||||
`Workspaces names can contain only (" "), ( - ), ( _ ) and alphanumeric characters.`,
|
||||
maxLength: {
|
||||
value: 80,
|
||||
message: "Limit your name to 80 characters.",
|
||||
},
|
||||
}}
|
||||
render={({ field: { value, ref, onChange } }) => (
|
||||
<Input
|
||||
@@ -123,13 +123,17 @@ export function WorkspaceCreateForm() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 text-tertiary">Set your workspace's URL</h4>
|
||||
<div className="flex w-full items-center gap-0.5 rounded-md border-[0.5px] border-subtle px-3">
|
||||
<span className="text-13 whitespace-nowrap text-secondary">{workspaceBaseURL}</span>
|
||||
<div className="flex gap-0.5 w-full items-center rounded-md border-[0.5px] border-subtle px-3">
|
||||
<span className="whitespace-nowrap text-13 text-secondary">{workspaceBaseURL}</span>
|
||||
<Controller
|
||||
control={control}
|
||||
name="slug"
|
||||
rules={{
|
||||
validate: (value) => validateSlug(value),
|
||||
required: "The URL is a required field.",
|
||||
maxLength: {
|
||||
value: 48,
|
||||
message: "Limit your URL to 48 characters.",
|
||||
},
|
||||
}}
|
||||
render={({ field: { onChange, value, ref } }) => (
|
||||
<Input
|
||||
@@ -188,7 +192,7 @@ export function WorkspaceCreateForm() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-4xl items-center gap-4 py-1">
|
||||
<div className="flex max-w-4xl items-center py-1 gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { PageWrapper } from "@/components/common/page-wrapper";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
@@ -83,11 +77,11 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{formattedConfig ? (
|
||||
<div className={cn("flex w-full items-center gap-14 rounded-sm")}>
|
||||
<div className={cn("w-full flex items-center gap-14 rounded-sm")}>
|
||||
<div className="flex grow items-center gap-4">
|
||||
<div className="grow">
|
||||
<div className="pb-1 text-16 font-medium">Prevent anyone else from creating a workspace.</div>
|
||||
<div className={cn("text-11 leading-5 font-regular text-tertiary")}>
|
||||
<div className="text-16 font-medium pb-1">Prevent anyone else from creating a workspace.</div>
|
||||
<div className={cn("font-regular leading-5 text-tertiary text-11")}>
|
||||
Toggling this on will let only you create workspaces. You will have to invite users to new workspaces.
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,15 +110,15 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
)}
|
||||
{workspaceLoader !== "init-loader" ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-2 pt-6">
|
||||
<div className="pt-6 flex items-center justify-between gap-2">
|
||||
<div className="flex flex-col items-start gap-x-2">
|
||||
<div className="flex items-center gap-2 text-16 font-medium">
|
||||
All workspaces on this instance <span className="text-tertiary">• {workspaceIds.length}</span>
|
||||
{workspaceLoader && ["mutation", "pagination"].includes(workspaceLoader) && (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||
<LoaderIcon className="w-4 h-4 animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
<div className={cn("text-11 leading-5 font-regular text-tertiary")}>
|
||||
<div className={cn("font-regular leading-5 text-tertiary text-11")}>
|
||||
You can't yet delete workspaces and you can only go to the workspace if you are an Admin or a
|
||||
Member.
|
||||
</div>
|
||||
@@ -149,7 +143,7 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
|
||||
disabled={workspaceLoader === "pagination"}
|
||||
>
|
||||
Load more
|
||||
{workspaceLoader === "pagination" && <LoaderIcon className="h-3 w-3 animate-spin" />}
|
||||
{workspaceLoader === "pagination" && <LoaderIcon className="w-3 h-3 animate-spin" />}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Info } from "lucide-react";
|
||||
// plane constants
|
||||
import type { TAdminAuthErrorInfo } from "@plane/constants";
|
||||
@@ -20,16 +14,16 @@ export function AuthBanner(props: TAuthBanner) {
|
||||
|
||||
if (!bannerData) return <></>;
|
||||
return (
|
||||
<div className="relative flex items-center gap-2 rounded-md border border-accent-strong/50 bg-accent-primary/10 p-2">
|
||||
<div className="relative flex h-4 w-4 flex-shrink-0 items-center justify-center">
|
||||
<div className="relative flex items-center p-2 rounded-md gap-2 border border-accent-strong/50 bg-accent-primary/10">
|
||||
<div className="w-4 h-4 flex-shrink-0 relative flex justify-center items-center">
|
||||
<Info size={16} className="text-accent-primary" />
|
||||
</div>
|
||||
<div className="w-full text-13 font-medium text-accent-primary">{bannerData?.message}</div>
|
||||
<div
|
||||
className="relative ml-auto flex h-6 w-6 cursor-pointer items-center justify-center rounded-xs text-accent-primary transition-all hover:bg-accent-primary/20"
|
||||
className="relative ml-auto w-6 h-6 rounded-xs flex justify-center items-center transition-all cursor-pointer hover:bg-accent-primary/20 text-accent-primary"
|
||||
onClick={() => handleBannerData && handleBannerData(undefined)}
|
||||
>
|
||||
<CloseIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<CloseIcon className="w-4 h-4 flex-shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
|
||||
export function AuthHeader() {
|
||||
return (
|
||||
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6">
|
||||
<div className="flex items-center justify-between gap-6 w-full flex-shrink-0 sticky top-0">
|
||||
<Link href="/">
|
||||
<PlaneLockup height={20} width={95} className="text-primary" />
|
||||
</Link>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
// plane packages
|
||||
import type { TAdminAuthErrorInfo } from "@plane/constants";
|
||||
import { SUPPORT_EMAIL, EAdminAuthErrorCodes } from "@plane/constants";
|
||||
import type { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
||||
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
||||
import { GithubConfiguration } from "@/components/authentication/github-config";
|
||||
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
|
||||
import { GoogleConfiguration } from "@/components/authentication/google-config";
|
||||
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
|
||||
// images
|
||||
|
||||
export enum EErrorAlertType {
|
||||
BANNER_ALERT = "BANNER_ALERT",
|
||||
@@ -50,7 +58,7 @@ const errorCodeMessages: {
|
||||
message: () => (
|
||||
<div>
|
||||
Admin user already exists.
|
||||
<Link className="font-medium underline underline-offset-4 transition-all hover:font-bold" href={`/admin`}>
|
||||
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||
Sign In
|
||||
</Link>
|
||||
now.
|
||||
@@ -62,7 +70,7 @@ const errorCodeMessages: {
|
||||
message: () => (
|
||||
<div>
|
||||
Admin user does not exist.
|
||||
<Link className="font-medium underline underline-offset-4 transition-all hover:font-bold" href={`/admin`}>
|
||||
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||
Sign In
|
||||
</Link>
|
||||
now.
|
||||
@@ -98,3 +106,53 @@ export const authErrorHandler = (errorCode: EAdminAuthErrorCodes, email?: string
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
|
||||
disabled,
|
||||
updateConfig,
|
||||
resolvedTheme,
|
||||
}) => [
|
||||
{
|
||||
key: "unique-codes",
|
||||
name: "Unique codes",
|
||||
description:
|
||||
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
||||
icon: <Mails className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "passwords-login",
|
||||
name: "Passwords",
|
||||
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
||||
icon: <KeyRound className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "google",
|
||||
name: "Google",
|
||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||
icon: <img src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "github",
|
||||
name: "GitHub",
|
||||
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
|
||||
icon: (
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
||||
height={20}
|
||||
width={20}
|
||||
alt="GitHub Logo"
|
||||
/>
|
||||
),
|
||||
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "gitlab",
|
||||
name: "GitLab",
|
||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||
icon: <img src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -22,7 +16,7 @@ function RootLayout() {
|
||||
}, [replace, isUserLoggedIn]);
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-screen w-screen flex-col items-center overflow-hidden overflow-y-auto bg-surface-1 px-8 pt-6 pb-10">
|
||||
<div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8 bg-surface-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
@@ -22,7 +16,7 @@ function HomePage() {
|
||||
// if instance is not fetched, show loading
|
||||
if (!instance && !error) {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
<div className="flex items-center justify-center h-screen w-full">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
@@ -16,7 +10,7 @@ import { Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { Banner } from "@/components/common/banner";
|
||||
// local components
|
||||
import { FormHeader } from "@/components/instance/form-header";
|
||||
import { FormHeader } from "../../../core/components/instance/form-header";
|
||||
import { AuthBanner } from "./auth-banner";
|
||||
import { AuthHeader } from "./auth-header";
|
||||
import { authErrorHandler } from "./auth-helpers";
|
||||
@@ -111,8 +105,8 @@ export function InstanceSignInForm() {
|
||||
return (
|
||||
<>
|
||||
<AuthHeader />
|
||||
<div className="mt-10 flex w-full flex-grow flex-col items-center justify-center py-6">
|
||||
<div className="relative flex w-full max-w-[22.5rem] flex-col gap-6">
|
||||
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
|
||||
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">
|
||||
<FormHeader
|
||||
heading="Manage your Plane instance"
|
||||
subHeading="Configure instance-wide settings to secure your instance"
|
||||
@@ -134,7 +128,7 @@ export function InstanceSignInForm() {
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="email">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="email">
|
||||
Email <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -146,13 +140,13 @@ export function InstanceSignInForm() {
|
||||
placeholder="name@company.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
autoComplete="off"
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="password">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="password">
|
||||
Password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -165,12 +159,12 @@ export function InstanceSignInForm() {
|
||||
placeholder="Enter your password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||
autoComplete="off"
|
||||
autoComplete="on"
|
||||
/>
|
||||
{showPassword ? (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -178,7 +172,7 @@ export function InstanceSignInForm() {
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ensures that a URL has a trailing slash while preserving query parameters and fragments
|
||||
* @param url - The URL to process
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
// Minimal shim so code using next/image compiles under React Router + Vite
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Link as RRLink } from "react-router";
|
||||
import { ensureTrailingSlash } from "./helper";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useLocation, useNavigate, useSearchParams as useSearchParamsRR } from "react-router";
|
||||
import { ensureTrailingSlash } from "./helper";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
// ui
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
import { startTransition, StrictMode } from "react";
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
import { HydratedRouter } from "react-router/dom";
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.VITE_SENTRY_DSN,
|
||||
environment: process.env.VITE_SENTRY_ENVIRONMENT,
|
||||
sendDefaultPii: process.env.VITE_SENTRY_SEND_DEFAULT_PII ? process.env.VITE_SENTRY_SEND_DEFAULT_PII === "1" : false,
|
||||
release: process.env.VITE_APP_VERSION,
|
||||
tracesSampleRate: process.env.VITE_SENTRY_TRACES_SAMPLE_RATE
|
||||
? parseFloat(process.env.VITE_SENTRY_TRACES_SAMPLE_RATE)
|
||||
: 0.1,
|
||||
profilesSampleRate: process.env.VITE_SENTRY_PROFILES_SAMPLE_RATE
|
||||
? parseFloat(process.env.VITE_SENTRY_PROFILES_SAMPLE_RATE)
|
||||
: 0.1,
|
||||
replaysSessionSampleRate: process.env.VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE
|
||||
? parseFloat(process.env.VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE)
|
||||
: 0.1,
|
||||
replaysOnErrorSampleRate: process.env.VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE
|
||||
? parseFloat(process.env.VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE)
|
||||
: 1.0,
|
||||
integrations: [],
|
||||
});
|
||||
|
||||
startTransition(() => {
|
||||
hydrateRoot(
|
||||
document,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { Links, Meta, Outlet, Scripts } from "react-router";
|
||||
import type { LinksFunction } from "react-router";
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
|
||||
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
|
||||
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
|
||||
@@ -74,7 +69,7 @@ export const meta: Route.MetaFunction = () => [
|
||||
|
||||
export default function Root() {
|
||||
return (
|
||||
<div className="min-h-screen bg-canvas">
|
||||
<div className="bg-canvas min-h-screen">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
@@ -88,7 +83,11 @@ export function HydrateFallback() {
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error: _error }: Route.ErrorBoundaryProps) {
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
if (error) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong.</p>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { index, layout, route } from "@react-router/dev/routes";
|
||||
import type { RouteConfig } from "@react-router/dev/routes";
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { enableStaticRendering } from "mobx-react";
|
||||
// stores
|
||||
import { CoreRootStore } from "@/store/root.store";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
export class RootStore extends CoreRootStore {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
hydrate(initialData: any) {
|
||||
super.hydrate(initialData);
|
||||
}
|
||||
|
||||
resetOnSignOut() {
|
||||
super.resetOnSignOut();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export const EXTENDED_HEADER_SEGMENT_LABELS: Record<string, string> = {};
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export function FormHeader({ heading, subHeading }: { heading: string; subHeading: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-20 leading-7 font-semibold text-primary">{heading}</span>
|
||||
<span className="text-16 leading-7 font-semibold text-placeholder">{subHeading}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+4
-10
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
@@ -22,8 +16,8 @@ export function AuthenticationMethodCard(props: Props) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("flex w-full items-center gap-14 rounded-lg bg-layer-2", {
|
||||
"border border-subtle px-4 py-3": withBorder,
|
||||
className={cn("w-full flex items-center gap-14 rounded-lg bg-layer-2", {
|
||||
"px-4 py-3 border border-subtle": withBorder,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -36,7 +30,7 @@ export function AuthenticationMethodCard(props: Props) {
|
||||
</div>
|
||||
<div className="grow">
|
||||
<div
|
||||
className={cn("leading-5 font-medium text-primary", {
|
||||
className={cn("font-medium leading-5 text-primary", {
|
||||
"text-13": withBorder,
|
||||
"text-18": !withBorder,
|
||||
})}
|
||||
@@ -44,7 +38,7 @@ export function AuthenticationMethodCard(props: Props) {
|
||||
{name}
|
||||
</div>
|
||||
<div
|
||||
className={cn("leading-5 font-regular text-tertiary", {
|
||||
className={cn("font-regular leading-5 text-tertiary", {
|
||||
"text-11": withBorder,
|
||||
"text-13": !withBorder,
|
||||
})}
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
|
||||
type TBanner = {
|
||||
@@ -16,12 +10,12 @@ export function Banner(props: TBanner) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full rounded-md border p-2 ${type === "error" ? "border-danger-strong bg-danger-subtle" : "border-success-strong bg-success-subtle"}`}
|
||||
className={`rounded-md p-2 w-full border ${type === "error" ? "bg-danger-subtle border-danger-strong" : "bg-success-subtle border-success-strong"}`}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex-shrink-0">
|
||||
{type === "error" ? (
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-full">
|
||||
<span className="flex items-center justify-center h-6 w-6 rounded-full">
|
||||
<AlertCircle className="h-5 w-5 text-danger-primary" aria-hidden="true" />
|
||||
</span>
|
||||
) : (
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
|
||||
@@ -22,12 +16,12 @@ export function BreadcrumbLink(props: Props) {
|
||||
{href ? (
|
||||
<Link className="flex items-center gap-1 text-13 font-medium text-tertiary hover:text-primary" href={href}>
|
||||
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden !text-16">{icon}</div>}
|
||||
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
|
||||
<div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="flex cursor-default items-center gap-1 text-13 font-medium text-primary">
|
||||
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden">{icon}</div>}
|
||||
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
|
||||
<div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type TProps = {
|
||||
@@ -16,9 +10,9 @@ export function CodeBlock({ children, className, darkerShade }: TProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-md border border-subtle bg-surface-2 px-0.5 text-11 font-semibold text-tertiary",
|
||||
"px-0.5 text-11 text-tertiary bg-surface-2 font-semibold rounded-md border border-subtle",
|
||||
{
|
||||
"border-subtle bg-layer-1 text-secondary": darkerShade,
|
||||
"text-secondary bg-layer-1 border-subtle": darkerShade,
|
||||
},
|
||||
className
|
||||
)}
|
||||
+3
-9
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
// headless ui
|
||||
@@ -46,10 +40,10 @@ export function ConfirmDiscardModal(props: Props) {
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-surface-1 text-left shadow-raised-200 transition-all sm:my-8 sm:w-[30rem]">
|
||||
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<Dialog.Title as="h3" className="text-16 leading-6 font-medium text-tertiary">
|
||||
<Dialog.Title as="h3" className="text-16 font-medium leading-6 text-tertiary">
|
||||
You have unsaved changes
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
@@ -60,7 +54,7 @@ export function ConfirmDiscardModal(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 p-4 sm:px-6">
|
||||
<div className="flex justify-end items-center p-4 sm:px-6 gap-2">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose}>
|
||||
Keep editing
|
||||
</Button>
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import type { Control } from "react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
@@ -67,7 +61,7 @@ export function ControllerInput(props: Props) {
|
||||
(showPassword ? (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(false)}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -75,7 +69,7 @@ export function ControllerInput(props: Props) {
|
||||
) : (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-2.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => setShowPassword(true)}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
// plane internal packages
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
+1
-7
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@plane/propel/button";
|
||||
|
||||
@@ -25,7 +19,7 @@ export function EmptyState({ title, description, image, primaryButton, secondary
|
||||
<div className={`flex h-full w-full items-center justify-center`}>
|
||||
<div className="flex w-full flex-col items-center text-center">
|
||||
{image && <img src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />}
|
||||
<h6 className="mt-6 mb-3 text-18 font-semibold sm:mt-8">{title}</h6>
|
||||
<h6 className="mb-3 mt-6 text-18 font-semibold sm:mt-8">{title}</h6>
|
||||
{description && <p className="mb-7 px-5 text-tertiary sm:mb-8">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
{primaryButton && (
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export const CORE_HEADER_SEGMENT_LABELS: Record<string, string> = {
|
||||
general: "General",
|
||||
ai: "Artificial Intelligence",
|
||||
@@ -0,0 +1 @@
|
||||
export const EXTENDED_HEADER_SEGMENT_LABELS: Record<string, string> = {};
|
||||
+3
-9
@@ -1,16 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, Settings } from "lucide-react";
|
||||
// icons
|
||||
import { Breadcrumbs } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "../breadcrumb-link";
|
||||
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||
// hooks
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// local imports
|
||||
@@ -21,10 +15,10 @@ export const HamburgerToggle = observer(function HamburgerToggle() {
|
||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||
return (
|
||||
<button
|
||||
className="group flex size-7 cursor-pointer items-center justify-center rounded-sm bg-layer-1 transition-all hover:bg-layer-1-hover md:hidden"
|
||||
className="size-7 rounded-sm flex justify-center items-center bg-layer-1 transition-all hover:bg-layer-1-hover cursor-pointer group md:hidden"
|
||||
onClick={() => toggleSidebar(!isSidebarCollapsed)}
|
||||
>
|
||||
<Menu size={14} className="text-secondary transition-all group-hover:text-primary" />
|
||||
<Menu size={14} className="text-secondary group-hover:text-primary transition-all" />
|
||||
</button>
|
||||
);
|
||||
});
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
|
||||
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
type TPageHeader = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
+7
-13
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
@@ -24,25 +18,25 @@ export const PageWrapper = (props: TPageWrapperProps) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("mx-auto h-full w-full space-y-6 py-4", {
|
||||
"max-w-[1000px] md:px-4 2xl:max-w-[1200px]": size === "md",
|
||||
className={cn("mx-auto w-full h-full space-y-6 py-4", {
|
||||
"md:px-4 max-w-[1000px] 2xl:max-w-[1200px]": size === "md",
|
||||
"px-4 lg:px-12": size === "lg",
|
||||
})}
|
||||
>
|
||||
{customHeader ? (
|
||||
<div className="mx-4 shrink-0 space-y-1 border-b border-subtle py-4">{customHeader}</div>
|
||||
<div className="border-b border-subtle mx-4 py-4 space-y-1 shrink-0">{customHeader}</div>
|
||||
) : (
|
||||
header && (
|
||||
<div className="mx-4 flex shrink-0 items-center justify-between gap-4 space-y-1 border-b border-subtle py-4">
|
||||
<div className="flex items-center justify-between gap-4 border-b border-subtle mx-4 py-4 space-y-1 shrink-0">
|
||||
<div className={header.actions ? "flex flex-col gap-1" : "space-y-1"}>
|
||||
<div className="text-h5-semibold text-primary">{header.title}</div>
|
||||
<div className="text-body-sm-regular text-secondary">{header.description}</div>
|
||||
<div className="text-primary text-h5-semibold">{header.title}</div>
|
||||
<div className="text-secondary text-body-sm-regular">{header.description}</div>
|
||||
</div>
|
||||
{header.actions && <div className="shrink-0">{header.actions}</div>}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className="vertical-scrollbar scrollbar-sm flex-grow overflow-hidden overflow-y-scroll px-4 pb-4">
|
||||
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-sm px-4 pb-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
+5
-11
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/propel/button";
|
||||
@@ -24,12 +18,12 @@ export const InstanceFailureView = observer(function InstanceFailureView() {
|
||||
return (
|
||||
<>
|
||||
<AuthHeader />
|
||||
<div className="mt-10 flex w-full flex-grow flex-col items-center justify-center py-6">
|
||||
<div className="relative flex w-full max-w-[22.5rem] flex-col gap-6">
|
||||
<div className="relative flex flex-col items-center justify-center space-y-4">
|
||||
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
|
||||
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">
|
||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||
<img src={instanceImage} alt="Instance failure illustration" />
|
||||
<h3 className="text-center text-20 font-medium text-on-color">Unable to fetch instance details.</h3>
|
||||
<p className="text-center text-14 font-medium">
|
||||
<h3 className="font-medium text-20 text-on-color text-center">Unable to fetch instance details.</h3>
|
||||
<p className="font-medium text-14 text-center">
|
||||
We were unable to fetch the details of the instance. Fret not, it might just be a connectivity issue.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
export function FormHeader({ heading, subHeading }: { heading: string; subHeading: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-20 font-semibold text-primary leading-7">{heading}</span>
|
||||
<span className="text-16 font-semibold text-placeholder leading-7">{subHeading}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+5
-11
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
@@ -11,12 +5,12 @@ import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
|
||||
|
||||
export function InstanceNotReady() {
|
||||
return (
|
||||
<div className="relative container mx-auto flex h-full w-full items-center justify-center px-5">
|
||||
<div className="relative w-auto max-w-2xl space-y-8 py-10">
|
||||
<div className="relative flex flex-col items-center justify-center space-y-4">
|
||||
<h1 className="pb-3 text-24 font-bold">Welcome aboard Plane!</h1>
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
|
||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||
<h1 className="text-24 font-bold pb-3">Welcome aboard Plane!</h1>
|
||||
<img src={PlaneTakeOffImage} alt="Plane Logo" />
|
||||
<p className="text-14 font-medium text-placeholder">Get started by setting up your instance and workspace</p>
|
||||
<p className="font-medium text-14 text-placeholder">Get started by setting up your instance and workspace</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
// assets
|
||||
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
|
||||
+26
-51
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// icons
|
||||
@@ -13,11 +7,11 @@ import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui";
|
||||
import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils";
|
||||
import { getPasswordStrength } from "@plane/utils";
|
||||
// components
|
||||
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
|
||||
import { Banner } from "../common/banner";
|
||||
import { FormHeader } from "./form-header";
|
||||
import { Banner } from "@/components/common/banner";
|
||||
import { FormHeader } from "@/components/instance/form-header";
|
||||
|
||||
// service initialization
|
||||
const authService = new AuthService();
|
||||
@@ -139,8 +133,8 @@ export function InstanceSetupForm() {
|
||||
return (
|
||||
<>
|
||||
<AuthHeader />
|
||||
<div className="mt-10 flex w-full flex-grow flex-col items-center justify-center py-6">
|
||||
<div className="relative flex w-full max-w-[22.5rem] flex-col gap-6">
|
||||
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
|
||||
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">
|
||||
<FormHeader
|
||||
heading="Setup your Plane Instance"
|
||||
subHeading="Post setup you will be able to manage this Plane instance."
|
||||
@@ -160,9 +154,9 @@ export function InstanceSetupForm() {
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
<input type="hidden" name="is_telemetry_enabled" value={formData.is_telemetry_enabled ? "True" : "False"} />
|
||||
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="first_name">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="first_name">
|
||||
First name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -173,19 +167,13 @@ export function InstanceSetupForm() {
|
||||
inputSize="md"
|
||||
placeholder="Wilber"
|
||||
value={formData.first_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("first_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
onChange={(e) => handleFormChange("first_name", e.target.value)}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
maxLength={50}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="last_name">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="last_name">
|
||||
Last name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -196,20 +184,14 @@ export function InstanceSetupForm() {
|
||||
inputSize="md"
|
||||
placeholder="Wright"
|
||||
value={formData.last_name}
|
||||
onChange={(e) => {
|
||||
const validation = validatePersonName(e.target.value);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("last_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
maxLength={50}
|
||||
onChange={(e) => handleFormChange("last_name", e.target.value)}
|
||||
autoComplete="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="email">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="email">
|
||||
Email <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -222,7 +204,7 @@ export function InstanceSetupForm() {
|
||||
value={formData.email}
|
||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false}
|
||||
autoComplete="off"
|
||||
autoComplete="on"
|
||||
/>
|
||||
{errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && (
|
||||
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
|
||||
@@ -230,7 +212,7 @@ export function InstanceSetupForm() {
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="company_name">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="company_name">
|
||||
Company name <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -241,18 +223,12 @@ export function InstanceSetupForm() {
|
||||
inputSize="md"
|
||||
placeholder="Company name"
|
||||
value={formData.company_name}
|
||||
onChange={(e) => {
|
||||
const validation = validateCompanyName(e.target.value, false);
|
||||
if (validation === true || e.target.value === "") {
|
||||
handleFormChange("company_name", e.target.value);
|
||||
}
|
||||
}}
|
||||
maxLength={80}
|
||||
onChange={(e) => handleFormChange("company_name", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="password">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="password">
|
||||
Set a password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -268,13 +244,13 @@ export function InstanceSetupForm() {
|
||||
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
autoComplete="new-password"
|
||||
autoComplete="on"
|
||||
/>
|
||||
{showPassword.password ? (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -283,7 +259,7 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
@@ -297,7 +273,7 @@ export function InstanceSetupForm() {
|
||||
</div>
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-13 font-medium text-tertiary" htmlFor="confirm_password">
|
||||
<label className="text-13 text-tertiary font-medium" htmlFor="confirm_password">
|
||||
Confirm password <span className="text-danger-primary">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -312,13 +288,12 @@ export function InstanceSetupForm() {
|
||||
className="w-full border border-subtle !bg-surface-1 pr-12 placeholder:text-placeholder"
|
||||
onFocus={() => setIsRetryPasswordInputFocused(true)}
|
||||
onBlur={() => setIsRetryPasswordInputFocused(false)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
{showPassword.retypePassword ? (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
@@ -327,7 +302,7 @@ export function InstanceSetupForm() {
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
|
||||
className="absolute right-3 top-3.5 flex items-center justify-center text-placeholder"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
@@ -344,21 +319,21 @@ export function InstanceSetupForm() {
|
||||
<div className="relative flex gap-2">
|
||||
<div>
|
||||
<Checkbox
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
iconClassName="w-3 h-3"
|
||||
id="is_telemetry_enabled"
|
||||
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
|
||||
checked={formData.is_telemetry_enabled}
|
||||
/>
|
||||
</div>
|
||||
<label className="cursor-pointer text-13 font-medium text-tertiary" htmlFor="is_telemetry_enabled">
|
||||
<label className="text-13 text-tertiary font-medium cursor-pointer" htmlFor="is_telemetry_enabled">
|
||||
Allow Plane to anonymously collect usage events.{" "}
|
||||
<a
|
||||
tabIndex={-1}
|
||||
href="https://developers.plane.so/self-hosting/telemetry"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:text-blue-600 flex-shrink-0 text-13 font-medium"
|
||||
className="text-13 font-medium text-blue-500 hover:text-blue-600 flex-shrink-0"
|
||||
>
|
||||
See More
|
||||
</a>
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
@@ -24,7 +18,7 @@ export const NewUserPopup = observer(function NewUserPopup() {
|
||||
|
||||
if (!isNewUserPopup) return <></>;
|
||||
return (
|
||||
<div className="shadow-md absolute right-8 bottom-8 w-96 rounded-lg border border-subtle bg-surface-1 p-6">
|
||||
<div className="absolute bottom-8 right-8 p-6 w-96 border border-subtle shadow-md rounded-lg bg-surface-1">
|
||||
<div className="flex gap-4">
|
||||
<div className="grow">
|
||||
<div className="text-14 font-semibold">Create workspace</div>
|
||||
@@ -41,7 +35,7 @@ export const NewUserPopup = observer(function NewUserPopup() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center justify-center">
|
||||
<div className="shrink-0 flex items-center justify-center">
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? TakeoffIconDark : TakeoffIconLight}
|
||||
height={80}
|
||||
+7
-13
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
// plane internal packages
|
||||
@@ -30,19 +24,19 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
key={workspaceId}
|
||||
href={`${WEB_BASE_URL}/${encodeURIComponent(workspace.slug)}`}
|
||||
target="_blank"
|
||||
className="group flex items-center justify-between gap-2.5 truncate rounded-lg border border-subtle bg-layer-1 p-3 hover:border-subtle-1 hover:bg-layer-1-hover hover:shadow-raised-100"
|
||||
className="group flex items-center justify-between p-3 gap-2.5 truncate border border-subtle hover:border-subtle-1 bg-layer-1 hover:bg-layer-1-hover hover:shadow-raised-100 rounded-lg"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<span
|
||||
className={`relative mt-1 flex h-8 w-8 flex-shrink-0 items-center justify-center p-2 text-11 uppercase ${
|
||||
className={`relative flex h-8 w-8 flex-shrink-0 items-center justify-center p-2 mt-1 text-11 uppercase ${
|
||||
!workspace?.logo_url && "rounded-lg bg-accent-primary text-on-color"
|
||||
}`}
|
||||
>
|
||||
{workspace?.logo_url && workspace.logo_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(workspace.logo_url)}
|
||||
className="absolute top-0 left-0 h-full w-full rounded-sm object-cover"
|
||||
className="absolute left-0 top-0 h-full w-full rounded-sm object-cover"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
) : (
|
||||
@@ -50,7 +44,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
)}
|
||||
</span>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div className="flex w-full flex-wrap items-center gap-2.5">
|
||||
<div className="flex flex-wrap w-full items-center gap-2.5">
|
||||
<h3 className={`text-14 font-medium capitalize`}>{workspace.name}</h3>/
|
||||
<Tooltip tooltipContent="The unique URL of your workspace">
|
||||
<h4 className="text-13 text-tertiary">[{workspace.slug}]</h4>
|
||||
@@ -58,14 +52,14 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
</div>
|
||||
{workspace.owner.email && (
|
||||
<div className="flex items-center gap-1 text-11">
|
||||
<h3 className="font-medium text-secondary">Owned by:</h3>
|
||||
<h3 className="text-secondary font-medium">Owned by:</h3>
|
||||
<h4 className="text-tertiary">{workspace.owner.email}</h4>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2.5 text-11">
|
||||
{workspace.total_projects !== null && (
|
||||
<span className="flex items-center gap-1">
|
||||
<h3 className="font-medium text-secondary">Total projects:</h3>
|
||||
<h3 className="text-secondary font-medium">Total projects:</h3>
|
||||
<h4 className="text-tertiary">{workspace.total_projects}</h4>
|
||||
</span>
|
||||
)}
|
||||
@@ -73,7 +67,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
|
||||
<>
|
||||
•
|
||||
<span className="flex items-center gap-1">
|
||||
<h3 className="font-medium text-secondary">Total members:</h3>
|
||||
<h3 className="text-secondary font-medium">Total members:</h3>
|
||||
<h4 className="text-tertiary">{workspace.total_members}</h4>
|
||||
</span>
|
||||
</>
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
// types
|
||||
import type {
|
||||
@@ -40,7 +34,6 @@ export const getCoreAuthenticationModesMap: (
|
||||
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
|
||||
icon: <Mails className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "ENABLE_MAGIC_LINK_LOGIN",
|
||||
},
|
||||
"passwords-login": {
|
||||
key: "passwords-login",
|
||||
@@ -48,7 +41,6 @@ export const getCoreAuthenticationModesMap: (
|
||||
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
|
||||
icon: <KeyRound className="h-6 w-6 p-0.5 text-tertiary" />,
|
||||
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "ENABLE_EMAIL_PASSWORD",
|
||||
},
|
||||
google: {
|
||||
key: "google",
|
||||
@@ -56,7 +48,6 @@ export const getCoreAuthenticationModesMap: (
|
||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||
icon: <img src={googleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "IS_GOOGLE_ENABLED",
|
||||
},
|
||||
github: {
|
||||
key: "github",
|
||||
@@ -71,7 +62,6 @@ export const getCoreAuthenticationModesMap: (
|
||||
/>
|
||||
),
|
||||
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "IS_GITHUB_ENABLED",
|
||||
},
|
||||
gitlab: {
|
||||
key: "gitlab",
|
||||
@@ -79,7 +69,6 @@ export const getCoreAuthenticationModesMap: (
|
||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||
icon: <img src={gitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "IS_GITLAB_ENABLED",
|
||||
},
|
||||
gitea: {
|
||||
key: "gitea",
|
||||
@@ -87,6 +76,5 @@ export const getCoreAuthenticationModesMap: (
|
||||
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
|
||||
icon: <img src={giteaLogo} height={20} width={20} alt="Gitea Logo" />,
|
||||
config: <GiteaConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
enabledConfigKey: "IS_GITEA_ENABLED",
|
||||
},
|
||||
});
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { getCoreAuthenticationModesMap } from "./core";
|
||||
import type { TGetAuthenticationModeProps } from "./types";
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
|
||||
export type TGetAuthenticationModeProps = {
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./use-theme";
|
||||
export * from "./use-instance";
|
||||
export * from "./use-user";
|
||||
export * from "./use-workspace";
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/providers/store.provider";
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/providers/store.provider";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user