Skip to content
1 change: 1 addition & 0 deletions apps/admin/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_API_BASE_URL="http://localhost:8000"

VITE_WEB_BASE_URL="http://localhost:3000"
VITE_WEB_BASE_PATH=""

VITE_ADMIN_BASE_URL="http://localhost:3001"
VITE_ADMIN_BASE_PATH="/god-mode"
Expand Down
4 changes: 2 additions & 2 deletions apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { observer } from "mobx-react";
import Link from "next/link";
import { HelpCircle, MoveLeft } from "lucide-react";
import { Transition } from "@headlessui/react";
import { WEB_BASE_URL } from "@plane/constants";
import { WEB_URL } from "@plane/constants";
// plane internal packages
import { DiscordIcon, GithubIcon, NewTabIcon, PageIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
Expand Down Expand Up @@ -45,7 +45,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);

const redirectionLink = encodeURI(WEB_BASE_URL + "/");
const redirectionLink = WEB_URL;

return (
<div
Expand Down
4 changes: 2 additions & 2 deletions apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
// plane imports
import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants";
import { WEB_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { InstanceWorkspaceService } from "@plane/services";
Expand Down Expand Up @@ -43,7 +43,7 @@ export function WorkspaceCreateForm() {
formState: { errors, isSubmitting, isValid },
} = useForm<IWorkspace>({ defaultValues, mode: "onChange" });
// derived values
const workspaceBaseURL = encodeURI(WEB_BASE_URL || window.location.origin + "/");
const workspaceBaseURL = WEB_URL || encodeURI(window.location.origin + "/");

const handleCreateWorkspace = async (formData: IWorkspace) => {
await instanceWorkspaceService
Expand Down
15 changes: 13 additions & 2 deletions apps/admin/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See the LICENSE file for details.
*/

import type { ReactNode } from "react";
import React, { type ReactNode } from "react";
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
import * as Sentry from "@sentry/react-router";
Expand All @@ -14,6 +14,7 @@ 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 { joinUrlPath } from "@plane/utils";
import { AppProviders } from "@/providers";
import type { Route } from "./+types/root";
// fonts
Expand All @@ -25,13 +26,15 @@ import "@fontsource/ibm-plex-mono";
const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
const APP_DESCRIPTION =
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.";
const WEB_BASE_PATH =
(typeof import.meta !== "undefined" && import.meta.env?.BASE_URL) || process.env.VITE_ADMIN_BASE_PATH || "/god-mode";

export const links: LinksFunction = () => [
{ rel: "apple-touch-icon", sizes: "180x180", href: appleTouchIcon },
{ rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 },
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
{ rel: "shortcut icon", href: faviconIco },
{ rel: "manifest", href: `/site.webmanifest.json` },
{ rel: "manifest", href: joinUrlPath(WEB_BASE_PATH, "site.webmanifest.json") },
{ rel: "stylesheet", href: globalStyles },
{
rel: "preload",
Expand Down Expand Up @@ -82,6 +85,14 @@ export default function Root() {
}

export function HydrateFallback() {
const [isMounted, setIsMounted] = React.useState(false);

React.useEffect(() => {
setIsMounted(true);
}, []);

if (typeof window === "undefined" || !isMounted) return <div />;

return (
<div className="relative flex h-screen w-full items-center justify-center">
<LogoSpinner />
Expand Down
9 changes: 4 additions & 5 deletions apps/admin/core/components/workspace/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { observer } from "mobx-react";

// plane internal packages
import { WEB_BASE_URL } from "@plane/constants";
import { WEB_URL } from "@plane/constants";
import { NewTabIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { getFileURL } from "@plane/utils";
Expand All @@ -28,16 +28,15 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
return (
<a
key={workspaceId}
href={`${WEB_BASE_URL}/${encodeURIComponent(workspace.slug)}`}
href={`${WEB_URL}/${encodeURIComponent(workspace.slug)}`}
target="_blank"
className="group flex items-center justify-between p-3 gap-2.5 truncate border border-subtle hover:border-subtle-1 bg-layer-1 hover:bg-layer-1-hover hover:shadow-raised-100 rounded-lg"
rel="noreferrer"
>
<div className="flex items-start gap-4">
<span
className={`relative flex h-8 w-8 flex-shrink-0 items-center justify-center p-2 mt-1 text-11 uppercase ${
!workspace?.logo_url && "rounded-lg bg-accent-primary text-on-color"
}`}
className={`relative flex h-8 w-8 flex-shrink-0 items-center justify-center p-2 mt-1 text-11 uppercase ${!workspace?.logo_url && "rounded-lg bg-accent-primary text-on-color"
}`}
>
{workspace?.logo_url && workspace.logo_url !== "" ? (
<img
Expand Down
12 changes: 10 additions & 2 deletions apps/admin/public/site.webmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
"background_color": "#f9fafb",
"theme_color": "#3f76ff",
"icons": [
{ "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
{
"src": "favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
3 changes: 2 additions & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ USE_MINIO=0



# Email redirections and minio domain settings
# Email redirections and minio domain settings (include subpath if any, e.g., http://localhost:3000/plane)
WEB_URL="http://localhost:8000"

# Gunicorn Workers
Expand All @@ -58,6 +58,7 @@ SPACE_BASE_URL="http://localhost:3002"
SPACE_BASE_PATH="/spaces"

APP_BASE_URL="http://localhost:3000"
# Base path for the app (e.g., /plane)
APP_BASE_PATH=""

LIVE_BASE_URL="http://localhost:3100"
Expand Down
1 change: 1 addition & 0 deletions apps/space/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_API_BASE_URL="http://localhost:8000"

VITE_WEB_BASE_URL="http://localhost:3000"
VITE_WEB_BASE_PATH=""

VITE_ADMIN_BASE_URL="http://localhost:3001"
VITE_ADMIN_BASE_PATH="/god-mode"
Expand Down
9 changes: 9 additions & 0 deletions apps/space/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* See the LICENSE file for details.
*/

import React from "react";
import * as Sentry from "@sentry/react-router";
import { Links, Meta, Outlet, Scripts } from "react-router";
// assets
Expand Down Expand Up @@ -89,6 +90,14 @@ export default function Root() {
}

export function HydrateFallback() {
const [isMounted, setIsMounted] = React.useState(false);

React.useEffect(() => {
setIsMounted(true);
}, []);

if (typeof window === "undefined" || !isMounted) return <div />;

return (
<div className="bg-surface-1 relative flex h-screen w-full items-center justify-center">
<LogoSpinner />
Expand Down
12 changes: 10 additions & 2 deletions apps/space/public/site.webmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
"background_color": "#f9fafb",
"theme_color": "#3f76ff",
"icons": [
{ "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
{
"src": "favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
1 change: 1 addition & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_API_BASE_URL="http://localhost:8000"

VITE_WEB_BASE_URL="http://localhost:3000"
VITE_WEB_BASE_PATH=""

VITE_ADMIN_BASE_URL="http://localhost:3001"
VITE_ADMIN_BASE_PATH="/god-mode"
Expand Down
3 changes: 3 additions & 0 deletions apps/web/Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ ENV VITE_SPACE_BASE_PATH=$VITE_SPACE_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

ENV NEXT_TELEMETRY_DISABLED=1
ENV TURBO_TELEMETRY_DISABLED=1

Expand Down
7 changes: 4 additions & 3 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import "@/styles/globals.css";
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";

// helpers
import { cn } from "@plane/utils";
import { cn, joinUrlPath } from "@plane/utils";

// assets
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
Expand Down Expand Up @@ -57,14 +57,16 @@ export const meta = () => [

export default function RootLayout({ children }: { children: React.ReactNode }) {
const isSessionRecorderEnabled = parseInt(process.env.VITE_ENABLE_SESSION_RECORDER || "0");
const WEB_BASE_PATH =
(typeof import.meta !== "undefined" && import.meta.env?.BASE_URL) || process.env.VITE_WEB_BASE_PATH || "/";

return (
<html lang="en">
<head>
<meta name="theme-color" content="#fff" />
<link rel="icon" type="image/png" sizes="32x32" href={favicon32} />
<link rel="icon" type="image/png" sizes="16x16" href={favicon16} />
<link rel="manifest" href="/site.webmanifest.json" />
<link rel="manifest" href={joinUrlPath(WEB_BASE_PATH, "site.webmanifest.json")} />
<link rel="shortcut icon" href={faviconIco} />
{/* Meta info for PWA */}
<meta name="application-name" content="Plane" />
Expand All @@ -76,7 +78,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<link rel="apple-touch-icon" href={icon512} />
<link rel="apple-touch-icon" sizes="180x180" href={icon180} />
<link rel="apple-touch-icon" sizes="512x512" href={icon512} />
<link rel="manifest" href="/manifest.json" />
</head>
<body>
<div id="context-menu-portal" />
Expand Down
18 changes: 13 additions & 5 deletions apps/web/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* See the LICENSE file for details.
*/

import type { ReactNode } from "react";
import React from "react";
import type {ReactNode} from "react";
import * as Sentry from "@sentry/react-router";
import Script from "next/script";
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
import { ThemeProvider, useTheme } from "next-themes";
// plane imports
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";
import { cn } from "@plane/utils";
import { cn, joinUrlPath } from "@plane/utils";
// types
// assets
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
Expand All @@ -35,16 +36,18 @@ import "@fontsource/material-symbols-rounded";
import "@fontsource/ibm-plex-mono";

const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
const WEB_BASE_PATH =
(typeof import.meta !== "undefined" && import.meta.env?.BASE_URL) || process.env.VITE_WEB_BASE_PATH || "/";

export const links: LinksFunction = () => [
{ rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 },
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
{ rel: "shortcut icon", href: faviconIco },
{ rel: "manifest", href: "/site.webmanifest.json" },
{ rel: "manifest", href: joinUrlPath(WEB_BASE_PATH, "site.webmanifest.json") },
{ rel: "apple-touch-icon", href: icon512 },
{ rel: "apple-touch-icon", sizes: "180x180", href: icon180 },
{ rel: "apple-touch-icon", sizes: "512x512", href: icon512 },
{ rel: "manifest", href: "/manifest.json" },

{ rel: "stylesheet", href: globalStyles },
{
rel: "preload",
Expand Down Expand Up @@ -135,9 +138,14 @@ export default function Root() {

export function HydrateFallback() {
const { resolvedTheme } = useTheme();
const [isMounted, setIsMounted] = React.useState(false);

React.useEffect(() => {
setIsMounted(true);
}, []);

// if we are on the server or the theme is not resolved, return an empty div
if (typeof window === "undefined" || resolvedTheme === undefined) return <div />;
if (typeof window === "undefined" || !isMounted || resolvedTheme === undefined) return <div />;

return (
<div className="relative flex bg-canvas h-screen w-full items-center justify-center">
Expand Down
8 changes: 4 additions & 4 deletions apps/web/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
"short_name": "Plane",
"icons": [
{
"src": "/icons/icon-192x192.png",
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-348x348.png",
"src": "icons/icon-348x348.png",
"sizes": "348x348",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#FFFFFF",
"background_color": "#FFFFFF",
"start_url": "/",
"start_url": ".",
"display": "standalone",
"orientation": "portrait"
}
12 changes: 10 additions & 2 deletions apps/web/public/site.webmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
"background_color": "#f9fafb",
"theme_color": "#3f76ff",
"icons": [
{ "src": "/plane-logos/plane-mobile-pwa.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/plane-logos/plane-mobile-pwa.png", "sizes": "512x512", "type": "image/png" }
{
"src": "plane-logos/plane-mobile-pwa.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "plane-logos/plane-mobile-pwa.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
4 changes: 4 additions & 0 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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";

dotenv.config({ path: path.resolve(__dirname, ".env") });

Expand All @@ -14,7 +15,10 @@ const viteEnv = Object.keys(process.env)
return a;
}, {});

const basePath = joinUrlPath(process.env.VITE_WEB_BASE_PATH ?? "", "/") ?? "/";

export default defineConfig(() => ({
base: basePath,
define: {
"process.env": JSON.stringify(viteEnv),
},
Expand Down
1 change: 1 addition & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"DEV",
"LOG_LEVEL",
"NODE_ENV",
"BASE_URL",
"SENTRY_DSN",
"SENTRY_ENVIRONMENT",
"SENTRY_TRACES_SAMPLE_RATE",
Expand Down