diff --git a/src/components/Factory/Canvas/callbacks/isValidConnection.ts b/src/components/Factory/Canvas/callbacks/isValidConnection.ts
new file mode 100644
index 000000000..9c52ebce3
--- /dev/null
+++ b/src/components/Factory/Canvas/callbacks/isValidConnection.ts
@@ -0,0 +1,30 @@
+import type { Connection, Edge } from "@xyflow/react";
+
+import { extractResource } from "../../utils/string";
+
+export const createIsValidConnection = (edges: Edge[]) => {
+ return (connection: Connection | Edge) => {
+ if (connection.source === connection.target) return false;
+
+ const sourceResource = extractResource(connection.sourceHandle);
+ const targetResource = extractResource(connection.targetHandle);
+
+ if (
+ sourceResource !== "any" &&
+ targetResource !== "any" &&
+ sourceResource !== targetResource
+ ) {
+ return false;
+ }
+
+ const hasExistingConnection = edges.some(
+ (edge) =>
+ (edge.source === connection.source &&
+ edge.sourceHandle === connection.sourceHandle) ||
+ (edge.target === connection.target &&
+ edge.targetHandle === connection.targetHandle),
+ );
+
+ return !hasExistingConnection;
+ };
+};
diff --git a/src/components/Factory/Canvas/callbacks/onConnect.ts b/src/components/Factory/Canvas/callbacks/onConnect.ts
index 2cd2cfb59..6eb924119 100644
--- a/src/components/Factory/Canvas/callbacks/onConnect.ts
+++ b/src/components/Factory/Canvas/callbacks/onConnect.ts
@@ -36,7 +36,6 @@ export const createOnConnect = (
};
setEdges((eds) => {
- // Remove any existing edges from the same source handle OR to the same target handle
const filteredEdges = eds.filter(
(edge) =>
!(
diff --git a/src/components/Factory/Context/BuildingContext.tsx b/src/components/Factory/Context/BuildingContext.tsx
new file mode 100644
index 000000000..1795b8705
--- /dev/null
+++ b/src/components/Factory/Context/BuildingContext.tsx
@@ -0,0 +1,158 @@
+import { Icon } from "@/components/ui/icon";
+import { BlockStack, InlineStack } from "@/components/ui/layout";
+import { Separator } from "@/components/ui/separator";
+import { Text } from "@/components/ui/typography";
+import { pluralize } from "@/utils/string";
+
+import type { Building } from "../types/buildings";
+
+interface BuildingContextProps {
+ building: Building;
+}
+
+const BuildingContext = ({ building }: BuildingContextProps) => {
+ const {
+ icon,
+ name,
+ description,
+ cost,
+ productionMethod,
+ inputs = [],
+ outputs = [],
+ stockpile = [],
+ } = building;
+
+ return (
+
+
+
+ {icon} {name}
+
+
+
+
+ {description}
+
+
+ {cost !== undefined && (
+
+
+ Cost: {cost}
+
+ )}
+
+
+
+
+
+ Production Method
+
+
+ {!!productionMethod?.name && (
+
+
+ {productionMethod.name}
+
+ )}
+
+ {productionMethod && (
+
+ {productionMethod.inputs.length > 0 && (
+
+
+ Inputs:
+
+ {productionMethod.inputs.map((input, idx) => (
+
+
+ • {input.amount}x {input.resource}
+
+
+ ))}
+
+ )}
+
+ {productionMethod.outputs.length > 0 && (
+
+
+ Outputs:
+
+ {productionMethod.outputs.map((output, idx) => (
+
+
+ • {output.amount}x {output.resource}
+
+
+ ))}
+
+ )}
+
+
+
+ {`${productionMethod.days} ${pluralize(productionMethod.days, "day")}`}
+
+
+ )}
+
+ {!productionMethod && (
+
+ No production method defined
+
+ )}
+
+
+
+
+
+
+ Stockpile
+
+ {stockpile.length > 0 ? (
+
+ {stockpile.map((stock, idx) => (
+
+
+ {stock.resource}: {stock.amount} / {stock.maxAmount}
+
+
+
+ ))}
+
+ ) : (
+
+ No stockpile
+
+ )}
+
+
+
+
+
+
+ Connections
+
+
+
+ Inputs: {inputs.length}
+
+
+ Outputs: {outputs.length}
+
+
+
+
+ );
+};
+
+export default BuildingContext;
diff --git a/src/components/Factory/Context/ResourceContext.tsx b/src/components/Factory/Context/ResourceContext.tsx
new file mode 100644
index 000000000..20763f1de
--- /dev/null
+++ b/src/components/Factory/Context/ResourceContext.tsx
@@ -0,0 +1,64 @@
+import { BlockStack, InlineStack } from "@/components/ui/layout";
+import { Separator } from "@/components/ui/separator";
+import { Text } from "@/components/ui/typography";
+
+import type { Resource } from "../types/resources";
+
+interface ResourceContextProps {
+ resource: Resource;
+ sourceNodeId?: string;
+ targetNodeId?: string;
+}
+
+const ResourceContext = ({
+ resource,
+ sourceNodeId,
+ targetNodeId,
+}: ResourceContextProps) => {
+ const { name, description, color, icon } = resource;
+
+ return (
+
+
+
+ {icon} {name}
+
+
+
+
+
+
+ {description}
+
+
+
+
+
+
+ Connection
+
+
+ {sourceNodeId && (
+
+ From: {sourceNodeId}
+
+ )}
+ {targetNodeId && (
+
+ To: {targetNodeId}
+
+ )}
+
+
+
+ );
+};
+
+export default ResourceContext;
diff --git a/src/components/Factory/Sidebar/BuildingItem.tsx b/src/components/Factory/Sidebar/BuildingItem.tsx
index 745453e35..6697e854a 100644
--- a/src/components/Factory/Sidebar/BuildingItem.tsx
+++ b/src/components/Factory/Sidebar/BuildingItem.tsx
@@ -4,6 +4,7 @@ import { useCallback } from "react";
import { InlineStack } from "@/components/ui/layout";
import { cn } from "@/lib/utils";
+import { RESOURCES } from "../data/resources";
import type { Building } from "../types/buildings";
interface BuildingItemProps {
@@ -55,7 +56,7 @@ const BuildingItem = ({ building }: BuildingItemProps) => {
{building.description}
- 💰 {building.cost}
+ {RESOURCES.coins.icon} {building.cost}
diff --git a/src/components/Factory/Sidebar/Resources.tsx b/src/components/Factory/Sidebar/Resources.tsx
index 01ef9cee4..1b76015d2 100644
--- a/src/components/Factory/Sidebar/Resources.tsx
+++ b/src/components/Factory/Sidebar/Resources.tsx
@@ -1,9 +1,12 @@
import { SidebarGroup, SidebarGroupLabel } from "@/components/ui/sidebar";
+import { RESOURCES } from "../data/resources";
+
const Resources = () => {
return (