diff --git a/packages/web/src/components/CreateAppModal.tsx b/packages/web/src/components/CreateAppModal.tsx
index 2170214..5f6b289 100644
--- a/packages/web/src/components/CreateAppModal.tsx
+++ b/packages/web/src/components/CreateAppModal.tsx
@@ -3,7 +3,65 @@ import { useEffect, useRef, useState } from "react";
import { useCreateApp } from "../hooks/useApps";
import { BuildPackSelector, type BuildPackValue } from "./BuildPackSelector";
+function parseGithubUrl(
+ val: string,
+): { repo: string; branch?: string; subdirectory?: string } | null {
+ const match = val.match(GITHUB_URL_RE);
+ if (!match) return null;
+
+ const repo = match[1];
+ const rest = match[2];
+
+ if (!rest) return { repo };
+
+ const parts = rest.split("/");
+ const branch = parts[0];
+ const subdirectory = parts.slice(1).join("/") || undefined;
+ return { repo, branch, subdirectory };
+}
+
const GITHUB_REPO_RE = /^[\w.-]+\/[\w.-]+$/;
+const GITHUB_URL_RE =
+ /^(?:https?:\/\/)?(?:www\.)?github\.com\/([\w.-]+\/[\w.-]+?)(?:\/tree\/([\w.\-/]+))?$/;
+
+const EXAMPLES = [
+ { label: "Pure HTML", sub: "static" },
+ { label: "Vue SPA", sub: "vue/spa" },
+ { label: "Vue SPA Router", sub: "vue/spa-router" },
+ { label: "Vue SSR", sub: "vue/ssr" },
+ { label: "Astro Static", sub: "astro/static" },
+ { label: "Astro Server", sub: "astro/server" },
+ { label: "Next.js SPA", sub: "nextjs/spa" },
+ { label: "Next.js SPA (standalone)", sub: "nextjs/spa-standalone" },
+ { label: "Next.js SPA + img opt", sub: "nextjs/spa-with-image-optimization" },
+ { label: "Next.js SSR", sub: "nextjs/ssr" },
+ { label: "Next.js Prisma", sub: "nextjs/prisma" },
+ { label: "Nuxt Static", sub: "nuxt/static" },
+ { label: "Nuxt Server", sub: "nuxt/server" },
+ { label: "Nuxt Nitro", sub: "nuxt/nitro" },
+ { label: "Remix", sub: "remix" },
+ { label: "Vite Vanilla JS", sub: "vite/vanilla-js" },
+ { label: "Vite Vanilla TS", sub: "vite/vanilla-ts" },
+ { label: "Bun", sub: "bun" },
+ { label: "Laravel", sub: "laravel" },
+ { label: "Laravel Inertia", sub: "laravel-inertia" },
+ { label: "Laravel Pure", sub: "laravel-pure" },
+ { label: "AdonisJS", sub: "adonisjs/simple" },
+ { label: "NestJS", sub: "nestjs" },
+ { label: "Node.js", sub: "nodejs" },
+ { label: "Flask", sub: "flask" },
+ { label: "Go Gin", sub: "go/gin" },
+ { label: "Rust", sub: "rust" },
+ { label: "Elixir Phoenix", sub: "elixir-phoenix" },
+ { label: "Symfony", sub: "symfony" },
+ { label: "Strapi", sub: "strapi" },
+ { label: "Rails", sub: "rails-example" },
+ { label: "t3 App", sub: "t3-app" },
+ { label: "t3 App + NextAuth", sub: "t3-nextauth" },
+ { label: "Turbo Next.js", sub: "turbo-nextjs" },
+ { label: "Turbo t3 NextAuth", sub: "turbo-t3-nextauth" },
+ { label: "Shopware 6", sub: "shopware6" },
+] as const;
function validate(input: {
name: string;
@@ -119,6 +177,13 @@ export function CreateAppModal({ open, onClose }: Props) {
}
}, [open]);
+ // Auto-set branch to v4.x when using the coolify-examples repo
+ useEffect(() => {
+ if (githubRepo === "coollabsio/coolify-examples") {
+ setBranch("v4.x");
+ }
+ }, [githubRepo]);
+
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitError(null);
@@ -251,7 +316,15 @@ export function CreateAppModal({ open, onClose }: Props) {
{
- setGithubRepo(e.target.value);
+ const val = e.target.value;
+ setGithubRepo(val);
+ const parsed = parseGithubUrl(val);
+ if (parsed) {
+ setGithubRepo(parsed.repo);
+ if (parsed.branch) setBranch(parsed.branch);
+ if (parsed.subdirectory)
+ setSubdirectory(parsed.subdirectory);
+ }
if (fieldErrors.githubRepo)
setFieldErrors((p) => ({ ...p, githubRepo: "" }));
}}
@@ -336,17 +409,40 @@ export function CreateAppModal({ open, onClose }: Props) {