diff --git a/navbar.js b/navbar.js
index b26f4f92d..56ac30c27 100644
--- a/navbar.js
+++ b/navbar.js
@@ -70,7 +70,7 @@ module.exports = {
{
position: 'left',
label: 'Blog',
- to: 'https://medium.com/sailpointengineering',
+ to: '/blog',
},
{
position: 'left',
diff --git a/package-lock.json b/package-lock.json
index 1ab33f327..481d9c765 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,10 @@
"docusaurus-theme-openapi-docs": "^1.5.1",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react-dom": "^17.0.2",
+ "react-markdown": "^8.0.7",
+ "react-spinners": "^0.13.8",
+ "react-tabs": "^4.3.0"
},
"devDependencies": {
"@docusaurus/core": "2.2.0",
@@ -11024,8 +11027,9 @@
"license": "MIT"
},
"node_modules/react-markdown": {
- "version": "8.0.5",
- "license": "MIT",
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
+ "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==",
"dependencies": {
"@types/hast": "^2.0.0",
"@types/prop-types": "^15.0.0",
@@ -11302,6 +11306,27 @@
"react": ">=15"
}
},
+ "node_modules/react-spinners": {
+ "version": "0.13.8",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
+ "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-tabs": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz",
+ "integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "prop-types": "^15.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-0 || ^18.0.0"
+ }
+ },
"node_modules/react-textarea-autosize": {
"version": "8.4.0",
"dev": true,
@@ -21693,7 +21718,9 @@
"version": "1.0.1"
},
"react-markdown": {
- "version": "8.0.5",
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
+ "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==",
"requires": {
"@types/hast": "^2.0.0",
"@types/prop-types": "^15.0.0",
@@ -21858,6 +21885,21 @@
"tiny-warning": "^1.0.0"
}
},
+ "react-spinners": {
+ "version": "0.13.8",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
+ "integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
+ "requires": {}
+ },
+ "react-tabs": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz",
+ "integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==",
+ "requires": {
+ "clsx": "^1.1.0",
+ "prop-types": "^15.5.0"
+ }
+ },
"react-textarea-autosize": {
"version": "8.4.0",
"dev": true,
diff --git a/package.json b/package.json
index ee1c3aeaa..2976b33c0 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,10 @@
"docusaurus-theme-openapi-docs": "^1.5.1",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react-dom": "^17.0.2",
+ "react-markdown": "^8.0.7",
+ "react-spinners": "^0.13.8",
+ "react-tabs": "^4.3.0"
},
"overrides": {
"mermaid": "9.1.7"
diff --git a/src/components/blog/BlogBanner/index.js b/src/components/blog/BlogBanner/index.js
new file mode 100644
index 000000000..5f9e5878f
--- /dev/null
+++ b/src/components/blog/BlogBanner/index.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import Link from '@docusaurus/Link';
+
+export default function BlogBanner() {
+ return (
+
+
+
+
+ SailPoint Developer Blog
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/blog/BlogBanner/styles.module.css b/src/components/blog/BlogBanner/styles.module.css
new file mode 100644
index 000000000..fa4843401
--- /dev/null
+++ b/src/components/blog/BlogBanner/styles.module.css
@@ -0,0 +1,24 @@
+
+.blogHeaderText {
+ position: relative;
+ color: #ffffff;
+ font-size: 48px;
+ max-width: 396px;
+ font-weight: bold;
+ line-height: 133%;
+ top: 21px;
+ left: 74px;
+}
+
+.background {
+ width: 100%;
+ object-fit: repeat;
+ height: 100%;
+}
+
+.imageContainer {
+ width: 100%;
+ height: 180px;
+ background-image: url('../../../../static/blog/blog_banner_template.png');
+ background-repeat: repeat;
+}
\ No newline at end of file
diff --git a/src/components/blog/BlogCard/index.js b/src/components/blog/BlogCard/index.js
new file mode 100644
index 000000000..a3af4a5ac
--- /dev/null
+++ b/src/components/blog/BlogCard/index.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import styles from './styles.module.css';
+import Link from '@docusaurus/Link';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+import {addDarkToFileName} from '../../../util/util';
+export default function BlogCard({
+ link,
+ title,
+ tags,
+ creatorImage,
+ image,
+ excerpt,
+ name,
+ views,
+ replies,
+ readTime
+}) {
+
+ return (
+
+
+
+
+
})
+
{views}
+
})
+
{replies}
+
})
+
{readTime} minute read
+
+
+
+
})
+
{name}
+
+
+
+
+
})
+
{title}
+
+ {tags?.map((tag, index) => {
+ if (index > 2) {
+ return '';
+ }
+ return
{tag}
;
+ })}
+
+
{excerpt}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/blog/BlogCard/styles.module.css b/src/components/blog/BlogCard/styles.module.css
new file mode 100644
index 000000000..19eaa3be2
--- /dev/null
+++ b/src/components/blog/BlogCard/styles.module.css
@@ -0,0 +1,127 @@
+/* Getting Started Card */
+.card {
+ position: relative;
+ margin-top: 20px;
+ height: 650px;
+ /* UI Properties */
+ background: var(--dev-card-background);
+ box-shadow: var(--dev-card-shadow);
+ border: 1px solid var(--dev-card-background);
+ border-radius: 40px;
+ opacity: 1;
+ transition: all 0.3s;
+ max-width: 600px;
+}
+
+.card:hover {
+ cursor: pointer;
+ transform: translate(0px, -5px);
+ box-shadow: var(--dev-card-selected);
+}
+
+
+
+
+
+.cardText {
+ position: absolute;
+ margin: 22px;
+ min-width: 170px;
+ top: 10px;
+ left: 0;
+}
+
+
+.cardTitle {
+ font-size: 22px;
+ font-weight: 700;
+}
+
+.cardBody {
+ font-size: 16px;
+ font-weight: 500;
+ top: 10px;
+ left: 0;
+}
+
+.tag {
+ font-size: 16px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ background-color: var(--dev-tag-highlight);
+ padding: 0px 8px;
+ margin-left: 5px;
+ margin-top: 5px;
+}
+
+.tags {
+ margin: 0px;
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+
+
+
+
+.cardUser {
+ position: absolute;
+ bottom: 10px;
+ left: 25px;
+ display: flex;
+}
+
+.cardFace {
+ border-radius: 9999px;
+ margin: auto;
+ height: 40px;
+ width: 40px;
+}
+
+.cardName {
+ min-width: 170px;
+ margin-top: 7px;
+ margin-left: 10px;
+}
+
+
+
+
+
+.cardData {
+ position: absolute;
+ bottom: 50px;
+ left: 20px;
+ display: flex;
+ align-items: center;
+}
+
+.cardEye {
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ margin-left: 5px;
+ height: 18px;
+ width: 18px;
+}
+
+.cardComment {
+ margin-left: 10px;
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ height: 18px;
+ width: 18px;
+ margin-bottom: 4px;
+}
+
+.cardCommentText {
+ color: #96a9ba;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ margin-left: 5px;
+ margin-bottom: 5px;
+ height: 20px;
+ font-size: 16px;
+}
+
diff --git a/src/components/blog/BlogCards/index.js b/src/components/blog/BlogCards/index.js
new file mode 100644
index 000000000..530d71dd9
--- /dev/null
+++ b/src/components/blog/BlogCards/index.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import styles from './styles.module.css';
+import BlogCard from '../BlogCard';
+import BounceLoader from 'react-spinners/BounceLoader';
+
+import {getBlogPosts, getTopic} from '../../../services/DiscourseService';
+export default function BlogCards({
+ filterCallback
+ }) {
+ const [cardData, setCardData] = React.useState();
+ const [loadingCards, setLoadingCards] = React.useState(true);
+
+ const getPosts = async () => {
+ const data = await getBlogPosts(filterCallback.join(','));
+ const resultset = []
+ if (data.topics) {
+ for (const topic of data.topics) {
+ resultset.push(await getPostList(topic))
+ }
+ setCardData(resultset);
+ } else {
+ setCardData(undefined);
+ }
+ setLoadingCards(false);
+ };
+
+ React.useEffect(() => {
+ getPosts();
+ setCardData(undefined);
+ setLoadingCards(true);
+ }, [filterCallback]);
+
+ if (cardData) {
+ return (
+
+
+ {cardData.map(function(a, index){
+ return
+ })}
+
+
+ );
+ } else if (loadingCards) {
+ return (
+
+ );
+ } else {
+ return (
+
+ {' '}
+ No Blogposts Found with the Given Search Criteria
+
+ );
+ }
+}
+
+async function getPostList(topic) {
+ const fullTopic = await getTopic(topic.id);
+ return {
+ name: fullTopic.details.created_by.name,
+ excerpt: styleExcerpt(topic.excerpt),
+ creatorImage: getavatarURL(fullTopic.details.created_by.avatar_template),
+ tags: topic.tags,
+ image: fullTopic.image_url,
+ link:
+ 'https://developer.sailpoint.com/discuss/t/' +
+ topic.slug +
+ '/' +
+ topic.id,
+ title: topic.title,
+ views: fullTopic.views,
+ liked: topic.like_count,
+ replies: fullTopic.posts_count,
+ solution: topic.has_accepted_answer,
+ readTime: parseInt(fullTopic.word_count/100),
+ };
+}
+
+function getavatarURL(avatar) {
+ return "https://developer.sailpoint.com" + avatar.replace("{size}", "120")
+}
+
+function styleExcerpt(excerpt) {
+ if (excerpt.length > 150) {
+ return excerpt.slice(0, 150) + "..."
+ } else {
+ return excerpt.replace("…", "")
+ }
+}
+
diff --git a/src/components/blog/BlogCards/styles.module.css b/src/components/blog/BlogCards/styles.module.css
new file mode 100644
index 000000000..6dea5ccfc
--- /dev/null
+++ b/src/components/blog/BlogCards/styles.module.css
@@ -0,0 +1,35 @@
+/* Getting Started Card container */
+.gridContainer {
+ display: grid;
+ place-content: center;
+ grid-template-columns: repeat(auto-fit, minmax(345px, 1fr));
+ grid-gap: 40px;
+ margin-left: 40px;
+ margin-right: 40px;
+}
+
+.center {
+ margin: 0px auto;
+ max-width: 1300px;
+ margin-bottom: 50px;
+}
+
+.space {
+ height: 200px;
+}
+
+.noFound {
+ font-size: 28px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ padding: 8px;
+ margin: 50px;
+}
+
+
+
+.spinnerCenter {
+ margin: 100px auto;
+ max-width: 1300px;
+ margin-bottom: 50px;
+}
\ No newline at end of file
diff --git a/src/components/blog/BlogSidebar/BlogSidebarButton/index.js b/src/components/blog/BlogSidebar/BlogSidebarButton/index.js
new file mode 100644
index 000000000..48b9e676a
--- /dev/null
+++ b/src/components/blog/BlogSidebar/BlogSidebarButton/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import styles from './styles.module.css';
+
+
+export default function BlogSidebarButton({
+ filterCallback,
+ text
+ }) {
+ const [isActive, setIsActive] = React.useState(false);
+ const activeClass = isActive ? styles.tagSelected : ''
+ function setFilters(e, f) {
+ filterCallback(f)
+ setIsActive(current => !current);
+ }
+ return setFilters(e, text)} className={activeClass + ' ' + styles.tag}>{text}
+
+}
diff --git a/src/components/blog/BlogSidebar/BlogSidebarButton/styles.module.css b/src/components/blog/BlogSidebar/BlogSidebarButton/styles.module.css
new file mode 100644
index 000000000..7be4bbadc
--- /dev/null
+++ b/src/components/blog/BlogSidebar/BlogSidebarButton/styles.module.css
@@ -0,0 +1,36 @@
+
+.tag {
+ font-size: 16px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ padding: 8px;
+ margin-right: 5px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: var(--dev-text-color-normal);
+ transition: background-color 500ms;
+}
+
+.tagSelected {
+ font-size: 16px;
+ font-weight: 500;
+ background-color: var(--dev-secondary-text);
+ color: var(--dev-card-background);
+ padding: 8px;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: var(--dev-text-color-normal);
+ transition: background-color 500ms;
+}
+
+.tag:hover {
+ cursor: pointer;
+ background-color: var(--dev-text-color-normal);
+ color: var(--dev-card-background);
+}
+
diff --git a/src/components/blog/BlogSidebar/index.js b/src/components/blog/BlogSidebar/index.js
new file mode 100644
index 000000000..e25b75e96
--- /dev/null
+++ b/src/components/blog/BlogSidebar/index.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import Link from '@docusaurus/Link';
+import { getTags } from '../../../services/DiscourseService';
+import BlogSidebarButton from './BlogSidebarButton';
+
+export default function BlogSidebar({
+ filterCallback
+ }) {
+ const [tagProductData, setTagProductData] = React.useState();
+ const [tagTechnologyData, setTagTechnologyData] = React.useState();
+ const [filterTags, setFilterTags] = React.useState(true);
+
+ const getTagData = async () => {
+ const data = await getTags();
+ const tagTechnologyResultset = []
+ const tagProductResultset = []
+ for (const tagGroup of data.extras.tag_groups) {
+ if (tagGroup.id === 20) {
+ for (const tag of tagGroup.tags) {
+ tagProductResultset.push(tag.text)
+ }
+ }
+ if (tagGroup.id === 17) {
+ for (const tag of tagGroup.tags) {
+ tagTechnologyResultset.push(tag.text)
+ }
+ }
+ }
+ setTagProductData(tagProductResultset)
+ setTagTechnologyData(tagTechnologyResultset)
+ };
+
+ function toggleSeeAll() {
+ filterTags ? setFilterTags(false) : setFilterTags(true)
+ }
+
+
+ React.useEffect(() => {
+ getTagData();
+ }, []);
+
+ const filterText = filterTags ? 'See All Tags' : 'See Less Tags'
+
+ if (tagProductData && tagTechnologyData) {
+ return (
+
+
Posts by Product
+
+ {tagProductData.map(function(a, index){
+ return
+ })}
+
+
Posts by Identity Governance
+
+ {tagTechnologyData.map(function(a, index){
+ return
10 && filterTags ? styles.hidden : ''} >
+ })}
+
+
toggleSeeAll()}>
+ {filterText}
+ {/*
})
*/}
+
+
+
+ );
+ } else {
+ return ;
+ }
+}
diff --git a/src/components/blog/BlogSidebar/styles.module.css b/src/components/blog/BlogSidebar/styles.module.css
new file mode 100644
index 000000000..4a15ed7d7
--- /dev/null
+++ b/src/components/blog/BlogSidebar/styles.module.css
@@ -0,0 +1,40 @@
+
+.sidebar {
+ width: 400px;
+ height: 100%;
+ margin-left: 50px;
+}
+
+.tagHeader {
+ margin-top: 10px;
+ font-size: 22px;
+ font-weight: 700;
+}
+
+.tagContainer {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+
+.hidden {
+ display: none;
+}
+
+.seeAll {
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: var(--dev-secondary-text);
+ border-style: solid;
+ border-width: 1px;
+ width: 100%;
+ transition: background-color 500ms;
+ text-align: center;
+
+}
+
+.seeAll:hover {
+ cursor: pointer;
+ background-color: var(--dev-text-color-normal);
+ color: var(--dev-card-background);
+}
diff --git a/src/components/homepage/DiscussCard/index.js b/src/components/homepage/DiscussCard/index.js
index 769c6087f..f9ed059a6 100644
--- a/src/components/homepage/DiscussCard/index.js
+++ b/src/components/homepage/DiscussCard/index.js
@@ -64,7 +64,7 @@ export default function DiscussCard({
if (index > 2) {
return '';
}
- return {tag}
;
+ return {tag}
;
})}
diff --git a/src/components/marketplace/MarketplaceBanner/index.js b/src/components/marketplace/MarketplaceBanner/index.js
new file mode 100644
index 000000000..8c9e2cacd
--- /dev/null
+++ b/src/components/marketplace/MarketplaceBanner/index.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import Link from '@docusaurus/Link';
+
+export default function MarketplaceBanner() {
+ return (
+
+
+
+
+ SailPoint Developer Marketplace
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/marketplace/MarketplaceBanner/styles.module.css b/src/components/marketplace/MarketplaceBanner/styles.module.css
new file mode 100644
index 000000000..447c3ecb9
--- /dev/null
+++ b/src/components/marketplace/MarketplaceBanner/styles.module.css
@@ -0,0 +1,25 @@
+
+.blogHeaderText {
+ position: relative;
+ color: #ffffff;
+ font-size: 48px;
+ max-width: 459px;
+ font-weight: bold;
+ line-height: 130%;
+ top: 29px;
+ left: 228px;
+}
+
+.background {
+ width: 100%;
+ object-fit: repeat;
+ height: 100%;
+}
+
+.imageContainer {
+ width: 100%;
+ height: 180px;
+ background-size: 1920px 180px;
+ background-image: url('../../../../static/blog/marketplace_banner_template.png');
+ background-repeat: repeat;
+}
\ No newline at end of file
diff --git a/src/components/marketplace/MarketplaceCard/index.js b/src/components/marketplace/MarketplaceCard/index.js
new file mode 100644
index 000000000..1c40f9af8
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCard/index.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import styles from './styles.module.css';
+import Link from '@docusaurus/Link';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+import {addDarkToFileName} from '../../../util/util';
+import ReactMarkdown from 'react-markdown'
+export default function MarketplaceCard({
+ post,
+ openDialogFunc,
+}) {
+
+ function setFilters(e) {
+ openDialogFunc({"title": post.title, "image": post.image, "link": post.link, "id": post.id});
+ }
+
+ let badge = (
+
+ );
+ if (post.tags.includes("sailpoint-authored")) {
+ badge = (
+
+ );
+ }
+
+
+ return (
+ setFilters(e)}>
+
+
+
+
})
+
{post.views}
+
})
+
{post.replies}
+
+
+
+
})
+
{post.name}
+
+
+
+ {badge}
+
+
+
})
+
{post.title}
+
+ {post.tags?.map((tag, index) => {
+ if (index > 2) {
+ return '';
+ }
+ return
{tag}
;
+ })}
+
+
{post.excerpt}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/marketplace/MarketplaceCard/styles.module.css b/src/components/marketplace/MarketplaceCard/styles.module.css
new file mode 100644
index 000000000..5dd9f3464
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCard/styles.module.css
@@ -0,0 +1,138 @@
+/* Getting Started Card */
+.card {
+ position: relative;
+ margin-top: 20px;
+ height: 650px;
+ /* UI Properties */
+ background: var(--dev-card-background);
+ box-shadow: var(--dev-card-shadow);
+ border: 1px solid var(--dev-card-background);
+ border-radius: 40px;
+ opacity: 1;
+ transition: all 0.3s;
+ max-width: 600px;
+}
+
+.card:hover {
+ cursor: pointer;
+ transform: translate(0px, -5px);
+ box-shadow: var(--dev-card-selected);
+}
+
+
+
+
+
+.cardText {
+ position: absolute;
+ margin: 22px;
+ min-width: 170px;
+ top: 10px;
+ left: 0;
+}
+
+
+.cardTitle {
+ font-size: 22px;
+ font-weight: 700;
+}
+
+.cardBody {
+ font-size: 16px;
+ font-weight: 500;
+ top: 10px;
+ left: 0;
+}
+
+.tag {
+ font-size: 16px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ background-color: var(--dev-tag-highlight);
+ padding: 0px 8px;
+ margin-left: 5px;
+ margin-top: 5px;
+}
+
+.tags {
+ margin: 0px;
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+
+
+
+
+.cardUser {
+ position: absolute;
+ bottom: 10px;
+ left: 25px;
+ display: flex;
+}
+
+.cardFace {
+ border-radius: 9999px;
+ margin: auto;
+ height: 40px;
+ width: 40px;
+}
+
+.cardName {
+ min-width: 170px;
+ margin-top: 7px;
+ margin-left: 10px;
+}
+
+
+
+
+
+.cardData {
+ position: absolute;
+ bottom: 50px;
+ left: 20px;
+ display: flex;
+ align-items: center;
+}
+
+.cardEye {
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ margin-left: 5px;
+ height: 18px;
+ width: 18px;
+}
+
+.cardComment {
+ margin-left: 10px;
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ height: 18px;
+ width: 18px;
+ margin-bottom: 4px;
+}
+
+.cardCommentText {
+ color: #96a9ba;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ margin-left: 5px;
+ margin-bottom: 5px;
+ height: 20px;
+ font-size: 16px;
+}
+
+
+
+
+.cardBadge {
+ position: absolute;
+ right: 5px;
+ bottom: 0px;
+ margin-left: 5px;
+ height: 120px;
+ width: 120px;
+}
\ No newline at end of file
diff --git a/src/components/marketplace/MarketplaceCardDetail/index.js b/src/components/marketplace/MarketplaceCardDetail/index.js
new file mode 100644
index 000000000..037f8dda9
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCardDetail/index.js
@@ -0,0 +1,108 @@
+import React from 'react';
+import styles from './styles.module.css';
+import Link from '@docusaurus/Link';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+import {addDarkToFileName} from '../../../util/util';
+import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
+import 'react-tabs/style/react-tabs.css';
+import ReactMarkdown from 'react-markdown';
+import BounceLoader from 'react-spinners/BounceLoader';
+export default function MarketplaceCardDetail({details, rawPost}) {
+ const getDivText = (data, id) => {
+ const requirementPosition = data.indexOf('id="' + id + '"');
+ const requirementEndPosition = data.indexOf('', requirementPosition);
+ if (requirementPosition > 0 && requirementEndPosition > 0) {
+ const validContent = data.substring(
+ requirementPosition + 6 + id.length,
+ requirementEndPosition,
+ );
+ const incorrectURLPattern = /upload:\/\/([^"]+)/g;
+ const correctURLPattern =
+ 'https://developer.identitysoon.com/discuss/uploads/short-url/$1';
+ return validContent.replace(incorrectURLPattern, correctURLPattern);
+ } else {
+ return 'No requirements found for this marketplace item';
+ }
+ // https://developer.identitysoon.com/discuss/uploads/short-url/f9rlt0eoj7RnpndvL0CHjcEesV0.png?dl=1\
+ // upload://f9rlt0eoj7RnpndvL0CHjcEesV0.png
+ };
+
+ const goToLink = (link) => {
+ window.open(link, '_blank');
+ };
+
+ if (details) {
+ return (
+
+
+
{details.title}
+
})
+
+
+
+
+ Details
+ Requirements
+ Support
+
+
+
+
+ {getDivText(rawPost, 'details')}
+
+
+
+
+
+
+ {getDivText(rawPost, 'requirements')}
+
+
+
+
+ {getDivText(rawPost, 'support')}
+
+
+
+
+ );
+ } else {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/marketplace/MarketplaceCardDetail/styles.module.css b/src/components/marketplace/MarketplaceCardDetail/styles.module.css
new file mode 100644
index 000000000..cd921b7da
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCardDetail/styles.module.css
@@ -0,0 +1,80 @@
+.detailContainer {
+ max-width: 937px;
+}
+
+.detailHeader {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 20px;
+}
+
+.detailImage {
+ border-radius: 40px;
+ max-width: 400px;
+ min-width: 180px;
+}
+
+.detailTitle {
+ margin: 45px;
+ font-size: 48px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ text-shadow: rgba(17, 61, 158, 0.64) 1px 2px 2px;
+}
+
+.detailTabContent {
+ margin: 20px;
+}
+
+.detailTabs {
+ margin: 10px;
+}
+
+
+.modalButtonText {
+ position: relative;
+ top: -2px;
+}
+
+.modalButton {
+ border: 1px solid var(--ifm-color-primary);
+ box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
+ border-radius: 24px;
+ color: var(--ifm-color-primary);
+ margin: 10px;
+ padding: 10px;
+ text-align: center;
+ width: 125px;
+ background-color: #ffffff31;
+}
+
+.modalButton:hover {
+ cursor: pointer;
+ top: -2px;
+ box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
+ background-color: #dae1e9;
+ color: #005fc4;
+}
+
+.spinnerCenter {
+ margin: 30vh 20vh;
+}
+
+@media only screen and (max-width: 768px) {
+ .detailImage {
+ display: none;
+ }
+}
+
+.buttonImage {
+ position: relative;
+ left: -7px;
+ top: 4px;
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ margin-left: 5px;
+ height: 18px;
+ width: 18px;
+}
\ No newline at end of file
diff --git a/src/components/marketplace/MarketplaceCards/index.js b/src/components/marketplace/MarketplaceCards/index.js
new file mode 100644
index 000000000..3dd5ca268
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCards/index.js
@@ -0,0 +1,154 @@
+import React from 'react';
+import styles from './styles.module.css';
+import MarketplaceCard from '../MarketplaceCard';
+import Modal from 'react-modal';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import BounceLoader from 'react-spinners/BounceLoader';
+
+import {
+ getMarketplacePosts,
+ getMarketplaceTopic,
+ getMarketplaceTopicRaw,
+} from '../../../services/DiscourseService';
+import MarketplaceCardDetail from '../MarketplaceCardDetail';
+export default function MarketplaceCards({filterCallback}) {
+ const [cardData, setCardData] = React.useState();
+ const [detailsOpen, setDetailsOpen] = React.useState(false);
+ const [details, setDetails] = React.useState('');
+ const [loadingCards, setLoadingCards] = React.useState(true);
+
+ const getPosts = async () => {
+ const data = await getMarketplacePosts(filterCallback.join(','));
+ console.log(data)
+ const resultset = [];
+ if (data.topics) {
+ for (const topic of data.topics) {
+ if (topic.tags.length > 0) {
+ resultset.push(await getPostList(topic));
+ }
+ }
+ setCardData(resultset);
+ } else {
+ setCardData(undefined);
+ }
+ setLoadingCards(false);
+ };
+
+ const getDetails = async (data) => {
+ const raw = await getMarketplaceTopicRaw(data.id);
+ setDetails({data: data, raw: raw});
+ };
+
+ // callback that gets called when clicking on a card
+ const openDialog = (data) => {
+ setDetails({data: undefined, raw: undefined});
+ getDetails(data);
+ setDetailsOpen(true);
+ };
+
+ React.useEffect(() => {}, [detailsOpen, details]);
+
+ Modal.setAppElement('#__docusaurus');
+ React.useEffect(() => {
+ getPosts();
+ setCardData(undefined);
+ setLoadingCards(true);
+ }, [filterCallback]);
+
+
+ const xImage = useBaseUrl('/icons/circle-xmark-regular.svg')
+
+ if (cardData) {
+ return (
+
+
+ {cardData.map(function (a, index) {
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+

{
+ setDetailsOpen(false);
+ }}>
+
+
+
+ );
+ } else if (loadingCards) {
+ return (
+
+ );
+ } else {
+ return (
+
+ {' '}
+ No Marketplace Item Found with the Given Search Criteria
+
+ );
+ }
+}
+
+async function getPostList(topic) {
+ const fullTopic = await getMarketplaceTopic(topic.id);
+ return {
+ id: topic.id,
+ name: fullTopic.details.created_by.name,
+ excerpt: styleExcerpt(topic.excerpt),
+ creatorImage: getavatarURL(fullTopic.details.created_by.avatar_template),
+ tags: topic.tags,
+ image: fullTopic.image_url,
+ link:
+ 'https://developer.sailpoint.com/discuss/t/' +
+ topic.slug +
+ '/' +
+ topic.id,
+ title: topic.title,
+ views: fullTopic.views,
+ liked: topic.like_count,
+ replies: fullTopic.posts_count,
+ solution: topic.has_accepted_answer,
+ readTime: parseInt(fullTopic.word_count / 100),
+ };
+}
+
+function getavatarURL(avatar) {
+ return 'https://developer.sailpoint.com' + avatar.replace('{size}', '120');
+}
+
+function styleExcerpt(excerpt) {
+ if (excerpt) {
+ // remove any strings that have colons between them
+ excerpt = excerpt.replace(/:[^:]*:/g, '');
+ if (excerpt.length > 150) {
+ return excerpt.slice(0, 100) + '...';
+ } else {
+ return excerpt.replace('…', '');
+ }
+ } else {
+ return '';
+ }
+}
diff --git a/src/components/marketplace/MarketplaceCards/styles.module.css b/src/components/marketplace/MarketplaceCards/styles.module.css
new file mode 100644
index 000000000..549642a08
--- /dev/null
+++ b/src/components/marketplace/MarketplaceCards/styles.module.css
@@ -0,0 +1,78 @@
+/* Getting Started Card container */
+.gridContainer {
+ display: grid;
+ place-content: center;
+ grid-template-columns: repeat(auto-fit, minmax(345px, 1fr));
+ grid-gap: 40px;
+ margin-left: 40px;
+ margin-right: 40px;
+}
+
+.center {
+ margin: 0px auto;
+ max-width: 1300px;
+ margin-bottom: 50px;
+}
+
+.space {
+ height: 200px;
+}
+
+.noFound {
+ font-size: 28px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ padding: 8px;
+ margin: 50px;
+}
+
+
+
+
+.modal {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ right: auto;
+ bottom: auto;
+ margin-right: -50%;
+ transform: translate(-50%, -50%);
+ box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
+ border-radius: 20px;
+ background-color: var(--dev-card-background);
+ max-height: 100%;
+ max-width: 90%;
+ min-height: 80%;
+ scroll-behavior: auto;
+ overflow-y: auto;
+}
+
+
+
+
+.cardExit {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ fill: #96a9bb;
+ /* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
+ filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
+ margin-left: 5px;
+ height: 24px;
+ width: 24px;
+ transition: all 0.2s;
+}
+
+.cardExit:hover {
+ top: 8px;
+ cursor: pointer;
+ height: 23px;
+ width: 23px;
+}
+
+
+.spinnerCenter {
+ margin: 100px auto;
+ max-width: 1300px;
+ margin-bottom: 50px;
+}
\ No newline at end of file
diff --git a/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/index.js b/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/index.js
new file mode 100644
index 000000000..d6c84eb81
--- /dev/null
+++ b/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import styles from './styles.module.css';
+
+
+export default function MarketplaceSidebarButton({
+ filterCallback,
+ text
+ }) {
+ const [isActive, setIsActive] = React.useState(false);
+ const activeClass = isActive ? styles.tagSelected : ''
+ function setFilters(e, f) {
+ filterCallback(f)
+ setIsActive(current => !current);
+ }
+ return setFilters(e, text)} className={activeClass + ' ' + styles.tag}>{text}
+
+}
diff --git a/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/styles.module.css b/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/styles.module.css
new file mode 100644
index 000000000..7be4bbadc
--- /dev/null
+++ b/src/components/marketplace/MarketplaceSidebar/MarketplaceSidebarButton/styles.module.css
@@ -0,0 +1,36 @@
+
+.tag {
+ font-size: 16px;
+ font-weight: 500;
+ color: var(--dev-secondary-text);
+ padding: 8px;
+ margin-right: 5px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: var(--dev-text-color-normal);
+ transition: background-color 500ms;
+}
+
+.tagSelected {
+ font-size: 16px;
+ font-weight: 500;
+ background-color: var(--dev-secondary-text);
+ color: var(--dev-card-background);
+ padding: 8px;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+ border-style: solid;
+ border-width: 1px;
+ border-color: var(--dev-text-color-normal);
+ transition: background-color 500ms;
+}
+
+.tag:hover {
+ cursor: pointer;
+ background-color: var(--dev-text-color-normal);
+ color: var(--dev-card-background);
+}
+
diff --git a/src/components/marketplace/MarketplaceSidebar/index.js b/src/components/marketplace/MarketplaceSidebar/index.js
new file mode 100644
index 000000000..f62bdaf67
--- /dev/null
+++ b/src/components/marketplace/MarketplaceSidebar/index.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import Link from '@docusaurus/Link';
+import { getTags } from '../../../services/DiscourseService';
+import MarketplaceSidebarButton from './MarketplaceSidebarButton';
+
+export default function MarketplaceSidebar({
+ filterCallback
+ }) {
+ const [tagProductData, setTagProductData] = React.useState();
+ const [tagTechnologyData, setTagTechnologyData] = React.useState();
+ const [filterTags, setFilterTags] = React.useState(true);
+
+ const getTagData = async () => {
+ const data = await getTags();
+ const tagTechnologyResultset = []
+ const tagProductResultset = []
+ for (const tagGroup of data.extras.tag_groups) {
+ if (tagGroup.id === 20) {
+ for (const tag of tagGroup.tags) {
+ tagProductResultset.push(tag.text)
+ }
+ }
+ if (tagGroup.id === 17) {
+ for (const tag of tagGroup.tags) {
+ tagTechnologyResultset.push(tag.text)
+ }
+ }
+ }
+ setTagProductData(tagProductResultset)
+ setTagTechnologyData(tagTechnologyResultset)
+ };
+
+ function toggleSeeAll() {
+ filterTags ? setFilterTags(false) : setFilterTags(true)
+ }
+
+
+ React.useEffect(() => {
+ getTagData();
+ }, []);
+
+ const filterText = filterTags ? 'See All Tags' : 'See Less Tags'
+
+ if (tagProductData && tagTechnologyData) {
+ return (
+
+
Items by Product
+
+ {tagProductData.map(function(a, index){
+ return
+ })}
+
+
Items by Identity Governance
+
+ {tagTechnologyData.map(function(a, index){
+ return
10 && filterTags ? styles.hidden : ''} >
+ })}
+
+
toggleSeeAll()}>
+ {filterText}
+ {/*
})
*/}
+
+
+
+ );
+ } else {
+ return ;
+ }
+}
diff --git a/src/components/marketplace/MarketplaceSidebar/styles.module.css b/src/components/marketplace/MarketplaceSidebar/styles.module.css
new file mode 100644
index 000000000..4a15ed7d7
--- /dev/null
+++ b/src/components/marketplace/MarketplaceSidebar/styles.module.css
@@ -0,0 +1,40 @@
+
+.sidebar {
+ width: 400px;
+ height: 100%;
+ margin-left: 50px;
+}
+
+.tagHeader {
+ margin-top: 10px;
+ font-size: 22px;
+ font-weight: 700;
+}
+
+.tagContainer {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+
+.hidden {
+ display: none;
+}
+
+.seeAll {
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: var(--dev-secondary-text);
+ border-style: solid;
+ border-width: 1px;
+ width: 100%;
+ transition: background-color 500ms;
+ text-align: center;
+
+}
+
+.seeAll:hover {
+ cursor: pointer;
+ background-color: var(--dev-text-color-normal);
+ color: var(--dev-card-background);
+}
diff --git a/src/css/custom.css b/src/css/custom.css
index d543c0a18..31ad70df0 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -244,6 +244,7 @@
--dev-text-color-secondary: #0033a1;
--dev-secondary-text: #415364;
+ --dev-tag-highlight: #eaeef1;
--text-on-primary: #ffffff;
@@ -286,6 +287,7 @@
--dev-card-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
--dev-card-selected: 0 5px 5px rgba(107, 107, 107, 0.2);
--dev-secondary-text: #dae1e9;
+ --dev-tag-highlight: #00000075;
/*main hero card*/
--main-hero-card-background: #202122;
diff --git a/src/pages/blog.js b/src/pages/blog.js
new file mode 100644
index 000000000..b8e30ec62
--- /dev/null
+++ b/src/pages/blog.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import clsx from 'clsx';
+import Link from '@docusaurus/Link';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import Layout from '@theme/Layout';
+import BlogBanner from '../components/blog/BlogBanner';
+
+import styles from './blog.module.css';
+import BlogCards from '../components/blog/BlogCards';
+import BlogSidebar from '../components/blog/BlogSidebar';
+
+export default function Blog() {
+ const [filteredProduct, setFilteredProduct] = React.useState([]);
+
+ const {siteConfig} = useDocusaurusContext();
+
+ const handleClick = (data) => {
+ var tempFilter = filteredProduct.slice()
+
+ const index = tempFilter.indexOf(data);
+ if (index !== -1) {
+ tempFilter.splice(index, 1);
+ } else {
+ tempFilter.push(data)
+ }
+
+ setFilteredProduct(tempFilter)
+ };
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/blog.module.css b/src/pages/blog.module.css
new file mode 100644
index 000000000..d16546f19
--- /dev/null
+++ b/src/pages/blog.module.css
@@ -0,0 +1,17 @@
+.blogContainer {
+ display: flex;
+}
+
+.blogSidbarContainer {
+ flex: 5%;
+}
+
+@media only screen and (max-width: 870px) {
+ .blogSidbarContainer {
+ display: none;
+ }
+}
+
+.blogCardContainer {
+ flex: 95%;
+}
\ No newline at end of file
diff --git a/src/pages/marketplace.js b/src/pages/marketplace.js
new file mode 100644
index 000000000..0bd99ac98
--- /dev/null
+++ b/src/pages/marketplace.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import clsx from 'clsx';
+import Link from '@docusaurus/Link';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import Layout from '@theme/Layout';
+import MarketplaceBanner from '../components/marketplace/MarketplaceBanner';
+
+import styles from './marketplace.module.css';
+import MarketplaceCards from '../components/marketplace/MarketplaceCards';
+import MarketplaceSidebar from '../components/marketplace/MarketplaceSidebar';
+
+export default function Marketplace() {
+ const [filteredProduct, setFilteredProduct] = React.useState([]);
+
+ const {siteConfig} = useDocusaurusContext();
+
+ const handleClick = (data) => {
+ var tempFilter = filteredProduct.slice()
+
+ const index = tempFilter.indexOf(data);
+ if (index !== -1) {
+ tempFilter.splice(index, 1);
+ } else {
+ tempFilter.push(data)
+ }
+
+ setFilteredProduct(tempFilter)
+ };
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/marketplace.module.css b/src/pages/marketplace.module.css
new file mode 100644
index 000000000..d16546f19
--- /dev/null
+++ b/src/pages/marketplace.module.css
@@ -0,0 +1,17 @@
+.blogContainer {
+ display: flex;
+}
+
+.blogSidbarContainer {
+ flex: 5%;
+}
+
+@media only screen and (max-width: 870px) {
+ .blogSidbarContainer {
+ display: none;
+ }
+}
+
+.blogCardContainer {
+ flex: 95%;
+}
\ No newline at end of file
diff --git a/src/services/DiscourseService.js b/src/services/DiscourseService.js
index 194f97876..7879b1cb4 100644
--- a/src/services/DiscourseService.js
+++ b/src/services/DiscourseService.js
@@ -8,3 +8,81 @@ export async function getTopPosts() {
return [];
}
}
+
+export async function getBlogPosts(tags) {
+ let url = ''
+ if (tags) {
+ url = 'https://developer.sailpoint.com/discuss/search.json?q=category:blog+tags:' + tags
+ } else {
+ url = 'https://developer.sailpoint.com/discuss/search.json?q=category:blog'
+ }
+ try {
+ const response = await fetch(
+ url,
+ );
+ return await response.json();
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getMarketplacePosts(tags) {
+ let url = ''
+ if (tags) {
+ url = 'https://developer.sailpoint.com/discuss/search.json?q=category:marketplace+tags:' + tags
+ } else {
+ url = 'https://developer.sailpoint.com/discuss/search.json?q=category:marketplace'
+ }
+ try {
+ const response = await fetch(
+ url,
+ );
+ return await response.json();
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getTopic(id) {
+ try {
+ const response = await fetch(
+ 'https://developer.sailpoint.com/discuss/t/' + id + '.json',
+ );
+ return await response.json();
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getMarketplaceTopic(id) {
+ try {
+ const response = await fetch(
+ 'https://developer.sailpoint.com/discuss/t/' + id + '.json',
+ );
+ return await response.json();
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getMarketplaceTopicRaw(id) {
+ try {
+ const response = await fetch(
+ 'https://developer.sailpoint.com/discuss/raw/' + id + '.json',
+ );
+ return await response.text();
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getTags() {
+ try {
+ const response = await fetch(
+ 'https://developer.sailpoint.com/discuss/tags.json',
+ );
+ return await response.json();
+ } catch (error) {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/static/blog/Identity-Security-Cloud-Platform.png b/static/blog/Identity-Security-Cloud-Platform.png
new file mode 100644
index 000000000..1424af27c
Binary files /dev/null and b/static/blog/Identity-Security-Cloud-Platform.png differ
diff --git a/static/blog/blog_banner_template.png b/static/blog/blog_banner_template.png
new file mode 100644
index 000000000..9859513c0
Binary files /dev/null and b/static/blog/blog_banner_template.png differ
diff --git a/static/blog/caret-down-thin.svg b/static/blog/caret-down-thin.svg
new file mode 100644
index 000000000..b97785a6a
--- /dev/null
+++ b/static/blog/caret-down-thin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/blog/clock-light.svg b/static/blog/clock-light.svg
new file mode 100644
index 000000000..e28560de9
--- /dev/null
+++ b/static/blog/clock-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/blog/comment-light.svg b/static/blog/comment-light.svg
new file mode 100644
index 000000000..41509edf3
--- /dev/null
+++ b/static/blog/comment-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/blog/comment-regular.svg b/static/blog/comment-regular.svg
new file mode 100644
index 000000000..9ef59bdbd
--- /dev/null
+++ b/static/blog/comment-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/blog/eye-regular.svg b/static/blog/eye-regular.svg
new file mode 100644
index 000000000..7eccb4333
--- /dev/null
+++ b/static/blog/eye-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/blog/marketplace_banner_template.png b/static/blog/marketplace_banner_template.png
new file mode 100644
index 000000000..d85daa14a
Binary files /dev/null and b/static/blog/marketplace_banner_template.png differ
diff --git a/static/blog/watch-light.svg b/static/blog/watch-light.svg
new file mode 100644
index 000000000..cea5a24d4
--- /dev/null
+++ b/static/blog/watch-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/SailPoint-LogoIcon-RGB-Color.svg b/static/icons/SailPoint-LogoIcon-RGB-Color.svg
new file mode 100644
index 000000000..858328dfd
--- /dev/null
+++ b/static/icons/SailPoint-LogoIcon-RGB-Color.svg
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
diff --git a/static/icons/circle-xmark-regular.svg b/static/icons/circle-xmark-regular.svg
new file mode 100644
index 000000000..ca484b8d3
--- /dev/null
+++ b/static/icons/circle-xmark-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/discourse.svg b/static/icons/discourse.svg
new file mode 100644
index 000000000..77a6b1de3
--- /dev/null
+++ b/static/icons/discourse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/github.svg b/static/icons/github.svg
new file mode 100644
index 000000000..40e8178e4
--- /dev/null
+++ b/static/icons/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file