+
+
+ Stack & Tools
+
+ {canEdit && (
+ }
+ onClick={() => setIsModalOpen(true)}
+ >
+ Add
+
+ )}
+
+
+ {hasItems ? (
+
+ {stackItems.map((item) => (
+
+ ))}
+
+ ) : (
+ canEdit && (
+
+
+ Share your squad's stack & tools
+
+ }
+ onClick={() => setIsModalOpen(true)}
+ >
+ Add your first item
+
+
+ )
+ )}
+
+ {isModalOpen && (
+
+ )}
+
+ );
+}
diff --git a/packages/shared/src/components/squads/stack/index.ts b/packages/shared/src/components/squads/stack/index.ts
new file mode 100644
index 0000000000..ff3e731e21
--- /dev/null
+++ b/packages/shared/src/components/squads/stack/index.ts
@@ -0,0 +1,4 @@
+export { SquadStack } from './SquadStack';
+export { SourceStackSection } from './SourceStackSection';
+export { SourceStackItem } from './SourceStackItem';
+export { SourceStackModal } from './SourceStackModal';
diff --git a/packages/shared/src/graphql/source/sourceStack.ts b/packages/shared/src/graphql/source/sourceStack.ts
new file mode 100644
index 0000000000..70f7b5abba
--- /dev/null
+++ b/packages/shared/src/graphql/source/sourceStack.ts
@@ -0,0 +1,153 @@
+import { gql } from 'graphql-request';
+import type { Connection } from '../common';
+import { gqlClient } from '../common';
+import type { DatasetTool } from '../user/userStack';
+
+export interface SourceStackCreatedBy {
+ id: string;
+ name: string;
+ image: string;
+}
+
+export interface SourceStack {
+ id: string;
+ tool: DatasetTool;
+ position: number;
+ icon: string | null;
+ title: string | null;
+ createdAt: string;
+ createdBy: SourceStackCreatedBy;
+}
+
+export interface AddSourceStackInput {
+ title: string;
+}
+
+export interface UpdateSourceStackInput {
+ icon?: string;
+ title?: string;
+}
+
+export interface ReorderSourceStackInput {
+ id: string;
+ position: number;
+}
+
+const SOURCE_STACK_FRAGMENT = gql`
+ fragment SourceStackFragment on SourceStack {
+ id
+ position
+ icon
+ title
+ createdAt
+ tool {
+ id
+ title
+ faviconUrl
+ }
+ createdBy {
+ id
+ name
+ image
+ }
+ }
+`;
+
+export const SOURCE_STACK_QUERY = gql`
+ query SourceStack($sourceId: ID!, $first: Int, $after: String) {
+ sourceStack(sourceId: $sourceId, first: $first, after: $after) {
+ edges {
+ node {
+ ...SourceStackFragment
+ }
+ }
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ }
+ }
+ ${SOURCE_STACK_FRAGMENT}
+`;
+
+const ADD_SOURCE_STACK_MUTATION = gql`
+ mutation AddSourceStack($sourceId: ID!, $input: AddSourceStackInput!) {
+ addSourceStack(sourceId: $sourceId, input: $input) {
+ ...SourceStackFragment
+ }
+ }
+ ${SOURCE_STACK_FRAGMENT}
+`;
+
+const UPDATE_SOURCE_STACK_MUTATION = gql`
+ mutation UpdateSourceStack($id: ID!, $input: UpdateSourceStackInput!) {
+ updateSourceStack(id: $id, input: $input) {
+ ...SourceStackFragment
+ }
+ }
+ ${SOURCE_STACK_FRAGMENT}
+`;
+
+const DELETE_SOURCE_STACK_MUTATION = gql`
+ mutation DeleteSourceStack($id: ID!) {
+ deleteSourceStack(id: $id) {
+ _
+ }
+ }
+`;
+
+const REORDER_SOURCE_STACK_MUTATION = gql`
+ mutation ReorderSourceStack(
+ $sourceId: ID!
+ $items: [ReorderSourceStackInput!]!
+ ) {
+ reorderSourceStack(sourceId: $sourceId, items: $items) {
+ ...SourceStackFragment
+ }
+ }
+ ${SOURCE_STACK_FRAGMENT}
+`;
+
+export const getSourceStack = async (
+ sourceId: string,
+ first = 50,
+): Promise