Skip to content

Commit

Permalink
Merge pull request #266 from contentstack/feat/MKT-5565
Browse files Browse the repository at this point in the history
Feat/mkt 5565 : Added Custom UI Loc : MKT-5565
  • Loading branch information
MaitriGurey1 authored Apr 23, 2024
2 parents 852fd52 + 1d9f8ce commit 5213001
Show file tree
Hide file tree
Showing 9 changed files with 21,041 additions and 366 deletions.
20,939 changes: 20,579 additions & 360 deletions audience-app/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions audience-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"private": true,
"homepage": ".",
"dependencies": {
"@contentstack/app-sdk": "^1.1.0",
"@contentstack/venus-components": "^1.0.3",
"@contentstack/app-sdk": "^2.0.1",
"@contentstack/venus-components": "^2.2.3",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
Expand All @@ -14,6 +14,7 @@
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.4",
"react": "^18.1.0",
"react-checkbox-tree": "^1.8.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
Expand Down
13 changes: 9 additions & 4 deletions audience-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React from "react";
import ConfigScreen from "./Config";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { CustomField } from "./Custom field";

const App: React.FC = function () {
return (
<div>
<ConfigScreen />
</div>
<Router>
<Routes>
<Route path="/app-config" element={<ConfigScreen />} />
<Route path="/custom-field" element={<CustomField />} />
</Routes>
</Router>
);
};

export default App;
export default App;
135 changes: 135 additions & 0 deletions audience-app/src/Custom field/AddAudienceModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { useEffect, useState } from "react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";

import ContentstackAppSDK from "@contentstack/app-sdk";

import {
Button,
ButtonGroup,
Field,
Icon,
ModalBody,
ModalFooter,
ModalHeader,
} from "@contentstack/venus-components";

import {
ExpandCloseIcon,
CheckedIcon,
SemiCheckedIcon,
UncheckedIcon,
ExpandOpenIcon,
} from "../common/icons";

import "./modal.css";
import { AudienceList } from "../common/types";

export const AddAudienceModal = (props: any) => {
let { audiences, selectedAudiences, setSelectedAudiences } = props;

const [checked, setChecked] = useState(selectedAudiences);
const [expanded, setExpanded] = useState<any>([]);
const [appSdk, setAppSdk] = useState<any>(null);

useEffect(() => {
const initializeSDK = async () => {
try {
const sdk = await ContentstackAppSDK.init();
setAppSdk(sdk);
} catch (error) {
console.error(error);
}
};
initializeSDK();
}, []);

const handleAddAudiences = async () => {
setSelectedAudiences(checked);
try {
if (appSdk?.location?.CustomField?.field) {
const formattedData = checked
.map((tagLabel: string) => {
const matchedAudience = audiences.find((audience: AudienceList) =>
audience.children.some((child) => child.value === tagLabel)
);
if (matchedAudience) {
const matchedChild = matchedAudience.children.find(
(child: any) => child.value === tagLabel
);
if (matchedChild) {
return {
label: matchedChild.label,
value: matchedChild.value,
uid: matchedChild.uid,
};
}
}
return null;
})
.filter((tag: string) => tag !== null);
await appSdk.location.CustomField.field.setData({
value: formattedData,
});
} else {
console.error("Something went wrong while saving data.");
}
} catch (error) {
console.error("Error occurred while saving data:", error);
}
props.closeModal();
};

return (
<div>
<ModalHeader title="Select Audience" closeModal={props.closeModal} />
<ModalBody>
<div
style={{
height: "234px",
}}
>
<Field>
<CheckboxTree
// iconsClass="fa5"
showNodeIcon={false}
nodes={audiences}
checked={checked}
expanded={expanded}
onCheck={(checked) => {
setChecked(checked);
}}
onExpand={(expanded) => {
setExpanded(expanded);
}}
nativeCheckboxes={false}
// checkModel="all"
icons={{
check: <CheckedIcon />,
uncheck: <UncheckedIcon />,
halfCheck: <SemiCheckedIcon />,
expandOpen: <ExpandOpenIcon />,
expandClose: <ExpandCloseIcon />,
}}
/>
</Field>
</div>
</ModalBody>
<ModalFooter>
<ButtonGroup>
<Button buttonType="light" onClick={props.closeModal}>
Cancel
</Button>
<Button
key="addButton"
id="addRegionBtn"
icon="CheckedWhite"
onClick={handleAddAudiences}
>
<span>Add Selected</span>
</Button>
</ButtonGroup>
</ModalFooter>
</div>
);
};
142 changes: 142 additions & 0 deletions audience-app/src/Custom field/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useEffect, useState } from "react";
import ContentstackAppSDK from "@contentstack/app-sdk";
import {
AsyncLoader,
Button,
Tags,
cbModal,
} from "@contentstack/venus-components";

import { fieldExtractor } from "../common/utils";
import { AudienceList } from "../common/types";

import { AddAudienceModal } from "./AddAudienceModal";

export const CustomField: React.FC = () => {
const [appSdk, setAppSdk] = useState<any>(null);
const [audienceList, setAudienceList] = useState<AudienceList[]>([]);
const [selectedAudiences, setSelectedAudiences] = useState<string[]>([]);

useEffect(() => {
const initializeSDK = async () => {
try {
const sdk = await ContentstackAppSDK.init();
setAppSdk(sdk);
const appConfig = await sdk.getConfig();
const query = await sdk.stack
.ContentType(appConfig.content_type)
.Entry.Query()
.find();
const entries = query.entries || [];

const audiences: AudienceList[] = entries.map((entry: any) => {
const audience: AudienceList = {
label: entry.title,
value: entry.title,
children: [
{
label: "",
value: "",
uid: "",
},
],
};
const field = appConfig.field ? entry[appConfig.field] : entry.group;
if (!entry.hasOwnProperty(appConfig.field || "group")) {
throw new Error("Invalid group title");
}
audience.children = fieldExtractor(field, appConfig.group_title);
return audience;
});
setAudienceList(audiences);
} catch (error) {
console.error("Error initializing the SDK:", error);
}
};
initializeSDK();
}, []);

useEffect(() => {
const initialData =
appSdk?.location?.CustomField?.field?.getData()?.value || [];

setSelectedAudiences(
initialData?.map((initialAudience: any) => initialAudience.value) || []
);
}, [appSdk]);

const openModal = () =>
cbModal({
component: (props: any) => (
<AddAudienceModal
audiences={audienceList}
selectedAudiences={selectedAudiences}
setSelectedAudiences={setSelectedAudiences}
{...props}
/>
),
modalProps: {
shouldReturnFocusAfterClose: false,
customClass: "variable-extension-modal",
},
});

const handleAudienceChange = async (selectedTags: string[]) => {
setSelectedAudiences(selectedTags);

try {
if (appSdk?.location?.CustomField?.field) {
// Match selectedTags with audienceList children values and extract label, value, and uid
const formattedData = selectedTags
.map((tagLabel: string) => {
const matchedAudience = audienceList.find((audience) =>
audience.children.some((child) => child.value === tagLabel)
);
if (matchedAudience) {
// Find the matched child
const matchedChild = matchedAudience.children.find(
(child) => child.value === tagLabel
);
if (matchedChild) {
return {
label: matchedChild.label,
value: matchedChild.value,
uid: matchedChild.uid,
};
}
}
return null;
})
.filter((tag) => tag !== null); // Remove any null values (tags without a match)
await appSdk.location.CustomField.field.setData({
value: formattedData,
});
} else {
console.error("Something went wrong while saving data.");
}
} catch (error) {
console.error("Error occurred while saving data:", error);
}
};

return (
<div>
{audienceList ? (
<>
<Button onClick={openModal}>Add Audiences</Button>
{selectedAudiences.length !== 0 && (
<div style={{ marginTop: "1rem" }}>
<Tags
tags={selectedAudiences}
version="v2"
onChange={handleAudienceChange}
/>
</div>
)}
</>
) : (
<AsyncLoader />
)}
</div>
);
};
9 changes: 9 additions & 0 deletions audience-app/src/Custom field/modal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.variable-extension-modal .ReactModal__Content__body {
height: 25rem;
}

.react-checkbox-tree label {
display: flex;
align-items: center;
font-family: Inter, sans-serif;
}
Loading

0 comments on commit 5213001

Please sign in to comment.