Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<a href="https://railway.com?referralCode=techulus">
<img src="https://railway.com/brand/logotype-light.png" alt="Railway" height="70" />
Expand Down
2 changes: 0 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -315,7 +314,6 @@ export default function RootLayout({
<Toaster position="top-center" />
{children}
</ThemeProvider>
<Analytics />
</body>
</html>
);
Expand Down
19 changes: 18 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -15,6 +15,10 @@ export default async function Home() {
return <ClientRedirect path="/start" />;
}

if (isSelfHosted()) {
return <ClientRedirect path="/sign-in" />;
}

const signupsDisabled = isSignupDisabled();

return (
Expand Down Expand Up @@ -289,6 +293,19 @@ export default async function Home() {
growing community. No vendor lock-in, no hidden costs.
</p>
<div className="flex flex-col sm:flex-row gap-4 mb-8">
<Link
href="https://railway.com/deploy/manage"
className="inline-flex items-center gap-3 px-6 py-3 rounded-full font-semibold bg-green-600 text-white shadow-sm shadow-green-600/25 border-b-4 border-green-700 hover:bg-green-500 hover:border-green-600 active:border-green-600 active:shadow-sm active:translate-y-0.5 transition-all duration-150"
>
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M.113 10.27A.691.691 0 0 1 0 9.92c0-.12.034-.24.113-.35A9.9 9.9 0 0 1 3.537 6.07a9.95 9.95 0 0 1 5.18-2.012 10.1 10.1 0 0 1 5.516.927c.41.193.59.713.404 1.16s-.626.644-1.036.45a8.2 8.2 0 0 0-4.482-.753 8.08 8.08 0 0 0-4.207 1.635 8.05 8.05 0 0 0-2.78 3.568.82.82 0 0 1-.362.42.75.75 0 0 1-.542.084.75.75 0 0 1-.47-.281l.113.35zm22.14.12c.098.152.147.333.14.517a9.88 9.88 0 0 1-1.836 5.137 9.92 9.92 0 0 1-4.408 3.422 9.95 9.95 0 0 1-5.533.644 10.03 10.03 0 0 1-4.92-2.26c-.346-.29-.41-.835-.143-1.22s.722-.47 1.068-.18a8.12 8.12 0 0 0 3.995 1.836 8.08 8.08 0 0 0 4.495-.523 8.05 8.05 0 0 0 3.58-2.78 8.02 8.02 0 0 0 1.49-4.173.82.82 0 0 1 .227-.495.75.75 0 0 1 .492-.225.75.75 0 0 1 .509.172l-.156-.872zm-9.907-6.15c.17 0 .335.077.447.21l2.845 3.39c.187.223.182.565-.012.782l-2.546 2.843c-.193.216-.505.249-.735.077l-.083-.077-2.546-2.843c-.193-.216-.2-.558-.012-.782l2.845-3.39a.54.54 0 0 1 .362-.184l.085-.006.35-.02zm-.7 1.636l-1.794 2.138 1.795 2.003 1.795-2.003-1.795-2.138zm.35 8.93a.54.54 0 0 1 .447.21l2.845 3.39c.187.223.182.565-.012.782l-2.546 2.843c-.193.216-.505.249-.735.077l-.083-.077-2.546-2.843c-.193-.216-.2-.558-.012-.782l2.845-3.39a.54.54 0 0 1 .362-.184l.085-.006.35-.02zm-.7 1.636l-1.794 2.138 1.795 2.003 1.795-2.003-1.795-2.138z" />
</svg>
Deploy on Railway
</Link>
<Link
href="https://github.com/techulus/manage"
className="inline-flex items-center gap-3 px-6 py-3 rounded-full font-semibold border-2 border-neutral-300 dark:border-neutral-600 hover:border-neutral-400 dark:hover:border-neutral-500 transition-colors"
Expand Down
6 changes: 2 additions & 4 deletions components/core/user-avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ type UserAvatarProps = {
user?: {
firstName?: string | null;
name?: string | null;
email?: string | null;
id?: string | null;
id: string;
} | null;
className?: string;
compact?: boolean;
Expand All @@ -21,8 +20,7 @@ export const UserAvatar = ({
className,
compact = false,
}: UserAvatarProps) => {
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 (
Expand Down
2 changes: 1 addition & 1 deletion components/core/user-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function UserMenu() {
<DropdownMenuTrigger asChild>
<button type="button" className="cursor-pointer">
<UserAvatar
user={{ id: user.id, name: user.name, email: user.email }}
user={{ id: user.id, name: user.name }}
className="h-8 w-8"
/>
</button>
Expand Down
2 changes: 1 addition & 1 deletion components/project/shared/creator-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const CreatorDetails = ({
user,
updatedAt,
}: {
user: Pick<User, "firstName" | "image">;
user: Pick<User, "firstName" | "image" | "id">;
updatedAt: Date;
}) => {
return (
Expand Down
6 changes: 4 additions & 2 deletions components/project/shared/user-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ export function UserBadge({
imageOnly = false,
}: {
className?: string;
user: Pick<User, "firstName" | "image">;
user: Pick<User, "firstName" | "image" | "id"> & { email?: string };
imageOnly?: boolean;
}) {
return (
<div className={cn("flex items-center", className)}>
<UserAvatar className="h-5 w-5" user={user} />
{!imageOnly ? <p className="ml-2">{user?.firstName}</p> : null}
{!imageOnly ? (
<p className="ml-2">{user?.firstName || user?.email}</p>
) : null}
</div>
);
}
1 change: 0 additions & 1 deletion components/settings/team-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ export function TeamSettings() {
id: member.user.id,
firstName: member.user.firstName,
name: member.user.name,
email: member.user.email,
}}
/>
<div>
Expand Down
6 changes: 5 additions & 1 deletion drizzle/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -62,7 +66,7 @@ export type TaskListWithTasks = TaskList & {
};

export type BlobWithCreater = Blob & {
creator: Pick<User, "firstName" | "image">;
creator: Pick<User, "id" | "firstName" | "image">;
};

export type EventWithCreator = CalendarEvent & {
Expand Down
2 changes: 2 additions & 0 deletions hooks/use-tasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export function isSignupDisabled() {
return process.env.DISABLE_SIGNUPS === "true";
}

export function isSelfHosted() {
return process.env.SELF_HOSTED === "true";
}
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions trpc/routers/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
Expand Down