From d0f6ca3bace3c38a0d826ec1c91ce403d3132c24 Mon Sep 17 00:00:00 2001 From: Henit Chobisa Date: Wed, 13 Sep 2023 20:21:02 +0530 Subject: [PATCH 1/5] [chore] Update `setup.sh`, with removed replacement script & added project-level ENVs (#2115) * chore: Updated Setup Script for Splitting Env File * chore: updated dockerfile for using inproject env varaibles * chore: removed husky replacement script * chore: updated shell script using sed * chore: updated dockerfiles with removed cp statement * chore: added example env for apiserver * chore: refactored secret generation for backend * chore: removed replacement script * chore: updated docker-compose with removed env variables * chore: resolved comments in setup.sh and docker-compose * chore: removed secret key placeholder in apiserver example env * chore: updated root env for project less env variables * chore: removed project level env update from root env logic * chore: updated API_BASE_URL in .env.example * chore: restored docker argument as env NEXT_PUBLIC_API_BASE_URL * chore: added pg missing env variables * [chore] Updated web and deploy backend configuration for reverse proxy & decoupled Plane Deploy URL generation for web (#2135) * chore: removed api url build arg from compose * chore: set public api default argument to black string for self hosted * chore: updated web services to accept blank string as API URL * chore: added env variables for pg compose service * chore: modified space app services to use accept empty string as api base * chore: conditionally trigger web url value based on argument * fix: made web to use identical host with spaces suffix on absense of Deploy URL for deploy * chore: added example env for PUBLIC_DEPLOY Env * chore: updated web dockerfile with addition as PLANE_DEPLOY Argument * API BASE URL global update * API BASE URL replace with api server * api base url fixes * typo fixes --------- Co-authored-by: sriram veeraghanta * dev: remove API_BASE_URL from environment variable --------- Co-authored-by: sriram veeraghanta Co-authored-by: pablohashescobar --- .env.example | 54 --------------- apiserver/.env.example | 60 ++++++++++++++++ docker-compose.yml | 69 +------------------ replace-env-vars.sh | 15 ---- setup.sh | 17 ++--- space/.env.example | 2 - space/Dockerfile.space | 18 ++--- space/helpers/common.helper.ts | 2 + space/services/authentication.service.ts | 3 +- space/services/file.service.ts | 6 +- space/services/issue.service.ts | 3 +- space/services/project.service.ts | 3 +- space/services/user.service.ts | 3 +- start.sh | 4 -- web/.env.example | 4 +- web/Dockerfile.web | 28 +++----- .../project/publish-project/modal.tsx | 6 +- web/helpers/common.helper.ts | 5 ++ web/layouts/app-layout/app-header.tsx | 6 +- web/lib/auth.ts | 33 ++++----- web/services/ai.service.ts | 8 +-- web/services/analytics.service.ts | 5 +- web/services/app-installations.service.ts | 5 +- web/services/authentication.service.ts | 5 +- web/services/cycles.service.ts | 6 +- web/services/estimates.service.ts | 5 +- web/services/file.service.ts | 5 +- web/services/inbox.service.ts | 5 +- web/services/integration/csv.services.ts | 6 +- web/services/integration/github.service.ts | 3 +- web/services/integration/index.ts | 5 +- web/services/integration/jira.service.ts | 4 +- web/services/issues.service.ts | 5 +- web/services/modules.service.ts | 6 +- web/services/notifications.service.ts | 7 +- web/services/pages.service.ts | 6 +- web/services/project-publish.service.ts | 5 +- web/services/project.service.ts | 6 +- web/services/reaction.service.ts | 6 +- web/services/state.service.ts | 6 +- web/services/user.service.ts | 4 +- web/services/views.service.ts | 4 +- web/services/workspace.service.ts | 7 +- 43 files changed, 179 insertions(+), 286 deletions(-) create mode 100644 apiserver/.env.example delete mode 100644 replace-env-vars.sh create mode 100644 space/helpers/common.helper.ts diff --git a/.env.example b/.env.example index 9fe0f47d9..082aa753b 100644 --- a/.env.example +++ b/.env.example @@ -1,38 +1,3 @@ -# Frontend -# Extra image domains that need to be added for Next Image -NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS= -# Google Client ID for Google OAuth -NEXT_PUBLIC_GOOGLE_CLIENTID="" -# Github ID for Github OAuth -NEXT_PUBLIC_GITHUB_ID="" -# Github App Name for GitHub Integration -NEXT_PUBLIC_GITHUB_APP_NAME="" -# Sentry DSN for error monitoring -NEXT_PUBLIC_SENTRY_DSN="" -# Enable/Disable OAUTH - default 0 for selfhosted instance -NEXT_PUBLIC_ENABLE_OAUTH=0 -# Enable/Disable sentry -NEXT_PUBLIC_ENABLE_SENTRY=0 -# Enable/Disable session recording -NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0 -# Enable/Disable event tracking -NEXT_PUBLIC_TRACK_EVENTS=0 -# Slack for Slack Integration -NEXT_PUBLIC_SLACK_CLIENT_ID="" -# For Telemetry, set it to "app.plane.so" -NEXT_PUBLIC_PLAUSIBLE_DOMAIN="" -# public boards deploy url -NEXT_PUBLIC_DEPLOY_URL="" -# plane deploy using nginx -NEXT_PUBLIC_DEPLOY_WITH_NGINX=1 - -# Backend -# Debug value for api server use it as 0 for production use -DEBUG=0 - -# Error logs -SENTRY_DSN="" - # Database Settings PGUSER="plane" PGPASSWORD="plane" @@ -45,15 +10,6 @@ REDIS_HOST="plane-redis" REDIS_PORT="6379" REDIS_URL="redis://${REDIS_HOST}:6379/" -# Email Settings -EMAIL_HOST="" -EMAIL_HOST_USER="" -EMAIL_HOST_PASSWORD="" -EMAIL_PORT=587 -EMAIL_FROM="Team Plane " -EMAIL_USE_TLS="1" -EMAIL_USE_SSL="0" - # AWS Settings AWS_REGION="" AWS_ACCESS_KEY_ID="access-key" @@ -69,9 +25,6 @@ OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint OPENAI_API_KEY="sk-" # add your openai key here GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access -# Github -GITHUB_CLIENT_SECRET="" # For fetching release notes - # Settings related to Docker DOCKERIZED=1 # set to 1 If using the pre-configured minio setup @@ -80,10 +33,3 @@ USE_MINIO=1 # Nginx Configuration NGINX_PORT=80 -# Default Creds -DEFAULT_EMAIL="captain@plane.so" -DEFAULT_PASSWORD="password123" - -# SignUps -ENABLE_SIGNUP="1" -# Auto generated and Required that will be generated from setup.sh diff --git a/apiserver/.env.example b/apiserver/.env.example new file mode 100644 index 000000000..a2a214fe6 --- /dev/null +++ b/apiserver/.env.example @@ -0,0 +1,60 @@ +# Backend +# Debug value for api server use it as 0 for production use +DEBUG=0 + +# Error logs +SENTRY_DSN="" + +# Database Settings +PGUSER="plane" +PGPASSWORD="plane" +PGHOST="plane-db" +PGDATABASE="plane" +DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE} + +# Redis Settings +REDIS_HOST="plane-redis" +REDIS_PORT="6379" +REDIS_URL="redis://${REDIS_HOST}:6379/" + +# Email Settings +EMAIL_HOST="" +EMAIL_HOST_USER="" +EMAIL_HOST_PASSWORD="" +EMAIL_PORT=587 +EMAIL_FROM="Team Plane " +EMAIL_USE_TLS="1" +EMAIL_USE_SSL="0" + +# AWS Settings +AWS_REGION="" +AWS_ACCESS_KEY_ID="access-key" +AWS_SECRET_ACCESS_KEY="secret-key" +AWS_S3_ENDPOINT_URL="http://plane-minio:9000" +# Changing this requires change in the nginx.conf for uploads if using minio setup +AWS_S3_BUCKET_NAME="uploads" +# Maximum file upload limit +FILE_SIZE_LIMIT=5242880 + +# GPT settings +OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint +OPENAI_API_KEY="sk-" # add your openai key here +GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access + +# Github +GITHUB_CLIENT_SECRET="" # For fetching release notes + +# Settings related to Docker +DOCKERIZED=1 +# set to 1 If using the pre-configured minio setup +USE_MINIO=1 + +# Nginx Configuration +NGINX_PORT=80 + +# Default Creds +DEFAULT_EMAIL="captain@plane.so" +DEFAULT_PASSWORD="password123" + +# SignUps +ENABLE_SIGNUP="1" diff --git a/docker-compose.yml b/docker-compose.yml index cf631face..e3c1b37be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,37 +1,5 @@ version: "3.8" -x-api-and-worker-env: &api-and-worker-env - DEBUG: ${DEBUG} - SENTRY_DSN: ${SENTRY_DSN} - DJANGO_SETTINGS_MODULE: plane.settings.production - DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} - REDIS_URL: redis://plane-redis:6379/ - EMAIL_HOST: ${EMAIL_HOST} - EMAIL_HOST_USER: ${EMAIL_HOST_USER} - EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} - EMAIL_PORT: ${EMAIL_PORT} - EMAIL_FROM: ${EMAIL_FROM} - EMAIL_USE_TLS: ${EMAIL_USE_TLS} - EMAIL_USE_SSL: ${EMAIL_USE_SSL} - AWS_REGION: ${AWS_REGION} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} - AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL} - FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT} - WEB_URL: ${WEB_URL} - GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} - DISABLE_COLLECTSTATIC: 1 - DOCKERIZED: 1 - OPENAI_API_BASE: ${OPENAI_API_BASE} - OPENAI_API_KEY: ${OPENAI_API_KEY} - GPT_ENGINE: ${GPT_ENGINE} - SECRET_KEY: ${SECRET_KEY} - DEFAULT_EMAIL: ${DEFAULT_EMAIL} - DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} - USE_MINIO: ${USE_MINIO} - ENABLE_SIGNUP: ${ENABLE_SIGNUP} - services: plane-web: container_name: planefrontend @@ -40,23 +8,8 @@ services: dockerfile: ./web/Dockerfile.web args: DOCKER_BUILDKIT: 1 - NEXT_PUBLIC_API_BASE_URL: http://localhost:8000 - NEXT_PUBLIC_DEPLOY_URL: http://localhost/spaces restart: always command: /usr/local/bin/start.sh web/server.js web - env_file: - - .env - environment: - NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL} - NEXT_PUBLIC_DEPLOY_URL: ${NEXT_PUBLIC_DEPLOY_URL} - NEXT_PUBLIC_GOOGLE_CLIENTID: "0" - NEXT_PUBLIC_GITHUB_APP_NAME: "0" - NEXT_PUBLIC_GITHUB_ID: "0" - NEXT_PUBLIC_SENTRY_DSN: "0" - NEXT_PUBLIC_ENABLE_OAUTH: "0" - NEXT_PUBLIC_ENABLE_SENTRY: "0" - NEXT_PUBLIC_ENABLE_SESSION_RECORDER: "0" - NEXT_PUBLIC_TRACK_EVENTS: "0" depends_on: - plane-api - plane-worker @@ -68,14 +21,8 @@ services: dockerfile: ./space/Dockerfile.space args: DOCKER_BUILDKIT: 1 - NEXT_PUBLIC_DEPLOY_WITH_NGINX: 1 - NEXT_PUBLIC_API_BASE_URL: http://localhost:8000 restart: always command: /usr/local/bin/start.sh space/server.js space - env_file: - - .env - environment: - - NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} depends_on: - plane-api - plane-worker @@ -91,9 +38,7 @@ services: restart: always command: ./bin/takeoff env_file: - - .env - environment: - <<: *api-and-worker-env + - ./apiserver/.env depends_on: - plane-db - plane-redis @@ -108,9 +53,7 @@ services: restart: always command: ./bin/worker env_file: - - .env - environment: - <<: *api-and-worker-env + - ./apiserver/.env depends_on: - plane-api - plane-db @@ -126,9 +69,7 @@ services: restart: always command: ./bin/beat env_file: - - .env - environment: - <<: *api-and-worker-env + - ./apiserver/.env depends_on: - plane-api - plane-db @@ -163,8 +104,6 @@ services: command: server /export --console-address ":9090" volumes: - uploads:/export - env_file: - - .env environment: MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} @@ -187,8 +126,6 @@ services: restart: always ports: - ${NGINX_PORT}:80 - env_file: - - .env environment: FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880} BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} diff --git a/replace-env-vars.sh b/replace-env-vars.sh deleted file mode 100644 index 949ffd7d7..000000000 --- a/replace-env-vars.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -FROM=$1 -TO=$2 -DIRECTORY=$3 - -if [ "${FROM}" = "${TO}" ]; then - echo "Nothing to replace, the value is already set to ${TO}." - - exit 0 -fi - -# Only perform action if $FROM and $TO are different. -echo "Replacing all statically built instances of $FROM with this string $TO ." - -grep -R -la "${FROM}" $DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}" diff --git a/setup.sh b/setup.sh index 235e1a977..87c0f445b 100755 --- a/setup.sh +++ b/setup.sh @@ -5,15 +5,12 @@ cp ./.env.example ./.env export LC_ALL=C export LC_CTYPE=C - -# Generate the NEXT_PUBLIC_API_BASE_URL with given IP -echo -e "\nNEXT_PUBLIC_API_BASE_URL=$1" >> ./.env +cp ./web/.env.example ./web/.env +cp ./space/.env.example ./space/.env +cp ./apiserver/.env.example ./apiserver/.env # Generate the SECRET_KEY that will be used by django -echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./.env - -# WEB_URL for email redirection and image saving -echo -e "WEB_URL=$1" >> ./.env +echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env # Generate Prompt for taking tiptap auth key echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token \e[0m \n" @@ -21,9 +18,7 @@ echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token echo -e "\e[1;38m 1. Head over to TipTap cloud's Pro Extensions Page, https://collab.tiptap.dev/pro-extensions \e[0m" echo -e "\e[1;38m 2. Copy the token given to you under the first paragraph, after 'Here it is' \e[0m \n" -read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken - +read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken echo "@tiptap-pro:registry=https://registry.tiptap.dev/ -//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc - +//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc \ No newline at end of file diff --git a/space/.env.example b/space/.env.example index 238f70854..c7063c155 100644 --- a/space/.env.example +++ b/space/.env.example @@ -1,5 +1,3 @@ -# Base url for the API requests -NEXT_PUBLIC_API_BASE_URL="" # Public boards deploy URL NEXT_PUBLIC_DEPLOY_URL="" # Google Client ID for Google OAuth diff --git a/space/Dockerfile.space b/space/Dockerfile.space index 963dad136..12c309134 100644 --- a/space/Dockerfile.space +++ b/space/Dockerfile.space @@ -1,7 +1,6 @@ FROM node:18-alpine AS builder RUN apk add --no-cache libc6-compat WORKDIR /app -ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER RUN yarn global add turbo COPY . . @@ -20,19 +19,16 @@ RUN yarn install --network-timeout 500000 COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json -COPY replace-env-vars.sh /usr/local/bin/ USER root -RUN chmod +x /usr/local/bin/replace-env-vars.sh -ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +ARG NEXT_PUBLIC_API_BASE_URL="" ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1 -ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX RUN yarn turbo run build --filter=space -RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} space - FROM node:18-alpine AS runner WORKDIR /app @@ -48,14 +44,14 @@ COPY --from=installer --chown=captain:plane /app/space/.next/standalone ./ COPY --from=installer --chown=captain:plane /app/space/.next ./space/.next COPY --from=installer --chown=captain:plane /app/space/public ./space/public -ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +ARG NEXT_PUBLIC_API_BASE_URL="" ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1 -ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX + +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX USER root -COPY replace-env-vars.sh /usr/local/bin/ COPY start.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/replace-env-vars.sh RUN chmod +x /usr/local/bin/start.sh USER captain diff --git a/space/helpers/common.helper.ts b/space/helpers/common.helper.ts new file mode 100644 index 000000000..d96c342b5 --- /dev/null +++ b/space/helpers/common.helper.ts @@ -0,0 +1,2 @@ +export const API_BASE_URL = + process.env.NEXT_PUBLIC_API_BASE_URL !== undefined ? process.env.NEXT_PUBLIC_API_BASE_URL : "http://localhost:8000"; diff --git a/space/services/authentication.service.ts b/space/services/authentication.service.ts index a6f1ec90f..4d861994f 100644 --- a/space/services/authentication.service.ts +++ b/space/services/authentication.service.ts @@ -1,9 +1,10 @@ // services import APIService from "services/api.service"; +import { API_BASE_URL } from "helpers/common.helper"; class AuthService extends APIService { constructor() { - super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async emailLogin(data: any) { diff --git a/space/services/file.service.ts b/space/services/file.service.ts index 5ef34fc76..d9783d29c 100644 --- a/space/services/file.service.ts +++ b/space/services/file.service.ts @@ -1,7 +1,5 @@ -// services import APIService from "services/api.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; interface UnSplashImage { id: string; @@ -29,7 +27,7 @@ interface UnSplashImageUrls { class FileServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async uploadFile(workspaceSlug: string, file: FormData): Promise { diff --git a/space/services/issue.service.ts b/space/services/issue.service.ts index 835778fb2..5feb1b00b 100644 --- a/space/services/issue.service.ts +++ b/space/services/issue.service.ts @@ -1,9 +1,10 @@ // services import APIService from "services/api.service"; +import { API_BASE_URL } from "helpers/common.helper"; class IssueService extends APIService { constructor() { - super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getPublicIssues(workspace_slug: string, project_slug: string, params: any): Promise { diff --git a/space/services/project.service.ts b/space/services/project.service.ts index 291a5f323..0d6eca951 100644 --- a/space/services/project.service.ts +++ b/space/services/project.service.ts @@ -1,9 +1,10 @@ // services import APIService from "services/api.service"; +import { API_BASE_URL } from "helpers/common.helper"; class ProjectService extends APIService { constructor() { - super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getProjectSettings(workspace_slug: string, project_slug: string): Promise { diff --git a/space/services/user.service.ts b/space/services/user.service.ts index 9a324bb95..21e9f941e 100644 --- a/space/services/user.service.ts +++ b/space/services/user.service.ts @@ -1,9 +1,10 @@ // services import APIService from "services/api.service"; +import { API_BASE_URL } from "helpers/common.helper"; class UserService extends APIService { constructor() { - super(process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async currentUser(): Promise { diff --git a/start.sh b/start.sh index dcb97db6d..2685c3826 100644 --- a/start.sh +++ b/start.sh @@ -1,9 +1,5 @@ #!/bin/sh set -x -# Replace the statically built BUILT_NEXT_PUBLIC_API_BASE_URL with run-time NEXT_PUBLIC_API_BASE_URL -# NOTE: if these values are the same, this will be skipped. -/usr/local/bin/replace-env-vars.sh "$BUILT_NEXT_PUBLIC_API_BASE_URL" "$NEXT_PUBLIC_API_BASE_URL" $2 - echo "Starting Plane Frontend.." node $1 diff --git a/web/.env.example b/web/.env.example index 50a6209b2..88a2064c5 100644 --- a/web/.env.example +++ b/web/.env.example @@ -1,5 +1,3 @@ -# Base url for the API requests -NEXT_PUBLIC_API_BASE_URL="" # Extra image domains that need to be added for Next Image NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS= # Google Client ID for Google OAuth @@ -23,4 +21,4 @@ NEXT_PUBLIC_SLACK_CLIENT_ID="" # For Telemetry, set it to "app.plane.so" NEXT_PUBLIC_PLAUSIBLE_DOMAIN="" # Public boards deploy URL -NEXT_PUBLIC_DEPLOY_URL="" \ No newline at end of file +NEXT_PUBLIC_DEPLOY_URL="http://localhost:3000/spaces" \ No newline at end of file diff --git a/web/Dockerfile.web b/web/Dockerfile.web index 40946fa2d..d9260e61d 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -2,7 +2,6 @@ FROM node:18-alpine AS builder RUN apk add --no-cache libc6-compat # Set working directory WORKDIR /app -ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER RUN yarn global add turbo COPY . . @@ -14,8 +13,8 @@ FROM node:18-alpine AS installer RUN apk add --no-cache libc6-compat WORKDIR /app -ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 -ARG NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces +ARG NEXT_PUBLIC_API_BASE_URL="" +ARG NEXT_PUBLIC_DEPLOY_URL="" # First install the dependencies (as they change less often) COPY .gitignore .gitignore @@ -26,18 +25,12 @@ RUN yarn install --network-timeout 500000 # Build the project COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json -COPY replace-env-vars.sh /usr/local/bin/ USER root -RUN chmod +x /usr/local/bin/replace-env-vars.sh - -ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ - BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ - NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL RUN yarn turbo run build --filter=web -RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL} web - FROM node:18-alpine AS runner WORKDIR /app @@ -52,20 +45,15 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer --chown=captain:plane /app/web/.next/standalone ./ - COPY --from=installer --chown=captain:plane /app/web/.next ./web/.next -ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 -ARG NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces - -ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ - BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \ - NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL +ARG NEXT_PUBLIC_API_BASE_URL="" +ARG NEXT_PUBLIC_DEPLOY_URL="" +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ENV NEXT_PUBLIC_DEPLOY_URL=$NEXT_PUBLIC_DEPLOY_URL USER root -COPY replace-env-vars.sh /usr/local/bin/ COPY start.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/replace-env-vars.sh RUN chmod +x /usr/local/bin/start.sh USER captain diff --git a/web/components/project/publish-project/modal.tsx b/web/components/project/publish-project/modal.tsx index 173a5242c..56ed10ee0 100644 --- a/web/components/project/publish-project/modal.tsx +++ b/web/components/project/publish-project/modal.tsx @@ -63,7 +63,11 @@ export const PublishProjectModal: React.FC = observer(() => { const [isUnpublishing, setIsUnpublishing] = useState(false); const [isUpdateRequired, setIsUpdateRequired] = useState(false); - const plane_deploy_url = process.env.NEXT_PUBLIC_DEPLOY_URL ?? "http://localhost:4000"; + let plane_deploy_url = process.env.NEXT_PUBLIC_DEPLOY_URL; + + if (typeof window !== 'undefined' && !plane_deploy_url) { + plane_deploy_url= window.location.protocol + "//" + window.location.host + "/spaces"; + } const router = useRouter(); const { workspaceSlug } = router.query; diff --git a/web/helpers/common.helper.ts b/web/helpers/common.helper.ts index 4220a7174..0829863c9 100644 --- a/web/helpers/common.helper.ts +++ b/web/helpers/common.helper.ts @@ -16,3 +16,8 @@ export const debounce = (func: any, wait: number, immediate: boolean = false) => if (callNow) func(...args); }; }; + +export const API_BASE_URL = + process.env.NEXT_PUBLIC_API_BASE_URL !== undefined + ? process.env.NEXT_PUBLIC_API_BASE_URL + : "http://localhost:8000"; diff --git a/web/layouts/app-layout/app-header.tsx b/web/layouts/app-layout/app-header.tsx index f2fbdc78c..27c52f654 100644 --- a/web/layouts/app-layout/app-header.tsx +++ b/web/layouts/app-layout/app-header.tsx @@ -17,7 +17,11 @@ type Props = { }; const { NEXT_PUBLIC_DEPLOY_URL } = process.env; -const plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL ? NEXT_PUBLIC_DEPLOY_URL : "http://localhost:3001"; +let plane_deploy_url = NEXT_PUBLIC_DEPLOY_URL + +if (typeof window !== 'undefined' && !plane_deploy_url) { + plane_deploy_url= window.location.protocol + "//" + window.location.host + "/spaces"; +} const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => { const { projectDetails } = useProjectDetails(); diff --git a/web/lib/auth.ts b/web/lib/auth.ts index 47a52663d..56cfab9ae 100644 --- a/web/lib/auth.ts +++ b/web/lib/auth.ts @@ -2,6 +2,8 @@ import { convertCookieStringToObject } from "./cookie"; // types import type { IProjectMember, IUser, IWorkspace, IWorkspaceMember } from "types"; +// helper +import { API_BASE_URL } from "helpers/common.helper"; export const requiredAuth = async (cookie?: string) => { const cookies = convertCookieStringToObject(cookie); @@ -9,12 +11,10 @@ export const requiredAuth = async (cookie?: string) => { if (!token) return null; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.plane.so"; - let user: IUser | null = null; try { - const data = await fetch(`${baseUrl}/api/users/me/`, { + const data = await fetch(`${API_BASE_URL}/api/users/me/`, { method: "GET", headers: { "Content-Type": "application/json", @@ -41,13 +41,11 @@ export const requiredAdmin = async (workspaceSlug: string, projectId: string, co const cookies = convertCookieStringToObject(cookie); const token = cookies?.accessToken; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.plane.so"; - let memberDetail: IProjectMember | null = null; try { const data = await fetch( - `${baseUrl}/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`, + `${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`, { method: "GET", headers: { @@ -75,17 +73,18 @@ export const requiredWorkspaceAdmin = async (workspaceSlug: string, cookie?: str const cookies = convertCookieStringToObject(cookie); const token = cookies?.accessToken; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.plane.so"; - let memberDetail: IWorkspaceMember | null = null; try { - const data = await fetch(`${baseUrl}/api/workspaces/${workspaceSlug}/workspace-members/me/`, { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - }, - }) + const data = await fetch( + `${API_BASE_URL}/api/workspaces/${workspaceSlug}/workspace-members/me/`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + } + ) .then((res) => res.json()) .then((data) => data); @@ -119,13 +118,11 @@ export const homePageRedirect = async (cookie?: string) => { let workspaces: IWorkspace[] = []; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.plane.so"; - const cookies = convertCookieStringToObject(cookie); const token = cookies?.accessToken; try { - const data = await fetch(`${baseUrl}/api/users/me/workspaces/`, { + const data = await fetch(`${API_BASE_URL}/api/users/me/workspaces/`, { method: "GET", headers: { "Content-Type": "application/json", @@ -166,7 +163,7 @@ export const homePageRedirect = async (cookie?: string) => { }; } - const invitations = await fetch(`${baseUrl}/api/users/me/invitations/workspaces/`, { + const invitations = await fetch(`${API_BASE_URL}/api/users/me/invitations/workspaces/`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/web/services/ai.service.ts b/web/services/ai.service.ts index ecb1ada52..33b59f3df 100644 --- a/web/services/ai.service.ts +++ b/web/services/ai.service.ts @@ -1,18 +1,16 @@ -// services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - // types import { ICurrentUserResponse, IGptResponse } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class AiServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createGptTask( diff --git a/web/services/analytics.service.ts b/web/services/analytics.service.ts index 0b38f8c57..66b6569e8 100644 --- a/web/services/analytics.service.ts +++ b/web/services/analytics.service.ts @@ -8,12 +8,11 @@ import { IExportAnalyticsFormData, ISaveAnalyticsFormData, } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; class AnalyticsServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getAnalytics(workspaceSlug: string, params: IAnalyticsParams): Promise { diff --git a/web/services/app-installations.service.ts b/web/services/app-installations.service.ts index dea6fa421..8b18cddfc 100644 --- a/web/services/app-installations.service.ts +++ b/web/services/app-installations.service.ts @@ -1,12 +1,11 @@ // services import axios from "axios"; import APIService from "services/api.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; class AppInstallationsService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async addInstallationApp(workspaceSlug: string, provider: string, data: any): Promise { diff --git a/web/services/authentication.service.ts b/web/services/authentication.service.ts index e4a33bff8..58eaa2aff 100644 --- a/web/services/authentication.service.ts +++ b/web/services/authentication.service.ts @@ -1,12 +1,11 @@ // services import APIService from "services/api.service"; import { ICurrentUserResponse } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; class AuthService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async emailLogin(data: any) { diff --git a/web/services/cycles.service.ts b/web/services/cycles.service.ts index 89cd50a2f..e0e42c687 100644 --- a/web/services/cycles.service.ts +++ b/web/services/cycles.service.ts @@ -1,18 +1,16 @@ // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - // types import type { CycleDateCheckData, ICurrentUserResponse, ICycle, IIssue } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class ProjectCycleServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createCycle( diff --git a/web/services/estimates.service.ts b/web/services/estimates.service.ts index 8b0fe25f4..eaa49e57c 100644 --- a/web/services/estimates.service.ts +++ b/web/services/estimates.service.ts @@ -3,15 +3,14 @@ import APIService from "services/api.service"; // types import type { ICurrentUserResponse, IEstimate, IEstimateFormData } from "types"; import trackEventServices from "services/track-event.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class ProjectEstimateServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createEstimate( diff --git a/web/services/file.service.ts b/web/services/file.service.ts index d2f01428d..cbed73fc8 100644 --- a/web/services/file.service.ts +++ b/web/services/file.service.ts @@ -1,7 +1,6 @@ // services import APIService from "services/api.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; interface UnSplashImage { id: string; @@ -29,7 +28,7 @@ interface UnSplashImageUrls { class FileServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async uploadFile(workspaceSlug: string, file: FormData): Promise { diff --git a/web/services/inbox.service.ts b/web/services/inbox.service.ts index 61949c877..16eed288e 100644 --- a/web/services/inbox.service.ts +++ b/web/services/inbox.service.ts @@ -1,7 +1,6 @@ import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; @@ -19,7 +18,7 @@ import type { class InboxServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getInboxes(workspaceSlug: string, projectId: string): Promise { diff --git a/web/services/integration/csv.services.ts b/web/services/integration/csv.services.ts index f19cc4a74..0b53f7778 100644 --- a/web/services/integration/csv.services.ts +++ b/web/services/integration/csv.services.ts @@ -1,16 +1,14 @@ import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - import { ICurrentUserResponse } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class CSVIntegrationService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async exportCSVService( diff --git a/web/services/integration/github.service.ts b/web/services/integration/github.service.ts index 494785f04..58aa12318 100644 --- a/web/services/integration/github.service.ts +++ b/web/services/integration/github.service.ts @@ -1,5 +1,6 @@ import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; +import { API_BASE_URL } from "helpers/common.helper"; import { ICurrentUserResponse, IGithubRepoInfo, IGithubServiceImportFormData } from "types"; @@ -11,7 +12,7 @@ const trackEvent = const integrationServiceType: string = "github"; class GithubIntegrationService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async listAllRepositories(workspaceSlug: string, integrationSlug: string): Promise { diff --git a/web/services/integration/index.ts b/web/services/integration/index.ts index 2b32a5bd0..b1bbefda8 100644 --- a/web/services/integration/index.ts +++ b/web/services/integration/index.ts @@ -9,15 +9,14 @@ import { IWorkspaceIntegration, IExportServiceResponse, } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class IntegrationService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getAppIntegrationsList(): Promise { diff --git a/web/services/integration/jira.service.ts b/web/services/integration/jira.service.ts index 8f6a9fec9..d4620f3ff 100644 --- a/web/services/integration/jira.service.ts +++ b/web/services/integration/jira.service.ts @@ -1,6 +1,6 @@ import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - +import { API_BASE_URL } from "helpers/common.helper"; // types import { IJiraMetadata, IJiraResponse, IJiraImporterForm, ICurrentUserResponse } from "types"; @@ -11,7 +11,7 @@ const trackEvent = class JiraImportedService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getJiraProjectInfo(workspaceSlug: string, params: IJiraMetadata): Promise { diff --git a/web/services/issues.service.ts b/web/services/issues.service.ts index 7b1481812..ede46ad5f 100644 --- a/web/services/issues.service.ts +++ b/web/services/issues.service.ts @@ -10,15 +10,14 @@ import type { IIssueLabels, ISubIssueResponse, } from "types"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class ProjectIssuesServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createIssues( diff --git a/web/services/modules.service.ts b/web/services/modules.service.ts index 898164366..dcac98d50 100644 --- a/web/services/modules.service.ts +++ b/web/services/modules.service.ts @@ -1,9 +1,9 @@ // services import APIService from "services/api.service"; import trackEventServices from "./track-event.service"; - // types -import type { IModule, IIssue, ICurrentUserResponse } from "types"; +import type { IIssueViewOptions, IModule, IIssue, ICurrentUserResponse } from "types"; +import { API_BASE_URL } from "helpers/common.helper"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; @@ -12,7 +12,7 @@ const trackEvent = class ProjectIssuesServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getModules(workspaceSlug: string, projectId: string): Promise { diff --git a/web/services/notifications.service.ts b/web/services/notifications.service.ts index 01c139b51..8ff64e915 100644 --- a/web/services/notifications.service.ts +++ b/web/services/notifications.service.ts @@ -1,8 +1,5 @@ // services import APIService from "services/api.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - // types import type { IUserNotification, @@ -11,10 +8,12 @@ import type { PaginatedUserNotification, IMarkAllAsReadPayload, } from "types"; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; class UserNotificationsServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getUserNotifications( diff --git a/web/services/pages.service.ts b/web/services/pages.service.ts index 72b12012e..b9dc580f3 100644 --- a/web/services/pages.service.ts +++ b/web/services/pages.service.ts @@ -1,18 +1,16 @@ +import { API_BASE_URL } from "helpers/common.helper"; // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - // types import { IPage, IPageBlock, RecentPagesResponse, IIssue, ICurrentUserResponse } from "types"; -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class PageServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createPage( diff --git a/web/services/project-publish.service.ts b/web/services/project-publish.service.ts index 4ee01a94b..05555bd29 100644 --- a/web/services/project-publish.service.ts +++ b/web/services/project-publish.service.ts @@ -1,3 +1,4 @@ +import { API_BASE_URL } from "helpers/common.helper"; // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; @@ -5,14 +6,12 @@ import trackEventServices from "services/track-event.service"; import { ICurrentUserResponse } from "types"; import { IProjectPublishSettings } from "store/project-publish"; -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class ProjectServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async getProjectSettingsAsync( diff --git a/web/services/project.service.ts b/web/services/project.service.ts index 0d97b9efb..829f9903d 100644 --- a/web/services/project.service.ts +++ b/web/services/project.service.ts @@ -1,7 +1,7 @@ +import { API_BASE_URL } from "helpers/common.helper"; // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - // types import type { GithubRepositoriesResponse, @@ -16,14 +16,12 @@ import type { TProjectIssuesSearchParams, } from "types"; -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; export class ProjectServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createProject( diff --git a/web/services/reaction.service.ts b/web/services/reaction.service.ts index 3ba8a83e4..35dc915a1 100644 --- a/web/services/reaction.service.ts +++ b/web/services/reaction.service.ts @@ -1,7 +1,7 @@ +import { API_BASE_URL } from "helpers/common.helper"; // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - // types import type { ICurrentUserResponse, @@ -11,14 +11,12 @@ import type { IssueCommentReactionForm, } from "types"; -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class ReactionService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createIssueReaction( diff --git a/web/services/state.service.ts b/web/services/state.service.ts index 52481f8bb..a97753d6d 100644 --- a/web/services/state.service.ts +++ b/web/services/state.service.ts @@ -1,8 +1,8 @@ // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; @@ -12,7 +12,7 @@ import type { ICurrentUserResponse, IState, IStateResponse } from "types"; class ProjectStateServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createState( diff --git a/web/services/user.service.ts b/web/services/user.service.ts index 0e5def647..6a8523348 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -12,14 +12,14 @@ import type { IUserWorkspaceDashboard, } from "types"; -const { NEXT_PUBLIC_API_BASE_URL } = process.env; +import { API_BASE_URL } from "helpers/common.helper"; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; class UserService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } currentUserConfig() { diff --git a/web/services/views.service.ts b/web/services/views.service.ts index e1d25925e..a684e41ff 100644 --- a/web/services/views.service.ts +++ b/web/services/views.service.ts @@ -6,6 +6,8 @@ import { ICurrentUserResponse } from "types"; // types import { IView } from "types/views"; +import { API_BASE_URL } from "helpers/common.helper"; + const { NEXT_PUBLIC_API_BASE_URL } = process.env; const trackEvent = @@ -13,7 +15,7 @@ const trackEvent = class ViewServices extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async createView( diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index 8097253e6..0539cdf8c 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -1,9 +1,8 @@ // services import APIService from "services/api.service"; import trackEventServices from "services/track-event.service"; - -const { NEXT_PUBLIC_API_BASE_URL } = process.env; - +// helpers +import { API_BASE_URL } from "helpers/common.helper"; // types import { IWorkspace, @@ -22,7 +21,7 @@ const trackEvent = class WorkspaceService extends APIService { constructor() { - super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); + super(API_BASE_URL); } async userWorkspaces(): Promise { From 87abf3ccb1b2c23b260f934c3c90ff2e47e89eee Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 13 Sep 2023 23:09:55 +0530 Subject: [PATCH 2/5] style: project setting ui revamp (#2177) * style: project settings navigation sidebar added * chore: emoji and image picker close on outside click added * style: project setting general page revamp * style: project setting member page revamp * style: project setting features page revamp * style: project setting state page revamp * style: project setting integrations page revamp * style: project setting estimates page revamp * style: project setting automation page revamp * style: project setting label page revamp * chore: member select improvement for member setting page * chore: toggle switch component improvement * style: project automation setting ui improvement * style: module icon added * style: toggle switch improvement * style: ui and spacing consistency * style: project label setting revamp * style: project state setting ui improvement * chore: integration setting repo select validation * chore: code refactor * fix: build fix --- .../automation/auto-archive-automation.tsx | 93 ++-- .../automation/auto-close-automation.tsx | 177 +++---- web/components/core/image-picker-popover.tsx | 12 +- web/components/emoji-icon-picker/index.tsx | 13 +- web/components/estimates/single-estimate.tsx | 2 +- web/components/icons/index.ts | 1 + web/components/icons/module-icon.tsx | 59 +++ .../integration/github/select-repository.tsx | 2 + .../integration/slack/select-channel.tsx | 8 +- .../labels/create-update-label-inline.tsx | 16 +- web/components/labels/single-label-group.tsx | 73 +-- web/components/labels/single-label.tsx | 57 ++- web/components/project/index.ts | 3 +- web/components/project/member-select.tsx | 74 +++ web/components/project/settings-header.tsx | 13 - web/components/project/settings-sidebar.tsx | 72 +++ .../project/single-integration-card.tsx | 11 +- web/components/states/single-state.tsx | 73 +-- .../integration-and-import-export-banner.tsx | 4 +- web/components/ui/toggle-switch.tsx | 16 +- web/package.json | 2 +- .../[projectId]/settings/automations.tsx | 15 +- .../projects/[projectId]/settings/control.tsx | 241 --------- .../[projectId]/settings/estimates.tsx | 120 ++--- .../[projectId]/settings/features.tsx | 64 ++- .../projects/[projectId]/settings/index.tsx | 458 ++++++++++-------- .../[projectId]/settings/integrations.tsx | 12 +- .../projects/[projectId]/settings/labels.tsx | 29 +- .../projects/[projectId]/settings/members.tsx | 214 +++++++- .../projects/[projectId]/settings/states.tsx | 30 +- web/services/modules.service.ts | 2 +- 31 files changed, 1090 insertions(+), 876 deletions(-) create mode 100644 web/components/icons/module-icon.tsx create mode 100644 web/components/project/member-select.tsx delete mode 100644 web/components/project/settings-header.tsx create mode 100644 web/components/project/settings-sidebar.tsx delete mode 100644 web/pages/[workspaceSlug]/projects/[projectId]/settings/control.tsx diff --git a/web/components/automation/auto-archive-automation.tsx b/web/components/automation/auto-archive-automation.tsx index 50ab4f904..bb4e72e0c 100644 --- a/web/components/automation/auto-archive-automation.tsx +++ b/web/components/automation/auto-archive-automation.tsx @@ -3,8 +3,8 @@ import React, { useState } from "react"; // component import { CustomSelect, ToggleSwitch } from "components/ui"; import { SelectMonthModal } from "components/automation"; -// icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +// icon +import { ArchiveRestore } from "lucide-react"; // constants import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; // types @@ -28,14 +28,18 @@ export const AutoArchiveAutomation: React.FC = ({ projectDetails, handleC handleClose={() => setmonthModal(false)} handleChange={handleChange} /> -
-
-
-

Auto-archive closed issues

-

- Plane will automatically archive issues that have been completed or cancelled for the - configured time period. -

+
+
+
+
+ +
+
+

Auto-archive closed issues

+

+ Plane will auto archive issues that have been completed or canceled. +

+
= ({ projectDetails, handleC size="sm" />
- {projectDetails?.archive_in !== 0 && ( -
-
- Auto-archive issues that are closed for -
-
- { - handleChange({ archive_in: val }); - }} - input - verticalPosition="top" - width="w-full" - > - <> - {PROJECT_AUTOMATION_MONTHS.map((month) => ( - - {month.label} - - ))} - - - + {projectDetails?.archive_in !== 0 && ( +
+
+
+ Auto-archive issues that are closed for +
+
+ { + handleChange({ archive_in: val }); + }} + input + verticalPosition="bottom" + width="w-full" + > + <> + {PROJECT_AUTOMATION_MONTHS.map((month) => ( + + {month.label} + + ))} + + + + +
)} diff --git a/web/components/automation/auto-close-automation.tsx b/web/components/automation/auto-close-automation.tsx index f6cf95f2d..8235c8063 100644 --- a/web/components/automation/auto-close-automation.tsx +++ b/web/components/automation/auto-close-automation.tsx @@ -5,11 +5,12 @@ import useSWR from "swr"; import { useRouter } from "next/router"; // component -import { CustomSearchSelect, CustomSelect, ToggleSwitch } from "components/ui"; +import { CustomSearchSelect, CustomSelect, Icon, ToggleSwitch } from "components/ui"; import { SelectMonthModal } from "components/automation"; // icons -import { ChevronDownIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; +import { Squares2X2Icon } from "@heroicons/react/24/outline"; import { StateGroupIcon } from "components/icons"; +import { ArchiveX } from "lucide-react"; // services import stateService from "services/state.service"; // constants @@ -76,14 +77,18 @@ export const AutoCloseAutomation: React.FC = ({ projectDetails, handleCha handleChange={handleChange} /> -
-
-
-

Auto-close inactive issues

-

- Plane will automatically close the issues that have not been updated for the - configured time period. -

+
+
+
+
+ +
+
+

Auto-close issues

+

+ Plane will automatically close issue that haven’t been completed or canceled. +

+
= ({ projectDetails, handleCha size="sm" />
+ {projectDetails?.close_in !== 0 && ( -
-
-
- Auto-close issues that are inactive for +
+
+
+
+ Auto-close issues that are inactive for +
+
+ { + handleChange({ close_in: val }); + }} + input + width="w-full" + > + <> + {PROJECT_AUTOMATION_MONTHS.map((month) => ( + + {month.label} + + ))} + + + +
-
- { - handleChange({ close_in: val }); - }} - input - width="w-full" - > - <> - {PROJECT_AUTOMATION_MONTHS.map((month) => ( - - {month.label} - - ))} - - - -
-
-
-
Auto-close Status
-
- - {selectedOption ? ( - - ) : currentDefaultState ? ( - - ) : ( - - )} - {selectedOption?.name - ? selectedOption.name - : currentDefaultState?.name ?? ( - State - )} -
- } - onChange={(val: string) => { - handleChange({ default_state: val }); - }} - options={options} - disabled={!multipleOptions} - width="w-full" - input - /> + +
+
Auto-close Status
+
+ + {selectedOption ? ( + + ) : currentDefaultState ? ( + + ) : ( + + )} + {selectedOption?.name + ? selectedOption.name + : currentDefaultState?.name ?? ( + State + )} +
+ } + onChange={(val: string) => { + handleChange({ default_state: val }); + }} + options={options} + disabled={!multipleOptions} + width="w-full" + input + /> +
diff --git a/web/components/core/image-picker-popover.tsx b/web/components/core/image-picker-popover.tsx index 5f13d960e..957f1131c 100644 --- a/web/components/core/image-picker-popover.tsx +++ b/web/components/core/image-picker-popover.tsx @@ -20,6 +20,7 @@ import fileService from "services/file.service"; import { Input, Spinner, PrimaryButton, SecondaryButton } from "components/ui"; // hooks import useWorkspaceDetails from "hooks/use-workspace-details"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; const unsplashEnabled = process.env.NEXT_PUBLIC_UNSPLASH_ENABLED === "true" || @@ -67,6 +68,8 @@ export const ImagePickerPopover: React.FC = ({ fileService.getUnsplashImages(1, searchParams) ); + const imagePickerRef = useRef(null); + const { workspaceDetails } = useWorkspaceDetails(); const onDrop = useCallback((acceptedFiles: File[]) => { @@ -116,12 +119,14 @@ export const ImagePickerPopover: React.FC = ({ onChange(images[0].urls.regular); }, [value, onChange, images]); + useOutsideClickDetector(imagePickerRef, () => setIsOpen(false)); + if (!unsplashEnabled) return null; return ( setIsOpen((prev) => !prev)} disabled={disabled} > @@ -137,7 +142,10 @@ export const ImagePickerPopover: React.FC = ({ leaveTo="transform opacity-0 scale-95" > -
+
diff --git a/web/components/emoji-icon-picker/index.tsx b/web/components/emoji-icon-picker/index.tsx index 7af3bb74f..ab4eb022e 100644 --- a/web/components/emoji-icon-picker/index.tsx +++ b/web/components/emoji-icon-picker/index.tsx @@ -1,8 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; // headless ui import { Tab, Transition, Popover } from "@headlessui/react"; // react colors import { TwitterPicker } from "react-color"; +// hooks +import useOutsideClickDetector from "hooks/use-outside-click-detector"; // types import { Props } from "./types"; // emojis @@ -36,6 +38,8 @@ const EmojiIconPicker: React.FC = ({ const [recentEmojis, setRecentEmojis] = useState([]); + const emojiPickerRef = useRef(null); + useEffect(() => { setRecentEmojis(getRecentEmojis()); }, []); @@ -44,6 +48,8 @@ const EmojiIconPicker: React.FC = ({ if (!value || value?.length === 0) onChange(getRandomEmoji()); }, [value, onChange]); + useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false)); + return ( = ({ leaveTo="transform opacity-0 scale-95" > -
+
{tabOptions.map((tab) => ( diff --git a/web/components/estimates/single-estimate.tsx b/web/components/estimates/single-estimate.tsx index 3adf986ae..43edfcb2c 100644 --- a/web/components/estimates/single-estimate.tsx +++ b/web/components/estimates/single-estimate.tsx @@ -66,7 +66,7 @@ export const SingleEstimate: React.FC = ({ return ( <> -
+
diff --git a/web/components/icons/index.ts b/web/components/icons/index.ts index ab661a092..bf3e94332 100644 --- a/web/components/icons/index.ts +++ b/web/components/icons/index.ts @@ -84,3 +84,4 @@ export * from "./clock-icon"; export * from "./bell-icon"; export * from "./single-comment-icon"; export * from "./related-icon"; +export * from "./module-icon"; \ No newline at end of file diff --git a/web/components/icons/module-icon.tsx b/web/components/icons/module-icon.tsx new file mode 100644 index 000000000..dbe58eb53 --- /dev/null +++ b/web/components/icons/module-icon.tsx @@ -0,0 +1,59 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const ModuleIcon: React.FC = ({ + width = "24", + height = "24", + className, + color = "#F15B5B", +}) => ( + + + + + + + +); diff --git a/web/components/integration/github/select-repository.tsx b/web/components/integration/github/select-repository.tsx index 9857c0088..b46942e6d 100644 --- a/web/components/integration/github/select-repository.tsx +++ b/web/components/integration/github/select-repository.tsx @@ -66,6 +66,8 @@ export const SelectRepository: React.FC = ({ content:

{truncateText(repo.full_name, characterLimit)}

, })) ?? []; + if (userRepositories.length < 1) return null; + return ( = ({ integration }) => { {projectIntegration ? ( diff --git a/web/components/labels/create-update-label-inline.tsx b/web/components/labels/create-update-label-inline.tsx index 6306d14ca..61064e777 100644 --- a/web/components/labels/create-update-label-inline.tsx +++ b/web/components/labels/create-update-label-inline.tsx @@ -17,7 +17,7 @@ import issuesService from "services/issues.service"; // ui import { Input, PrimaryButton, SecondaryButton } from "components/ui"; // icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { Component } from "lucide-react"; // types import { IIssueLabels } from "types"; // fetch-keys @@ -132,7 +132,7 @@ export const CreateUpdateLabelInline = forwardRef( return (
( open ? "text-custom-text-100" : "text-custom-text-200" }`} > - -