diff --git a/.env.example b/.env.example index a0f48f8..9753add 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,9 @@ BETTER_AUTH_URL=http://localhost:3000 # Set to "true" to disable new user signups (existing users can still sign in) # DISABLE_SIGNUPS=true +# Set to "true" for self-hosted instances (skips marketing landing page) +# SELF_HOSTED=true + # Email (SMTP) SMTP_HOST=smtp.example.com SMTP_PORT=587 diff --git a/README.md b/README.md index 09146a8..e4f1352 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,23 @@ Manage is an open-source project management platform. With its intuitive interfa # See .env.example for all available environment variables ``` -## Deployment +## Self-Hosting + +Deploy your own instance of Manage with one click: + +[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/manage) + +### Manual Deployment + +Manage can be deployed on any platform that supports Node.js/Bun. You'll need: + +- PostgreSQL database +- S3-compatible storage (optional, for file uploads) +- SMTP server (for email notifications) + +See `.env.example` for all required environment variables. + +## Hosting Railway diff --git a/app/layout.tsx b/app/layout.tsx index f5fe66a..667e831 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,3 @@ -import { Analytics } from "@vercel/analytics/next"; import { ThemeProvider } from "@/components/core/theme-provider"; import { Toaster } from "@/components/ui/sonner"; import { SITE_METADATA } from "@/data/marketing"; @@ -315,7 +314,6 @@ export default function RootLayout({ {children} - ); diff --git a/app/page.tsx b/app/page.tsx index ccfeee0..8272ffb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,7 +5,7 @@ import { ClientRedirect } from "@/components/core/client-redirect"; import { Footer } from "@/components/layout/footer"; import { Header } from "@/components/layout/header"; import { auth } from "@/lib/auth"; -import { isSignupDisabled } from "@/lib/config"; +import { isSelfHosted, isSignupDisabled } from "@/lib/config"; export default async function Home() { const session = await auth.api.getSession({ @@ -15,6 +15,10 @@ export default async function Home() { return ; } + if (isSelfHosted()) { + return ; + } + const signupsDisabled = isSignupDisabled(); return ( @@ -289,6 +293,19 @@ export default async function Home() { growing community. No vendor lock-in, no hidden costs.

+ + + + + Deploy on Railway + { - const seed = - user?.id || user?.email || user?.firstName || user?.name || "default"; + const seed = user?.id || "default"; const fallbackText = user?.firstName?.[0] || user?.name?.[0] || "U"; return ( diff --git a/components/core/user-menu.tsx b/components/core/user-menu.tsx index 9190ffe..2c39d18 100644 --- a/components/core/user-menu.tsx +++ b/components/core/user-menu.tsx @@ -40,7 +40,7 @@ export function UserMenu() { diff --git a/components/project/shared/creator-details.tsx b/components/project/shared/creator-details.tsx index 31a76eb..0b35f81 100644 --- a/components/project/shared/creator-details.tsx +++ b/components/project/shared/creator-details.tsx @@ -5,7 +5,7 @@ export const CreatorDetails = ({ user, updatedAt, }: { - user: Pick; + user: Pick; updatedAt: Date; }) => { return ( diff --git a/components/project/shared/user-badge.tsx b/components/project/shared/user-badge.tsx index 37d78cd..69a3510 100644 --- a/components/project/shared/user-badge.tsx +++ b/components/project/shared/user-badge.tsx @@ -8,13 +8,15 @@ export function UserBadge({ imageOnly = false, }: { className?: string; - user: Pick; + user: Pick & { email?: string }; imageOnly?: boolean; }) { return (
- {!imageOnly ?

{user?.firstName}

: null} + {!imageOnly ? ( +

{user?.firstName || user?.email}

+ ) : null}
); } diff --git a/components/settings/team-settings.tsx b/components/settings/team-settings.tsx index 8352fc1..aeb7e9a 100644 --- a/components/settings/team-settings.tsx +++ b/components/settings/team-settings.tsx @@ -193,7 +193,6 @@ export function TeamSettings() { id: member.user.id, firstName: member.user.firstName, name: member.user.name, - email: member.user.email, }} />
diff --git a/drizzle/types.ts b/drizzle/types.ts index d2f7980..be46678 100644 --- a/drizzle/types.ts +++ b/drizzle/types.ts @@ -48,11 +48,15 @@ export type ProjectWithCreator = Project & { creator: User }; export type TaskWithDetails = Task & { creator: { + id: string; firstName: string | null; + email: string; image: string | null; }; assignee: { + id: string; firstName: string | null; + email: string; image: string | null; } | null; }; @@ -62,7 +66,7 @@ export type TaskListWithTasks = TaskList & { }; export type BlobWithCreater = Blob & { - creator: Pick; + creator: Pick; }; export type EventWithCreator = CalendarEvent & { diff --git a/hooks/use-tasks.tsx b/hooks/use-tasks.tsx index c626e5e..a9e42db 100644 --- a/hooks/use-tasks.tsx +++ b/hooks/use-tasks.tsx @@ -86,7 +86,9 @@ export function TasksProvider({ position: 0, assignedToUser: null, creator: { + id: session?.user?.id || "", firstName: session?.user?.name || null, + email: session?.user?.email || "", image: session?.user?.image || null, }, assignee: null, diff --git a/lib/config.ts b/lib/config.ts index bb0c127..2b09a8b 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,3 +1,7 @@ export function isSignupDisabled() { return process.env.DISABLE_SIGNUPS === "true"; } + +export function isSelfHosted() { + return process.env.SELF_HOSTED === "true"; +} diff --git a/package.json b/package.json index 84b1899..a15ecad 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "private": true, "scripts": { "dev": "TZ=UTC next dev --turbopack", - "prebuild": "npm run db:push", "build": "next build", "start": "next start", "lint": "biome lint", @@ -45,7 +44,6 @@ "@trpc/tanstack-react-query": "^11.0.2", "@turbowire/react": "^0.1.0", "@turbowire/serverless": "^0.2.0", - "@vercel/analytics": "^1.5.0", "autoprefixer": "10.4.14", "babel-plugin-react-compiler": "^1.0.0", "baseline-browser-mapping": "^2.9.10", diff --git a/trpc/routers/tasks.ts b/trpc/routers/tasks.ts index 07c6c7f..2e2b922 100644 --- a/trpc/routers/tasks.ts +++ b/trpc/routers/tasks.ts @@ -240,13 +240,17 @@ export const tasksRouter = createTRPCRouter({ with: { creator: { columns: { + id: true, firstName: true, + email: true, image: true, }, }, assignee: { columns: { + id: true, firstName: true, + email: true, image: true, }, },