Compare commits
146
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0046d54d25 | ||
|
|
69b64680d1 | ||
|
|
2f45bfb7f6 | ||
|
|
4b59998e52 | ||
|
|
f70384bff7 | ||
|
|
7caa1bb482 | ||
|
|
af939fca41 | ||
|
|
a3e651854a | ||
|
|
2240ac0e74 | ||
|
|
11e7bd115b | ||
|
|
0ab94ed6d6 | ||
|
|
a9e9cb2983 | ||
|
|
f0bc2bd3bd | ||
|
|
7659997b53 | ||
|
|
316856a555 | ||
|
|
f41e121e58 | ||
|
|
85daa1572c | ||
|
|
b359849606 | ||
|
|
5ea74e1112 | ||
|
|
d9ea119588 | ||
|
|
d34c7f9a0e | ||
|
|
f81e120328 | ||
|
|
85d90030cf | ||
|
|
82c970ac4b | ||
|
|
392c8cf2e1 | ||
|
|
c3906048ea | ||
|
|
1090b3e938 | ||
|
|
fe867135c4 | ||
|
|
65166d19be | ||
|
|
3bc85c8430 | ||
|
|
cf84ade52c | ||
|
|
9699a01fb8 | ||
|
|
bc96d34cb7 | ||
|
|
739b1bfb75 | ||
|
|
22da26742c | ||
|
|
63f3a57022 | ||
|
|
36d42856e9 | ||
|
|
105ac5ece5 | ||
|
|
b8a41ad5a0 | ||
|
|
7c74d0a403 | ||
|
|
5f7ffcb37a | ||
|
|
584a1aa725 | ||
|
|
1e1520b85f | ||
|
|
0bc45e3047 | ||
|
|
cacd1b489e | ||
|
|
e650b19933 | ||
|
|
ec478a8979 | ||
|
|
e07d91e857 | ||
|
|
9ccb3e321a | ||
|
|
f2c046dff9 | ||
|
|
2a378b3bc1 | ||
|
|
8b0a797906 | ||
|
|
cea6f7530b | ||
|
|
a7e2e596bf | ||
|
|
980428b204 | ||
|
|
f428c3bdaf | ||
|
|
a05cd88a53 | ||
|
|
60220801ac | ||
|
|
8db95d9ec0 | ||
|
|
27bf2575bd | ||
|
|
22bb3c5ecc | ||
|
|
6b85d67f6c | ||
|
|
123f59e74b | ||
|
|
c7bf912cf2 | ||
|
|
2980836015 | ||
|
|
78fbdde165 | ||
|
|
dbc5a6348d | ||
|
|
c685042a47 | ||
|
|
a4de486cf7 | ||
|
|
3c84e75350 | ||
|
|
39749106a2 | ||
|
|
9bcb1fa469 | ||
|
|
c31a225775 | ||
|
|
73c317f283 | ||
|
|
a0da806a79 | ||
|
|
eddf80aaed | ||
|
|
05b1c147a9 | ||
|
|
ae7898aaee | ||
|
|
4806bdf99c | ||
|
|
37c59ef0d1 | ||
|
|
3f11183768 | ||
|
|
d38147b875 | ||
|
|
3436c4f1f5 | ||
|
|
31e8563725 | ||
|
|
5ddfd0e1a9 | ||
|
|
ef99f8bbca | ||
|
|
2804987fe8 | ||
|
|
09f4e3d4ae | ||
|
|
7c8cbc4ead | ||
|
|
ce6299937f | ||
|
|
d6fce114d6 | ||
|
|
e6d584fde7 | ||
|
|
0240e46f04 | ||
|
|
e36f4f47f1 | ||
|
|
ba4e711f9b | ||
|
|
0f4309659a | ||
|
|
5cfb9538df | ||
|
|
6a26ce3a2b | ||
|
|
ec9c868f85 | ||
|
|
03c581433a | ||
|
|
2e15e4f786 | ||
|
|
9611cd1e73 | ||
|
|
8307badae5 | ||
|
|
83fdebf64d | ||
|
|
90866fb925 | ||
|
|
f3031a44f7 | ||
|
|
83679806fd | ||
|
|
d462546055 | ||
|
|
f510020daa | ||
|
|
1eaa48c95c | ||
|
|
d6503232de | ||
|
|
88a41479f4 | ||
|
|
b73cf4f0e2 | ||
|
|
2e6225a883 | ||
|
|
f34ca18a34 | ||
|
|
69323f4164 | ||
|
|
06300d1a8e | ||
|
|
6e32cb51b6 | ||
|
|
bd0361de92 | ||
|
|
ebab5e209f | ||
|
|
c65e2c4aab | ||
|
|
c04ae51d20 | ||
|
|
19a0ef490b | ||
|
|
4e357c4ad0 | ||
|
|
80670b2b3f | ||
|
|
64f90b828b | ||
|
|
95c742244e | ||
|
|
34c0e81d8e | ||
|
|
2a2c4921a7 | ||
|
|
f4c2d519fc | ||
|
|
cbfdcd5638 | ||
|
|
a04d3b5c29 | ||
|
|
30da349475 | ||
|
|
0b78e03055 | ||
|
|
f8a9a1576d | ||
|
|
6ba15545d9 | ||
|
|
da22c2f670 | ||
|
|
d47755b4c6 | ||
|
|
b51da2f997 | ||
|
|
1447be12e6 | ||
|
|
433b5a4fe1 | ||
|
|
4ae0763d0f | ||
|
|
a407105bb8 | ||
|
|
5d7bf554dd | ||
|
|
5ec09a7fe1 | ||
|
|
3f0721c59a |
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: Guidelines for bash commands and tooling in the monorepo
|
||||
applyTo: "**/*.sh"
|
||||
---
|
||||
|
||||
# Bash & Tooling Instructions
|
||||
|
||||
This document outlines the standard tools and commands used in this monorepo.
|
||||
|
||||
## Package Manager
|
||||
|
||||
We use **pnpm** for package management.
|
||||
- **Do not use `npm` or `yarn`.**
|
||||
- Lockfile: `pnpm-lock.yaml`
|
||||
- Workspace configuration: `pnpm-workspace.yaml`
|
||||
|
||||
### Common Commands
|
||||
- Install dependencies: `pnpm install`
|
||||
- Run a script in a specific package: `pnpm --filter <package_name> run <script>`
|
||||
- Run a script in all packages: `pnpm -r run <script>`
|
||||
|
||||
## Monorepo Tooling
|
||||
|
||||
We use **Turbo** for build system orchestration.
|
||||
- Configuration: `turbo.json`
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `apps/`: Contains application services (admin, api, live, proxy, space, web).
|
||||
- `packages/`: Contains shared packages and libraries.
|
||||
- `deployments/`: Deployment configurations.
|
||||
|
||||
## Running Tests
|
||||
|
||||
- To run tests in a specific package (e.g., codemods):
|
||||
```bash
|
||||
cd packages/codemods
|
||||
pnpm run test
|
||||
```
|
||||
- Or from root:
|
||||
```bash
|
||||
pnpm --filter @plane/codemods run test
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
- Local development uses `docker-compose-local.yml`.
|
||||
- Production/Staging uses `docker-compose.yml`.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
description: Guidelines for using modern TypeScript features (v5.0-v5.8)
|
||||
applyTo: "**/*.{ts,tsx,mts,cts}"
|
||||
---
|
||||
|
||||
# TypeScript Coding Guidelines & Modern Features (v5.0 - v5.8)
|
||||
|
||||
When writing TypeScript code, prioritize using modern features and best practices introduced in recent versions (up to 5.8).
|
||||
|
||||
## Global Themes Across 5.x
|
||||
|
||||
1. **Standard decorators are here; legacy decorators are legacy.**
|
||||
New TC39-compliant decorators landed in 5.0 and were extended in 5.2 (metadata). Old `experimentalDecorators`-style behavior is still supported but should be treated as legacy.
|
||||
|
||||
2. **Type system is more precise and less noisy.**
|
||||
Major work went into narrowing, control flow analysis, error messages, and new helpers like `NoInfer`, inferred predicates, and better `undefined`/`never`/uninitialized checks.
|
||||
|
||||
3. **Module / runtime interop has been modernized.**
|
||||
Options like `--moduleResolution bundler`, `--module nodenext`/`node18`, `--rewriteRelativeImportExtensions`, `--erasableSyntaxOnly`, and `--verbatimModuleSyntax` are about playing nicely with ESM, Node 18+/22+, direct TypeScript execution, and bundlers.
|
||||
|
||||
4. **The standard library keeps tracking modern JS.**
|
||||
Support for new ES features (iterator helpers, `Object.groupBy`/`Map.groupBy`, new Set/ES2024 APIs) shows up as type declarations and sometimes extra checks (regex syntax checking, etc.).
|
||||
|
||||
When generating or refactoring code, prefer these newer idioms, and avoid patterns that conflict with updated checks.
|
||||
|
||||
## Modern Features to Utilize
|
||||
|
||||
### Type System & Inference
|
||||
- **`const` Type Parameters (5.0)**: Use `const` type parameters for more precise literal inference.
|
||||
```typescript
|
||||
declare function names<const T extends string[]>(...names: T): void;
|
||||
```
|
||||
- **`@satisfies` Operator (5.0)**: Use `satisfies` to validate types without widening them.
|
||||
- **Inferred Type Predicates (5.5)**: Allow TypeScript to infer type predicates for functions that filter arrays or check types, reducing the need for explicit `is` return types.
|
||||
- **`NoInfer` Utility (5.4)**: Use `NoInfer<T>` to block inference for specific type arguments when you want them to be determined by other arguments.
|
||||
- **Narrowing**:
|
||||
- **Switch(true) (5.3)**: Utilize narrowing in `switch(true)` blocks.
|
||||
- **Boolean Comparisons (5.3)**: Rely on narrowing from direct boolean comparisons.
|
||||
- **Closures (5.4)**: Trust preserved narrowing in closures when variables aren't modified after the check.
|
||||
- **Constant Indexed Access (5.5)**: Use constant indices to narrow object/array properties.
|
||||
|
||||
### Syntax & Control Flow
|
||||
- **Decorators (5.0)**: Use standard ECMAScript decorators (Stage 3).
|
||||
- **`using` Declarations (5.2)**: Use `using` for explicit resource management (Disposable pattern) instead of manual cleanup.
|
||||
```typescript
|
||||
using resource = new Resource();
|
||||
```
|
||||
- **Import Attributes (5.3/5.8)**: Use `with { type: "json" }` for import attributes. Avoid the deprecated `assert` syntax.
|
||||
- **`switch` Exhaustiveness**: Rely on TypeScript's exhaustiveness checking in switch statements.
|
||||
|
||||
### Modules & Imports
|
||||
- **`verbatimModuleSyntax` (5.0)**: Respect this flag by using `import type` explicitly when importing types to ensure they are erased during compilation.
|
||||
- **Type-Only Imports with Extensions (5.2)**: You can use `.ts`, `.mts`, `.cts` extensions in `import type` statements.
|
||||
- **`resolution-mode` (5.3)**: Use `import type { Type } from "mod" with { "resolution-mode": "import" }` if needed for specific module resolution contexts.
|
||||
- **JSDoc `@import` (5.5)**: Use `@import` tags in JSDoc for cleaner type imports in JS files if working in a mixed codebase.
|
||||
|
||||
### Standard Library & Built-ins
|
||||
- **Iterator Helpers (5.6)**: Use new iterator methods (map, filter, etc.) if targeting modern environments.
|
||||
- **Set Methods (5.5)**: Utilize new `Set` methods like `union`, `intersection`, etc., when available.
|
||||
- **`Object.groupBy` / `Map.groupBy` (5.4)**: Use these standard methods for grouping instead of external libraries like Lodash when appropriate.
|
||||
- **`Promise.withResolvers` (5.7)**: Use `Promise.withResolvers()` for creating promises with exposed resolve/reject functions.
|
||||
|
||||
### Configuration & Tooling
|
||||
- **`--moduleResolution bundler` (5.0)**: Assume this resolution strategy for modern web projects (Vite, Next.js, etc.).
|
||||
- **`--erasableSyntaxOnly` (5.8)**: Be aware of this flag; avoid TypeScript-specific syntax that cannot be simply erased (like `enum`s or `namespaces`) if the project aims for maximum compatibility with tools like Node.js's `--strip-types`. Prefer `const` objects or unions over `enum`s if requested.
|
||||
|
||||
## Specific Coding Patterns
|
||||
|
||||
### Arrays & Collections
|
||||
- Use **Copying Array Methods (5.2)** (`toSorted`, `toSpliced`, `with`) for immutable array operations.
|
||||
- **TypedArrays (5.7)**: Be aware that TypedArrays are now generic over `ArrayBufferLike`.
|
||||
|
||||
### Classes
|
||||
- **Parameter Decorators (5.0/5.2)**: Use modern standard decorators.
|
||||
- **`super` Property Access (5.3)**: Avoid accessing instance fields via `super`.
|
||||
|
||||
### Error Handling
|
||||
- **Checks for Never-Initialized Variables (5.7)**: Ensure variables are initialized before use to avoid new errors.
|
||||
|
||||
## Deprecations to Avoid
|
||||
- Avoid `import ... assert` (use `with`).
|
||||
- Avoid implicit `any` returns in `undefined`-returning functions (though 5.1 makes this easier, explicit is better).
|
||||
- Avoid `enum`s if the project prefers erasable syntax (5.8).
|
||||
|
||||
## Version-Specific Highlights
|
||||
|
||||
### TypeScript 5.0
|
||||
- **Decorators**: Use standard decorators unless `experimentalDecorators` is explicitly enabled.
|
||||
- **`const` Type Parameters**: Use for literal inference.
|
||||
- **Enums**: All enums are union enums.
|
||||
- **Modules**: `--moduleResolution bundler` and `--verbatimModuleSyntax` are key for modern bundlers.
|
||||
|
||||
### TypeScript 5.1
|
||||
- **Returns**: `undefined`-returning functions don't need explicit returns.
|
||||
- **Getters/Setters**: Can have unrelated types with explicit annotations.
|
||||
|
||||
### TypeScript 5.2
|
||||
- **Resource Management**: `using` declarations for `Symbol.dispose`.
|
||||
- **Decorator Metadata**: Use `context.metadata` for design-time metadata.
|
||||
|
||||
### TypeScript 5.3
|
||||
- **Import Attributes**: Use `with { type: "json" }`.
|
||||
- **Switch(true)**: Narrowing works in `switch(true)`.
|
||||
|
||||
### TypeScript 5.4
|
||||
- **Closures**: Narrowing preserved in closures if last assignment is before creation.
|
||||
- **`NoInfer`**: Block inference for specific arguments.
|
||||
- **Grouping**: `Object.groupBy` / `Map.groupBy`.
|
||||
|
||||
### TypeScript 5.5
|
||||
- **Inferred Predicates**: Functions checking types often don't need explicit `is` return types.
|
||||
- **Constant Index Access**: Better narrowing for constant keys.
|
||||
- **Regex**: Syntax checking for regex literals.
|
||||
|
||||
### TypeScript 5.6
|
||||
- **Truthiness Checks**: Errors on always-truthy/falsy conditions (e.g., `if (/regex/)`).
|
||||
- **Iterator Helpers**: `.map`, `.filter` on iterators.
|
||||
|
||||
### TypeScript 5.7
|
||||
- **Uninitialized Variables**: Stricter checks for never-initialized variables.
|
||||
- **Relative Imports**: `--rewriteRelativeImportExtensions` for `.ts` imports in output.
|
||||
- **ES2024**: Support for `Promise.withResolvers`, `Atomics.waitAsync`.
|
||||
|
||||
### TypeScript 5.8
|
||||
- **Return Checks**: Granular checks for conditional returns.
|
||||
- **Node Modules**: `--module node18` stable; `require()` of ESM allowed in `nodenext`.
|
||||
- **Erasable Syntax**: `--erasableSyntaxOnly` forbids enums, namespaces, etc.
|
||||
|
||||
When generating code, always prefer the most modern, standard, and type-safe approach available in TypeScript 5.8.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
python-version: "3.12.x"
|
||||
- name: Install Pylint
|
||||
run: python -m pip install ruff
|
||||
- name: Install API Dependencies
|
||||
|
||||
@@ -43,11 +43,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Affected
|
||||
run: pnpm turbo run build --affected
|
||||
|
||||
- name: Lint Affected
|
||||
run: pnpm turbo run check:lint --affected
|
||||
|
||||
- name: Check Affected format
|
||||
run: pnpm turbo run check:format --affected
|
||||
|
||||
- name: Build Affected
|
||||
run: pnpm turbo run build --affected
|
||||
- name: Check Affected types
|
||||
run: pnpm turbo run check:types --affected
|
||||
|
||||
@@ -111,3 +111,4 @@ build/
|
||||
.react-router/
|
||||
AGENTS.md
|
||||
temp/
|
||||
scripts/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pnpm lint-staged
|
||||
@@ -1,34 +1,54 @@
|
||||
# Enforce pnpm workspace behavior and allow Turbo's lifecycle hooks if scripts are disabled
|
||||
# This repo uses pnpm with workspaces.
|
||||
# ------------------------------
|
||||
# Core Workspace Behavior
|
||||
# ------------------------------
|
||||
|
||||
# Prefer linking local workspace packages when available
|
||||
prefer-workspace-packages=true
|
||||
link-workspace-packages=true
|
||||
shared-workspace-lockfile=true
|
||||
# Always prefer using local workspace packages when available
|
||||
prefer-workspace-packages = true
|
||||
|
||||
# Make peer installs smoother across the monorepo
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
# Symlink workspace packages instead of duplicating them
|
||||
link-workspace-packages = true
|
||||
|
||||
# If scripts are disabled (e.g., CI with --ignore-scripts), allowlisted packages can still run their hooks
|
||||
# Turbo occasionally performs postinstall tasks for optimal performance
|
||||
# moved to pnpm-workspace.yaml: onlyBuiltDependencies (e.g., allow turbo)
|
||||
# Use a single lockfile across the whole monorepo
|
||||
shared-workspace-lockfile = true
|
||||
|
||||
public-hoist-pattern[]=*eslint*
|
||||
public-hoist-pattern[]=prettier
|
||||
public-hoist-pattern[]=typescript
|
||||
# Ensure packages added from workspace save using workspace: protocol
|
||||
save-workspace-protocol = true
|
||||
|
||||
# Reproducible installs across CI and dev
|
||||
prefer-frozen-lockfile=true
|
||||
|
||||
# Prefer resolving to highest versions in monorepo to reduce duplication
|
||||
resolution-mode=highest
|
||||
# ------------------------------
|
||||
# Dependency Resolution
|
||||
# ------------------------------
|
||||
|
||||
# Speed up native module builds by caching side effects
|
||||
side-effects-cache=true
|
||||
# Choose the highest compatible version across the workspace
|
||||
# → reduces fragmentation & node_modules bloat
|
||||
resolution-mode = highest
|
||||
|
||||
# Speed up local dev by reusing local store when possible
|
||||
prefer-offline=true
|
||||
# Automatically install peer dependencies instead of forcing every package to declare them
|
||||
auto-install-peers = true
|
||||
|
||||
# Ensure workspace protocol is used when adding internal deps
|
||||
save-workspace-protocol=true
|
||||
# Don't break the install if peers are missing
|
||||
strict-peer-dependencies = false
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# Performance Optimizations
|
||||
# ------------------------------
|
||||
|
||||
# Use cached artifacts for native modules (sharp, esbuild, etc.)
|
||||
side-effects-cache = true
|
||||
|
||||
# Prefer local cached packages rather than hitting network
|
||||
prefer-offline = true
|
||||
|
||||
# In CI, refuse to modify lockfile (prevents drift)
|
||||
prefer-frozen-lockfile = true
|
||||
|
||||
# Use isolated linker (best compatibility with Node ecosystem tools)
|
||||
node-linker = isolated
|
||||
|
||||
# Hoist commonly used tools to the root to prevent duplicates and speed up resolution
|
||||
public-hoist-pattern[] = typescript
|
||||
public-hoist-pattern[] = eslint
|
||||
public-hoist-pattern[] = *@plane/*
|
||||
public-hoist-pattern[] = vite
|
||||
public-hoist-pattern[] = turbo
|
||||
@@ -0,0 +1,10 @@
|
||||
.next/
|
||||
.react-router/
|
||||
.turbo/
|
||||
.vite/
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
out/
|
||||
pnpm-lock.yaml
|
||||
storybook-static/
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["packages/codemods/**/*"],
|
||||
"options": {
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
],
|
||||
"plugins": ["@prettier/plugin-oxc"],
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
eslint.config.mjs @lifeiscontent
|
||||
+10
-8
@@ -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 use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier.
|
||||
- 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
|
||||
|
||||
@@ -187,18 +187,19 @@ Adding a new language involves several steps to ensure it integrates seamlessly
|
||||
Add the new language to the TLanguage type in the language definitions file:
|
||||
|
||||
```ts
|
||||
// packages/i18n/src/types/language.ts
|
||||
export type TLanguage = "en" | "fr" | "your-lang";
|
||||
// packages/i18n/src/types/language.ts
|
||||
export type TLanguage = "en" | "fr" | "your-lang";
|
||||
```
|
||||
|
||||
1. **Add language configuration**
|
||||
Include the new language in the list of supported languages:
|
||||
|
||||
```ts
|
||||
// packages/i18n/src/constants/language.ts
|
||||
export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Your Language", value: "your-lang" }
|
||||
];
|
||||
// packages/i18n/src/constants/language.ts
|
||||
export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Your Language", value: "your-lang" },
|
||||
];
|
||||
```
|
||||
|
||||
2. **Create translation files**
|
||||
@@ -210,6 +211,7 @@ Adding a new language involves several steps to ensure it integrates seamlessly
|
||||
|
||||
3. **Update import logic**
|
||||
Modify the language import logic to include your new language:
|
||||
|
||||
```ts
|
||||
private importLanguageFile(language: TLanguage): Promise<any> {
|
||||
switch (language) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
NEXT_PUBLIC_API_BASE_URL="http://localhost:8000"
|
||||
VITE_API_BASE_URL="http://localhost:8000"
|
||||
|
||||
NEXT_PUBLIC_WEB_BASE_URL="http://localhost:3000"
|
||||
VITE_WEB_BASE_URL="http://localhost:3000"
|
||||
|
||||
NEXT_PUBLIC_ADMIN_BASE_URL="http://localhost:3001"
|
||||
NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||
VITE_ADMIN_BASE_URL="http://localhost:3001"
|
||||
VITE_ADMIN_BASE_PATH="/god-mode"
|
||||
|
||||
NEXT_PUBLIC_SPACE_BASE_URL="http://localhost:3002"
|
||||
NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
|
||||
VITE_SPACE_BASE_URL="http://localhost:3002"
|
||||
VITE_SPACE_BASE_PATH="/spaces"
|
||||
|
||||
NEXT_PUBLIC_LIVE_BASE_URL="http://localhost:3100"
|
||||
NEXT_PUBLIC_LIVE_BASE_PATH="/live"
|
||||
VITE_LIVE_BASE_URL="http://localhost:3100"
|
||||
VITE_LIVE_BASE_PATH="/live"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
.next/*
|
||||
out/*
|
||||
public/*
|
||||
dist/*
|
||||
node_modules/*
|
||||
.turbo/*
|
||||
.env*
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.production
|
||||
.env.test
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@plane/eslint-config/next.js"],
|
||||
ignorePatterns: ["build/**", "dist/**", ".vite/**"],
|
||||
rules: {
|
||||
"no-duplicate-imports": "off",
|
||||
"import/no-duplicates": ["error", { "prefer-inline": false }],
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
prefer: "type-imports",
|
||||
fixStyle: "separate-type-imports",
|
||||
disallowTypeAnnotations: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
.next
|
||||
.vercel
|
||||
.tubro
|
||||
out/
|
||||
dist/
|
||||
.next/
|
||||
.react-router/
|
||||
.turbo/
|
||||
.vite/
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
out/
|
||||
pnpm-lock.yaml
|
||||
storybook-static/
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
+31
-29
@@ -13,7 +13,7 @@ RUN corepack enable pnpm
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN pnpm add -g turbo@2.5.8
|
||||
RUN pnpm add -g turbo@2.6.3
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -28,52 +28,54 @@ FROM base AS installer
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Public envs required at build time (pick up via process.env)
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||
ARG NEXT_PUBLIC_API_BASE_PATH="/api"
|
||||
ENV NEXT_PUBLIC_API_BASE_PATH=$NEXT_PUBLIC_API_BASE_PATH
|
||||
ARG VITE_API_BASE_URL=""
|
||||
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||
ARG VITE_API_BASE_PATH="/api"
|
||||
ENV VITE_API_BASE_PATH=$VITE_API_BASE_PATH
|
||||
|
||||
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
|
||||
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
|
||||
ARG VITE_ADMIN_BASE_URL=""
|
||||
ENV VITE_ADMIN_BASE_URL=$VITE_ADMIN_BASE_URL
|
||||
ARG VITE_ADMIN_BASE_PATH="/god-mode"
|
||||
ENV VITE_ADMIN_BASE_PATH=$VITE_ADMIN_BASE_PATH
|
||||
|
||||
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
|
||||
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
|
||||
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
|
||||
ARG VITE_SPACE_BASE_URL=""
|
||||
ENV VITE_SPACE_BASE_URL=$VITE_SPACE_BASE_URL
|
||||
ARG VITE_SPACE_BASE_PATH="/spaces"
|
||||
ENV VITE_SPACE_BASE_PATH=$VITE_SPACE_BASE_PATH
|
||||
|
||||
ARG NEXT_PUBLIC_LIVE_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_LIVE_BASE_URL=$NEXT_PUBLIC_LIVE_BASE_URL
|
||||
ARG NEXT_PUBLIC_LIVE_BASE_PATH="/live"
|
||||
ENV NEXT_PUBLIC_LIVE_BASE_PATH=$NEXT_PUBLIC_LIVE_BASE_PATH
|
||||
ARG VITE_LIVE_BASE_URL=""
|
||||
ENV VITE_LIVE_BASE_URL=$VITE_LIVE_BASE_URL
|
||||
ARG VITE_LIVE_BASE_PATH="/live"
|
||||
ENV VITE_LIVE_BASE_PATH=$VITE_LIVE_BASE_PATH
|
||||
|
||||
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
||||
ARG NEXT_PUBLIC_WEB_BASE_PATH=""
|
||||
ENV NEXT_PUBLIC_WEB_BASE_PATH=$NEXT_PUBLIC_WEB_BASE_PATH
|
||||
ARG VITE_WEB_BASE_URL=""
|
||||
ENV VITE_WEB_BASE_URL=$VITE_WEB_BASE_URL
|
||||
ARG VITE_WEB_BASE_PATH=""
|
||||
ENV VITE_WEB_BASE_PATH=$VITE_WEB_BASE_PATH
|
||||
|
||||
ARG NEXT_PUBLIC_WEBSITE_URL="https://plane.so"
|
||||
ENV NEXT_PUBLIC_WEBSITE_URL=$NEXT_PUBLIC_WEBSITE_URL
|
||||
ARG NEXT_PUBLIC_SUPPORT_EMAIL="support@plane.so"
|
||||
ENV NEXT_PUBLIC_SUPPORT_EMAIL=$NEXT_PUBLIC_SUPPORT_EMAIL
|
||||
ARG VITE_WEBSITE_URL="https://plane.so"
|
||||
ENV VITE_WEBSITE_URL=$VITE_WEBSITE_URL
|
||||
ARG VITE_SUPPORT_EMAIL="support@plane.so"
|
||||
ENV VITE_SUPPORT_EMAIL=$VITE_SUPPORT_EMAIL
|
||||
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=builder /app/out/json/ .
|
||||
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
|
||||
# Fetch dependencies to cache store, then install offline with dev deps
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store
|
||||
# Copy full directory structure before fetch to ensure all package.json files are available
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store --prod=false
|
||||
|
||||
# Fetch dependencies to cache store, then install offline with dev deps
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store CI=true pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store --prod=false
|
||||
|
||||
# Build only the admin package
|
||||
RUN pnpm turbo run build --filter=admin
|
||||
|
||||
# =========================================================================== #
|
||||
|
||||
FROM nginx:1.27-alpine AS production
|
||||
FROM nginx:1.29-alpine AS production
|
||||
|
||||
COPY apps/admin/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=installer /app/apps/admin/build/client /usr/share/nginx/html/god-mode
|
||||
|
||||
@@ -8,7 +8,7 @@ COPY . .
|
||||
RUN corepack enable pnpm && pnpm add -g turbo
|
||||
RUN pnpm install
|
||||
|
||||
ENV NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||
ENV VITE_ADMIN_BASE_PATH="/god-mode"
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
import { Button } from "@plane/propel/button";
|
||||
@@ -17,7 +15,7 @@ type IInstanceAIForm = {
|
||||
|
||||
type AIFormValues = Record<TInstanceAIConfigurationKeys, string>;
|
||||
|
||||
export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => {
|
||||
export function InstanceAIForm(props: IInstanceAIForm) {
|
||||
const { config } = props;
|
||||
// store
|
||||
const { updateInstanceConfigurations } = useInstance();
|
||||
@@ -133,4 +131,4 @@ export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Loader } from "@plane/ui";
|
||||
@@ -9,7 +7,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceAIForm } from "./form";
|
||||
|
||||
const InstanceAIPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentProps) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig } = useInstance();
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -27,7 +24,7 @@ type Props = {
|
||||
|
||||
type GiteaConfigFormValues = Record<TInstanceGiteaAuthenticationConfigurationKeys, string>;
|
||||
|
||||
export const InstanceGiteaConfigForm: FC<Props> = (props) => {
|
||||
export function InstanceGiteaConfigForm(props: Props) {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
|
||||
@@ -207,4 +204,4 @@ export const InstanceGiteaConfigForm: FC<Props> = (props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
@@ -16,7 +13,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGiteaConfigForm } from "./form";
|
||||
|
||||
const InstanceGiteaAuthenticationPage = observer(() => {
|
||||
const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthenticationPage() {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -65,7 +62,7 @@ const InstanceGiteaAuthenticationPage = observer(() => {
|
||||
<AuthenticationMethodCard
|
||||
name="Gitea"
|
||||
description="Allow members to login or sign up to plane with their Gitea accounts."
|
||||
icon={<Image src={giteaLogo} height={24} width={24} alt="Gitea Logo" />}
|
||||
icon={<img src={giteaLogo} height={24} width={24} alt="Gitea Logo" />}
|
||||
config={
|
||||
<ToggleSwitch
|
||||
value={isGiteaEnabled}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -28,7 +26,7 @@ type Props = {
|
||||
|
||||
type GithubConfigFormValues = Record<TInstanceGithubAuthenticationConfigurationKeys, string>;
|
||||
|
||||
export const InstanceGithubConfigForm: React.FC<Props> = (props) => {
|
||||
export function InstanceGithubConfigForm(props: Props) {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
|
||||
@@ -245,4 +243,4 @@ export const InstanceGithubConfigForm: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
@@ -20,7 +17,9 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGithubConfigForm } from "./form";
|
||||
|
||||
const InstanceGithubAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthenticationPage(
|
||||
_props: Route.ComponentProps
|
||||
) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -73,7 +72,7 @@ const InstanceGithubAuthenticationPage = observer<React.FC<Route.ComponentProps>
|
||||
name="GitHub"
|
||||
description="Allow members to login or sign up to plane with their GitHub accounts."
|
||||
icon={
|
||||
<Image
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
||||
height={24}
|
||||
width={24}
|
||||
|
||||
@@ -24,7 +24,7 @@ type Props = {
|
||||
|
||||
type GitlabConfigFormValues = Record<TInstanceGitlabAuthenticationConfigurationKeys, string>;
|
||||
|
||||
export const InstanceGitlabConfigForm: React.FC<Props> = (props) => {
|
||||
export function InstanceGitlabConfigForm(props: Props) {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
|
||||
@@ -208,4 +208,4 @@ export const InstanceGitlabConfigForm: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import useSWR from "swr";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
@@ -16,7 +13,9 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGitlabConfigForm } from "./form";
|
||||
|
||||
const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthenticationPage(
|
||||
_props: Route.ComponentProps
|
||||
) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -63,7 +62,7 @@ const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>
|
||||
<AuthenticationMethodCard
|
||||
name="GitLab"
|
||||
description="Allow members to login or sign up to plane with their GitLab accounts."
|
||||
icon={<Image src={GitlabLogo} height={24} width={24} alt="GitLab Logo" />}
|
||||
icon={<img src={GitlabLogo} height={24} width={24} alt="GitLab Logo" />}
|
||||
config={
|
||||
<ToggleSwitch
|
||||
value={Boolean(parseInt(enableGitlabConfig))}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import Link from "next/link";
|
||||
@@ -27,7 +25,7 @@ type Props = {
|
||||
|
||||
type GoogleConfigFormValues = Record<TInstanceGoogleAuthenticationConfigurationKeys, string>;
|
||||
|
||||
export const InstanceGoogleConfigForm: React.FC<Props> = (props) => {
|
||||
export function InstanceGoogleConfigForm(props: Props) {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
|
||||
@@ -232,4 +230,4 @@ export const InstanceGoogleConfigForm: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import useSWR from "swr";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
@@ -16,7 +13,9 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGoogleConfigForm } from "./form";
|
||||
|
||||
const InstanceGoogleAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthenticationPage(
|
||||
_props: Route.ComponentProps
|
||||
) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -64,7 +63,7 @@ const InstanceGoogleAuthenticationPage = observer<React.FC<Route.ComponentProps>
|
||||
name="Google"
|
||||
description="Allow members to login or sign up to plane with their Google
|
||||
accounts."
|
||||
icon={<Image src={GoogleLogo} height={24} width={24} alt="Google Logo" />}
|
||||
icon={<img src={GoogleLogo} height={24} width={24} alt="Google Logo" />}
|
||||
config={
|
||||
<ToggleSwitch
|
||||
value={Boolean(parseInt(enableGoogleConfig))}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
@@ -14,7 +12,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import { AuthenticationModes } from "@/plane-admin/components/authentication";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
@@ -30,7 +28,7 @@ const EMAIL_SECURITY_OPTIONS: { [key in TEmailSecurityKeys]: string } = {
|
||||
NONE: "No email security",
|
||||
};
|
||||
|
||||
export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
|
||||
export function InstanceEmailForm(props: IInstanceEmailForm) {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [isSendTestEmailModalOpen, setIsSendTestEmailModalOpen] = useState(false);
|
||||
@@ -165,7 +163,6 @@ export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
|
||||
label={EMAIL_SECURITY_OPTIONS[emailSecurityKey]}
|
||||
onChange={handleEmailSecurityChange}
|
||||
buttonClassName="rounded-md border-custom-border-200"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
>
|
||||
{Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => (
|
||||
@@ -224,4 +221,4 @@ export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
@@ -11,7 +9,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceEmailForm } from "./email-config-form";
|
||||
|
||||
const InstanceEmailPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceEmailPage = observer(function InstanceEmailPage(_props: Route.ComponentProps) {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, disableEmail } = useInstance();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ enum ESendEmailSteps {
|
||||
|
||||
const instanceService = new InstanceService();
|
||||
|
||||
export const SendTestEmailModal: React.FC<Props> = (props) => {
|
||||
export function SendTestEmailModal(props: Props) {
|
||||
const { isOpen, handleClose } = props;
|
||||
|
||||
// state
|
||||
@@ -133,4 +133,4 @@ export const SendTestEmailModal: React.FC<Props> = (props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Telescope } from "lucide-react";
|
||||
@@ -19,7 +18,7 @@ export interface IGeneralConfigurationForm {
|
||||
instanceAdmins: IInstanceAdmin[];
|
||||
}
|
||||
|
||||
export const GeneralConfigurationForm: React.FC<IGeneralConfigurationForm> = observer((props) => {
|
||||
export const GeneralConfigurationForm = observer(function GeneralConfigurationForm(props: IGeneralConfigurationForm) {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// hooks
|
||||
const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
@@ -13,7 +11,7 @@ type TIntercomConfig = {
|
||||
isTelemetryEnabled: boolean;
|
||||
};
|
||||
|
||||
export const IntercomConfig: React.FC<TIntercomConfig> = observer((props) => {
|
||||
export const IntercomConfig = observer(function IntercomConfig(props: TIntercomConfig) {
|
||||
const { isTelemetryEnabled } = props;
|
||||
// hooks
|
||||
const { instanceConfigurations, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, Settings } from "lucide-react";
|
||||
@@ -10,7 +8,7 @@ import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||
// hooks
|
||||
import { useTheme } from "@/hooks/store";
|
||||
|
||||
export const HamburgerToggle = observer(() => {
|
||||
export const HamburgerToggle = observer(function HamburgerToggle() {
|
||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||
return (
|
||||
<div
|
||||
@@ -22,7 +20,7 @@ export const HamburgerToggle = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const AdminHeader = observer(() => {
|
||||
export const AdminHeader = observer(function AdminHeader() {
|
||||
const pathName = usePathname();
|
||||
|
||||
const getHeaderTitle = (pathName: string) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
@@ -14,7 +13,7 @@ type IInstanceImageConfigForm = {
|
||||
|
||||
type ImageConfigFormValues = Record<TInstanceImageConfigurationKeys, string>;
|
||||
|
||||
export const InstanceImageConfigForm: React.FC<IInstanceImageConfigForm> = (props) => {
|
||||
export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
|
||||
const { config } = props;
|
||||
// store hooks
|
||||
const { updateInstanceConfigurations } = useInstance();
|
||||
@@ -77,4 +76,4 @@ export const InstanceImageConfigForm: React.FC<IInstanceImageConfigForm> = (prop
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
import { Loader } from "@plane/ui";
|
||||
@@ -9,7 +7,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceImageConfigForm } from "./form";
|
||||
|
||||
const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const InstanceImagePage = observer(function InstanceImagePage(_props: Route.ComponentProps) {
|
||||
// store
|
||||
const { formattedConfig, fetchInstanceConfigurations } = useInstance();
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -14,7 +12,7 @@ import type { Route } from "./+types/layout";
|
||||
import { AdminHeader } from "./header";
|
||||
import { AdminSidebar } from "./sidebar";
|
||||
|
||||
const AdminLayout: React.FC<Route.ComponentProps> = () => {
|
||||
function AdminLayout(_props: Route.ComponentProps) {
|
||||
// router
|
||||
const { replace } = useRouter();
|
||||
// store hooks
|
||||
@@ -48,6 +46,6 @@ const AdminLayout: React.FC<Route.ComponentProps> = () => {
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
}
|
||||
|
||||
export default observer(AdminLayout);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
@@ -16,7 +14,7 @@ import { useTheme, useUser } from "@/hooks/store";
|
||||
// service initialization
|
||||
const authService = new AuthService();
|
||||
|
||||
export const AdminSidebarDropdown = observer(() => {
|
||||
export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
|
||||
// store hooks
|
||||
const { isSidebarCollapsed } = useTheme();
|
||||
const { currentUser, signOut } = useUser();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
@@ -13,7 +11,7 @@ import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// assets
|
||||
// eslint-disable-next-line import/order
|
||||
|
||||
import packageJson from "package.json";
|
||||
|
||||
const helpOptions = [
|
||||
@@ -34,7 +32,7 @@ const helpOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
export const AdminSidebarHelpSection: React.FC = observer(() => {
|
||||
export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection() {
|
||||
// states
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
// store
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -50,7 +48,7 @@ const INSTANCE_ADMIN_LINKS = [
|
||||
},
|
||||
];
|
||||
|
||||
export const AdminSidebarMenu = observer(() => {
|
||||
export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
|
||||
// store hooks
|
||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||
// router
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane helpers
|
||||
@@ -11,7 +9,7 @@ import { AdminSidebarDropdown } from "./sidebar-dropdown";
|
||||
import { AdminSidebarHelpSection } from "./sidebar-help-section";
|
||||
import { AdminSidebarMenu } from "./sidebar-menu";
|
||||
|
||||
export const AdminSidebar = observer(() => {
|
||||
export const AdminSidebar = observer(function AdminSidebar() {
|
||||
// store
|
||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
const instanceWorkspaceService = new InstanceWorkspaceService();
|
||||
|
||||
export const WorkspaceCreateForm = () => {
|
||||
export function WorkspaceCreateForm() {
|
||||
// router
|
||||
const router = useRouter();
|
||||
// states
|
||||
@@ -177,7 +177,6 @@ export const WorkspaceCreateForm = () => {
|
||||
}
|
||||
buttonClassName="!border-[0.5px] !border-custom-border-200 !shadow-none"
|
||||
input
|
||||
optionsClassName="w-full"
|
||||
>
|
||||
{ORGANIZATION_SIZE.map((item) => (
|
||||
<CustomSelect.Option key={item} value={item}>
|
||||
@@ -209,4 +208,4 @@ export const WorkspaceCreateForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { WorkspaceCreateForm } from "./form";
|
||||
|
||||
const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => (
|
||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||
<div className="text-xl font-medium text-custom-text-100">Create a new workspace on this instance.</div>
|
||||
<div className="text-sm font-normal text-custom-text-300">
|
||||
You will need to invite users from Workspace Settings after you create this workspace.
|
||||
const WorkspaceCreatePage = observer(function WorkspaceCreatePage(_props: Route.ComponentProps) {
|
||||
return (
|
||||
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
|
||||
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
|
||||
<div className="text-xl font-medium text-custom-text-100">Create a new workspace on this instance.</div>
|
||||
<div className="text-sm font-normal text-custom-text-300">
|
||||
You will need to invite users from Workspace Settings after you create this workspace.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
|
||||
<WorkspaceCreateForm />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
|
||||
<WorkspaceCreateForm />
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace - God Mode" }];
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
@@ -18,7 +16,7 @@ import { WorkspaceListItem } from "@/components/workspace/list-item";
|
||||
import { useInstance, useWorkspace } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WorkspaceManagementPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props: Route.ComponentProps) {
|
||||
// states
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
// store
|
||||
|
||||
@@ -9,7 +9,7 @@ type TAuthBanner = {
|
||||
handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void;
|
||||
};
|
||||
|
||||
export const AuthBanner: React.FC<TAuthBanner> = (props) => {
|
||||
export function AuthBanner(props: TAuthBanner) {
|
||||
const { bannerData, handleBannerData } = props;
|
||||
|
||||
if (!bannerData) return <></>;
|
||||
@@ -27,4 +27,4 @@ export const AuthBanner: React.FC<TAuthBanner> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
|
||||
export const AuthHeader = () => (
|
||||
<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-custom-text-100" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
export function AuthHeader() {
|
||||
return (
|
||||
<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-custom-text-100" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
// plane packages
|
||||
@@ -27,7 +26,7 @@ export enum EErrorAlertType {
|
||||
}
|
||||
|
||||
const errorCodeMessages: {
|
||||
[key in EAdminAuthErrorCodes]: { title: string; message: (email?: string | undefined) => React.ReactNode };
|
||||
[key in EAdminAuthErrorCodes]: { title: string; message: (email?: string) => React.ReactNode };
|
||||
} = {
|
||||
// admin
|
||||
[EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||
@@ -80,14 +79,11 @@ const errorCodeMessages: {
|
||||
},
|
||||
[EAdminAuthErrorCodes.ADMIN_USER_DEACTIVATED]: {
|
||||
title: `User account deactivated`,
|
||||
message: () => `User account deactivated. Please contact ${!!SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`,
|
||||
message: () => `User account deactivated. Please contact ${SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`,
|
||||
},
|
||||
};
|
||||
|
||||
export const authErrorHandler = (
|
||||
errorCode: EAdminAuthErrorCodes,
|
||||
email?: string | undefined
|
||||
): TAdminAuthErrorInfo | undefined => {
|
||||
export const authErrorHandler = (errorCode: EAdminAuthErrorCodes, email?: string): TAdminAuthErrorInfo | undefined => {
|
||||
const bannerAlertErrorCodes = [
|
||||
EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST,
|
||||
EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
||||
@@ -135,7 +131,7 @@ export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps
|
||||
key: "google",
|
||||
name: "Google",
|
||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||
icon: <Image src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
icon: <img src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
@@ -143,7 +139,7 @@ export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps
|
||||
name: "GitHub",
|
||||
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
|
||||
icon: (
|
||||
<Image
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
||||
height={20}
|
||||
width={20}
|
||||
@@ -156,7 +152,7 @@ export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps
|
||||
key: "gitlab",
|
||||
name: "GitLab",
|
||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||
icon: <Image src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
icon: <img src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
@@ -11,7 +9,7 @@ import { useInstance } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceSignInForm } from "./sign-in-form";
|
||||
|
||||
const HomePage = () => {
|
||||
function HomePage() {
|
||||
// store hooks
|
||||
const { instance, error } = useInstance();
|
||||
|
||||
@@ -36,7 +34,7 @@ const HomePage = () => {
|
||||
|
||||
// if instance is fetched and setup is done, show sign in form
|
||||
return <InstanceSignInForm />;
|
||||
};
|
||||
}
|
||||
|
||||
export default observer(HomePage);
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
@@ -45,7 +43,7 @@ const defaultFromData: TFormData = {
|
||||
password: "",
|
||||
};
|
||||
|
||||
export const InstanceSignInForm: React.FC = () => {
|
||||
export function InstanceSignInForm() {
|
||||
// search params
|
||||
const searchParams = useSearchParams();
|
||||
const emailParam = searchParams.get("email") || undefined;
|
||||
@@ -192,4 +190,4 @@ export const InstanceSignInForm: React.FC = () => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import useSWR from "swr";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
|
||||
export const InstanceProvider = observer(function InstanceProvider(props: React.PropsWithChildren) {
|
||||
const { children } = props;
|
||||
// store hooks
|
||||
const { fetchInstanceInfo } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { createContext } from "react";
|
||||
// plane admin store
|
||||
import { RootStore } from "@/plane-admin/store/root.store";
|
||||
@@ -28,7 +26,7 @@ export type StoreProviderProps = {
|
||||
initialState?: any;
|
||||
};
|
||||
|
||||
export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => {
|
||||
export function StoreProvider({ children, initialState = {} }: StoreProviderProps) {
|
||||
const store = initializeStore(initialState);
|
||||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toast } from "@plane/propel/toast";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
|
||||
export const ToastWithTheme = () => {
|
||||
export function ToastWithTheme() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// hooks
|
||||
import { useInstance, useTheme, useUser } from "@/hooks/store";
|
||||
|
||||
export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => {
|
||||
export const UserProvider = observer(function UserProvider({ children }: React.PropsWithChildren) {
|
||||
// hooks
|
||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||
const { currentUser, fetchCurrentUser } = useUser();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
// Minimal shim so code using next/image compiles under React Router + Vite
|
||||
@@ -9,6 +7,8 @@ type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
|
||||
src: string;
|
||||
};
|
||||
|
||||
const Image: React.FC<NextImageProps> = ({ src, alt = "", ...rest }) => <img src={src} alt={alt} {...rest} />;
|
||||
function Image({ src, alt = "", ...rest }: NextImageProps) {
|
||||
return <img src={src} alt={alt} {...rest} />;
|
||||
}
|
||||
|
||||
export default Image;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Link as RRLink } from "react-router";
|
||||
import { ensureTrailingSlash } from "./helper";
|
||||
@@ -12,13 +10,8 @@ type NextLinkProps = React.ComponentProps<"a"> & {
|
||||
shallow?: boolean; // next.js prop, ignored
|
||||
};
|
||||
|
||||
const Link: React.FC<NextLinkProps> = ({
|
||||
href,
|
||||
replace,
|
||||
prefetch: _prefetch,
|
||||
scroll: _scroll,
|
||||
shallow: _shallow,
|
||||
...rest
|
||||
}) => <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
|
||||
function Link({ href, replace, prefetch: _prefetch, scroll: _scroll, shallow: _shallow, ...rest }: NextLinkProps) {
|
||||
return <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
|
||||
}
|
||||
|
||||
export default Link;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useLocation, useNavigate, useSearchParams as useSearchParamsRR } from "react-router";
|
||||
import { ensureTrailingSlash } from "./helper";
|
||||
|
||||
@@ -5,30 +5,32 @@ import { Button } from "@plane/propel/button";
|
||||
// images
|
||||
import Image404 from "@/app/assets/images/404.svg?url";
|
||||
|
||||
const PageNotFound = () => (
|
||||
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
|
||||
<div className="grid h-full place-items-center p-4">
|
||||
<div className="space-y-8 text-center">
|
||||
<div className="relative mx-auto h-60 w-60 lg:h-80 lg:w-80">
|
||||
<img src={Image404} alt="404 - Page not found" className="h-full w-full object-contain" />
|
||||
function PageNotFound() {
|
||||
return (
|
||||
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
|
||||
<div className="grid h-full place-items-center p-4">
|
||||
<div className="space-y-8 text-center">
|
||||
<div className="relative mx-auto h-60 w-60 lg:h-80 lg:w-80">
|
||||
<img src={Image404} alt="404 - Page not found" className="h-full w-full object-contain" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Oops! Something went wrong.</h3>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
|
||||
temporarily unavailable.
|
||||
</p>
|
||||
</div>
|
||||
<Link to="/general/">
|
||||
<span className="flex justify-center py-4">
|
||||
<Button variant="neutral-primary" size="md">
|
||||
Go to general settings
|
||||
</Button>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Oops! Something went wrong.</h3>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
|
||||
temporarily unavailable.
|
||||
</p>
|
||||
</div>
|
||||
<Link to="/general/">
|
||||
<span className="flex justify-center py-4">
|
||||
<Button variant="neutral-primary" size="md">
|
||||
Go to general settings
|
||||
</Button>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default PageNotFound;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
<StrictMode>
|
||||
<HydratedRouter />
|
||||
</StrictMode>
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { SWRConfig } from "swr";
|
||||
import { AppProgressBar } from "@/lib/b-progress";
|
||||
|
||||
+12
-2
@@ -1,10 +1,12 @@
|
||||
import type { ReactNode } from "react";
|
||||
import * as Sentry from "@sentry/react-router";
|
||||
import { Links, Meta, Outlet, Scripts } from "react-router";
|
||||
import type { LinksFunction } from "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";
|
||||
import faviconIco from "@/app/assets/favicon/favicon.ico?url";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import globalStyles from "@/styles/globals.css?url";
|
||||
import type { Route } from "./+types/root";
|
||||
import { AppProviders } from "./providers";
|
||||
@@ -58,10 +60,18 @@ export default function Root() {
|
||||
}
|
||||
|
||||
export function HydrateFallback() {
|
||||
return null;
|
||||
return (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
if (error) {
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong.</p>
|
||||
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
declare module "next/image" {
|
||||
type Props = React.ComponentProps<"img"> & { src: string };
|
||||
const Image: React.FC<Props>;
|
||||
export default Image;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
// types
|
||||
@@ -58,7 +57,7 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
|
||||
key: "google",
|
||||
name: "Google",
|
||||
description: "Allow members to log in or sign up for Plane with their Google accounts.",
|
||||
icon: <Image src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
icon: <img src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
|
||||
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
@@ -66,7 +65,7 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
|
||||
name: "GitHub",
|
||||
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
|
||||
icon: (
|
||||
<Image
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
|
||||
height={20}
|
||||
width={20}
|
||||
@@ -79,21 +78,21 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
|
||||
key: "gitlab",
|
||||
name: "GitLab",
|
||||
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
|
||||
icon: <Image src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
icon: <img src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
|
||||
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "gitea",
|
||||
name: "Gitea",
|
||||
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
|
||||
icon: <Image src={giteaLogo} height={20} width={20} alt="Gitea Logo" />,
|
||||
icon: <img src={giteaLogo} height={20} width={20} alt="Gitea Logo" />,
|
||||
config: <GiteaConfiguration disabled={disabled} updateConfig={updateConfig} />,
|
||||
},
|
||||
{
|
||||
key: "oidc",
|
||||
name: "OIDC",
|
||||
description: "Authenticate your users via the OpenID Connect protocol.",
|
||||
icon: <Image src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
|
||||
icon: <img src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
|
||||
config: <UpgradeButton />,
|
||||
unavailable: true,
|
||||
},
|
||||
@@ -101,13 +100,13 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
|
||||
key: "saml",
|
||||
name: "SAML",
|
||||
description: "Authenticate your users via the Security Assertion Markup Language protocol.",
|
||||
icon: <Image src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
|
||||
icon: <img src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
|
||||
config: <UpgradeButton />,
|
||||
unavailable: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => {
|
||||
export const AuthenticationModes = observer(function AuthenticationModes(props: TAuthenticationModeProps) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
// icons
|
||||
import { SquareArrowOutUpRight } from "lucide-react";
|
||||
@@ -7,9 +5,16 @@ import { SquareArrowOutUpRight } from "lucide-react";
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
export const UpgradeButton: React.FC = () => (
|
||||
<a href="https://plane.so/pricing?mode=self-hosted" target="_blank" className={cn(getButtonStyling("primary", "sm"))}>
|
||||
Upgrade
|
||||
<SquareArrowOutUpRight className="h-3.5 w-3.5 p-0.5" />
|
||||
</a>
|
||||
);
|
||||
export function UpgradeButton() {
|
||||
return (
|
||||
<a
|
||||
href="https://plane.so/pricing?mode=self-hosted"
|
||||
target="_blank"
|
||||
className={cn(getButtonStyling("primary", "sm"))}
|
||||
rel="noreferrer"
|
||||
>
|
||||
Upgrade
|
||||
<SquareArrowOutUpRight className="h-3.5 w-3.5 p-0.5" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
@@ -13,7 +11,7 @@ type Props = {
|
||||
unavailable?: boolean;
|
||||
};
|
||||
|
||||
export const AuthenticationMethodCard: React.FC<Props> = (props) => {
|
||||
export function AuthenticationMethodCard(props: Props) {
|
||||
const { name, description, icon, config, disabled = false, withBorder = true, unavailable = false } = props;
|
||||
|
||||
return (
|
||||
@@ -52,4 +50,4 @@ export const AuthenticationMethodCard: React.FC<Props> = (props) => {
|
||||
<div className={`shrink-0 ${disabled && "opacity-70"}`}>{config}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
@@ -14,7 +12,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const EmailCodesConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const EmailCodesConfiguration = observer(function EmailCodesConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
@@ -17,7 +15,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const GiteaConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const GiteaConfiguration = observer(function GiteaConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
@@ -18,7 +16,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const GithubConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const GithubConfiguration = observer(function GithubConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
@@ -17,7 +15,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const GitlabConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const GitlabConfiguration = observer(function GitlabConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// icons
|
||||
@@ -17,7 +15,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const GoogleConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const GoogleConfiguration = observer(function GoogleConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
@@ -14,7 +12,7 @@ type Props = {
|
||||
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
|
||||
};
|
||||
|
||||
export const PasswordLoginConfiguration: React.FC<Props> = observer((props) => {
|
||||
export const PasswordLoginConfiguration = observer(function PasswordLoginConfiguration(props: Props) {
|
||||
const { disabled, updateConfig } = props;
|
||||
// store
|
||||
const { formattedConfig } = useInstance();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { FC } from "react";
|
||||
import { AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
|
||||
type TBanner = {
|
||||
@@ -6,7 +5,7 @@ type TBanner = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const Banner: FC<TBanner> = (props) => {
|
||||
export function Banner(props: TBanner) {
|
||||
const { type, message } = props;
|
||||
|
||||
return (
|
||||
@@ -29,4 +28,4 @@ export const Banner: FC<TBanner> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
|
||||
@@ -9,7 +7,7 @@ type Props = {
|
||||
icon?: React.ReactNode | undefined;
|
||||
};
|
||||
|
||||
export const BreadcrumbLink: React.FC<Props> = (props) => {
|
||||
export function BreadcrumbLink(props: Props) {
|
||||
const { href, label, icon } = props;
|
||||
return (
|
||||
<Tooltip tooltipContent={label} position="bottom">
|
||||
@@ -35,4 +33,4 @@ export const BreadcrumbLink: React.FC<Props> = (props) => {
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,16 +6,18 @@ type TProps = {
|
||||
darkerShade?: boolean;
|
||||
};
|
||||
|
||||
export const CodeBlock = ({ children, className, darkerShade }: TProps) => (
|
||||
<span
|
||||
className={cn(
|
||||
"px-0.5 text-xs text-custom-text-300 bg-custom-background-90 font-semibold rounded-md border border-custom-border-100",
|
||||
{
|
||||
"text-custom-text-200 bg-custom-background-80 border-custom-border-200": darkerShade,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
export function CodeBlock({ children, className, darkerShade }: TProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"px-0.5 text-xs text-custom-text-300 bg-custom-background-90 font-semibold rounded-md border border-custom-border-100",
|
||||
{
|
||||
"text-custom-text-200 bg-custom-background-80 border-custom-border-200": darkerShade,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
// headless ui
|
||||
@@ -13,7 +11,7 @@ type Props = {
|
||||
onDiscardHref: string;
|
||||
};
|
||||
|
||||
export const ConfirmDiscardModal: React.FC<Props> = (props) => {
|
||||
export function ConfirmDiscardModal(props: Props) {
|
||||
const { isOpen, handleClose, onDiscardHref } = props;
|
||||
|
||||
return (
|
||||
@@ -71,4 +69,4 @@ export const ConfirmDiscardModal: React.FC<Props> = (props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import type { Control } from "react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
@@ -30,7 +28,7 @@ export type TControllerInputFormField = {
|
||||
required: boolean;
|
||||
};
|
||||
|
||||
export const ControllerInput: React.FC<Props> = (props) => {
|
||||
export function ControllerInput(props: Props) {
|
||||
const { name, control, type, label, description, placeholder, error, required } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
@@ -81,4 +79,4 @@ export const ControllerInput: React.FC<Props> = (props) => {
|
||||
{description && <p className="pt-0.5 text-xs text-custom-text-300">{description}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
// ui
|
||||
import { Copy } from "lucide-react";
|
||||
@@ -19,7 +17,7 @@ export type TCopyField = {
|
||||
description: string | React.ReactNode;
|
||||
};
|
||||
|
||||
export const CopyField: React.FC<Props> = (props) => {
|
||||
export function CopyField(props: Props) {
|
||||
const { label, url, description } = props;
|
||||
|
||||
return (
|
||||
@@ -43,4 +41,4 @@ export const CopyField: React.FC<Props> = (props) => {
|
||||
<div className="text-xs text-custom-text-300">{description}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@plane/propel/button";
|
||||
|
||||
type Props = {
|
||||
@@ -17,32 +14,27 @@ type Props = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const EmptyState: React.FC<Props> = ({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
disabled = false,
|
||||
}) => (
|
||||
<div className={`flex h-full w-full items-center justify-center`}>
|
||||
<div className="flex w-full flex-col items-center text-center">
|
||||
{image && <Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />}
|
||||
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
|
||||
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
{primaryButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
prependIcon={primaryButton.icon}
|
||||
onClick={primaryButton.onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{primaryButton.text}
|
||||
</Button>
|
||||
)}
|
||||
{secondaryButton}
|
||||
export function EmptyState({ title, description, image, primaryButton, secondaryButton, disabled = false }: Props) {
|
||||
return (
|
||||
<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="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
|
||||
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
{primaryButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
prependIcon={primaryButton.icon}
|
||||
onClick={primaryButton.onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{primaryButton.text}
|
||||
</Button>
|
||||
)}
|
||||
{secondaryButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import Image from "next/image";
|
||||
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";
|
||||
|
||||
export const LogoSpinner = () => {
|
||||
export function LogoSpinner() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerLight : LogoSpinnerDark;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<Image src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
|
||||
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
type TPageHeader = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export const PageHeader: React.FC<TPageHeader> = (props) => {
|
||||
export function PageHeader(props: TPageHeader) {
|
||||
const { title = "God Mode - Plane", description = "Plane god mode" } = props;
|
||||
|
||||
return (
|
||||
@@ -14,4 +12,4 @@ export const PageHeader: React.FC<TPageHeader> = (props) => {
|
||||
<meta name="description" content={description} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"use client";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
@@ -8,7 +6,7 @@ import { AuthHeader } from "@/app/(all)/(home)/auth-header";
|
||||
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
|
||||
import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url";
|
||||
|
||||
export const InstanceFailureView: React.FC = observer(() => {
|
||||
export const InstanceFailureView = observer(function InstanceFailureView() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||
@@ -23,7 +21,7 @@ export const InstanceFailureView: React.FC = observer(() => {
|
||||
<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">
|
||||
<Image src={instanceImage} alt="Plane Logo" />
|
||||
<img src={instanceImage} alt="Instance failure illustration" />
|
||||
<h3 className="font-medium text-2xl text-white text-center">Unable to fetch instance details.</h3>
|
||||
<p className="font-medium text-base text-center">
|
||||
We were unable to fetch the details of the instance. Fret not, it might just be a connectivity issue.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
export const FormHeader = ({ heading, subHeading }: { heading: string; subHeading: string }) => (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-2xl font-semibold text-custom-text-100 leading-7">{heading}</span>
|
||||
<span className="text-lg font-semibold text-custom-text-400 leading-7">{subHeading}</span>
|
||||
</div>
|
||||
);
|
||||
export function FormHeader({ heading, subHeading }: { heading: string; subHeading: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-2xl font-semibold text-custom-text-100 leading-7">{heading}</span>
|
||||
<span className="text-lg font-semibold text-custom-text-400 leading-7">{subHeading}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
|
||||
|
||||
export const InstanceNotReady: React.FC = () => (
|
||||
<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-3xl font-bold pb-3">Welcome aboard Plane!</h1>
|
||||
<Image src={PlaneTakeOffImage} alt="Plane Logo" />
|
||||
<p className="font-medium text-base text-custom-text-400">
|
||||
Get started by setting up your instance and workspace
|
||||
</p>
|
||||
</div>
|
||||
export function InstanceNotReady() {
|
||||
return (
|
||||
<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-3xl font-bold pb-3">Welcome aboard Plane!</h1>
|
||||
<img src={PlaneTakeOffImage} alt="Plane Logo" />
|
||||
<p className="font-medium text-base text-custom-text-400">
|
||||
Get started by setting up your instance and workspace
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Link href={"/setup/?auth_enabled=0"}>
|
||||
<Button size="lg" className="w-full">
|
||||
Get started
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<Link href={"/setup/?auth_enabled=0"}>
|
||||
<Button size="lg" className="w-full">
|
||||
Get started
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// assets
|
||||
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
|
||||
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
|
||||
|
||||
export const InstanceLoading = () => {
|
||||
export function InstanceLoading() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerLight : LogoSpinnerDark;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<Image src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
|
||||
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// icons
|
||||
@@ -53,8 +51,7 @@ const defaultFromData: TFormData = {
|
||||
is_telemetry_enabled: true,
|
||||
};
|
||||
|
||||
export const InstanceSetupForm: React.FC = (props) => {
|
||||
const {} = props;
|
||||
export function InstanceSetupForm() {
|
||||
// search params
|
||||
const searchParams = useSearchParams();
|
||||
const firstNameParam = searchParams.get("first_name") || undefined;
|
||||
@@ -351,4 +348,4 @@ export const InstanceSetupForm: React.FC = (props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
// ui
|
||||
@@ -13,7 +10,7 @@ import TakeoffIconLight from "@/app/assets/logos/takeoff-icon-light.svg?url";
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// icons
|
||||
|
||||
export const NewUserPopup = observer(() => {
|
||||
export const NewUserPopup = observer(function NewUserPopup() {
|
||||
// hooks
|
||||
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
||||
// theme
|
||||
@@ -39,7 +36,7 @@ export const NewUserPopup = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 flex items-center justify-center">
|
||||
<Image
|
||||
<img
|
||||
src={resolveGeneralTheme(resolvedTheme) === "dark" ? TakeoffIconDark : TakeoffIconLight}
|
||||
height={80}
|
||||
width={80}
|
||||
|
||||
@@ -11,7 +11,7 @@ type TWorkspaceListItemProps = {
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const WorkspaceListItem = observer(({ workspaceId }: TWorkspaceListItemProps) => {
|
||||
export const WorkspaceListItem = observer(function WorkspaceListItem({ workspaceId }: TWorkspaceListItemProps) {
|
||||
// store hooks
|
||||
const { getWorkspaceById } = useWorkspace();
|
||||
// derived values
|
||||
@@ -24,6 +24,7 @@ export const WorkspaceListItem = observer(({ workspaceId }: TWorkspaceListItemPr
|
||||
href={`${WEB_BASE_URL}/${encodeURIComponent(workspace.slug)}`}
|
||||
target="_blank"
|
||||
className="group flex items-center justify-between p-4 gap-2.5 truncate border border-custom-border-200/70 hover:border-custom-border-200 hover:bg-custom-background-90 rounded-md"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<span
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { BProgress } from "@bprogress/core";
|
||||
import { useNavigation } from "react-router";
|
||||
@@ -38,7 +36,6 @@ const PROGRESS_CONFIG: Readonly<ProgressConfig> = {
|
||||
easing: "ease",
|
||||
trickle: true,
|
||||
delay: 0,
|
||||
isDisabled: import.meta.env.PROD, // Disable progress bar in production builds
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { next } from '@vercel/edge';
|
||||
|
||||
export default function middleware() {
|
||||
return next({
|
||||
headers: {
|
||||
'Referrer-Policy': 'origin-when-cross-origin',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-DNS-Prefetch-Control': 'on',
|
||||
'Strict-Transport-Security':
|
||||
'max-age=31536000; includeSubDomains; preload',
|
||||
},
|
||||
});
|
||||
}
|
||||
+14
-24
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Admin UI for Plane",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development PORT=3001 node server.mjs",
|
||||
"dev": "react-router dev --port 3001",
|
||||
"build": "react-router build",
|
||||
"preview": "react-router build && cross-env NODE_ENV=production PORT=3001 node server.mjs",
|
||||
"preview": "react-router build && serve -s build/client -l 3001",
|
||||
"start": "serve -s build/client -l 3001",
|
||||
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist && rm -rf build",
|
||||
"check:lint": "eslint . --max-warnings 19",
|
||||
"check:lint": "eslint . --max-warnings=485",
|
||||
"check:types": "react-router typegen && tsc --noEmit",
|
||||
"check:format": "prettier --check \"**/*.{ts,tsx,md,json,css,scss}\"",
|
||||
"fix:lint": "eslint . --fix",
|
||||
"fix:format": "prettier --write \"**/*.{ts,tsx,md,json,css,scss}\""
|
||||
"check:format": "prettier --check .",
|
||||
"fix:lint": "eslint . --fix --max-warnings=485",
|
||||
"fix:format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@bprogress/core": "catalog:",
|
||||
@@ -27,47 +27,37 @@
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/ui": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
"@react-router/express": "^7.9.3",
|
||||
"@react-router/node": "^7.9.3",
|
||||
"@react-router/node": "catalog:",
|
||||
"@sentry/react-router": "catalog:",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tanstack/virtual-core": "^3.13.12",
|
||||
"@vercel/edge": "1.2.2",
|
||||
"axios": "catalog:",
|
||||
"compression": "^1.8.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^5.1.0",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"isbot": "^5.1.31",
|
||||
"lodash-es": "catalog:",
|
||||
"lucide-react": "catalog:",
|
||||
"mobx": "catalog:",
|
||||
"mobx-react": "catalog:",
|
||||
"morgan": "^1.10.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-hook-form": "7.51.5",
|
||||
"react-router": "^7.9.1",
|
||||
"react-router-dom": "^7.9.1",
|
||||
"react-router": "catalog:",
|
||||
"react-router-dom": "catalog:",
|
||||
"serve": "14.2.5",
|
||||
"swr": "catalog:",
|
||||
"uuid": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@plane/eslint-config": "workspace:*",
|
||||
"@dotenvx/dotenvx": "catalog:",
|
||||
"@plane/tailwind-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@react-router/dev": "^7.9.1",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/express": "4.17.23",
|
||||
"@react-router/dev": "catalog:",
|
||||
"@types/lodash-es": "catalog:",
|
||||
"@types/morgan": "^1.9.10",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "7.1.7",
|
||||
"vite": "catalog:",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
module.exports = require("@plane/tailwind-config/postcss.config.js");
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { Config } from "@react-router/dev/config";
|
||||
import { joinUrlPath } from "@plane/utils";
|
||||
|
||||
const basePath = joinUrlPath(process.env.VITE_ADMIN_BASE_PATH ?? "", "/") ?? "/";
|
||||
|
||||
export default {
|
||||
appDirectory: "app",
|
||||
basename: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH,
|
||||
basename: basePath,
|
||||
// Admin runs as a client-side app; build a static client bundle only
|
||||
ssr: false,
|
||||
} satisfies Config;
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import compression from "compression";
|
||||
import dotenv from "dotenv";
|
||||
import express from "express";
|
||||
import morgan from "morgan";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, ".env") });
|
||||
|
||||
const BUILD_PATH = "./build/server/index.js";
|
||||
const DEVELOPMENT = process.env.NODE_ENV !== "production";
|
||||
|
||||
// Derive the port from NEXT_PUBLIC_ADMIN_BASE_URL when available, otherwise
|
||||
// default to http://localhost:3001 and fall back to PORT env if explicitly set.
|
||||
const DEFAULT_BASE_URL = "http://localhost:3001";
|
||||
const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || DEFAULT_BASE_URL;
|
||||
let parsedBaseUrl;
|
||||
try {
|
||||
parsedBaseUrl = new URL(ADMIN_BASE_URL);
|
||||
} catch {
|
||||
parsedBaseUrl = new URL(DEFAULT_BASE_URL);
|
||||
}
|
||||
|
||||
const PORT = Number.parseInt(parsedBaseUrl.port, 10);
|
||||
|
||||
async function start() {
|
||||
const app = express();
|
||||
|
||||
app.use(compression());
|
||||
app.disable("x-powered-by");
|
||||
|
||||
if (DEVELOPMENT) {
|
||||
console.log("Starting development server");
|
||||
|
||||
const vite = await import("vite").then((vite) =>
|
||||
vite.createServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: "custom",
|
||||
})
|
||||
);
|
||||
|
||||
app.use(vite.middlewares);
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
try {
|
||||
const source = await vite.ssrLoadModule("./server/app.ts");
|
||||
return source.app(req, res, next);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
vite.ssrFixStacktrace(error);
|
||||
}
|
||||
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("Starting production server");
|
||||
|
||||
app.use("/assets", express.static("build/client/assets", { immutable: true, maxAge: "1y" }));
|
||||
app.use(morgan("tiny"));
|
||||
app.use(express.static("build/client", { maxAge: "1h" }));
|
||||
app.use(await import(BUILD_PATH).then((mod) => mod.app));
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const origin = `${parsedBaseUrl.protocol}//${parsedBaseUrl.hostname}:${PORT}`;
|
||||
console.log(`Server is running on ${origin}`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import "react-router";
|
||||
import { createRequestHandler } from "@react-router/express";
|
||||
import express from "express";
|
||||
import type { Express } from "express";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
|
||||
const NEXT_PUBLIC_API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
|
||||
? process.env.NEXT_PUBLIC_API_BASE_URL.replace(/\/$/, "")
|
||||
: "http://127.0.0.1:8000";
|
||||
const NEXT_PUBLIC_API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH
|
||||
? process.env.NEXT_PUBLIC_API_BASE_PATH.replace(/\/+$/, "")
|
||||
: "/api";
|
||||
const NORMALIZED_API_BASE_PATH = NEXT_PUBLIC_API_BASE_PATH.startsWith("/")
|
||||
? NEXT_PUBLIC_API_BASE_PATH
|
||||
: `/${NEXT_PUBLIC_API_BASE_PATH}`;
|
||||
const NEXT_PUBLIC_ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH
|
||||
? process.env.NEXT_PUBLIC_ADMIN_BASE_PATH.replace(/\/$/, "")
|
||||
: "/";
|
||||
|
||||
export const app: Express = express();
|
||||
|
||||
// Ensure proxy-aware hostname/URL handling (e.g., X-Forwarded-Host/Proto)
|
||||
// so generated URLs/redirects reflect the public host when behind Nginx.
|
||||
// See related fix in Remix Express adapter.
|
||||
app.set("trust proxy", true);
|
||||
|
||||
app.use(
|
||||
"/api",
|
||||
createProxyMiddleware({
|
||||
target: NEXT_PUBLIC_API_BASE_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
pathRewrite: (path: string) =>
|
||||
NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH),
|
||||
})
|
||||
);
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(
|
||||
createRequestHandler({
|
||||
build: () => import("virtual:react-router/server-build"),
|
||||
})
|
||||
);
|
||||
|
||||
app.use(NEXT_PUBLIC_ADMIN_BASE_PATH, router);
|
||||
@@ -296,6 +296,10 @@ body {
|
||||
}
|
||||
|
||||
/* scrollbar style */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
* {
|
||||
scrollbar-width: none;
|
||||
@@ -356,6 +360,23 @@ body {
|
||||
margin-top: 44px;
|
||||
}
|
||||
|
||||
/* scrollbar xs size */
|
||||
.scrollbar-xs::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
.scrollbar-xs::-webkit-scrollbar-thumb {
|
||||
border: 3px solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.shadow-custom {
|
||||
box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */
|
||||
}
|
||||
/* backdrop filter */
|
||||
.backdrop-blur-custom {
|
||||
@apply backdrop-filter blur-[9px];
|
||||
}
|
||||
|
||||
/* scrollbar sm size */
|
||||
.scrollbar-sm::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const sharedConfig = require("@plane/tailwind-config/tailwind.config.js");
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
{
|
||||
"extends": "@plane/typescript-config/react-router.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"noImplicitOverride": false,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": false,
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"types": ["node", "vite/client"],
|
||||
"types": ["vite/client"],
|
||||
"paths": {
|
||||
"@/app/*": ["app/*"],
|
||||
"@/*": ["core/*"],
|
||||
"@/plane-admin/*": ["ce/*"],
|
||||
"@/styles/*": ["styles/*"]
|
||||
},
|
||||
"strictNullChecks": true
|
||||
"package.json": ["./package.json"],
|
||||
"ce/*": ["./ce/*"],
|
||||
"@/app/*": ["./app/*"],
|
||||
"@/*": ["./core/*"],
|
||||
"@/plane-admin/*": ["./ce/*"],
|
||||
"@/ce/*": ["./ce/*"],
|
||||
"@/styles/*": ["./styles/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
+31
-49
@@ -1,59 +1,41 @@
|
||||
import path from "node:path";
|
||||
import * as dotenv from "@dotenvx/dotenvx";
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { joinUrlPath } from "@plane/utils";
|
||||
|
||||
const PUBLIC_ENV_KEYS = [
|
||||
"NEXT_PUBLIC_API_BASE_URL",
|
||||
"NEXT_PUBLIC_API_BASE_PATH",
|
||||
"NEXT_PUBLIC_ADMIN_BASE_URL",
|
||||
"NEXT_PUBLIC_ADMIN_BASE_PATH",
|
||||
"NEXT_PUBLIC_SPACE_BASE_URL",
|
||||
"NEXT_PUBLIC_SPACE_BASE_PATH",
|
||||
"NEXT_PUBLIC_LIVE_BASE_URL",
|
||||
"NEXT_PUBLIC_LIVE_BASE_PATH",
|
||||
"NEXT_PUBLIC_WEB_BASE_URL",
|
||||
"NEXT_PUBLIC_WEB_BASE_PATH",
|
||||
"NEXT_PUBLIC_WEBSITE_URL",
|
||||
"NEXT_PUBLIC_SUPPORT_EMAIL",
|
||||
];
|
||||
dotenv.config({ path: path.resolve(__dirname, ".env") });
|
||||
|
||||
const publicEnv = PUBLIC_ENV_KEYS.reduce<Record<string, string>>((acc, key) => {
|
||||
acc[key] = process.env[key] ?? "";
|
||||
return acc;
|
||||
}, {});
|
||||
// Expose only vars starting with VITE_
|
||||
const viteEnv = Object.keys(process.env)
|
||||
.filter((k) => k.startsWith("VITE_"))
|
||||
.reduce<Record<string, string>>((a, k) => {
|
||||
a[k] = process.env[k] ?? "";
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
export default defineConfig(({ isSsrBuild }) => {
|
||||
// Only produce an SSR bundle when explicitly enabled.
|
||||
// For static deployments (default), we skip the server build entirely.
|
||||
const enableSsrBuild = process.env.ADMIN_ENABLE_SSR_BUILD === "true";
|
||||
const basePath = joinUrlPath(process.env.NEXT_PUBLIC_ADMIN_BASE_PATH ?? "", "/") ?? "/";
|
||||
const basePath = joinUrlPath(process.env.VITE_ADMIN_BASE_PATH ?? "", "/") ?? "/";
|
||||
|
||||
return {
|
||||
base: basePath,
|
||||
define: {
|
||||
"process.env": JSON.stringify(publicEnv),
|
||||
export default defineConfig(() => ({
|
||||
base: basePath,
|
||||
define: {
|
||||
"process.env": JSON.stringify(viteEnv),
|
||||
},
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
},
|
||||
plugins: [reactRouter(), tsconfigPaths({ projects: [path.resolve(__dirname, "tsconfig.json")] })],
|
||||
resolve: {
|
||||
alias: {
|
||||
// Next.js compatibility shims used within admin
|
||||
"next/link": path.resolve(__dirname, "app/compat/next/link.tsx"),
|
||||
"next/navigation": path.resolve(__dirname, "app/compat/next/navigation.ts"),
|
||||
},
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
rollupOptions:
|
||||
isSsrBuild && enableSsrBuild
|
||||
? {
|
||||
input: path.resolve(__dirname, "server/app.ts"),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
plugins: [reactRouter(), tsconfigPaths({ projects: [path.resolve(__dirname, "tsconfig.json")] })],
|
||||
resolve: {
|
||||
alias: {
|
||||
// Next.js compatibility shims used within admin
|
||||
"next/image": path.resolve(__dirname, "app/compat/next/image.tsx"),
|
||||
"next/link": path.resolve(__dirname, "app/compat/next/link.tsx"),
|
||||
"next/navigation": path.resolve(__dirname, "app/compat/next/navigation.ts"),
|
||||
},
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
// No SSR-specific overrides needed; alias resolves to ESM build
|
||||
};
|
||||
});
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
server: {
|
||||
host: "127.0.0.1",
|
||||
},
|
||||
// No SSR-specific overrides needed; alias resolves to ESM build
|
||||
}));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user