mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(web): add web and server deploy chart in analytics page
This commit is contained in:
@@ -31,10 +31,8 @@ function TechIcon({
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
// If no icon, return empty
|
||||
if (!icon) return null;
|
||||
|
||||
// If it's an emoji or text icon, render as span
|
||||
if (!icon.startsWith("https://")) {
|
||||
return (
|
||||
<span
|
||||
@@ -48,7 +46,6 @@ function TechIcon({
|
||||
);
|
||||
}
|
||||
|
||||
// Handle light theme variants
|
||||
let iconSrc = icon;
|
||||
if (
|
||||
theme === "light" &&
|
||||
@@ -59,7 +56,6 @@ function TechIcon({
|
||||
iconSrc = icon.replace(".svg", "-light.svg");
|
||||
}
|
||||
|
||||
// Render as image
|
||||
return (
|
||||
<Image
|
||||
src={iconSrc}
|
||||
|
||||
@@ -637,7 +637,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
});
|
||||
}
|
||||
|
||||
// Workers runtime requires a server deployment (wrangler or alchemy)
|
||||
if (nextStack.serverDeploy === "none") {
|
||||
notes.serverDeploy.notes.push(
|
||||
"Cloudflare Workers runtime requires a server deployment. Wrangler will be selected.",
|
||||
@@ -898,7 +897,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
});
|
||||
}
|
||||
|
||||
// Server deployment requires a backend (and not Convex)
|
||||
if (
|
||||
nextStack.serverDeploy !== "none" &&
|
||||
(nextStack.backend === "none" || nextStack.backend === "convex")
|
||||
@@ -919,7 +917,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
});
|
||||
}
|
||||
|
||||
// Cloudflare server deployments (wrangler/alchemy) require Workers runtime
|
||||
if (nextStack.serverDeploy !== "none" && nextStack.runtime !== "workers") {
|
||||
notes.serverDeploy.notes.push(
|
||||
"Selected server deployment targets Cloudflare Workers. Runtime will be set to 'Cloudflare Workers'.",
|
||||
@@ -937,7 +934,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
"Runtime set to 'Cloudflare Workers' (required by server deployment)",
|
||||
});
|
||||
|
||||
// Apply Workers runtime compatibility adjustments
|
||||
if (nextStack.backend !== "hono") {
|
||||
notes.runtime.notes.push(
|
||||
"Cloudflare Workers runtime requires Hono backend. Hono will be selected.",
|
||||
@@ -1004,7 +1000,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
}
|
||||
}
|
||||
|
||||
// Alchemy deployment validation - temporarily not compatible with Next.js and React Router
|
||||
const isAlchemyWebDeploy = nextStack.webDeploy === "alchemy";
|
||||
const isAlchemyServerDeploy = nextStack.serverDeploy === "alchemy";
|
||||
|
||||
@@ -1034,12 +1029,10 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
||||
notes.webDeploy.hasIssue = true;
|
||||
notes.serverDeploy.hasIssue = true;
|
||||
|
||||
// Remove incompatible frontends
|
||||
nextStack.webFrontend = nextStack.webFrontend.filter(
|
||||
(f) => f !== "next" && f !== "react-router",
|
||||
);
|
||||
|
||||
// If no web frontends remain, set to default
|
||||
if (nextStack.webFrontend.length === 0) {
|
||||
nextStack.webFrontend = ["tanstack-router"];
|
||||
}
|
||||
@@ -1639,7 +1632,6 @@ const StackBuilder = () => {
|
||||
const { adjustedStack } = analyzeStackCompatibility(simulatedStack);
|
||||
const finalStack = adjustedStack ?? simulatedStack;
|
||||
|
||||
// Additional check for Alchemy compatibility with Next.js and React Router
|
||||
if (
|
||||
category === "webFrontend" &&
|
||||
(optionId === "next" || optionId === "react-router")
|
||||
|
||||
@@ -90,6 +90,34 @@ export const getProjectTypeData = (data: AggregatedAnalyticsData | null) => {
|
||||
return data.projectTypeDistribution || [];
|
||||
};
|
||||
|
||||
export const getWebDeployData = (data: AggregatedAnalyticsData | null) => {
|
||||
if (!data) return [];
|
||||
|
||||
const filteredData = (data.webDeployDistribution || []).filter(
|
||||
(item) => item.name !== "none",
|
||||
);
|
||||
|
||||
const wranglerCount = filteredData
|
||||
.filter((item) => item.name === "wrangler" || item.name === "workers")
|
||||
.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const alchemyCount = filteredData
|
||||
.filter((item) => item.name === "alchemy")
|
||||
.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
return [
|
||||
{ name: "wrangler", value: wranglerCount },
|
||||
{ name: "alchemy", value: alchemyCount },
|
||||
].filter((item) => item.value > 0);
|
||||
};
|
||||
|
||||
export const getServerDeployData = (data: AggregatedAnalyticsData | null) => {
|
||||
if (!data) return [];
|
||||
return (data.serverDeployDistribution || []).filter(
|
||||
(item) => item.name !== "none",
|
||||
);
|
||||
};
|
||||
|
||||
export const getMonthlyTimeSeriesData = (
|
||||
data: AggregatedAnalyticsData | null,
|
||||
) => {
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
getPopularStackCombinations,
|
||||
getProjectTypeData,
|
||||
getRuntimeData,
|
||||
getServerDeployData,
|
||||
getWebDeployData,
|
||||
} from "./data-utils";
|
||||
import type { AggregatedAnalyticsData } from "./types";
|
||||
import {
|
||||
@@ -39,6 +41,8 @@ import {
|
||||
ormConfig,
|
||||
projectTypeConfig,
|
||||
runtimeConfig,
|
||||
serverDeployConfig,
|
||||
webDeployConfig,
|
||||
} from "./types";
|
||||
|
||||
interface StackConfigurationChartsProps {
|
||||
@@ -57,6 +61,8 @@ export function StackConfigurationCharts({
|
||||
const authData = getAuthData(data);
|
||||
const runtimeData = getRuntimeData(data);
|
||||
const projectTypeData = getProjectTypeData(data);
|
||||
const webDeployData = getWebDeployData(data);
|
||||
const serverDeployData = getServerDeployData(data);
|
||||
const popularStackCombinations = getPopularStackCombinations(data);
|
||||
const databaseORMCombinations = getDatabaseORMCombinations(data);
|
||||
|
||||
@@ -546,6 +552,108 @@ export function StackConfigurationCharts({
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-border">
|
||||
<div className="border-border border-b px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-primary text-xs">▶</span>
|
||||
<span className="font-semibold text-sm">
|
||||
WEB_DEPLOYMENT_COMPARISON.PIE
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-muted-foreground text-xs">
|
||||
Web deployment platform comparison (Wrangler vs Alchemy)
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<ChartContainer
|
||||
config={webDeployConfig}
|
||||
className="h-[300px] w-full"
|
||||
>
|
||||
<PieChart>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent nameKey="name" />}
|
||||
/>
|
||||
<Pie
|
||||
data={webDeployData}
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
label={({ name, percent }) =>
|
||||
`${name} ${(percent * 100).toFixed(0)}%`
|
||||
}
|
||||
>
|
||||
{webDeployData.map((entry) => (
|
||||
<Cell
|
||||
key={`web-deploy-${entry.name}`}
|
||||
fill={
|
||||
entry.name === "wrangler"
|
||||
? "hsl(var(--chart-1))"
|
||||
: entry.name === "alchemy"
|
||||
? "hsl(var(--chart-2))"
|
||||
: "hsl(var(--chart-3))"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-border">
|
||||
<div className="border-border border-b px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-primary text-xs">▶</span>
|
||||
<span className="font-semibold text-sm">
|
||||
SERVER_DEPLOYMENT_COMPARISON.PIE
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-muted-foreground text-xs">
|
||||
Server deployment platform comparison (Wrangler vs Alchemy)
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<ChartContainer
|
||||
config={serverDeployConfig}
|
||||
className="h-[300px] w-full"
|
||||
>
|
||||
<PieChart>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent nameKey="name" />}
|
||||
/>
|
||||
<Pie
|
||||
data={serverDeployData}
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
label={({ name, percent }) =>
|
||||
`${name} ${(percent * 100).toFixed(0)}%`
|
||||
}
|
||||
>
|
||||
{serverDeployData.map((entry) => (
|
||||
<Cell
|
||||
key={`server-deploy-${entry.name}`}
|
||||
fill={
|
||||
entry.name === "wrangler"
|
||||
? "hsl(var(--chart-1))"
|
||||
: entry.name === "alchemy"
|
||||
? "hsl(var(--chart-2))"
|
||||
: "hsl(var(--chart-3))"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-border">
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface AggregatedAnalyticsData {
|
||||
addonsDistribution: Array<{ name: string; value: number }>;
|
||||
runtimeDistribution: Array<{ name: string; value: number }>;
|
||||
projectTypeDistribution: Array<{ name: string; value: number }>;
|
||||
webDeployDistribution: Array<{ name: string; value: number }>;
|
||||
serverDeployDistribution: Array<{ name: string; value: number }>;
|
||||
popularStackCombinations: Array<{ name: string; value: number }>;
|
||||
databaseORMCombinations: Array<{ name: string; value: number }>;
|
||||
hourlyDistribution: Array<{
|
||||
@@ -43,6 +45,8 @@ export interface AggregatedAnalyticsData {
|
||||
mostPopularORM: string;
|
||||
mostPopularAPI: string;
|
||||
mostPopularPackageManager: string;
|
||||
mostPopularWebDeploy: string;
|
||||
mostPopularServerDeploy: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -396,6 +400,28 @@ export const projectTypeConfig = {
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export const webDeployConfig = {
|
||||
wrangler: {
|
||||
label: "Cloudflare Wrangler",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
alchemy: {
|
||||
label: "Alchemy",
|
||||
color: "hsl(var(--chart-2))",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export const serverDeployConfig = {
|
||||
wrangler: {
|
||||
label: "Cloudflare Wrangler",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
alchemy: {
|
||||
label: "Alchemy",
|
||||
color: "hsl(var(--chart-2))",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export const hourlyDistributionConfig = {
|
||||
count: {
|
||||
label: "Projects Created",
|
||||
|
||||
Reference in New Issue
Block a user