- {members.length > 0
- ? members.map((member) => (
-
-
-
- {member.avatar && member.avatar !== "" ? (
-
- ) : member.first_name !== "" ? (
- member.first_name.charAt(0)
- ) : (
- member.email.charAt(0)
- )}
-
-
-
- {member.first_name} {member.last_name}
-
-
{member.email}
-
-
-
- {!member?.status && (
-
- )}
-
{
- workspaceService
- .updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
- role: value,
- })
- .then(() => {
- mutateMembers(
- (prevData) =>
- prevData?.map((m) =>
- m.id === member.id ? { ...m, role: value } : m
- ),
- false
- );
- setToastAlert({
- title: "Success",
- type: "success",
- message: "Member role updated successfully.",
- });
- })
- .catch(() => {
- setToastAlert({
- title: "Error",
- type: "error",
- message: "An error occurred while updating member role.",
- });
- });
- }}
- position="right"
- >
- {Object.keys(ROLE).map((key) => (
-
- <>{ROLE[parseInt(key) as keyof typeof ROLE]}>
-
- ))}
-
-
- {
- if (member.member) {
- setSelectedRemoveMember(member.id);
- } else {
- setSelectedInviteRemoveMember(member.id);
- }
- }}
- >
- Remove member
-
-
-
-
- ))
- : null}
+
+
+
+
+
Members
+
setInviteModal(true)}
+ >
+
+ Add Member
+
- )}
-
+ {!workspaceMembers || !workspaceInvitations ? (
+
+
+
+
+
+
+ ) : (
+
+ {members.length > 0
+ ? members.map((member) => (
+
+
+
+ {member.avatar && member.avatar !== "" ? (
+
+ ) : member.first_name !== "" ? (
+ member.first_name.charAt(0)
+ ) : (
+ member.email.charAt(0)
+ )}
+
+
+
+ {member.first_name} {member.last_name}
+
+
{member.email}
+
+
+
+ {!member?.status && (
+
+ )}
+
{
+ workspaceService
+ .updateWorkspaceMember(activeWorkspace?.slug as string, member.id, {
+ role: value,
+ })
+ .then(() => {
+ mutateMembers(
+ (prevData) =>
+ prevData?.map((m) =>
+ m.id === member.id ? { ...m, role: value } : m
+ ),
+ false
+ );
+ setToastAlert({
+ title: "Success",
+ type: "success",
+ message: "Member role updated successfully.",
+ });
+ })
+ .catch(() => {
+ setToastAlert({
+ title: "Error",
+ type: "error",
+ message: "An error occurred while updating member role.",
+ });
+ });
+ }}
+ position="right"
+ >
+ {Object.keys(ROLE).map((key) => (
+
+ <>{ROLE[parseInt(key) as keyof typeof ROLE]}>
+
+ ))}
+
+
+ {
+ if (member.member) {
+ setSelectedRemoveMember(member.id);
+ } else {
+ setSelectedInviteRemoveMember(member.id);
+ }
+ }}
+ >
+ Remove member
+
+
+
+
+ ))
+ : null}
+
+ )}
+
+
>
);
diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx
index 6f372b4e3..0f50b3b8c 100644
--- a/apps/app/pages/_app.tsx
+++ b/apps/app/pages/_app.tsx
@@ -8,6 +8,7 @@ import "styles/globals.css";
import "styles/editor.css";
import "styles/command-pallette.css";
import "styles/nprogress.css";
+import "styles/react-datepicker.css";
// router
import Router from "next/router";
diff --git a/apps/app/pages/signin.tsx b/apps/app/pages/signin.tsx
index 3dee9a9e6..ee2469b86 100644
--- a/apps/app/pages/signin.tsx
+++ b/apps/app/pages/signin.tsx
@@ -1,8 +1,6 @@
import React, { useCallback, useState } from "react";
-
import { useRouter } from "next/router";
import Image from "next/image";
-
// hooks
import useUser from "hooks/use-user";
import useToast from "hooks/use-toast";
diff --git a/apps/app/services/api.service.ts b/apps/app/services/api.service.ts
index a625c0b37..958205005 100644
--- a/apps/app/services/api.service.ts
+++ b/apps/app/services/api.service.ts
@@ -1,6 +1,21 @@
import axios from "axios";
import Cookies from "js-cookie";
+const unAuthorizedStatus = [401];
+axios.interceptors.response.use(
+ (response) => response,
+ (error) => {
+ const { status }: any = error.response;
+ if (unAuthorizedStatus.includes(status)) {
+ Cookies.remove("refreshToken", { path: "/" });
+ Cookies.remove("accessToken", { path: "/" });
+ console.log("window.location.href", window.location.pathname);
+ if (window.location.pathname != "/signin") window.location.href = "/signin";
+ }
+ return Promise.reject(error);
+ }
+);
+
abstract class APIService {
protected baseURL: string;
protected headers: any = {};
diff --git a/apps/app/services/estimates.service.ts b/apps/app/services/estimates.service.ts
index 0718f286b..d64f37f28 100644
--- a/apps/app/services/estimates.service.ts
+++ b/apps/app/services/estimates.service.ts
@@ -1,10 +1,14 @@
// services
import APIService from "services/api.service";
// types
-import type { IEstimate, IEstimateFormData, IEstimatePoint } from "types";
+import type { IEstimate, IEstimateFormData } from "types";
+import trackEventServices from "services/track-event.service";
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 ProjectEstimateServices extends APIService {
constructor() {
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
@@ -16,7 +20,11 @@ class ProjectEstimateServices extends APIService {
data: IEstimateFormData
): Promise
{
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/`, data)
- .then((response) => response?.data)
+ .then((response) => {
+ if (trackEvent)
+ trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_CREATE");
+ return response?.data;
+ })
.catch((error) => {
throw error?.response;
});
@@ -32,7 +40,11 @@ class ProjectEstimateServices extends APIService {
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`,
data
)
- .then((response) => response?.data)
+ .then((response) => {
+ if (trackEvent)
+ trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_UPDATE");
+ return response?.data;
+ })
.catch((error) => {
throw error?.response?.data;
});
@@ -64,7 +76,11 @@ class ProjectEstimateServices extends APIService {
return this.delete(
`/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/`
)
- .then((response) => response?.data)
+ .then((response) => {
+ if (trackEvent)
+ trackEventServices.trackIssueEstimateEvent(response?.data, "ESTIMATE_DELETE");
+ return response?.data;
+ })
.catch((error) => {
throw error?.response?.data;
});
diff --git a/apps/app/services/integration/github.service.ts b/apps/app/services/integration/github.service.ts
index 641d2cd2c..101e7ac67 100644
--- a/apps/app/services/integration/github.service.ts
+++ b/apps/app/services/integration/github.service.ts
@@ -1,10 +1,14 @@
import APIService from "services/api.service";
+import trackEventServices from "services/track-event.service";
+
import { IGithubRepoInfo, IGithubServiceImportFormData } from "types";
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
-const integrationServiceType: string = "github";
+const trackEvent =
+ process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1";
+const integrationServiceType: string = "github";
class GithubIntegrationService extends APIService {
constructor() {
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
@@ -41,7 +45,11 @@ class GithubIntegrationService extends APIService {
`/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`,
data
)
- .then((response) => response?.data)
+ .then((response) => {
+ if (trackEvent)
+ trackEventServices.trackImporterEvent(response?.data, "GITHUB_IMPORTER_CREATE");
+ return response?.data;
+ })
.catch((error) => {
throw error?.response?.data;
});
diff --git a/apps/app/services/integration/index.ts b/apps/app/services/integration/index.ts
index 425087085..51ecd33c1 100644
--- a/apps/app/services/integration/index.ts
+++ b/apps/app/services/integration/index.ts
@@ -1,9 +1,14 @@
import APIService from "services/api.service";
+import trackEventServices from "services/track-event.service";
+
// types
import { IAppIntegration, IImporterService, IWorkspaceIntegration } 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 IntegrationService extends APIService {
constructor() {
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
@@ -49,7 +54,12 @@ class IntegrationService extends APIService {
importerId: string
): Promise {
return this.delete(`/api/workspaces/${workspaceSlug}/importers/${service}/${importerId}/`)
- .then((res) => res?.data)
+ .then((response) => {
+ const eventName = service === "github" ? "GITHUB_IMPORTER_DELETE" : "JIRA_IMPORTER_DELETE";
+
+ if (trackEvent) trackEventServices.trackImporterEvent(response?.data, eventName);
+ return response?.data;
+ })
.catch((error) => {
throw error?.response?.data;
});
diff --git a/apps/app/services/integration/jira.service.ts b/apps/app/services/integration/jira.service.ts
index 20ad8166a..456530308 100644
--- a/apps/app/services/integration/jira.service.ts
+++ b/apps/app/services/integration/jira.service.ts
@@ -1,10 +1,14 @@
import APIService from "services/api.service";
+import trackEventServices from "services/track-event.service";
// types
import { IJiraMetadata, IJiraResponse, IJiraImporterForm } 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 JiraImportedService extends APIService {
constructor() {
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
@@ -22,7 +26,11 @@ class JiraImportedService extends APIService {
async createJiraImporter(workspaceSlug: string, data: IJiraImporterForm): Promise {
return this.post(`/api/workspaces/${workspaceSlug}/projects/importers/jira/`, data)
- .then((response) => response?.data)
+ .then((response) => {
+ if (trackEvent)
+ trackEventServices.trackImporterEvent(response?.data, "JIRA_IMPORTER_CREATE");
+ return response?.data;
+ })
.catch((error) => {
throw error?.response?.data;
});
diff --git a/apps/app/services/track-event.service.ts b/apps/app/services/track-event.service.ts
index 5d7ed32f1..54de7119b 100644
--- a/apps/app/services/track-event.service.ts
+++ b/apps/app/services/track-event.service.ts
@@ -7,6 +7,7 @@ const trackEvent =
// types
import type {
ICycle,
+ IEstimate,
IGptResponse,
IIssue,
IIssueComment,
@@ -45,7 +46,10 @@ type PagesEventType = "PAGE_CREATE" | "PAGE_UPDATE" | "PAGE_DELETE";
type ViewEventType = "VIEW_CREATE" | "VIEW_UPDATE" | "VIEW_DELETE";
-type IssueCommentType = "ISSUE_COMMENT_CREATE" | "ISSUE_COMMENT_UPDATE" | "ISSUE_COMMENT_DELETE";
+type IssueCommentEventType =
+ | "ISSUE_COMMENT_CREATE"
+ | "ISSUE_COMMENT_UPDATE"
+ | "ISSUE_COMMENT_DELETE";
export type MiscellaneousEventType =
| "TOGGLE_CYCLE_ON"
@@ -73,6 +77,13 @@ type IssueLabelEventType = "ISSUE_LABEL_CREATE" | "ISSUE_LABEL_UPDATE" | "ISSUE_
type GptEventType = "ASK_GPT" | "USE_GPT_RESPONSE_IN_ISSUE" | "USE_GPT_RESPONSE_IN_PAGE_BLOCK";
+type IssueEstimateEventType = "ESTIMATE_CREATE" | "ESTIMATE_UPDATE" | "ESTIMATE_DELETE";
+
+type ImporterEventType =
+ | "GITHUB_IMPORTER_CREATE"
+ | "GITHUB_IMPORTER_DELETE"
+ | "JIRA_IMPORTER_CREATE"
+ | "JIRA_IMPORTER_DELETE";
class TrackEventServices extends APIService {
constructor() {
super("/");
@@ -209,7 +220,7 @@ class TrackEventServices extends APIService {
async trackIssueCommentEvent(
data: Partial | any,
- eventName: IssueCommentType
+ eventName: IssueCommentEventType
): Promise {
let payload: any;
if (eventName !== "ISSUE_COMMENT_DELETE")
@@ -549,6 +560,61 @@ class TrackEventServices extends APIService {
},
});
}
+
+ async trackIssueEstimateEvent(
+ data: { estimate: IEstimate },
+ eventName: IssueEstimateEventType
+ ): Promise {
+ let payload: any;
+ if (eventName === "ESTIMATE_DELETE") payload = data;
+ else
+ payload = {
+ workspaceId: data?.estimate?.workspace_detail?.id,
+ workspaceName: data?.estimate?.workspace_detail?.name,
+ workspaceSlug: data?.estimate?.workspace_detail?.slug,
+ projectId: data?.estimate?.project_detail?.id,
+ projectName: data?.estimate?.project_detail?.name,
+ projectIdentifier: data?.estimate?.project_detail?.identifier,
+ estimateId: data.estimate?.id,
+ };
+
+ return this.request({
+ url: "/api/track-event",
+ method: "POST",
+ data: {
+ eventName,
+ extra: {
+ ...payload,
+ },
+ },
+ });
+ }
+
+ async trackImporterEvent(data: any, eventName: ImporterEventType): Promise {
+ let payload: any;
+ if (eventName === "GITHUB_IMPORTER_DELETE" || eventName === "JIRA_IMPORTER_DELETE")
+ payload = data;
+ else
+ payload = {
+ workspaceId: data?.workspace_detail?.id,
+ workspaceName: data?.workspace_detail?.name,
+ workspaceSlug: data?.workspace_detail?.slug,
+ projectId: data?.project_detail?.id,
+ projectName: data?.project_detail?.name,
+ projectIdentifier: data?.project_detail?.identifier,
+ };
+
+ return this.request({
+ url: "/api/track-event",
+ method: "POST",
+ data: {
+ eventName,
+ extra: {
+ ...payload,
+ },
+ },
+ });
+ }
}
const trackEventServices = new TrackEventServices();
diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css
index 8ca808544..4bdd47dbe 100644
--- a/apps/app/styles/globals.css
+++ b/apps/app/styles/globals.css
@@ -112,6 +112,7 @@ body {
.horizontal-scroll-enable::-webkit-scrollbar {
display: block;
height: 7px;
+ width: 0;
}
.horizontal-scroll-enable::-webkit-scrollbar-track {
diff --git a/apps/app/styles/react-datepicker.css b/apps/app/styles/react-datepicker.css
new file mode 100644
index 000000000..3c5f9a5ca
--- /dev/null
+++ b/apps/app/styles/react-datepicker.css
@@ -0,0 +1,118 @@
+.react-datepicker-wrapper input::placeholder {
+ color: rgba(var(--color-text-secondary));
+ opacity: 1;
+}
+
+.react-datepicker-wrapper input:-ms-input-placeholder {
+ color: rgba(var(--color-text-secondary));
+}
+
+.react-datepicker-wrapper .react-datepicker__close-icon::after {
+ background: transparent;
+ color: rgba(var(--color-text-secondary));
+}
+
+.react-datepicker-popper {
+ z-index: 30 !important;
+}
+
+.react-datepicker-wrapper {
+ position: relative;
+ background-color: rgba(var(--color-bg-base)) !important;
+}
+
+.react-datepicker {
+ font-family: "Inter" !important;
+ border: none !important;
+ background-color: rgba(var(--color-bg-base)) !important;
+}
+
+.react-datepicker__month-container {
+ width: 300px;
+ background-color: rgba(var(--color-bg-base)) !important;
+ color: rgba(var(--color-text-base)) !important;
+ border-radius: 10px !important;
+ /* border: 1px solid rgba(var(--color-border)) !important; */
+}
+
+.react-datepicker__header {
+ border-radius: 10px !important;
+ background-color: rgba(var(--color-bg-base)) !important;
+ border: none !important;
+}
+
+.react-datepicker__navigation {
+ line-height: 0.78;
+}
+
+.react-datepicker__triangle {
+ border-color: rgba(var(--color-bg-base)) transparent transparent transparent !important;
+}
+
+.react-datepicker__triangle:before {
+ border-bottom-color: rgba(var(--color-border)) !important;
+}
+.react-datepicker__triangle:after {
+ border-bottom-color: rgba(var(--color-bg-base)) !important;
+}
+
+.react-datepicker__current-month {
+ font-weight: 500 !important;
+ color: rgba(var(--color-text-base)) !important;
+}
+
+.react-datepicker__month {
+ border-collapse: collapse;
+ color: rgba(var(--color-text-base)) !important;
+}
+
+.react-datepicker__day-names {
+ margin-top: 10px;
+ margin-left: 14px;
+ width: 280px;
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 0;
+}
+
+.react-datepicker__day-name {
+ color: rgba(var(--color-text-base)) !important;
+}
+
+.react-datepicker__week {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ margin-left: 8px;
+}
+
+.react-datepicker__day {
+ color: rgba(var(--color-text-base)) !important;
+}
+
+.react-datepicker__day {
+ border-radius: 50% !important;
+ transition: all 0.15s ease-in-out;
+}
+
+.react-datepicker__day:hover {
+ background-color: rgba(var(--color-bg-surface-2)) !important;
+ color: rgba(var(--color-text-base)) !important;
+}
+
+.react-datepicker__day--selected {
+ background-color: #216ba5 !important;
+ color: white !important;
+}
+
+.react-datepicker__day--today {
+ font-weight: 800;
+}
+
+.react-datepicker__day--highlighted {
+ background-color: rgba(var(--color-bg-surface-2)) !important;
+}
+
+.react-datepicker__day--keyboard-selected {
+ background-color: #216ba5 !important;
+ color: white !important;
+}
diff --git a/apps/app/tailwind.config.js b/apps/app/tailwind.config.js
index 9dde3bb74..3e2a2da4b 100644
--- a/apps/app/tailwind.config.js
+++ b/apps/app/tailwind.config.js
@@ -12,10 +12,6 @@ module.exports = {
theme: {
extend: {
colors: {
- theme: "#3f76ff",
- "hover-gray": "#f5f5f5",
- primary: "#f9fafb", // gray-50
- secondary: "white",
brand: {
accent: withOpacity("--color-accent"),
base: withOpacity("--color-bg-base"),
diff --git a/apps/app/types/estimate.d.ts b/apps/app/types/estimate.d.ts
index 6d14a20ca..32925c793 100644
--- a/apps/app/types/estimate.d.ts
+++ b/apps/app/types/estimate.d.ts
@@ -8,7 +8,9 @@ export interface IEstimate {
updated_by: string;
points: IEstimatePoint[];
project: string;
+ project_detail: IProject;
workspace: string;
+ workspace_detail: IWorkspace;
}
export interface IEstimatePoint {
diff --git a/docker-compose-hub.yml b/docker-compose-hub.yml
index 3f67ef48b..435e47b29 100644
--- a/docker-compose-hub.yml
+++ b/docker-compose-hub.yml
@@ -35,24 +35,46 @@ services:
- redisdata:/data
plane-web:
container_name: planefrontend
- image: makeplane/plane-frontend:0.5-dev
+ image: makeplane/plane-frontend:0.6
restart: always
- command: node apps/app/server.js
- env_file:
- - ./apps/app/.env
+ command: [ "/usr/local/bin/start.sh" ]
+ environment:
+ NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_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
ports:
- 3000:3000
plane-api:
container_name: planebackend
- image: makeplane/plane-backend:0.5-dev
+ image: makeplane/plane-backend:0.6
build:
context: ./apiserver
dockerfile: Dockerfile.api
restart: always
ports:
- 8000:8000
- env_file:
- - ./apiserver/.env
+ environment:
+ DJANGO_SETTINGS_MODULE: plane.settings.production
+ DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane
+ REDIS_URL: redis://redis:6379/
+ EMAIL_HOST: ${EMAIL_HOST}
+ EMAIL_HOST_USER: ${EMAIL_HOST_USER}
+ EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
+ 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}
+ WEB_URL: localhost/
+ GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
+ DISABLE_COLLECTSTATIC: 1
+ DOCKERIZED: 1
+ OPENAI_API_KEY: ${OPENAI_API_KEY}
+ GPT_ENGINE: ${GPT_ENGINE}
+ SECRET_KEY: ${SECRET_KEY}
depends_on:
- db
- redis
@@ -62,7 +84,7 @@ services:
- redis:redis
plane-worker:
container_name: planerqworker
- image: makeplane/plane-worker:0.5-dev
+ image: makeplane/plane-worker:0.6
depends_on:
- redis
- db
@@ -71,8 +93,24 @@ services:
links:
- redis:redis
- db:db
- env_file:
- - ./apiserver/.env
+ environment:
+ DJANGO_SETTINGS_MODULE: plane.settings.production
+ DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane
+ REDIS_URL: redis://redis:6379/
+ EMAIL_HOST: ${EMAIL_HOST}
+ EMAIL_HOST_USER: ${EMAIL_HOST_USER}
+ EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
+ 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}
+ WEB_URL: localhost/
+ GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
+ DISABLE_COLLECTSTATIC: 1
+ DOCKERIZED: 1
+ OPENAI_API_KEY: ${OPENAI_API_KEY}
+ GPT_ENGINE: ${GPT_ENGINE}
+ SECRET_KEY: ${SECRET_KEY}
volumes:
pgdata:
redisdata:
diff --git a/docker-compose.yml b/docker-compose.yml
index 8d05e03cd..e4086acb2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -38,12 +38,21 @@ services:
build:
context: .
dockerfile: ./apps/app/Dockerfile.web
- restart: always
- command: node apps/app/server.js
- env_file:
- - ./apps/app/.env
+ args:
+ NEXT_PUBLIC_API_BASE_URL: http://localhost:8000
+ command: [ "/usr/local/bin/start.sh" ]
ports:
- 3000:3000
+ environment:
+ NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_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"
plane-api:
container_name: planebackend
build:
@@ -52,8 +61,24 @@ services:
restart: always
ports:
- 8000:8000
- env_file:
- - ./apiserver/.env
+ environment:
+ DJANGO_SETTINGS_MODULE: plane.settings.production
+ DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane
+ REDIS_URL: redis://redis:6379/
+ EMAIL_HOST: ${EMAIL_HOST}
+ EMAIL_HOST_USER: ${EMAIL_HOST_USER}
+ EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
+ 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}
+ WEB_URL: localhost/
+ GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
+ DISABLE_COLLECTSTATIC: 1
+ DOCKERIZED: 1
+ OPENAI_API_KEY: ${OPENAI_API_KEY}
+ GPT_ENGINE: ${GPT_ENGINE}
+ SECRET_KEY: ${SECRET_KEY}
depends_on:
- db
- redis
@@ -74,8 +99,24 @@ services:
links:
- redis:redis
- db:db
- env_file:
- - ./apiserver/.env
+ environment:
+ DJANGO_SETTINGS_MODULE: plane.settings.production
+ DATABASE_URL: postgres://plane:xyzzyspoon@db:5432/plane
+ REDIS_URL: redis://redis:6379/
+ EMAIL_HOST: ${EMAIL_HOST}
+ EMAIL_HOST_USER: ${EMAIL_HOST_USER}
+ EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
+ 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}
+ WEB_URL: localhost/
+ GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
+ DISABLE_COLLECTSTATIC: 1
+ DOCKERIZED: 1
+ OPENAI_API_KEY: ${OPENAI_API_KEY}
+ GPT_ENGINE: ${GPT_ENGINE}
+ SECRET_KEY: ${SECRET_KEY}
volumes:
pgdata:
redisdata:
diff --git a/nginx/supervisor.conf b/nginx/supervisor.conf
index db615812e..54b4ca04d 100644
--- a/nginx/supervisor.conf
+++ b/nginx/supervisor.conf
@@ -2,7 +2,7 @@
nodaemon=true
[program:node]
-command=node /app/apps/app/server.js
+command=sh /usr/local/bin/start.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/node.err.log
@@ -21,4 +21,12 @@ command=nginx -g "daemon off;"
autostart=true
autorestart=true
stderr_logfile=/var/log/nginx.err.log
-stdout_logfile=/var/log/nginx.out.log
\ No newline at end of file
+stdout_logfile=/var/log/nginx.out.log
+
+[program:worker]
+directory=/code
+command=sh bin/worker
+autostart=true
+autorestart=true
+stderr_logfile=/var/log/worker.err.log
+stdout_logfile=/var/log/worker.out.log
\ No newline at end of file
diff --git a/replace-env-vars.sh b/replace-env-vars.sh
new file mode 100644
index 000000000..fe7acc698
--- /dev/null
+++ b/replace-env-vars.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+FROM=$1
+TO=$2
+
+if [ "${FROM}" = "${TO}" ]; then
+ echo "Nothing to replace, the value is already set to ${TO}."
+
+ exit 0
+fi
+
+# Only peform action if $FROM and $TO are different.
+echo "Replacing all statically built instances of $FROM with this string $TO ."
+
+find apps/app/.next -type f |
+while read file; do
+ sed -i "s|$FROM|$TO|g" "$file"
+done
\ No newline at end of file
diff --git a/setup.sh b/setup.sh
index de95db2f8..e7f9a52dd 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,9 +1,7 @@
#!/bin/bash
-cp ./apiserver/.env.example ./apiserver/.env
-# Generating App environmental variables
-cp ./apps/app/.env.example ./apps/app/.env
+cp ./.env.example ./.env
-echo -e "\nNEXT_PUBLIC_API_BASE_URL=http://$1" >> ./apps/app/.env
+echo -e "\nNEXT_PUBLIC_API_BASE_URL=http://$1" >> ./.env
export LC_ALL=C
export LC_CTYPE=C
-echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
+echo -e "\nSECRET_KEY=\"$(tr -dc 'a-z0-9!@#$%^&*(-_=+)' < /dev/urandom | head -c50)\"" >> ./.env
diff --git a/start.sh b/start.sh
new file mode 100644
index 000000000..173e333a4
--- /dev/null
+++ b/start.sh
@@ -0,0 +1,9 @@
+#!/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"
+
+echo "Starting Plane Frontend.."
+node apps/app/server.js
\ No newline at end of file
diff --git a/turbo.json b/turbo.json
index 12476ceb9..1eba6fc9a 100644
--- a/turbo.json
+++ b/turbo.json
@@ -4,6 +4,7 @@
"NEXT_PUBLIC_GITHUB_ID",
"NEXT_PUBLIC_GOOGLE_CLIENTID",
"NEXT_PUBLIC_API_BASE_URL",
+ "API_BASE_URL",
"NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_AUTH_TOKEN",
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
@@ -17,6 +18,7 @@
"NEXT_PUBLIC_CRISP_ID",
"NEXT_PUBLIC_ENABLE_SESSION_RECORDER",
"NEXT_PUBLIC_SESSION_RECORDER_KEY",
+ "NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS",
"NEXT_PUBLIC_SLACK_CLIENT_ID",
"NEXT_PUBLIC_SLACK_CLIENT_SECRET"
],
diff --git a/yarn.lock b/yarn.lock
index a81d16621..65c491ddc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4229,6 +4229,11 @@ dot-case@^3.0.4:
no-case "^3.0.4"
tslib "^2.0.3"
+dotenv@^16.0.3:
+ version "16.0.3"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
+ integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
+
ejs@^3.1.6:
version "3.1.8"
resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz"