mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
several bug fixes
This commit is contained in:
5
.changeset/odd-months-move.md
Normal file
5
.changeset/odd-months-move.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": patch
|
||||
---
|
||||
|
||||
fix several bugs
|
||||
@@ -7,10 +7,7 @@
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"template"
|
||||
],
|
||||
"files": ["dist", "template"],
|
||||
"keywords": [],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { PKG_ROOT } from "../constants";
|
||||
import type { PackageManager, ProjectAddons } from "../types";
|
||||
import type { PackageManager, ProjectAddons, ProjectFrontend } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
import { setupTauri } from "./tauri-setup";
|
||||
|
||||
@@ -9,14 +9,17 @@ export async function setupAddons(
|
||||
projectDir: string,
|
||||
addons: ProjectAddons[],
|
||||
packageManager: PackageManager,
|
||||
frontends: ProjectFrontend[],
|
||||
) {
|
||||
const hasWebFrontend = frontends.includes("web");
|
||||
|
||||
// if (addons.includes("docker")) {
|
||||
// await setupDocker(projectDir);
|
||||
// }
|
||||
if (addons.includes("pwa")) {
|
||||
if (addons.includes("pwa") && hasWebFrontend) {
|
||||
await setupPwa(projectDir);
|
||||
}
|
||||
if (addons.includes("tauri")) {
|
||||
if (addons.includes("tauri") && hasWebFrontend) {
|
||||
await setupTauri(projectDir, packageManager);
|
||||
}
|
||||
if (addons.includes("biome")) {
|
||||
@@ -89,6 +92,10 @@ async function setupPwa(projectDir: string) {
|
||||
|
||||
const clientPackageDir = path.join(projectDir, "apps/web");
|
||||
|
||||
if (!(await fs.pathExists(clientPackageDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
addPackageDependency({
|
||||
dependencies: ["vite-plugin-pwa"],
|
||||
devDependencies: ["@vite-pwa/assets-generator"],
|
||||
|
||||
@@ -71,6 +71,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
options.examples,
|
||||
options.orm,
|
||||
options.auth,
|
||||
options.frontend,
|
||||
);
|
||||
|
||||
await setupEnvironmentVariables(projectDir, options);
|
||||
@@ -78,7 +79,12 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
await initializeGit(projectDir, options.git);
|
||||
|
||||
if (options.addons.length > 0) {
|
||||
await setupAddons(projectDir, options.addons, options.packageManager);
|
||||
await setupAddons(
|
||||
projectDir,
|
||||
options.addons,
|
||||
options.packageManager,
|
||||
options.frontend,
|
||||
);
|
||||
}
|
||||
|
||||
await updatePackageConfigurations(projectDir, options);
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import { PKG_ROOT } from "../constants";
|
||||
import type { ProjectOrm } from "../types";
|
||||
import type { ProjectFrontend, ProjectOrm } from "../types";
|
||||
|
||||
export async function setupExamples(
|
||||
projectDir: string,
|
||||
examples: string[],
|
||||
orm: ProjectOrm,
|
||||
auth: boolean,
|
||||
frontend: ProjectFrontend[] = ["web"],
|
||||
): Promise<void> {
|
||||
if (examples.includes("todo")) {
|
||||
const hasWebFrontend = frontend.includes("web");
|
||||
|
||||
const webAppExists = await fs.pathExists(path.join(projectDir, "apps/web"));
|
||||
|
||||
if (examples.includes("todo") && hasWebFrontend && webAppExists) {
|
||||
await setupTodoExample(projectDir, orm, auth);
|
||||
} else {
|
||||
await cleanupTodoFiles(projectDir, orm);
|
||||
|
||||
@@ -13,6 +13,10 @@ export async function setupTauri(
|
||||
const s = spinner();
|
||||
const clientPackageDir = path.join(projectDir, "apps/web");
|
||||
|
||||
if (!(await fs.pathExists(clientPackageDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
s.start("Setting up Tauri desktop app support...");
|
||||
|
||||
|
||||
@@ -122,13 +122,13 @@ async function main() {
|
||||
}),
|
||||
...((options.web !== undefined || options.native !== undefined) && {
|
||||
frontend: [
|
||||
...(options.web === false ? [] : options.web === true ? ["web"] : []),
|
||||
...(options.web === false ? [] : ["web"]),
|
||||
...(options.native === false
|
||||
? []
|
||||
: options.native === true
|
||||
? ["native"]
|
||||
: []),
|
||||
] as ProjectFrontend[],
|
||||
].filter(Boolean) as ProjectFrontend[],
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -137,7 +137,6 @@ async function main() {
|
||||
log.message(displayConfig(flagConfig));
|
||||
log.message("");
|
||||
}
|
||||
|
||||
const config = options.yes
|
||||
? {
|
||||
...DEFAULT_CONFIG,
|
||||
@@ -180,6 +179,13 @@ async function main() {
|
||||
runtime: options.runtime
|
||||
? (options.runtime as Runtime)
|
||||
: DEFAULT_CONFIG.runtime,
|
||||
frontend:
|
||||
options.web === false || options.native === true
|
||||
? ([
|
||||
...(options.web === false ? [] : ["web"]),
|
||||
...(options.native ? ["native"] : []),
|
||||
] as ProjectFrontend[])
|
||||
: DEFAULT_CONFIG.frontend,
|
||||
}
|
||||
: await gatherConfig(flagConfig);
|
||||
|
||||
|
||||
@@ -1,38 +1,52 @@
|
||||
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { ProjectAddons } from "../types";
|
||||
import type { ProjectAddons, ProjectFrontend } from "../types";
|
||||
|
||||
export async function getAddonsChoice(
|
||||
Addons?: ProjectAddons[],
|
||||
frontends?: ProjectFrontend[],
|
||||
): Promise<ProjectAddons[]> {
|
||||
if (Addons !== undefined) return Addons;
|
||||
|
||||
const hasWeb = frontends?.includes("web");
|
||||
|
||||
const addonOptions = [
|
||||
{
|
||||
value: "biome" as const,
|
||||
label: "Biome",
|
||||
hint: "Add Biome for linting and formatting",
|
||||
},
|
||||
{
|
||||
value: "husky" as const,
|
||||
label: "Husky",
|
||||
hint: "Add Git hooks with Husky, lint-staged (requires Biome)",
|
||||
},
|
||||
];
|
||||
|
||||
const webAddonOptions = [
|
||||
{
|
||||
value: "pwa" as const,
|
||||
label: "PWA (Progressive Web App)",
|
||||
hint: "Make your app installable and work offline",
|
||||
},
|
||||
{
|
||||
value: "tauri" as const,
|
||||
label: "Tauri Desktop App",
|
||||
hint: "Build native desktop apps from your web frontend",
|
||||
},
|
||||
];
|
||||
|
||||
const options = hasWeb ? [...webAddonOptions, ...addonOptions] : addonOptions;
|
||||
|
||||
const initialValues = DEFAULT_CONFIG.addons.filter(
|
||||
(addon) => hasWeb || (addon !== "pwa" && addon !== "tauri"),
|
||||
);
|
||||
|
||||
const response = await multiselect<ProjectAddons>({
|
||||
message: "Which Addons would you like to add?",
|
||||
options: [
|
||||
{
|
||||
value: "pwa",
|
||||
label: "PWA (Progressive Web App)",
|
||||
hint: "Make your app installable and work offline",
|
||||
},
|
||||
{
|
||||
value: "tauri",
|
||||
label: "Tauri Desktop App",
|
||||
hint: "Build native desktop apps from your web frontend",
|
||||
},
|
||||
{
|
||||
value: "biome",
|
||||
label: "Biome",
|
||||
hint: "Add Biome for linting and formatting",
|
||||
},
|
||||
{
|
||||
value: "husky",
|
||||
label: "Husky",
|
||||
hint: "Add Git hooks with Husky, lint-staged (requires Biome)",
|
||||
},
|
||||
],
|
||||
initialValues: DEFAULT_CONFIG.addons,
|
||||
options,
|
||||
initialValues,
|
||||
required: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ export async function gatherConfig(
|
||||
results.database === "sqlite" && results.orm !== "prisma"
|
||||
? getTursoSetupChoice(flags.turso)
|
||||
: Promise.resolve(false),
|
||||
addons: () => getAddonsChoice(flags.addons),
|
||||
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
|
||||
examples: ({ results }) =>
|
||||
getExamplesChoice(flags.examples, results.database),
|
||||
getExamplesChoice(flags.examples, results.database, results.frontend),
|
||||
git: () => getGitChoice(flags.git),
|
||||
packageManager: () => getPackageManagerChoice(flags.packageManager),
|
||||
noInstall: () => getNoInstallChoice(flags.noInstall),
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { ProjectDatabase, ProjectExamples } from "../types";
|
||||
import type {
|
||||
ProjectDatabase,
|
||||
ProjectExamples,
|
||||
ProjectFrontend,
|
||||
} from "../types";
|
||||
|
||||
export async function getExamplesChoice(
|
||||
examples?: ProjectExamples[],
|
||||
database?: ProjectDatabase,
|
||||
frontends?: ProjectFrontend[],
|
||||
): Promise<ProjectExamples[]> {
|
||||
if (examples !== undefined) return examples;
|
||||
|
||||
if (database === "none") return [];
|
||||
|
||||
const hasWebFrontend = frontends?.includes("web");
|
||||
if (!hasWebFrontend) return [];
|
||||
|
||||
const response = await multiselect<ProjectExamples>({
|
||||
message: "Which examples would you like to include?",
|
||||
options: [
|
||||
|
||||
@@ -8,6 +8,12 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
||||
configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
|
||||
}
|
||||
|
||||
if (config.frontend !== undefined) {
|
||||
const frontendText =
|
||||
config.frontend.length > 0 ? config.frontend.join(", ") : "none";
|
||||
configDisplay.push(`${pc.blue("Frontend:")} ${frontendText}`);
|
||||
}
|
||||
|
||||
if (config.backendFramework !== undefined) {
|
||||
configDisplay.push(
|
||||
`${pc.blue("Backend Framework:")} ${config.backendFramework}`,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Circle,
|
||||
CircleCheck,
|
||||
ClipboardCopy,
|
||||
InfoIcon,
|
||||
Terminal,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -41,7 +42,7 @@ const triggerConfetti = () => {
|
||||
const animate = () => {
|
||||
posX += vx;
|
||||
posY += vy;
|
||||
vy += 0.1; // Gravity
|
||||
vy += 0.1;
|
||||
opacity -= 0.01;
|
||||
rotation += 5;
|
||||
|
||||
@@ -68,6 +69,31 @@ const triggerConfetti = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const validateProjectName = (name: string): string | undefined => {
|
||||
const INVALID_CHARS = ["<", ">", ":", '"', "|", "?", "*"];
|
||||
const MAX_LENGTH = 255;
|
||||
|
||||
if (name === ".") return undefined;
|
||||
|
||||
if (!name) return "Project name cannot be empty";
|
||||
if (name.length > MAX_LENGTH) {
|
||||
return `Project name must be less than ${MAX_LENGTH} characters`;
|
||||
}
|
||||
if (INVALID_CHARS.some((char) => name.includes(char))) {
|
||||
return "Project name contains invalid characters";
|
||||
}
|
||||
if (name.startsWith(".") || name.startsWith("-")) {
|
||||
return "Project name cannot start with a dot or dash";
|
||||
}
|
||||
if (
|
||||
name.toLowerCase() === "node_modules" ||
|
||||
name.toLowerCase() === "favicon.ico"
|
||||
) {
|
||||
return "Project name is reserved";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const TECH_OPTIONS = {
|
||||
frontend: [
|
||||
{
|
||||
@@ -310,6 +336,7 @@ const TECH_OPTIONS = {
|
||||
};
|
||||
|
||||
interface StackState {
|
||||
projectName: string;
|
||||
frontend: string[];
|
||||
runtime: string;
|
||||
backendFramework: string;
|
||||
@@ -325,6 +352,7 @@ interface StackState {
|
||||
}
|
||||
|
||||
const DEFAULT_STACK: StackState = {
|
||||
projectName: "my-better-t-app",
|
||||
frontend: ["web"],
|
||||
runtime: "bun",
|
||||
backendFramework: "hono",
|
||||
@@ -341,13 +369,65 @@ const DEFAULT_STACK: StackState = {
|
||||
|
||||
const StackArchitect = () => {
|
||||
const [stack, setStack] = useState<StackState>(DEFAULT_STACK);
|
||||
const [command, setCommand] = useState("npx create-better-t-stack my-app -y");
|
||||
const [command, setCommand] = useState(
|
||||
"npx create-better-t-stack my-better-t-app --yes",
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState("frontend");
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [compatNotes, setCompatNotes] = useState<Record<string, string[]>>({});
|
||||
const [projectNameError, setProjectNameError] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stack.frontend.includes("web") && stack.auth === "true") {
|
||||
setStack((prev) => ({
|
||||
...prev,
|
||||
auth: "false",
|
||||
}));
|
||||
}
|
||||
}, [stack.frontend, stack.auth]);
|
||||
|
||||
useEffect(() => {
|
||||
const cmd = generateCommand(stack);
|
||||
setCommand(cmd);
|
||||
|
||||
const notes: Record<string, string[]> = {};
|
||||
|
||||
notes.frontend = [];
|
||||
|
||||
notes.auth = [];
|
||||
if (!stack.frontend.includes("web") && stack.auth === "true") {
|
||||
notes.auth.push("Authentication is only available with React Web.");
|
||||
}
|
||||
|
||||
notes.addons = [];
|
||||
if (!stack.frontend.includes("web")) {
|
||||
notes.addons.push("PWA and Tauri are only available with React Web.");
|
||||
}
|
||||
|
||||
notes.database = [];
|
||||
|
||||
notes.orm = [];
|
||||
if (stack.database === "none") {
|
||||
notes.orm.push(
|
||||
"ORM options are only available when a database is selected.",
|
||||
);
|
||||
}
|
||||
|
||||
notes.turso = [];
|
||||
if (stack.database !== "sqlite") {
|
||||
notes.turso.push(
|
||||
"Turso integration is only available with SQLite database.",
|
||||
);
|
||||
}
|
||||
|
||||
notes.examples = [];
|
||||
if (!stack.frontend.includes("web")) {
|
||||
notes.examples.push("Todo example is only available with React Web.");
|
||||
}
|
||||
|
||||
setCompatNotes(notes);
|
||||
}, [stack]);
|
||||
|
||||
const generateCommand = useCallback((stackState: StackState) => {
|
||||
@@ -360,7 +440,7 @@ const StackArchitect = () => {
|
||||
base = "bun create better-t-stack@latest";
|
||||
}
|
||||
|
||||
const projectName = "my-better-t-app";
|
||||
const projectName = stackState.projectName || "my-better-t-app";
|
||||
const flags: string[] = [];
|
||||
|
||||
const isAllDefault =
|
||||
@@ -452,39 +532,80 @@ const StackArchitect = () => {
|
||||
if (techId === "none") {
|
||||
return {
|
||||
...prev,
|
||||
frontend: [],
|
||||
frontend: ["none"],
|
||||
auth: "false",
|
||||
examples: prev.examples.filter((ex) => ex !== "todo"),
|
||||
addons: prev.addons.filter(
|
||||
(addon) => addon !== "pwa" && addon !== "tauri",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (currentSelection.includes(techId)) {
|
||||
if (
|
||||
techId === "web" &&
|
||||
currentSelection.filter((id) => id !== techId).length === 0
|
||||
) {
|
||||
if (techId === "web") {
|
||||
const newFrontend = currentSelection.filter(
|
||||
(id) => id !== techId,
|
||||
);
|
||||
|
||||
if (newFrontend.length === 0) {
|
||||
return {
|
||||
...prev,
|
||||
frontend: ["none"],
|
||||
auth: "false",
|
||||
examples: prev.examples.filter((ex) => ex !== "todo"),
|
||||
addons: prev.addons.filter(
|
||||
(addon) => addon !== "pwa" && addon !== "tauri",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
frontend: currentSelection.filter((id) => id !== techId),
|
||||
frontend: newFrontend,
|
||||
auth: "false",
|
||||
examples: prev.examples.filter((ex) => ex !== "todo"),
|
||||
addons: prev.addons.filter(
|
||||
(addon) => addon !== "pwa" && addon !== "tauri",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const newFrontend = currentSelection.filter((id) => id !== techId);
|
||||
|
||||
if (newFrontend.length === 0) {
|
||||
return {
|
||||
...prev,
|
||||
frontend: ["none"],
|
||||
auth: "false",
|
||||
addons: prev.addons.filter(
|
||||
(addon) => addon !== "pwa" && addon !== "tauri",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
frontend: currentSelection.filter((id) => id !== techId),
|
||||
frontend: newFrontend,
|
||||
};
|
||||
}
|
||||
|
||||
if (techId === "web") {
|
||||
const cleanedSelection = currentSelection.filter(
|
||||
(id) => id !== "none",
|
||||
);
|
||||
return {
|
||||
...prev,
|
||||
frontend: [...currentSelection, techId],
|
||||
frontend: [...cleanedSelection, techId],
|
||||
auth: "true",
|
||||
};
|
||||
}
|
||||
|
||||
const cleanedSelection = currentSelection.filter(
|
||||
(id) => id !== "none",
|
||||
);
|
||||
return {
|
||||
...prev,
|
||||
frontend: [...currentSelection, techId],
|
||||
frontend: [...cleanedSelection, techId],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -495,6 +616,20 @@ const StackArchitect = () => {
|
||||
if (index >= 0) {
|
||||
currentArray.splice(index, 1);
|
||||
} else {
|
||||
if (
|
||||
category === "examples" &&
|
||||
techId === "todo" &&
|
||||
!prev.frontend.includes("web")
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
if (
|
||||
category === "addons" &&
|
||||
(techId === "pwa" || techId === "tauri") &&
|
||||
!prev.frontend.includes("web")
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
currentArray.push(techId);
|
||||
}
|
||||
|
||||
@@ -510,6 +645,7 @@ const StackArchitect = () => {
|
||||
...prev,
|
||||
database: techId,
|
||||
orm: null,
|
||||
turso: "false",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -520,17 +656,15 @@ const StackArchitect = () => {
|
||||
orm: "drizzle",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (category === "database" && techId === "sqlite") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
turso: prev.turso,
|
||||
};
|
||||
}
|
||||
if (techId === "sqlite") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
turso: prev.turso,
|
||||
};
|
||||
}
|
||||
|
||||
if (category === "database" && techId !== "sqlite") {
|
||||
return {
|
||||
...prev,
|
||||
database: techId,
|
||||
@@ -538,6 +672,10 @@ const StackArchitect = () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (category === "turso" && prev.database !== "sqlite") {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[category]: techId,
|
||||
@@ -582,8 +720,34 @@ const StackArchitect = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 font-mono">
|
||||
<div className="mb-4">
|
||||
<label className="flex flex-col mb-2">
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
Project Name:
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={stack.projectName || ""}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setStack((prev) => ({ ...prev, projectName: newValue }));
|
||||
setProjectNameError(validateProjectName(newValue));
|
||||
}}
|
||||
className={`bg-gray-200 dark:bg-gray-800 border ${
|
||||
projectNameError
|
||||
? "border-red-500 dark:border-red-500"
|
||||
: "border-gray-300 dark:border-gray-700"
|
||||
} rounded px-2 py-1 font-mono text-sm focus:outline-none focus:border-blue-500 dark:focus:border-blue-400`}
|
||||
placeholder="my-better-t-app"
|
||||
/>
|
||||
</div>
|
||||
{projectNameError && (
|
||||
<p className="text-red-500 text-xs mt-1">{projectNameError}</p>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="flex">
|
||||
<span className="text-green-600 dark:text-green-400 mr-2">$</span>
|
||||
@@ -592,7 +756,19 @@ const StackArchitect = () => {
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{compatNotes[activeTab] && compatNotes[activeTab].length > 0 && (
|
||||
<div className="mb-4 p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-center gap-2 mb-2 text-sm font-medium text-blue-800 dark:text-blue-300">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<span>Compatibility Notes</span>
|
||||
</div>
|
||||
<ul className="list-disc list-inside text-xs text-blue-700 dark:text-blue-400 space-y-1">
|
||||
{compatNotes[activeTab].map((note) => (
|
||||
<li key={note}>{note}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t border-gray-300 dark:border-gray-700 pt-4 mt-4">
|
||||
<div className="mb-3 text-gray-600 dark:text-gray-400 flex items-center">
|
||||
<Terminal className="w-4 h-4 mr-2" />
|
||||
@@ -618,7 +794,13 @@ const StackArchitect = () => {
|
||||
const isDisabled =
|
||||
(activeTab === "orm" && stack.database === "none") ||
|
||||
(activeTab === "turso" && stack.database !== "sqlite") ||
|
||||
(activeTab === "auth" && !stack.frontend.includes("web"));
|
||||
(activeTab === "auth" && !stack.frontend.includes("web")) ||
|
||||
(activeTab === "examples" &&
|
||||
tech.id === "todo" &&
|
||||
!stack.frontend.includes("web")) ||
|
||||
(activeTab === "addons" &&
|
||||
(tech.id === "pwa" || tech.id === "tauri") &&
|
||||
!stack.frontend.includes("web"));
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -768,7 +950,6 @@ const StackArchitect = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-200 dark:bg-gray-900 border-t border-gray-300 dark:border-gray-700 flex overflow-x-auto">
|
||||
{Object.keys(TECH_OPTIONS).map((category) => (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user