From 5fd0009785be955959ad2f16a9de39bea8e6ef2a Mon Sep 17 00:00:00 2001 From: Krishna Gehlot Date: Thu, 27 Aug 2026 03:51:54 +0000 Subject: [PATCH 1/2] refactor: extract duplicated code into shared utilities Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .lintstagedrc.js | 2 +- src/components/Related-Posts/index.js | 124 ++-- src/pages/blog/index.js | 92 +-- .../Company/Legal/cookie-notice/index.js | 296 +++++--- .../Company/Legal/legal-table.style.js | 47 ++ src/sections/Company/Legal/privacy/index.js | 657 +++++++++++++----- .../Company/Legal/sub-processors/index.js | 334 +++++---- src/sections/FeatureHero/index.js | 27 +- src/sections/Home/So-Special-Section/index.js | 184 +++-- src/sections/Pricing/review-slider.js | 71 +- src/sections/SolutionHero/index.js | 27 +- src/templates/blog-category-list.js | 104 ++- src/templates/blog-tag-list.js | 103 ++- src/utils/usePaginatedSearch.js | 39 ++ src/utils/useResponsiveSlider.js | 51 ++ src/utils/useScrollTiltEffect.js | 33 + 16 files changed, 1329 insertions(+), 862 deletions(-) create mode 100644 src/sections/Company/Legal/legal-table.style.js create mode 100644 src/utils/usePaginatedSearch.js create mode 100644 src/utils/useResponsiveSlider.js create mode 100644 src/utils/useScrollTiltEffect.js diff --git a/.lintstagedrc.js b/.lintstagedrc.js index 717b7424a3c85a..74436ca10182cd 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -1,7 +1,7 @@ module.exports = { // Lint & Prettify TS and JS files - only the staged files "**/*.(ts|tsx|js|jsx)": (filenames) => [ - `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, + `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 --no-warn-ignored ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, `npx prettier --write ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, ], diff --git a/src/components/Related-Posts/index.js b/src/components/Related-Posts/index.js index cbbc43dbbc2a85..e5d3a890a53b0f 100644 --- a/src/components/Related-Posts/index.js +++ b/src/components/Related-Posts/index.js @@ -1,55 +1,33 @@ -import React, { useLayoutEffect, useState, useRef } from "react"; +import React from "react"; import { Link } from "gatsby"; import { IoIosArrowRoundForward } from "@react-icons/all-files/io/IoIosArrowRoundForward"; import Card from "../Card"; import RelatedPostsWrapper from "./relatedPosts.style"; import { Col } from "../../reusecore/Layout"; import Slider from "react-slick"; +import useResponsiveSlider from "../../utils/useResponsiveSlider"; -const RelatedPosts = props => { - const [isClient, setIsClient] = useState(false); - const [slidesToShowState, setSlidesToShowState] = useState(null); - const sliderRef = useRef(null); - - const { postType , relatedPosts , mainHead , lastCardHead , linkToAllItems } = props; - - const computeSlides = () => { - const w = typeof window !== "undefined" ? (window.innerWidth || document.documentElement.clientWidth) : 1200; - if (w <= 600) return 1; - if (w <= 800) return 1.5; - if (w <= 1024) return 2; - if (w <= 1200) return 3; - return 3; - }; - - useLayoutEffect(() => { - setIsClient(true); - setSlidesToShowState(computeSlides()); - - let resizeTimeout = null; - const onResizeDebounced = () => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { - const slides = computeSlides(); - setSlidesToShowState((prev) => { - if (prev !== slides) return slides; - return prev; - }); - if (sliderRef.current && sliderRef.current.innerSlider && typeof sliderRef.current.innerSlider.onWindowResized === "function") { - sliderRef.current.innerSlider.onWindowResized(); - } - }, 100); - }; +const computeSlides = () => { + const w = + typeof window !== "undefined" + ? window.innerWidth || document.documentElement.clientWidth + : 1200; + if (w <= 600) return 1; + if (w <= 800) return 1.5; + if (w <= 1024) return 2; + if (w <= 1200) return 3; + return 3; +}; - window.addEventListener("resize", onResizeDebounced); - window.addEventListener("load", onResizeDebounced); +const RelatedPosts = (props) => { + const { + isClient, + slidesToShow: slidesToShowState, + sliderRef, + } = useResponsiveSlider(computeSlides); - return () => { - window.removeEventListener("resize", onResizeDebounced); - window.removeEventListener("load", onResizeDebounced); - clearTimeout(resizeTimeout); - }; - }, []); + const { postType, relatedPosts, mainHead, lastCardHead, linkToAllItems } = + props; const settings = { dots: slidesToShowState < 2, @@ -63,41 +41,43 @@ const RelatedPosts = props => { touchThreshold: 10, }; - return (

{mainHead}

- { - isClient && - - { - postType === "blogs" ? relatedPosts.map((item) => { - const post = item.post || item; - return ( - - - - ); - }) : relatedPosts.map((post) => { - return ( - - - - ); - }) - } - -
- -

{lastCardHead}

- - -
+ {isClient && ( + + {postType === "blogs" + ? relatedPosts.map((item) => { + const post = item.post || item; + return ( + + + + ); + }) + : relatedPosts.map((post) => { + return ( + + - - } + ); + })} + +
+ +

{lastCardHead}

+ + +
+ + + )}
); }; diff --git a/src/pages/blog/index.js b/src/pages/blog/index.js index e6901cc309c270..6c5d5a4b1fa07b 100644 --- a/src/pages/blog/index.js +++ b/src/pages/blog/index.js @@ -1,44 +1,49 @@ import React, { useState, useEffect } from "react"; -import useDataList from "../../utils/usedataList"; +import usePaginatedSearch from "../../utils/usePaginatedSearch"; import SEO from "../../components/seo"; import BlogGrid from "../../sections/Blog/Blog-grid"; import { graphql } from "gatsby"; import loadable from "@loadable/component"; import LitePlaceholder from "../../templates/lite-placeholder"; -const BlogList = loadable(() => import ("../../sections/Blog/Blog-list")); +const BlogList = loadable(() => import("../../sections/Blog/Blog-list")); -export const query = graphql`query allBlogs { - allMdx( - sort: {fields: {dateForSort: DESC}} - filter: {fields: {collection: {eq: "blog"}}, frontmatter: {published: {eq: true}}} - ) { - nodes { - id - frontmatter { - title - date(formatString: "MMM Do, YYYY") - author - thumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) +export const query = graphql` + query allBlogs { + allMdx( + sort: { fields: { dateForSort: DESC } } + filter: { + fields: { collection: { eq: "blog" } } + frontmatter: { published: { eq: true } } + } + ) { + nodes { + id + frontmatter { + title + date(formatString: "MMM Do, YYYY") + author + thumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } - } - darkthumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) + darkthumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } } - } - fields { - slug + fields { + slug + } } } } -}`; +`; const Blog = (props) => { const nodes = props.data?.allMdx?.nodes ?? []; @@ -56,19 +61,16 @@ const Blog = (props) => { } const [isListView, setIsListView] = useState(false); - const [searchQuery, setSearchQuery] = useState(""); - const { queryResults, searchData } = useDataList( - nodes, - setSearchQuery, + const { + searchedPosts, + queryResults, + searchData, searchQuery, - ["frontmatter", "title"], - "id" - ); - const [currentPage, setCurrentPage] = useState(1); - const [postsPerPage] = useState(10); - const indexOfLastPost = currentPage * postsPerPage; - const indexOfFirstPost = indexOfLastPost - postsPerPage; - const searchedPosts = queryResults.slice(indexOfFirstPost, indexOfLastPost); + setSearchQuery, + currentPage, + setCurrentPage, + postsPerPage, + } = usePaginatedSearch(nodes); const setListView = () => { setIsListView(true); }; @@ -100,11 +102,15 @@ const Blog = (props) => { currentPage={currentPage} queryResults={queryResults} /> - ); }; export default Blog; export const Head = () => { - return ; -}; \ No newline at end of file + return ( + + ); +}; diff --git a/src/sections/Company/Legal/cookie-notice/index.js b/src/sections/Company/Legal/cookie-notice/index.js index 46ea138fe37050..379536ca2d9ae2 100644 --- a/src/sections/Company/Legal/cookie-notice/index.js +++ b/src/sections/Company/Legal/cookie-notice/index.js @@ -3,53 +3,21 @@ import { Container, Row, Col } from "../../../../reusecore/Layout"; import TermsWrapper from "../terms.style"; import { useStyledDarkMode } from "../../../../theme/app/useStyledDarkMode"; import { - styled, Table, - TableContainer, - TableCell, TableRow, TableHead, TableBody, SistentThemeProvider, - createTheme, } from "@sistent/sistent"; import TOC from "../../../../components/legal-navigation/index"; import TocPagination from "../../../../components/legal-navigation/TocPagination"; - - - -const StyledTableContainer = styled(TableContainer)(() => ({ - width: "100%", - backgroundColor: "transparent", - boxShadow: "none", - padding: 0, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-child(odd)": { - backgroundColor: theme.palette.background.default, - borderRadius: "0px", - }, - "&:nth-child(even)": { - backgroundColor: theme.palette.background.secondary, - borderRadius: "0px", - }, -})); - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: "0.975rem", - padding: "0.75rem", - borderBottom: `1px solid ${theme.palette.divider}`, - borderRadius: "0px", - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); - -const StyledHeaderCell = styled(StyledTableCell)(() => ({ - fontWeight: 600, - backgroundColor: "transparent", - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); +import { + StyledTableContainer, + StyledTableRow, + StyledTableCell, + StyledHeaderCell, + legalTableTheme, +} from "../legal-table.style"; const cookieData = [ { @@ -57,39 +25,40 @@ const cookieData = [ rows: [ { provider: "Google", - description: "We use Google Tag Manager, Google Analytics, Google Ads conversion tracking, and Google reCAPTCHA to manage tracking, analyze website usage, and prevent automated abuse. Google Analytics uses anonymized IP addresses to ensure no direct correlation to individuals.", + description: + "We use Google Tag Manager, Google Analytics, Google Ads conversion tracking, and Google reCAPTCHA to manage tracking, analyze website usage, and prevent automated abuse. Google Analytics uses anonymized IP addresses to ensure no direct correlation to individuals.", category: "Functional and Marketing", - privacyLink: "Google Privacy Preferences", + privacyLink: + 'Google Privacy Preferences', }, { provider: "Cloudflare", - description: "Cloudflare cookies distinguish users sharing the same IP address for rate limiting and performance optimization.", + description: + "Cloudflare cookies distinguish users sharing the same IP address for rate limiting and performance optimization.", category: "Necessary & Performance", - privacyLink: "Cloudflare Privacy Policy", + privacyLink: + 'Cloudflare Privacy Policy', }, { provider: "GitHub", - description: "GitHub cookies support seamless integration with our open-source community platforms, such as remembering your preferences during contributions.", + description: + "GitHub cookies support seamless integration with our open-source community platforms, such as remembering your preferences during contributions.", category: "Functional", - privacyLink: "GitHub Privacy Policy", + privacyLink: + 'GitHub Privacy Policy', }, { provider: "Slack", - description: "Slack cookies enhance your experience in our community channels by remembering preferences and enabling seamless communication.", + description: + "Slack cookies enhance your experience in our community channels by remembering preferences and enabling seamless communication.", category: "Functional", - privacyLink: "Slack Privacy Policy", + privacyLink: + 'Slack Privacy Policy', }, ], }, ]; -// Create custom theme with Qanelas Soft font family -const customTheme = createTheme({ - typography: { - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", - }, -}); - const CookieNotice = () => { const { isDark } = useStyledDarkMode(); const [didLoad, setDidLoad] = React.useState(false); @@ -114,97 +83,196 @@ const CookieNotice = () => {

- Welcome to Layer5, Inc. We are committed to protecting your privacy and providing transparency about how we use cookies and similar technologies on our websites, including layer5.io, meshery.dev, getnighthawk.dev, cloud.layer5.io, kanvas.new, and any related subdomains. This Cookie Notice explains what cookies are, how we use them, the types of cookies we employ, and how you can manage your cookie preferences. + Welcome to Layer5, Inc. We are committed to protecting your + privacy and providing transparency about how we use cookies and + similar technologies on our websites, including layer5.io, + meshery.dev, getnighthawk.dev, cloud.layer5.io, kanvas.new, and + any related subdomains. This Cookie Notice explains what cookies + are, how we use them, the types of cookies we employ, and how + you can manage your cookie preferences.

General Information

- When you visit our websites, we may store or retrieve information on your browser, primarily through cookies. Cookies are small text files placed on your device to store information such as your preferences, login details, or browsing behavior. This information helps our websites function properly, enhances your user experience, and supports analytics and marketing efforts. + When you visit our websites, we may store or retrieve + information on your browser, primarily through cookies. Cookies + are small text files placed on your device to store information + such as your preferences, login details, or browsing behavior. + This information helps our websites function properly, enhances + your user experience, and supports analytics and marketing + efforts.

- We use both first-party cookies (set by us) and third-party cookies (set by our trusted partners) for various purposes, such as improving site performance, personalizing content, and tailoring advertisements. This Cookie Notice applies to all Layer5 websites as defined in our Privacy Notice. + We use both first-party cookies (set by us) and{" "} + third-party cookies (set by our trusted + partners) for various purposes, such as improving site + performance, personalizing content, and tailoring + advertisements. This Cookie Notice applies to all Layer5 + websites as defined in our{" "} + Privacy Notice.

- Because we respect your right to privacy, you can choose not to allow certain types of cookies. However, blocking some cookies may affect your experience on our websites and the services we provide. + Because we respect your right to privacy, you can choose not to + allow certain types of cookies. However, blocking some cookies + may affect your experience on our websites and the services we + provide.

Types of Cookies We Use

- We utilize session-based and persistent cookies, as well as similar technologies like web beacons, to enhance user-friendliness and gather insights about website usage. Below are the categories of cookies we use: + We utilize session-based and persistent cookies, as well as + similar technologies like web beacons, to enhance + user-friendliness and gather insights about website usage. Below + are the categories of cookies we use:

  • - Necessary Cookies: These cookies are essential for our websites to function properly and cannot be disabled in our systems. They are typically set in response to your actions, such as setting privacy preferences, logging in, or filling out forms. While you can configure your browser to block these cookies, doing so may cause some parts of our websites to malfunction. These cookies do not store personally identifiable information. + Necessary Cookies: These cookies are + essential for our websites to function properly and cannot be + disabled in our systems. They are typically set in response to + your actions, such as setting privacy preferences, logging in, + or filling out forms. While you can configure your browser to + block these cookies, doing so may cause some parts of our + websites to malfunction. These cookies do not store personally + identifiable information.
  • - Performance Cookies: These cookies help us measure and improve our websites’ performance by collecting aggregated, anonymous data about visits and traffic sources. They allow us to understand which pages are most and least popular and how visitors navigate our sites. If you disable these cookies, we will not be able to track your visits or optimize site performance. + Performance Cookies: These cookies help us + measure and improve our websites’ performance by collecting + aggregated, anonymous data about visits and traffic sources. + They allow us to understand which pages are most and least + popular and how visitors navigate our sites. If you disable + these cookies, we will not be able to track your visits or + optimize site performance.
  • - Functional Cookies: These cookies enable enhanced functionality and personalization, such as remembering your preferences or enabling specific features. They may be set by us or by third-party providers whose services we integrate. Disabling these cookies may limit the functionality of certain services. + Functional Cookies: These cookies enable + enhanced functionality and personalization, such as + remembering your preferences or enabling specific features. + They may be set by us or by third-party providers whose + services we integrate. Disabling these cookies may limit the + functionality of certain services.
  • - Marketing Cookies: These cookies are used by our advertising partners to build a profile of your interests and deliver relevant ads on other websites. They identify your browser and device but do not store directly personal information. If you disable these cookies, you will experience less targeted advertising. + Marketing Cookies: These cookies are used by + our advertising partners to build a profile of your interests + and deliver relevant ads on other websites. They identify your + browser and device but do not store directly personal + information. If you disable these cookies, you will experience + less targeted advertising.

- To review or modify your cookie preferences, click Select Cookie Settings in the cookie banner or the link in our website footer. + To review or modify your cookie preferences, click{" "} + Select Cookie Settings in the cookie banner or + the link in our website footer.

Cookie Settings

- You can manage your cookie preferences at any time through our Privacy Preference Center, accessible via the Select Cookie Settings link in the cookie banner or our website footer. This tool allows you to enable or disable non-essential cookies and withdraw consent as easily as you provided it. + You can manage your cookie preferences at any time through our{" "} + Privacy Preference Center, accessible via the{" "} + Select Cookie Settings link in the cookie + banner or our website footer. This tool allows you to enable or + disable non-essential cookies and withdraw consent as easily as + you provided it.

- You can also prevent cookies by adjusting your browser settings to block them. Note that disabling cookies may limit certain features or functionalities on our websites. For specific third-party cookies, you can opt out by following the instructions in their respective privacy notices (see below). + You can also prevent cookies by adjusting your browser settings + to block them. Note that disabling cookies may limit certain + features or functionalities on our websites. For specific + third-party cookies, you can opt out by following the + instructions in their respective privacy notices (see below).

Third-Party Cookies

- Third-party cookies are set by domains other than Layer5’s to support analytics, advertising, or other functionalities. These cookies may collect data across multiple websites to tailor ads based on your interests. Below is a list of third-party providers we use, along with their purposes and opt-out options: + Third-party cookies are set by domains other than Layer5’s to + support analytics, advertising, or other functionalities. These + cookies may collect data across multiple websites to tailor ads + based on your interests. Below is a list of third-party + providers we use, along with their purposes and opt-out options:

- {didLoad && ( - - - - - - - - Third Party - Description - Category - Privacy Notice - - - - {cookieData.map((section, index) => ( - - - - {section.section} - - - {section.rows.map((row, rowIndex) => ( - - {row.provider} - {row.description} - {row.category} - + {didLoad && ( + + + + +
Table 1: Third-Party Cookies Used on Layer5 Websites
+ + + + Third Party + Description + Category + + Privacy Notice + + + + + {cookieData.map((section, index) => ( + + + + {section.section} + - ))} - - ))} - -
+ Table 1: Third-Party Cookies Used on Layer5 Websites +
-
- -
-
)} + {section.rows.map((row, rowIndex) => ( + + + {row.provider} + + + {row.description} + + + {row.category} + + + + ))} + + ))} + + + + +
+ + )}

- You can also opt out of certain third-party cookies via the Digital Advertising Alliance’s opt-out tool or, for European visitors, Your Online Choices. + You can also opt out of certain third-party cookies via the{" "} + + Digital Advertising Alliance’s opt-out tool + {" "} + or, for European visitors,{" "} + + Your Online Choices + + .

- We do not knowingly share personal information with third-party advertising or marketing services in a manner that would constitute a “sale” under the California Consumer Privacy Act (CCPA). + We do not knowingly share personal information with third-party + advertising or marketing services in a manner that would + constitute a “sale” under the California Consumer Privacy Act + (CCPA).

Further Information

- For more details about how we process your personal data, please review our Privacy Notice. If you have questions or wish to exercise your data protection rights, contact us at the details below. + For more details about how we process your personal data, please + review our Privacy Notice. + If you have questions or wish to exercise your data protection + rights, contact us at the details below.

@@ -214,18 +282,20 @@ const CookieNotice = () => {

How to Contact Us

- We welcome your comments or questions about this Cookie Notice. You may contact us as follows: + We welcome your comments or questions about this Cookie Notice. + You may contact us as follows:

Layer5, Inc
- 1000 Congress Avenue + 1000 Congress Avenue
- Austin, Texas 78735 + Austin, Texas 78735
- Email Address: privacy@layer5.io + Email Address:{" "} + privacy@layer5.io
- Telephone number: 512-810-8200 + Telephone number: 512-810-8200

@@ -241,4 +311,4 @@ const CookieNotice = () => { ); }; -export default CookieNotice; \ No newline at end of file +export default CookieNotice; diff --git a/src/sections/Company/Legal/legal-table.style.js b/src/sections/Company/Legal/legal-table.style.js new file mode 100644 index 00000000000000..472a512bf78cb4 --- /dev/null +++ b/src/sections/Company/Legal/legal-table.style.js @@ -0,0 +1,47 @@ +import { + styled, + TableCell, + TableContainer, + TableRow, + createTheme, +} from "@sistent/sistent"; + +export const StyledTableContainer = styled(TableContainer)(() => ({ + width: "100%", + backgroundColor: "transparent", + boxShadow: "none", + padding: 0, +})); + +export const StyledTableRow = styled(TableRow)(({ theme }) => ({ + "&:nth-child(odd)": { + backgroundColor: theme.palette.background.default, + borderRadius: 0, + }, + "&:nth-child(even)": { + backgroundColor: theme.palette.background.secondary, + borderRadius: 0, + }, +})); + +export const StyledTableCell = styled(TableCell)(({ theme }) => ({ + color: theme.palette.text.secondary, + fontSize: "0.975rem", + padding: "0.75rem", + borderBottom: `1px solid ${theme.palette.divider}`, + borderRadius: 0, + fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', +})); + +export const StyledHeaderCell = styled(StyledTableCell)(() => ({ + fontWeight: 600, + backgroundColor: "transparent", + fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', +})); + +// Custom theme with Qanelas Soft font family for legal tables +export const legalTableTheme = createTheme({ + typography: { + fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', + }, +}); diff --git a/src/sections/Company/Legal/privacy/index.js b/src/sections/Company/Legal/privacy/index.js index d246a861a7da77..b2bd4371974f93 100644 --- a/src/sections/Company/Legal/privacy/index.js +++ b/src/sections/Company/Legal/privacy/index.js @@ -3,67 +3,44 @@ import { Container, Row, Col } from "../../../../reusecore/Layout"; import TermsWrapper from "../terms.style"; import { useStyledDarkMode } from "../../../../theme/app/useStyledDarkMode"; import { - styled, Table, - TableContainer, - TableCell, TableRow, TableHead, TableBody, SistentThemeProvider, - createTheme, } from "@sistent/sistent"; import TOC from "../../../../components/legal-navigation/index"; import TocPagination from "../../../../components/legal-navigation/TocPagination"; - -const StyledTableContainer = styled(TableContainer)(() => ({ - width: "100%", - backgroundColor: "transparent", - boxShadow: "none", - padding: 0, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-child(odd)": { - backgroundColor: theme.palette.background.default, - borderRadius: 0, - }, - "&:nth-child(even)": { - backgroundColor: theme.palette.background.secondary, - borderRadius: 0, - }, -})); - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: "0.975rem", - padding: "0.75rem", - borderBottom: `1px solid ${theme.palette.divider}`, - borderRadius: 0, - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); - -const StyledHeaderCell = styled(StyledTableCell)(() => ({ - fontWeight: 600, - backgroundColor: "transparent", - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); +import { + StyledTableContainer, + StyledTableRow, + StyledTableCell, + StyledHeaderCell, + legalTableTheme, +} from "../legal-table.style"; const privacyData = [ { - section: "When you browse our websites (layer5.io, getnighthawk.dev, meshery.dev, ) and applications (cloud.layer5.io, kanvas.new)", + section: + "When you browse our websites (layer5.io, getnighthawk.dev, meshery.dev, ) and applications (cloud.layer5.io, kanvas.new)", rows: [ { purpose: "To operate, secure, and maintain our website.", - categories: "IP Address, device type, browser information, server logs.", - lawfulBasis: "Legitimate Interest (Article 6(1)(f)) - to ensure the security, availability, and performance of our digital properties.", - retention: "Up to 26 months for server logs, depending on the specific data and its purpose.", + categories: + "IP Address, device type, browser information, server logs.", + lawfulBasis: + 'Legitimate Interest (Article 6(1)(f)) - to ensure the security, availability, and performance of our digital properties.', + retention: + "Up to 26 months for server logs, depending on the specific data and its purpose.", }, { purpose: "To analyze website usage and improve user experience.", - categories: "Anonymized IP address, cookie identifiers, pages visited, duration of visit, clickstream data.", - lawfulBasis: "Consent (Article 6(1)(a)) - for non-essential analytics and tracking cookies. You can manage your consent through our cookie banner.", - retention: "Up to 26 months for analytics data, depending on the specific service used.", + categories: + "Anonymized IP address, cookie identifiers, pages visited, duration of visit, clickstream data.", + lawfulBasis: + 'Consent (Article 6(1)(a)) - for non-essential analytics and tracking cookies. You can manage your consent through our cookie banner.', + retention: + "Up to 26 months for analytics data, depending on the specific service used.", }, ], }, @@ -71,10 +48,14 @@ const privacyData = [ section: "When you join our community (e.g., Slack, Forum, GitHub)", rows: [ { - purpose: "To manage your participation and facilitate collaboration in our open-source projects.", - categories: "Name, email address, GitHub username, public profile information, content of your contributions (code, comments, issues), and communications.", - lawfulBasis: "Legitimate Interest (Article 6(1)(f)) - to operate, manage, and grow our collaborative open source community.", - retention: "Your contributions are retained indefinitely as part of the public project record. Other personal data is retained for the duration of your participation and up to 12 months thereafter.", + purpose: + "To manage your participation and facilitate collaboration in our open-source projects.", + categories: + "Name, email address, GitHub username, public profile information, content of your contributions (code, comments, issues), and communications.", + lawfulBasis: + 'Legitimate Interest (Article 6(1)(f)) - to operate, manage, and grow our collaborative open source community.', + retention: + "Your contributions are retained indefinitely as part of the public project record. Other personal data is retained for the duration of your participation and up to 12 months thereafter.", }, ], }, @@ -82,28 +63,41 @@ const privacyData = [ section: "When you sign up for and use Layer5 Cloud", rows: [ { - purpose: "To create and manage your account and provide our services to you.", - categories: "Name, email address, company name, securely hashed password, GitHub user ID (if used for single sign-on).", - lawfulBasis: "Performance of a Contract (Article 6(1)(b)) - this data is necessary to fulfill our contractual obligation to provide the service you have signed up for.", - retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", + purpose: + "To create and manage your account and provide our services to you.", + categories: + "Name, email address, company name, securely hashed password, GitHub user ID (if used for single sign-on).", + lawfulBasis: + 'Performance of a Contract (Article 6(1)(b)) - this data is necessary to fulfill our contractual obligation to provide the service you have signed up for.', + retention: + "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, { purpose: "To process payments for our paid subscription plans.", - categories: "Billing address, payment card information (we do not store full card details; they are securely processed by our payment provider, who provides us with a transaction token and confirmation).", - lawfulBasis: "Performance of a Contract (Article 6(1)(b)).", - retention: "Financial data is retained for 7 years to comply with legal and tax obligations.", + categories: + "Billing address, payment card information (we do not store full card details; they are securely processed by our payment provider, who provides us with a transaction token and confirmation).", + lawfulBasis: + 'Performance of a Contract (Article 6(1)(b)).', + retention: + "Financial data is retained for 7 years to comply with legal and tax obligations.", }, { - purpose: "To communicate with you about the service (e.g., important updates, security alerts, billing information).", + purpose: + "To communicate with you about the service (e.g., important updates, security alerts, billing information).", categories: "Email address.", - lawfulBasis: "Performance of a Contract (Article 6(1)(b)) and Legitimate Interest (Article 6(1)(f)) - to provide you with essential information about the service you are using.", - retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", + lawfulBasis: + 'Performance of a Contract (Article 6(1)(b)) and Legitimate Interest (Article 6(1)(f)) - to provide you with essential information about the service you are using.', + retention: + "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, { purpose: "To monitor service performance and improve our products.", - categories: "Service usage data, API logs, user activity logs, device and browser information.", - lawfulBasis: "Legitimate Interest (Article 6(1)(f)) - to maintain and improve the quality and functionality of our services.", - retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", + categories: + "Service usage data, API logs, user activity logs, device and browser information.", + lawfulBasis: + 'Legitimate Interest (Article 6(1)(f)) - to maintain and improve the quality and functionality of our services.', + retention: + "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, ], }, @@ -112,9 +106,12 @@ const privacyData = [ rows: [ { purpose: "To respond to your inquiries and provide customer support.", - categories: "Name, email address, and any other information you provide in the content of your message.", - lawfulBasis: "Legitimate Interest (Article 6(1)(f)) - to effectively respond to user inquiries and provide assistance.", - retention: "For the duration of the support interaction and up to 12 months thereafter for quality assurance purposes.", + categories: + "Name, email address, and any other information you provide in the content of your message.", + lawfulBasis: + 'Legitimate Interest (Article 6(1)(f)) - to effectively respond to user inquiries and provide assistance.', + retention: + "For the duration of the support interaction and up to 12 months thereafter for quality assurance purposes.", }, ], }, @@ -122,22 +119,17 @@ const privacyData = [ section: "When you subscribe to our marketing communications", rows: [ { - purpose: "To send you newsletters, product updates, and other marketing materials.", + purpose: + "To send you newsletters, product updates, and other marketing materials.", categories: "Name, email address.", - lawfulBasis: "Consent (Article 6(1)(a)) - you provide your consent when you opt-in to receive these communications, and you can unsubscribe at any time.", + lawfulBasis: + 'Consent (Article 6(1)(a)) - you provide your consent when you opt-in to receive these communications, and you can unsubscribe at any time.', retention: "Until you unsubscribe.", }, ], }, ]; -// Create custom theme with Qanelas Soft font family -const customTheme = createTheme({ - typography: { - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", - }, -}); - const Privacy = () => { const { isDark } = useStyledDarkMode(); const [didLoad, setDidLoad] = React.useState(false); @@ -164,203 +156,485 @@ const Privacy = () => {

- Welcome to Layer5. We are Layer5, Inc., the company behind the cloud native management platform Meshery, the Layer5 Cloud service, Kanvas, and a vibrant global open-source community. We are committed to protecting your privacy and being transparent about how we handle your personal data. + Welcome to Layer5. We are Layer5, Inc., the company behind the + cloud native management platform Meshery, the Layer5 Cloud + service, Kanvas, and a vibrant global open-source community. We + are committed to protecting your privacy and being transparent + about how we handle your personal data.

- This Privacy Notice explains what personal data we collect when you visit our websites, use our services, or participate in our community. It also describes why we collect it, how we use and protect it, and what rights you have regarding your personal data. + This Privacy Notice explains what personal data we collect when + you visit our websites, use our services, or participate in our + community. It also describes why we collect it, how we use and + protect it, and what rights you have regarding your personal + data.

- For detailed information about the third-party service providers we work with to deliver our services, please see our Sub-Processors and Service Providers, which includes our Data Protection Addendum. + For detailed information about the third-party service providers + we work with to deliver our services, please see our{" "} + + Sub-Processors and Service Providers + + , which includes our{" "} + + Data Protection Addendum + + .

Who We Are

- Welcome to Layer5. We are Layer5, Inc., the company behind the cloud native management platform Meshery, the Layer5 Cloud service, Kanvas, and a vibrant global open-source community. We are committed to protecting your privacy and being transparent about how we handle your personal data. -

-

- The data controller responsible for your personal data is: + Welcome to Layer5. We are Layer5, Inc., the company behind the + cloud native management platform Meshery, the Layer5 Cloud + service, Kanvas, and a vibrant global open-source community. We + are committed to protecting your privacy and being transparent + about how we handle your personal data.

+

The data controller responsible for your personal data is:

+

Layer5, Inc.

- Layer5, Inc. + If you have any questions about this Privacy Notice or wish to + exercise your data protection rights, please see the{" "} + How to Contact Us section + below.

- If you have any questions about this Privacy Notice or wish to exercise your data protection rights, please see the How to Contact Us section below. -

-

- Important Clarification: This Privacy Notice applies solely to the services, websites, and communities operated by Layer5, Inc., incorporated in the United States, accessible via the layer5.io domain and its subdomains. It does not apply to any other company or organization that may have a similar name, such as "Layer 5 Solutions Ltd". We are not affiliated with any other entity named "Layer5" or "Layer 5." + Important Clarification: This Privacy Notice + applies solely to the services, websites, and communities + operated by Layer5, Inc., incorporated in the United States, + accessible via the layer5.io domain and its subdomains. It does + not apply to any other company or organization that may have a + similar name, such as "Layer 5 Solutions Ltd". We are not + affiliated with any other entity named "Layer5" or "Layer 5."

Personal Data We Collect, Our Purposes, and Lawful Bases

- We process your personal data for a variety of purposes depending on how you interact with us. The table below details what we collect, why we collect it, and the legal justification (lawful basis) under the GDPR for doing so. -

- {didLoad && ( - - - - - - - - Purpose for Processing Your Data - Categories of Personal Data We Process - Lawful Basis for Processing (under GDPR) - Data Retention Duration - - - - {privacyData.map((section, index) => ( - - - - {section.section} - - - {section.rows.map((row, rowIndex) => ( - - {row.purpose} - {row.categories} - - {row.retention} + We process your personal data for a variety of purposes + depending on how you interact with us. The table below details + what we collect, why we collect it, and the legal justification + (lawful basis) under the GDPR for doing so. +

+ {didLoad && ( + + +
+ +
Table 1: Summary of Our Data Processing Activities under GDPR
+ + + + + Purpose for Processing Your Data + + + Categories of Personal Data We Process + + + Lawful Basis for Processing (under GDPR) + + + Data Retention Duration + + + + + {privacyData.map((section, index) => ( + + + + {section.section} + - ))} - - ))} - - INTERNAL DOC# - #gdpr-10c - #gdpr-2 - #gdpr-9b - - -
+ Table 1: Summary of Our Data Processing Activities + under GDPR +
-
- -
-
)} + {section.rows.map((row, rowIndex) => ( + + + {row.purpose} + + + {row.categories} + + + + {row.retention} + + + ))} + + ))} + + + INTERNAL DOC# + + #gdpr-10c + #gdpr-2 + #gdpr-9b + + + + + + + + )}

Cookies and Tracking Technologies

- We use cookies and similar technologies on our website to help it function, to analyze performance, and to personalize your experience. A cookie is a small text file stored on your device. + We use cookies and similar technologies on our website to help + it function, to analyze performance, and to personalize your + experience. A cookie is a small text file stored on your device.

    -
  • Strictly Necessary Cookies: These are essential for the website to function and cannot be switched off. They are usually set in response to actions made by you, such as setting your privacy preferences or filling in forms.
  • -
  • Performance and Analytics Cookies: These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site.
  • -
  • Marketing Cookies: These cookies may be set through our site by our advertising partners to build a profile of your interests and show you relevant adverts on other sites.
  • +
  • + Strictly Necessary Cookies: These are + essential for the website to function and cannot be switched + off. They are usually set in response to actions made by you, + such as setting your privacy preferences or filling in forms. +
  • +
  • + Performance and Analytics Cookies: These + cookies allow us to count visits and traffic sources so we can + measure and improve the performance of our site. They help us + to know which pages are the most and least popular and see how + visitors move around the site. +
  • +
  • + Marketing Cookies: These cookies may be set + through our site by our advertising partners to build a + profile of your interests and show you relevant adverts on + other sites. +

- We will not set non-essential cookies on your device without your explicit consent. You can manage your cookie preferences at any time through our cookie consent tool, which is accessible via a link in the footer of our website. Withdrawing your consent is as easy as giving it. + We will not set non-essential cookies on your device without + your explicit consent. You can manage your cookie preferences at + any time through our cookie consent tool, which is accessible + via a link in the footer of our website. Withdrawing your + consent is as easy as giving it.

Who We Share Your Personal Data With

- We do not sell your personal data. However, we share it with trusted third-party service providers who act as our data processors to help us operate our business and services. We only share the minimum amount of data necessary and have contracts in place that require them to keep your information secure and only use it for the purposes we specify. -

-

- Categories of these recipients include: + We do not sell your personal data. However, we share it with + trusted third-party service providers who act as our data + processors to help us operate our business and services. We only + share the minimum amount of data necessary and have contracts in + place that require them to keep your information secure and only + use it for the purposes we specify.

+

Categories of these recipients include:

    -
  • Cloud & Hosting Providers: We use third-party cloud infrastructure providers (e.g., Amazon Web Services, Equinix) to host our website, services, and data.
  • -
  • Payment Processors: We use secure third-party payment processors (e.g., Stripe) to handle financial transactions for our paid services.
  • -
  • Analytics & Monitoring Services: We use services (e.g., Google Analytics) to help us understand how our website and services are used so we can improve them.
  • -
  • Communication & Collaboration Platforms: Our open source community operates on platforms like GitHub and Slack. When you participate, your data is processed by these platforms according to their own privacy policies.
  • -
  • Business & Support Tools: We use third-party software for customer support (e.g., ClickUp) and customer relationship management to communicate with you effectively.
  • -
  • Email and Marketing Automation Providers: We use third-party services to send transactional emails and marketing communications (e.g., MailChimp and Google Groups).
  • +
  • + Cloud & Hosting Providers: We use third-party + cloud infrastructure providers (e.g., Amazon Web Services, + Equinix) to host our website, services, and data. +
  • +
  • + Payment Processors: We use secure third-party + payment processors (e.g., Stripe) to handle financial + transactions for our paid services. +
  • +
  • + Analytics & Monitoring Services: We use + services (e.g., Google Analytics) to help us understand how + our website and services are used so we can improve them. +
  • +
  • + Communication & Collaboration Platforms: Our + open source community operates on platforms like GitHub and + Slack. When you participate, your data is processed by these + platforms according to their own privacy policies. +
  • +
  • + Business & Support Tools: We use third-party + software for customer support (e.g., ClickUp) and customer + relationship management to communicate with you effectively. +
  • +
  • + Email and Marketing Automation Providers: We + use third-party services to send transactional emails and + marketing communications (e.g., MailChimp and Google Groups). +

- We may also disclose your personal data if required to do so by law or in response to valid requests by public authorities (e.g., a court or a government agency). + We may also disclose your personal data if required to do so by + law or in response to valid requests by public authorities + (e.g., a court or a government agency).

Linked Websites and Third-Party Apps

- We may provide access or links to third-party websites, Apps, and services that are outside Layer5's control and governed by the respective third party’s privacy policy, not by this Privacy Statement. We encourage you to review the privacy statements posted on the websites you visit and in the applications you use. + We may provide access or links to third-party websites, Apps, + and services that are outside Layer5's control and governed by + the respective third party’s privacy policy, not by this Privacy + Statement. We encourage you to review the privacy statements + posted on the websites you visit and in the applications you + use.

Forums and Chat Rooms

- If you participate in a discussion forum, local communities, or chat room on a Layer5 website, you should be aware that the information you provide there (such as your public profile and comments) will be made broadly available to others and could be used to contact you, to send you unsolicited messages, or for purposes neither Layer5 nor you have control over. Also, please recognize that individual forums and chat rooms may have additional rules and conditions. Layer5 is not responsible for the Personal Data or any other information you choose to submit in these forums. To request removal of your Personal Data from our blog or community forum, please submit a Privacy Request. In some cases, we may not be able to remove all Personal Data and comments. In such cases, we will provide you with a response and explanation. + If you participate in a discussion forum, local communities, or + chat room on a Layer5 website, you should be aware that the + information you provide there (such as your public profile and + comments) will be made broadly available to others and could be + used to contact you, to send you unsolicited messages, or for + purposes neither Layer5 nor you have control over. Also, please + recognize that individual forums and chat rooms may have + additional rules and conditions. Layer5 is not responsible for + the Personal Data or any other information you choose to submit + in these forums. To request removal of your Personal Data from + our blog or community forum, please submit a Privacy Request. In + some cases, we may not be able to remove all Personal Data and + comments. In such cases, we will provide you with a response and + explanation.

International Transfers of Personal Data

- Layer5, Inc. is based in the United States. Your personal data will be processed in the United States and other countries where our third-party service providers are located. When we transfer personal data from the European Economic Area (EEA), the UK, or Switzerland to other countries, we do so in compliance with applicable data protection laws. + Layer5, Inc. is based in the United States. Your personal data + will be processed in the United States and other countries where + our third-party service providers are located. When we transfer + personal data from the European Economic Area (EEA), the UK, or + Switzerland to other countries, we do so in compliance with + applicable data protection laws.

- For transfers of data to countries not deemed to provide an adequate level of data protection by the European Commission, we rely on legal safeguards, primarily the European Commission's Standard Contractual Clauses (SCCs), to ensure your data is protected. + For transfers of data to countries not deemed to provide an + adequate level of data protection by the European Commission, we + rely on legal safeguards, primarily the European Commission's + Standard Contractual Clauses (SCCs), to ensure your data is + protected.

How Long We Keep Your Personal Data

- We retain your personal data only for as long as is necessary to fulfill the purposes for which we collected it, including for the purposes of satisfying any legal, accounting, or reporting requirements. + We retain your personal data only for as long as is necessary to + fulfill the purposes for which we collected it, including for + the purposes of satisfying any legal, accounting, or reporting + requirements.

- To determine the appropriate retention period, we consider the amount, nature, and sensitivity of the personal data, the potential risk of harm from unauthorized use or disclosure, the purposes for which we process it, and whether we can achieve those purposes through other means. + To determine the appropriate retention period, we consider the + amount, nature, and sensitivity of the personal data, the + potential risk of harm from unauthorized use or disclosure, the + purposes for which we process it, and whether we can achieve + those purposes through other means.

    -
  • Account Data: We retain your Layer5 Cloud account data for as long as your account is active and for a period of 12 months thereafter to allow for account reactivation and to resolve any final billing or support issues.
  • -
  • Financial Data: Data related to financial transactions is retained for 7 years to comply with legal and tax obligations.
  • -
  • Community Contributions: Your contributions to our open-source projects on platforms like GitHub (e.g., code, comments, issues) are part of the public project record and are retained indefinitely.
  • -
  • Website Analytics Data: Data collected for analytics purposes is typically retained in an aggregated or anonymized form for up to 26 months.
  • +
  • + Account Data: We retain your Layer5 Cloud + account data for as long as your account is active and for a + period of 12 months thereafter to allow for account + reactivation and to resolve any final billing or support + issues. +
  • +
  • + Financial Data: Data related to financial + transactions is retained for 7 years to comply with legal and + tax obligations. +
  • +
  • + Community Contributions: Your contributions + to our open-source projects on platforms like GitHub (e.g., + code, comments, issues) are part of the public project record + and are retained indefinitely. +
  • +
  • + Website Analytics Data: Data collected for + analytics purposes is typically retained in an aggregated or + anonymized form for up to 26 months. +

Your Data Protection Rights

- Under the GDPR, you have several important rights regarding your personal data. These include: + Under the GDPR, you have several important rights regarding your + personal data. These include:

    -
  • The right to be informed: You have the right to be provided with clear, transparent, and easily understandable information about how we use your information and your rights.
  • -
  • The right of access: You have the right to obtain access to your personal data.
  • -
  • The right to rectification: You are entitled to have your information corrected if it is inaccurate or incomplete.
  • -
  • The right to erasure: Also known as ‘the right to be forgotten,’ this enables you to request the deletion or removal of your information where there is no compelling reason for us to keep using it.
  • -
  • The right to restrict processing: You have rights to ‘block’ or suppress further use of your information.
  • -
  • The right to data portability: You have the right to obtain and reuse your personal data for your own purposes across different services.
  • -
  • The right to object: You have the right to object to certain types of processing, such as direct marketing.
  • -
  • Rights in relation to automated decision making and profiling: You have the right not to be subject to a decision based solely on automated processing, including profiling, which produces legal effects concerning you or similarly significantly affects you.
  • +
  • + The right to be informed: You have the right + to be provided with clear, transparent, and easily + understandable information about how we use your information + and your rights. +
  • +
  • + The right of access: You have the right to + obtain access to your personal data. +
  • +
  • + The right to rectification: You are entitled + to have your information corrected if it is inaccurate or + incomplete. +
  • +
  • + The right to erasure: Also known as ‘the + right to be forgotten,’ this enables you to request the + deletion or removal of your information where there is no + compelling reason for us to keep using it. +
  • +
  • + The right to restrict processing: You have + rights to ‘block’ or suppress further use of your information. +
  • +
  • + The right to data portability: You have the + right to obtain and reuse your personal data for your own + purposes across different services. +
  • +
  • + The right to object: You have the right to + object to certain types of processing, such as direct + marketing. +
  • +
  • + + Rights in relation to automated decision making and + profiling: + {" "} + You have the right not to be subject to a decision based + solely on automated processing, including profiling, which + produces legal effects concerning you or similarly + significantly affects you. +

How to Exercise Your Rights

- To exercise any of the rights described above, please send your request to our dedicated privacy email address: privacy@layer5.io. + To exercise any of the rights described above, please send your + request to our dedicated privacy email address:{" "} + privacy@layer5.io.

- We will respond to your request within one month of receipt. We may need to request specific information from you to help us confirm your identity and ensure your right to access your personal data (or to exercise any of your other rights). This is a security measure to ensure that personal data is not disclosed to any person who has no right to receive it. + We will respond to your request within one month of receipt. We + may need to request specific information from you to help us + confirm your identity and ensure your right to access your + personal data (or to exercise any of your other rights). This is + a security measure to ensure that personal data is not disclosed + to any person who has no right to receive it.

Children's Privacy

- Our services and community are not directed to individuals under the age of 16. We do not knowingly collect personal data from children under 16. If we become aware that a child under 16 has provided us with personal data, we will take steps to delete such information. If you believe that a child has provided us with personal data, please contact us at privacy@layer5.io. + Our services and community are not directed to individuals under + the age of 16. We do not knowingly collect personal data from + children under 16. If we become aware that a child under 16 has + provided us with personal data, we will take steps to delete + such information. If you believe that a child has provided us + with personal data, please contact us at{" "} + privacy@layer5.io.

Updates to This Privacy Notice

- We may update this Privacy Notice from time to time to reflect changes in our practices or for other operational, legal, or regulatory reasons. We will notify you of any significant changes by posting the new notice on our website and, where appropriate, by notifying you directly via email. We encourage you to review this notice periodically. + We may update this Privacy Notice from time to time to reflect + changes in our practices or for other operational, legal, or + regulatory reasons. We will notify you of any significant + changes by posting the new notice on our website and, where + appropriate, by notifying you directly via email. We encourage + you to review this notice periodically.

How to Make a Complaint

- We are committed to resolving any concerns you may have about our use of your information. If you have a complaint, we hope you will contact us first at privacy@layer5.io so that we can try to resolve it. + We are committed to resolving any concerns you may have about + our use of your information. If you have a complaint, we hope + you will contact us first at{" "} + privacy@layer5.io so that + we can try to resolve it.

- However, if you are not satisfied with our response, you have the right to lodge a complaint with a data protection supervisory authority. This will typically be the authority in the EU country where you live or work, or where the alleged infringement of data protection law occurred. + However, if you are not satisfied with our response, you have + the right to lodge a complaint with a data protection + supervisory authority. This will typically be the authority in + the EU country where you live or work, or where the alleged + infringement of data protection law occurred.


Commitment to Data Privacy and Ongoing Compliance

Operationalizing Data Subject Rights

- A privacy notice that promises data subject rights without the internal capacity to fulfill them creates a significant liability. Layer5 must establish a formal, documented process for managing received at the designated privacy@layer5.io email address. + A privacy notice that promises data subject rights without the + internal capacity to fulfill them creates a significant + liability. Layer5 must establish a formal, documented process + for managing received at the designated{" "} + privacy@layer5.io email + address.

    -
  1. Request Logging: Creating a centralized log to track the date of each request, the identity of the requester, the nature of the request, the deadline for response (30 days), and the date of fulfillment.
  2. -
  3. Identity Verification: Implementing a secure procedure to verify the identity of the individual making the request to prevent unauthorized disclosure of personal data.
  4. -
  5. Data Discovery and Compilation: Developing clear workflows for locating and gathering all relevant personal data for a specific individual across all of Layer5's systems. This includes the Layer5 Cloud production database, CRM systems, payment processor dashboards, community platforms (to the extent possible), and marketing automation tools.
  6. -
  7. Secure Delivery: Establishing a secure method for delivering the compiled information to the data subject.
  8. +
  9. + Request Logging: Creating a centralized log + to track the date of each request, the identity of the + requester, the nature of the request, the deadline for + response (30 days), and the date of fulfillment. +
  10. +
  11. + Identity Verification: Implementing a secure + procedure to verify the identity of the individual making the + request to prevent unauthorized disclosure of personal data. +
  12. +
  13. + Data Discovery and Compilation: Developing + clear workflows for locating and gathering all relevant + personal data for a specific individual across all of Layer5's + systems. This includes the Layer5 Cloud production database, + CRM systems, payment processor dashboards, community platforms + (to the extent possible), and marketing automation tools. +
  14. +
  15. + Secure Delivery: Establishing a secure method + for delivering the compiled information to the data subject. +

- Operational readiness is a core component of the accountability principle under GDPR. Having this process in place ensures that Layer5 meets legal obligations efficiently and demonstrably. + Operational readiness is a core component of the accountability + principle under GDPR. Having this process in place ensures that + Layer5 meets legal obligations efficiently and demonstrably.

Implementing Compliant Consent Management

- To lawfully use non-essential cookies for analytics and marketing, we obtain valid, explicit consent from users in the EU/EEA. This requires more than a simple "we use cookies" banner. + To lawfully use non-essential cookies for analytics and + marketing, we obtain valid, explicit consent from users in the + EU/EEA. This requires more than a simple "we use cookies" + banner.

    -
  • Block all non-essential cookies and tracking scripts from firing until the user has given their explicit consent.
  • -
  • Provide users with granular control, allowing them to consent to specific categories of cookies (e.g., "Analytics") while rejecting others (e.g., "Marketing").
  • -
  • Make it as easy for users to withdraw consent as it was to give it, typically via an easily accessible link or icon on the website.
  • -
  • Log user consent choices to provide an audit trail for demonstrating compliance.
  • +
  • + Block all non-essential cookies and tracking scripts from + firing until the user has given their explicit + consent. +
  • +
  • + Provide users with granular control, allowing them to consent + to specific categories of cookies (e.g., "Analytics") while + rejecting others (e.g., "Marketing"). +
  • +
  • + Make it as easy for users to withdraw consent as it was to + give it, typically via an easily accessible link or icon on + the website. +
  • +
  • + Log user consent choices to provide an audit trail for + demonstrating compliance. +

- We uphold our commitment to lawfully conducting website analytics and honoring users' right to withdraw consent at any time. + We uphold our commitment to lawfully conducting website + analytics and honoring users' right to withdraw consent at any + time.

Maintaining a Record of Processing Activities (ROPA)

- In accordance with Article 30 of the GDPR, as a data controller, we maintain an internal Record of Processing Activities (ROPA). This document details all categories of personal data we process, the purposes of processing, data subjects, data recipients, international transfers, retention periods, and security measures. + In accordance with{" "} + Article 30 of + the GDPR, as a data controller, we maintain an internal Record + of Processing Activities (ROPA). This document details all + categories of personal data we process, the purposes of + processing, data subjects, data recipients, international + transfers, retention periods, and security measures.

@@ -370,18 +644,21 @@ const Privacy = () => {

How to Contact Us

- We are committed to proactively ensuring clarity in all corporate communications to reinforce professionalism and transparency. We welcome your comments or questions about this privacy policy. You may also contact us as follows: + We are committed to proactively ensuring clarity in all + corporate communications to reinforce professionalism and + transparency. We welcome your comments or questions about this + privacy policy. You may also contact us as follows:

Layer5, Inc
- 1000 Congress Avenue + 1000 Congress Avenue
- Austin, Texas 78735 + Austin, Texas 78735
- Email Address: legal@layer5.io + Email Address: legal@layer5.io
- Telephone number: 512-810-8200 + Telephone number: 512-810-8200

@@ -397,4 +674,4 @@ const Privacy = () => { ); }; -export default Privacy; \ No newline at end of file +export default Privacy; diff --git a/src/sections/Company/Legal/sub-processors/index.js b/src/sections/Company/Legal/sub-processors/index.js index 5c6256a921d97a..89e4fd5d230f98 100644 --- a/src/sections/Company/Legal/sub-processors/index.js +++ b/src/sections/Company/Legal/sub-processors/index.js @@ -3,54 +3,21 @@ import { Container, Row, Col } from "../../../../reusecore/Layout"; import TermsWrapper from "../terms.style"; import { useStyledDarkMode } from "../../../../theme/app/useStyledDarkMode"; import { - styled, Table, - TableContainer, - TableCell, TableRow, TableHead, TableBody, SistentThemeProvider, - createTheme, } from "@sistent/sistent"; import TOC from "../../../../components/legal-navigation/index"; import TocPagination from "../../../../components/legal-navigation/TocPagination"; - - - -// Styled components from Privacy page -const StyledTableContainer = styled(TableContainer)(() => ({ - width: "100%", - backgroundColor: "transparent", - boxShadow: "none", - padding: 0, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-child(odd)": { - backgroundColor: theme.palette.background.default, - borderRadius: "0px", - }, - "&:nth-child(even)": { - backgroundColor: theme.palette.background.secondary, - borderRadius: "0px", - }, -})); - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: "0.975rem", - padding: "0.75rem", - borderBottom: `1px solid ${theme.palette.divider}`, - borderRadius: "0px", - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); - -const StyledHeaderCell = styled(StyledTableCell)(() => ({ - fontWeight: 600, - backgroundColor: "transparent", - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", -})); +import { + StyledTableContainer, + StyledTableRow, + StyledTableCell, + StyledHeaderCell, + legalTableTheme, +} from "../legal-table.style"; // Data structure for sub-processors and service providers const subProcessorData = [ @@ -61,7 +28,8 @@ const subProcessorData = [ name: "Amazon Web Services US", applicableServices: "Community Discussion Forum", typeOfService: "Cloud computing provider", - dataShared: "Community Discussion Forum user account data, posts, and comments therein.", + dataShared: + "Community Discussion Forum user account data, posts, and comments therein.", hostingRegions: "United States", additionalInfo: "All accounts", }, @@ -69,7 +37,8 @@ const subProcessorData = [ name: "Equinix", applicableServices: "Layer5 Cloud", typeOfService: "Data center provider", - dataShared: "Layer5 Cloud user account data, including email addresses and hashed passwords.", + dataShared: + "Layer5 Cloud user account data, including email addresses and hashed passwords.", hostingRegions: "United States", additionalInfo: "All accounts", }, @@ -99,19 +68,23 @@ const subProcessorData = [ }, { name: "Slack", - applicableServices: "Internal Communications, Customer support messaging", + applicableServices: + "Internal Communications, Customer support messaging", typeOfService: "Communication platform", dataShared: "Messages, files, and user information", hostingRegions: "United States", - additionalInfo: "Used for internal team communication and customer support interactions.", + additionalInfo: + "Used for internal team communication and customer support interactions.", }, { name: "Celonis, Inc. (make.com)", - applicableServices: "Layer5 Cloud (for workflow automation), open source community, Slack, and MailChimp", + applicableServices: + "Layer5 Cloud (for workflow automation), open source community, Slack, and MailChimp", typeOfService: "Workflow automation platform", dataShared: "Workflow data and user information", hostingRegions: "United States", - additionalInfo: "Used for automating workflows within Layer5 Cloud and open source community.", + additionalInfo: + "Used for automating workflows within Layer5 Cloud and open source community.", }, { name: "ClickUp", @@ -119,7 +92,8 @@ const subProcessorData = [ typeOfService: "Project management software", dataShared: "Project data, tasks, and user information", hostingRegions: "United States", - additionalInfo: "Used for managing internal projects and customer support tasks.", + additionalInfo: + "Used for managing internal projects and customer support tasks.", }, { name: "Atlassian StatusPage", @@ -127,7 +101,8 @@ const subProcessorData = [ typeOfService: "Status page hosting and incident communication", dataShared: "Service status data and user information", hostingRegions: "United States", - additionalInfo: "Used for communicating service status and incidents to users.", + additionalInfo: + "Used for communicating service status and incidents to users.", }, ], }, @@ -136,60 +111,71 @@ const subProcessorData = [ rows: [ { name: "Amazon Web Services US", - contactInfo: "https://aws.amazon.com/contact-us/", - dataProtectionAddendum: "https://aws.amazon.com/compliance/data-privacy-faq/", + contactInfo: + 'https://aws.amazon.com/contact-us/', + dataProtectionAddendum: + 'https://aws.amazon.com/compliance/data-privacy-faq/', }, { name: "Equinix", - contactInfo: "https://www.equinix.com/contact-us/", - dataProtectionAddendum: "https://www.equinix.com/legal/data-protection-addendum/", + contactInfo: + 'https://www.equinix.com/contact-us/', + dataProtectionAddendum: + 'https://www.equinix.com/legal/data-protection-addendum/', }, { name: "GitHub, Inc.", - contactInfo: "GitHub Privacy Office
88 Colin P. Kelly Jr. Street
San Francisco, California 94107 USA
dpo@github.com
https://support.github.com/contact", - dataProtectionAddendum: "https://docs.github.com/en/site-policy/privacy-policies", + contactInfo: + 'GitHub Privacy Office
88 Colin P. Kelly Jr. Street
San Francisco, California 94107 USA
dpo@github.com
https://support.github.com/contact', + dataProtectionAddendum: + 'https://docs.github.com/en/site-policy/privacy-policies', }, { name: "Cloudflare, Inc.", - contactInfo: "101 Townsend Street
San Francisco, CA 94107
USA
Emily Hancock
Data Protection Officer
legal@cloudflare.com
https://www.cloudflare.com/contact/", - dataProtectionAddendum: "https://www.cloudflare.com/terms/cloudflare-dpa/", + contactInfo: + '101 Townsend Street
San Francisco, CA 94107
USA
Emily Hancock
Data Protection Officer
legal@cloudflare.com
https://www.cloudflare.com/contact/', + dataProtectionAddendum: + 'https://www.cloudflare.com/terms/cloudflare-dpa/', }, { name: "Stripe", - contactInfo: "Stripe, Inc.
510 Townsend St.
San Francisco, CA 94103
USA
dpo@stripe.com
https://support.stripe.com/contact", - dataProtectionAddendum: "https://stripe.com/privacy", + contactInfo: + 'Stripe, Inc.
510 Townsend St.
San Francisco, CA 94103
USA
dpo@stripe.com
https://support.stripe.com/contact', + dataProtectionAddendum: + 'https://stripe.com/privacy', }, { name: "Slack", - contactInfo: "Slack Technologies, LLC
500 Howard Street
San Francisco, CA 94105
USA
dpo@slack.com
https://slack.com/contact", - dataProtectionAddendum: "https://slack.com/privacy", + contactInfo: + 'Slack Technologies, LLC
500 Howard Street
San Francisco, CA 94105
USA
dpo@slack.com
https://slack.com/contact', + dataProtectionAddendum: + 'https://slack.com/privacy', }, { name: "Celonis, Inc. (make.com)", - contactInfo: "Celonis, Inc.
800 W El Camino Real, Suite 180
Mountain View, CA 94040
USA
dpo@celonis.com
https://www.celonis.com/contact/", - dataProtectionAddendum: "https://www.make.com/en/privacy-and-gdpr", + contactInfo: + 'Celonis, Inc.
800 W El Camino Real, Suite 180
Mountain View, CA 94040
USA
dpo@celonis.com
https://www.celonis.com/contact/', + dataProtectionAddendum: + 'https://www.make.com/en/privacy-and-gdpr', }, { name: "ClickUp", - contactInfo: "ClickUp, Inc.
55 Almaden Blvd, Suite 650
San Jose, CA 95113
USA
dpo@clickup.com
https://www.clickup.com/contact", - dataProtectionAddendum: "https://www.clickup.com/privacy", + contactInfo: + 'ClickUp, Inc.
55 Almaden Blvd, Suite 650
San Jose, CA 95113
USA
dpo@clickup.com
https://www.clickup.com/contact', + dataProtectionAddendum: + 'https://www.clickup.com/privacy', }, { name: "Atlassian StatusPage", - contactInfo: "Atlassian, Inc.
350 Bush Street, 6th Floor
San Francisco, CA 94104
USA
dpo@atlassian.com
https://www.atlassian.com/contact", - dataProtectionAddendum: "https://www.atlassian.com/legal/privacy-policy", + contactInfo: + 'Atlassian, Inc.
350 Bush Street, 6th Floor
San Francisco, CA 94104
USA
dpo@atlassian.com
https://www.atlassian.com/contact', + dataProtectionAddendum: + 'https://www.atlassian.com/legal/privacy-policy', }, ], }, ]; -// Create custom theme with Qanelas Soft font family -const customTheme = createTheme({ - typography: { - fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", - }, -}); - const SubProcessors = () => { const { isDark } = useStyledDarkMode(); const [didLoad, setDidLoad] = React.useState(false); @@ -216,86 +202,166 @@ const SubProcessors = () => {

- This document outlines the sub-processors and service providers that Layer5 works with to deliver our services. For comprehensive information about how we collect, use, and protect your personal data, please refer to our Privacy Policy. + This document outlines the sub-processors and service providers + that Layer5 works with to deliver our services. For + comprehensive information about how we collect, use, and protect + your personal data, please refer to our{" "} + Privacy Policy.

- To deliver our services effectively, Layer5, Inc. partners with various sub-processors, subsidiaries, and support providers. We may share customer data with the entities listed below to support our platform's functionality and performance. + To deliver our services effectively, Layer5, Inc. partners with + various sub-processors, subsidiaries, and support providers. We + may share customer data with the entities listed below to + support our platform's functionality and performance.

Data Hosting Locations

- To provide our users with transparency about where their data is stored, Layer5 has defined primary hosting locations for its services. Data is stored in a specific region in the United States based on the service used. + To provide our users with transparency about where their data is + stored, Layer5 has defined primary hosting locations for its + services. Data is stored in a specific region in the United + States based on the service used.

    -
  • Layer5 Cloud Account Data (cloud.layer5.io): All user account data is hosted in the United States at an Equinix datacenter in Dallas, Texas.
  • -
  • Kanvas (kanvas.new): Design data is hosted in the United States at an Equinix datacenter in Dallas, Texas.
  • -
  • Layer5 Discussion Forum Data (discuss.meshery.io): All forum data is hosted in the United States within the Amazon Web Services (AWS) US East region.
  • +
  • + Layer5 Cloud Account Data (cloud.layer5.io):{" "} + All user account data is hosted in the United States at an + Equinix datacenter in Dallas, Texas. +
  • +
  • + Kanvas (kanvas.new): Design data is hosted in + the United States at an Equinix datacenter in Dallas, Texas. +
  • +
  • + + Layer5 Discussion Forum Data (discuss.meshery.io): + {" "} + All forum data is hosted in the United States within the + Amazon Web Services (AWS) US East region. +

- Please note that in some cases, certain third-party services (sub-processors) that we use to deliver our services may operate and process data in additional regions. + Please note that in some cases, certain third-party services + (sub-processors) that we use to deliver our services may operate + and process data in additional regions.

Can I change my data region?

- Since data is physically stored in a predetermined location, once an account begins storing data it cannot be moved. If you want to switch to a different region, contact your local partner or Enterprise account manager to create a new account hosted in the region of your choice going forward. + Since data is physically stored in a predetermined location, + once an account begins storing data it cannot be moved. If you + want to switch to a different region, contact your local partner + or Enterprise account manager to create a new account hosted in + the region of your choice going forward.

- {didLoad && ( - - - {subProcessorData.map((section, index) => ( - - - - - - {section.section === "Third-Party Sub-Processors" ? ( - <> - Name - Applicable Services - Type of Service - Data Shared - Sub-Processor Hosting Regions - Additional Information - - ) : ( - <> - Name - Contact Information - Data Protection Addendum - - )} - - - - - - {section.section} - - - {section.rows.map((row, rowIndex) => ( - - {row.name} - {section.section === "Third-Party Sub-Processors" ? ( + {didLoad && ( + + + + {subProcessorData.map((section, index) => ( + +
{`Table ${index + 1}: ${section.section}`}
+ + + + {section.section === + "Third-Party Sub-Processors" ? ( <> - {row.applicableServices} - {row.typeOfService} - {row.dataShared} - {row.hostingRegions} - {row.additionalInfo} + Name + + Applicable Services + + + Type of Service + + + Data Shared + + + Sub-Processor Hosting Regions + + + Additional Information + ) : ( <> - - + Name + + Contact Information + + + Data Protection Addendum + )} + + + + + + {section.section} + - ))} - -
{`Table ${index + 1}: ${section.section}`}
-
- ))} - -
-
)} + {section.rows.map((row, rowIndex) => ( + + {row.name} + {section.section === + "Third-Party Sub-Processors" ? ( + <> + + {row.applicableServices} + + + {row.typeOfService} + + + {row.dataShared} + + + {row.hostingRegions} + + + {row.additionalInfo} + + + ) : ( + <> + + + + )} + + ))} + + + + ))} + + + + )}

Last Updated: Sept 25th, 2025 @@ -310,4 +376,4 @@ const SubProcessors = () => { ); }; -export default SubProcessors; \ No newline at end of file +export default SubProcessors; diff --git a/src/sections/FeatureHero/index.js b/src/sections/FeatureHero/index.js index c1081b43df9297..0655b94d720e97 100644 --- a/src/sections/FeatureHero/index.js +++ b/src/sections/FeatureHero/index.js @@ -1,31 +1,10 @@ -import React, { useEffect } from "react"; +import React from "react"; import FeatureHeroWrapper from "./featureHero.style"; import { Container } from "../../reusecore/Layout"; import Button from "../../reusecore/Button"; +import useScrollTiltEffect from "../../utils/useScrollTiltEffect"; const FeatureHero = (props) => { - useEffect(() => { - const scrollingImage = document.getElementById("whiteboard-svg"); - - const handleScroll = () => { - const imageRect = scrollingImage.getBoundingClientRect(); - const isImageAtTop = imageRect.top <= 110; - - if (isImageAtTop) { - const scrollPosition = window.scrollY; - const translateY = -scrollPosition / 50; - scrollingImage.style.transform = `rotateX(${translateY}deg)`; - } else { - scrollingImage.style.transform = null; - } - }; - - window.addEventListener("scroll", handleScroll); - - // Cleanup the event listener on component unmount - return () => { - window.removeEventListener("scroll", handleScroll); - }; - }, []); + useScrollTiltEffect("whiteboard-svg"); return ( diff --git a/src/sections/Home/So-Special-Section/index.js b/src/sections/Home/So-Special-Section/index.js index 853aa88a7a1e6d..94d7889cf20156 100644 --- a/src/sections/Home/So-Special-Section/index.js +++ b/src/sections/Home/So-Special-Section/index.js @@ -1,60 +1,77 @@ - -import React, { useLayoutEffect, useState, useRef } from "react"; +import React from "react"; import Slider from "react-slick"; import SoSpecialWrapper from "./so-special-style"; import Button from "../../../reusecore/Button"; import { graphql, useStaticQuery } from "gatsby"; import Image from "../../../components/image"; +import useResponsiveSlider from "../../../utils/useResponsiveSlider"; // import { useStyledDarkMode } from "../../../theme/app/useStyledDarkMode"; +const computeSlides = () => { + const w = + typeof window !== "undefined" + ? window.innerWidth || document.documentElement.clientWidth + : 1200; + if (w <= 600) return 1; + if (w <= 800) return 1.5; + if (w <= 1024) return 2; + if (w <= 1200) return 2.2; + return 3; +}; + const SoSpecial = () => { - const [isClient, setIsClient] = useState(false); - const [slidesToShowState, setSlidesToShowState] = useState(null); - const sliderRef = useRef(null); - const data = useStaticQuery( - graphql`query newsList { - allMdx( - filter: {fields: {collection: {eq: "news"}}, frontmatter: {published: {eq: true}}} - sort: {frontmatter: {date: DESC}} - limit: 8 - ) { - nodes { - id - frontmatter { - title - author - eurl - thumbnail { - childImageSharp { - gatsbyImageData(layout: FULL_WIDTH) - } - extension - publicURL - } - thumbnail_svg { - extension - publicURL + const { + isClient, + slidesToShow: slidesToShowState, + sliderRef, + } = useResponsiveSlider(computeSlides); + const data = useStaticQuery(graphql` + query newsList { + allMdx( + filter: { + fields: { collection: { eq: "news" } } + frontmatter: { published: { eq: true } } } - darkthumbnail { - childImageSharp { - gatsbyImageData(layout: FULL_WIDTH) + sort: { frontmatter: { date: DESC } } + limit: 8 + ) { + nodes { + id + frontmatter { + title + author + eurl + thumbnail { + childImageSharp { + gatsbyImageData(layout: FULL_WIDTH) + } + extension + publicURL + } + thumbnail_svg { + extension + publicURL + } + darkthumbnail { + childImageSharp { + gatsbyImageData(layout: FULL_WIDTH) + } + extension + publicURL + } + darkthumbnail_svg { + extension + publicURL + } + } + fields { + slug } - extension - publicURL - } - darkthumbnail_svg { - extension - publicURL } } - fields { - slug - } } - } -}` - ); + `); const settings = { dots: slidesToShowState <= 1, infinite: false, @@ -65,45 +82,6 @@ const SoSpecial = () => { slidesToShow: slidesToShowState ?? 2.5, }; - const computeSlides = () => { - const w = typeof window !== "undefined" ? (window.innerWidth || document.documentElement.clientWidth) : 1200; - if (w <= 600) return 1; - if (w <= 800) return 1.5; - if (w <= 1024) return 2; - if (w <= 1200) return 2.2; - return 3; - }; - - useLayoutEffect(() => { - - setIsClient(true); - setSlidesToShowState(computeSlides()); - - let resizeTimeout = null; - const onResizeDebounced = () => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { - const slides = computeSlides(); - setSlidesToShowState((prev) => { - if (prev !== slides) return slides; - return prev; - }); - if (sliderRef.current && sliderRef.current.innerSlider && typeof sliderRef.current.innerSlider.onWindowResized === "function") { - sliderRef.current.innerSlider.onWindowResized(); - } - }, 100); - }; - - window.addEventListener("resize", onResizeDebounced); - window.addEventListener("load", onResizeDebounced); - - return () => { - window.removeEventListener("resize", onResizeDebounced); - window.removeEventListener("load", onResizeDebounced); - clearTimeout(resizeTimeout); - }; - }, []); - if (!isClient || slidesToShowState === null) return null; // const { isDark } = useStyledDarkMode(); @@ -115,32 +93,34 @@ const SoSpecial = () => {

We're making a splash

- - { - data.allMdx.nodes.map(({ id, frontmatter, fields }) => ( - - ))} - +
+

{frontmatter.title}

+
+
+ + ))} {/*
diff --git a/src/sections/Pricing/review-slider.js b/src/sections/Pricing/review-slider.js index dffc07b323018f..8a01a63b4df26e 100644 --- a/src/sections/Pricing/review-slider.js +++ b/src/sections/Pricing/review-slider.js @@ -1,7 +1,8 @@ -import React, { useRef, useState, useLayoutEffect } from "react"; +import React from "react"; import styled from "styled-components"; import Customers from "../../reusecore/Blockquote/Blockquote-image"; import Slider from "react-slick"; +import useResponsiveSlider from "../../utils/useResponsiveSlider"; import Maxi from "../../collections/members/maximiliano-churichi/Maximiliano-Churichi.webp"; import Otto from "../../collections/members/otto-van-der-schaaf/otto-van-der-schaaf.webp"; import Nic from "../../collections/members/nicholas-jackson/nic-jackson.webp"; @@ -98,10 +99,24 @@ const ReviewsWrapper = styled.div` } `; +const computeSlides = () => { + const w = + typeof window !== "undefined" + ? window.innerWidth || document.documentElement.clientWidth + : 1200; + if (w <= 800) return 1; + if (w <= 1024) return 2; + return 3; +}; + const Reviews = () => { - const [isClient, setIsClient] = useState(false); - const [slidesToShowState, setSlidesToShowState] = useState(null); - const sliderRef = useRef(null); + const { + isClient, + slidesToShow: slidesToShowState, + sliderRef, + } = useResponsiveSlider(computeSlides, { + watchImagesSelector: ".slider img", + }); const mergedSettings = { ...settings, @@ -110,54 +125,6 @@ const Reviews = () => { responsive: [], }; - const computeSlides = () => { - const w = - typeof window !== "undefined" - ? window.innerWidth || document.documentElement.clientWidth - : 1200; - if (w <= 800) return 1; - if (w <= 1024) return 2; - return 3; - }; - - useLayoutEffect(() => { - setIsClient(true); - setSlidesToShowState(computeSlides()); - - let resizeTimeout = null; - const onResizeDebounced = () => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { - const slides = computeSlides(); - setSlidesToShowState((prev) => { - if (prev !== slides) return slides; - return prev; - }); - if ( - sliderRef.current && - sliderRef.current.innerSlider && - typeof sliderRef.current.innerSlider.onWindowResized === "function" - ) { - sliderRef.current.innerSlider.onWindowResized(); - } - }, 100); - }; - - const onLoad = () => onResizeDebounced(); - - window.addEventListener("resize", onResizeDebounced); - window.addEventListener("load", onLoad); - const imgs = document.querySelectorAll(".slider img"); - imgs.forEach((img) => img.addEventListener("load", onLoad)); - - return () => { - window.removeEventListener("resize", onResizeDebounced); - window.removeEventListener("load", onLoad); - imgs.forEach((img) => img.removeEventListener("load", onLoad)); - clearTimeout(resizeTimeout); - }; - }, []); - if (!isClient || slidesToShowState === null) return null; return ( diff --git a/src/sections/SolutionHero/index.js b/src/sections/SolutionHero/index.js index be24b22d9b4808..679b43885a9fb9 100644 --- a/src/sections/SolutionHero/index.js +++ b/src/sections/SolutionHero/index.js @@ -1,32 +1,11 @@ -import React, { useEffect } from "react"; +import React from "react"; import FeatureHeroWrapper from "./featureHero.style"; import { Container } from "../../reusecore/Layout"; import Button from "../../reusecore/Button"; import { FiDownloadCloud } from "@react-icons/all-files/fi/FiDownloadCloud"; +import useScrollTiltEffect from "../../utils/useScrollTiltEffect"; const FeatureHero = (props) => { - useEffect(() => { - const scrollingImage = document.getElementById("whiteboard-svg"); - - const handleScroll = () => { - const imageRect = scrollingImage.getBoundingClientRect(); - const isImageAtTop = imageRect.top <= 110; - - if (isImageAtTop) { - const scrollPosition = window.scrollY; - const translateY = -scrollPosition / 50; - scrollingImage.style.transform = `rotateX(${translateY}deg)`; - } else { - scrollingImage.style.transform = null; - } - }; - - window.addEventListener("scroll", handleScroll); - - // Cleanup the event listener on component unmount - return () => { - window.removeEventListener("scroll", handleScroll); - }; - }, []); + useScrollTiltEffect("whiteboard-svg"); return ( diff --git a/src/templates/blog-category-list.js b/src/templates/blog-category-list.js index 9ccc0b821d7553..793e49332aefc5 100644 --- a/src/templates/blog-category-list.js +++ b/src/templates/blog-category-list.js @@ -1,65 +1,61 @@ import React from "react"; -import { useState } from "react"; -import useDataList from "../utils/usedataList"; +import usePaginatedSearch from "../utils/usePaginatedSearch"; import SEO from "../components/seo"; import BlogList from "../sections/Blog/Blog-list"; import { graphql } from "gatsby"; -export const query = graphql`query BlogsByCategory($category: String!) { - allMdx( - sort: {fields: {dateForSort: DESC}} - filter: {fields: {collection: {eq: "blog"}}, frontmatter: {category: {eq: $category}, published: {eq: true}}} - ) { - nodes { - id - frontmatter { - title - subtitle - date(formatString: "MMMM Do, YYYY") - author - thumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) +export const query = graphql` + query BlogsByCategory($category: String!) { + allMdx( + sort: { fields: { dateForSort: DESC } } + filter: { + fields: { collection: { eq: "blog" } } + frontmatter: { category: { eq: $category }, published: { eq: true } } + } + ) { + nodes { + id + frontmatter { + title + subtitle + date(formatString: "MMMM Do, YYYY") + author + thumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } - } - darkthumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) + darkthumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } } - } - fields { - slug + fields { + slug + } } } } -}`; +`; const BlogListPage = ({ pageContext, data }) => { - - const [currentPage, setCurrentPage] = useState(1); - const [postsPerPage] = useState(10); - const indexOfLastPost = currentPage * postsPerPage; - const indexOfFirstPost = indexOfLastPost - postsPerPage; - let { nodes } = data.allMdx; - const [searchQuery, setSearchQuery] = useState(""); - const { queryResults, searchData } = useDataList( - nodes, - setSearchQuery, + const { + searchedPosts, + queryResults, + searchData, searchQuery, - ["frontmatter", "title"], - "id" - ); - const searchedPosts = queryResults.slice(indexOfFirstPost, indexOfLastPost); + setSearchQuery, + currentPage, + setCurrentPage, + postsPerPage, + } = usePaginatedSearch(data.allMdx.nodes); return ( - <> - - { currentPage={currentPage} queryResults={queryResults} /> - - ); }; export default BlogListPage; export const Head = () => { - return ; -}; \ No newline at end of file + return ( + + ); +}; diff --git a/src/templates/blog-tag-list.js b/src/templates/blog-tag-list.js index 1789bb979dfef0..4560fa16a0a02a 100644 --- a/src/templates/blog-tag-list.js +++ b/src/templates/blog-tag-list.js @@ -1,65 +1,62 @@ import React from "react"; -import { useState } from "react"; -import useDataList from "../utils/usedataList"; +import usePaginatedSearch from "../utils/usePaginatedSearch"; import SEO from "../components/seo"; import BlogList from "../sections/Blog/Blog-list"; import { graphql } from "gatsby"; -export const query = graphql`query BlogsByTags($tag: String!) { - allMdx( - sort: {fields: {dateForSort: DESC}} - filter: {fields: {collection: {eq: "blog"}}, frontmatter: {tags: {in: [$tag]}, published: {eq: true}}} - ) { - nodes { - id - frontmatter { - title - subtitle - date(formatString: "MMMM Do, YYYY") - author - thumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) +export const query = graphql` + query BlogsByTags($tag: String!) { + allMdx( + sort: { fields: { dateForSort: DESC } } + filter: { + fields: { collection: { eq: "blog" } } + frontmatter: { tags: { in: [$tag] }, published: { eq: true } } + } + ) { + nodes { + id + frontmatter { + title + subtitle + date(formatString: "MMMM Do, YYYY") + author + thumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } - } - darkthumbnail { - extension - publicURL - childImageSharp { - gatsbyImageData(width: 480, layout: CONSTRAINED) + darkthumbnail { + extension + publicURL + childImageSharp { + gatsbyImageData(width: 480, layout: CONSTRAINED) + } } } - } - fields { - slug + fields { + slug + } } } } -}`; +`; const BlogListPage = ({ pageContext, data }) => { - - const [currentPage, setCurrentPage] = useState(1); - const [postsPerPage] = useState(10); - const indexOfLastPost = currentPage * postsPerPage; - const indexOfFirstPost = indexOfLastPost - postsPerPage; - let { nodes } = data.allMdx; - const [searchQuery, setSearchQuery] = useState(""); - const { queryResults, searchData } = useDataList( - nodes, - setSearchQuery, + const { + searchedPosts, + queryResults, + searchData, searchQuery, - ["frontmatter", "title"], - "id" - ); - const searchedPosts = queryResults.slice(indexOfFirstPost, indexOfLastPost); + setSearchQuery, + currentPage, + setCurrentPage, + postsPerPage, + } = usePaginatedSearch(data.allMdx.nodes); return ( - <> - { currentPage={currentPage} queryResults={queryResults} /> - - ); }; export default BlogListPage; export const Head = () => { - return ; -}; \ No newline at end of file + return ( + + ); +}; diff --git a/src/utils/usePaginatedSearch.js b/src/utils/usePaginatedSearch.js new file mode 100644 index 00000000000000..26f46d108d56d5 --- /dev/null +++ b/src/utils/usePaginatedSearch.js @@ -0,0 +1,39 @@ +import { useState } from "react"; +import useDataList from "./usedataList"; + +// Combines useDataList search with page-based slicing of the results. +const usePaginatedSearch = ( + nodes, + { + paramsIndex = ["frontmatter", "title"], + paramSearch = "id", + perPage = 10, + } = {}, +) => { + const [currentPage, setCurrentPage] = useState(1); + const [postsPerPage] = useState(perPage); + const indexOfLastPost = currentPage * postsPerPage; + const indexOfFirstPost = indexOfLastPost - postsPerPage; + const [searchQuery, setSearchQuery] = useState(""); + const { queryResults, searchData } = useDataList( + nodes, + setSearchQuery, + searchQuery, + paramsIndex, + paramSearch, + ); + const searchedPosts = queryResults.slice(indexOfFirstPost, indexOfLastPost); + + return { + searchedPosts, + queryResults, + searchData, + searchQuery, + setSearchQuery, + currentPage, + setCurrentPage, + postsPerPage, + }; +}; + +export default usePaginatedSearch; diff --git a/src/utils/useResponsiveSlider.js b/src/utils/useResponsiveSlider.js new file mode 100644 index 00000000000000..64a3652d0dd482 --- /dev/null +++ b/src/utils/useResponsiveSlider.js @@ -0,0 +1,51 @@ +import { useLayoutEffect, useRef, useState } from "react"; + +// Shared client-side state for react-slick sliders: computes slidesToShow from +// the viewport width and keeps it in sync on resize/load (debounced). +const useResponsiveSlider = (computeSlides, { watchImagesSelector } = {}) => { + const [isClient, setIsClient] = useState(false); + const [slidesToShow, setSlidesToShow] = useState(null); + const sliderRef = useRef(null); + + useLayoutEffect(() => { + setIsClient(true); + setSlidesToShow(computeSlides()); + + let resizeTimeout = null; + const onResizeDebounced = () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + const slides = computeSlides(); + setSlidesToShow((prev) => { + if (prev !== slides) return slides; + return prev; + }); + if ( + sliderRef.current && + sliderRef.current.innerSlider && + typeof sliderRef.current.innerSlider.onWindowResized === "function" + ) { + sliderRef.current.innerSlider.onWindowResized(); + } + }, 100); + }; + + window.addEventListener("resize", onResizeDebounced); + window.addEventListener("load", onResizeDebounced); + const imgs = watchImagesSelector + ? Array.from(document.querySelectorAll(watchImagesSelector)) + : []; + imgs.forEach((img) => img.addEventListener("load", onResizeDebounced)); + + return () => { + window.removeEventListener("resize", onResizeDebounced); + window.removeEventListener("load", onResizeDebounced); + imgs.forEach((img) => img.removeEventListener("load", onResizeDebounced)); + clearTimeout(resizeTimeout); + }; + }, []); + + return { isClient, slidesToShow, sliderRef }; +}; + +export default useResponsiveSlider; diff --git a/src/utils/useScrollTiltEffect.js b/src/utils/useScrollTiltEffect.js new file mode 100644 index 00000000000000..e8e4baf27f7e26 --- /dev/null +++ b/src/utils/useScrollTiltEffect.js @@ -0,0 +1,33 @@ +import { useEffect } from "react"; + +// Tilts the element with the given id around the X axis as the page scrolls, +// once the element reaches the top of the viewport. +const useScrollTiltEffect = ( + elementId, + { topOffset = 110, scrollFactor = 50 } = {}, +) => { + useEffect(() => { + const scrollingImage = document.getElementById(elementId); + + const handleScroll = () => { + const imageRect = scrollingImage.getBoundingClientRect(); + const isImageAtTop = imageRect.top <= topOffset; + + if (isImageAtTop) { + const scrollPosition = window.scrollY; + const translateY = -scrollPosition / scrollFactor; + scrollingImage.style.transform = `rotateX(${translateY}deg)`; + } else { + scrollingImage.style.transform = null; + } + }; + + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, [elementId, topOffset, scrollFactor]); +}; + +export default useScrollTiltEffect; From a8baef0525c689cd0074ddcd00768e1596c07b64 Mon Sep 17 00:00:00 2001 From: Krishna Gehlot Date: Thu, 27 Aug 2026 03:52:51 +0000 Subject: [PATCH 2/2] fix: run prettier before eslint in lint-staged so eslint fixes are final Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .lintstagedrc.js | 2 +- src/components/Related-Posts/index.js | 26 ++-- .../Company/Legal/cookie-notice/index.js | 8 +- .../Company/Legal/legal-table.style.js | 6 +- src/sections/Company/Legal/privacy/index.js | 18 +-- .../Company/Legal/sub-processors/index.js | 142 +++++++++--------- 6 files changed, 101 insertions(+), 101 deletions(-) diff --git a/.lintstagedrc.js b/.lintstagedrc.js index 74436ca10182cd..22d35cb56e0f5b 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -1,8 +1,8 @@ module.exports = { // Lint & Prettify TS and JS files - only the staged files "**/*.(ts|tsx|js|jsx)": (filenames) => [ - `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 --no-warn-ignored ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, `npx prettier --write ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, + `cross-env NODE_ENV=test npx eslint --fix --max-warnings=0 --no-warn-ignored ${filenames.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ")}`, ], // Prettify only Markdown and JSON files diff --git a/src/components/Related-Posts/index.js b/src/components/Related-Posts/index.js index e5d3a890a53b0f..09555b09e01a2c 100644 --- a/src/components/Related-Posts/index.js +++ b/src/components/Related-Posts/index.js @@ -54,20 +54,20 @@ const RelatedPosts = (props) => { > {postType === "blogs" ? relatedPosts.map((item) => { - const post = item.post || item; - return ( - - - - ); - }) + const post = item.post || item; + return ( + + + + ); + }) : relatedPosts.map((post) => { - return ( - - - - ); - })} + return ( + + + + ); + })}
diff --git a/src/sections/Company/Legal/cookie-notice/index.js b/src/sections/Company/Legal/cookie-notice/index.js index 379536ca2d9ae2..28a2602b04e8f1 100644 --- a/src/sections/Company/Legal/cookie-notice/index.js +++ b/src/sections/Company/Legal/cookie-notice/index.js @@ -29,7 +29,7 @@ const cookieData = [ "We use Google Tag Manager, Google Analytics, Google Ads conversion tracking, and Google reCAPTCHA to manage tracking, analyze website usage, and prevent automated abuse. Google Analytics uses anonymized IP addresses to ensure no direct correlation to individuals.", category: "Functional and Marketing", privacyLink: - 'Google Privacy Preferences', + "Google Privacy Preferences", }, { provider: "Cloudflare", @@ -37,7 +37,7 @@ const cookieData = [ "Cloudflare cookies distinguish users sharing the same IP address for rate limiting and performance optimization.", category: "Necessary & Performance", privacyLink: - 'Cloudflare Privacy Policy', + "Cloudflare Privacy Policy", }, { provider: "GitHub", @@ -45,7 +45,7 @@ const cookieData = [ "GitHub cookies support seamless integration with our open-source community platforms, such as remembering your preferences during contributions.", category: "Functional", privacyLink: - 'GitHub Privacy Policy', + "GitHub Privacy Policy", }, { provider: "Slack", @@ -53,7 +53,7 @@ const cookieData = [ "Slack cookies enhance your experience in our community channels by remembering preferences and enabling seamless communication.", category: "Functional", privacyLink: - 'Slack Privacy Policy', + "Slack Privacy Policy", }, ], }, diff --git a/src/sections/Company/Legal/legal-table.style.js b/src/sections/Company/Legal/legal-table.style.js index 472a512bf78cb4..31357b0a52730d 100644 --- a/src/sections/Company/Legal/legal-table.style.js +++ b/src/sections/Company/Legal/legal-table.style.js @@ -30,18 +30,18 @@ export const StyledTableCell = styled(TableCell)(({ theme }) => ({ padding: "0.75rem", borderBottom: `1px solid ${theme.palette.divider}`, borderRadius: 0, - fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', + fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", })); export const StyledHeaderCell = styled(StyledTableCell)(() => ({ fontWeight: 600, backgroundColor: "transparent", - fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', + fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", })); // Custom theme with Qanelas Soft font family for legal tables export const legalTableTheme = createTheme({ typography: { - fontFamily: '"Qanelas Soft", "Open Sans", sans-serif', + fontFamily: "\"Qanelas Soft\", \"Open Sans\", sans-serif", }, }); diff --git a/src/sections/Company/Legal/privacy/index.js b/src/sections/Company/Legal/privacy/index.js index b2bd4371974f93..ca6a23f6c3f337 100644 --- a/src/sections/Company/Legal/privacy/index.js +++ b/src/sections/Company/Legal/privacy/index.js @@ -29,7 +29,7 @@ const privacyData = [ categories: "IP Address, device type, browser information, server logs.", lawfulBasis: - 'Legitimate Interest (Article 6(1)(f)) - to ensure the security, availability, and performance of our digital properties.', + "Legitimate Interest (Article 6(1)(f)) - to ensure the security, availability, and performance of our digital properties.", retention: "Up to 26 months for server logs, depending on the specific data and its purpose.", }, @@ -38,7 +38,7 @@ const privacyData = [ categories: "Anonymized IP address, cookie identifiers, pages visited, duration of visit, clickstream data.", lawfulBasis: - 'Consent (Article 6(1)(a)) - for non-essential analytics and tracking cookies. You can manage your consent through our cookie banner.', + "Consent (Article 6(1)(a)) - for non-essential analytics and tracking cookies. You can manage your consent through our cookie banner.", retention: "Up to 26 months for analytics data, depending on the specific service used.", }, @@ -53,7 +53,7 @@ const privacyData = [ categories: "Name, email address, GitHub username, public profile information, content of your contributions (code, comments, issues), and communications.", lawfulBasis: - 'Legitimate Interest (Article 6(1)(f)) - to operate, manage, and grow our collaborative open source community.', + "Legitimate Interest (Article 6(1)(f)) - to operate, manage, and grow our collaborative open source community.", retention: "Your contributions are retained indefinitely as part of the public project record. Other personal data is retained for the duration of your participation and up to 12 months thereafter.", }, @@ -68,7 +68,7 @@ const privacyData = [ categories: "Name, email address, company name, securely hashed password, GitHub user ID (if used for single sign-on).", lawfulBasis: - 'Performance of a Contract (Article 6(1)(b)) - this data is necessary to fulfill our contractual obligation to provide the service you have signed up for.', + "Performance of a Contract (Article 6(1)(b)) - this data is necessary to fulfill our contractual obligation to provide the service you have signed up for.", retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, @@ -77,7 +77,7 @@ const privacyData = [ categories: "Billing address, payment card information (we do not store full card details; they are securely processed by our payment provider, who provides us with a transaction token and confirmation).", lawfulBasis: - 'Performance of a Contract (Article 6(1)(b)).', + "Performance of a Contract (Article 6(1)(b)).", retention: "Financial data is retained for 7 years to comply with legal and tax obligations.", }, @@ -86,7 +86,7 @@ const privacyData = [ "To communicate with you about the service (e.g., important updates, security alerts, billing information).", categories: "Email address.", lawfulBasis: - 'Performance of a Contract (Article 6(1)(b)) and Legitimate Interest (Article 6(1)(f)) - to provide you with essential information about the service you are using.', + "Performance of a Contract (Article 6(1)(b)) and Legitimate Interest (Article 6(1)(f)) - to provide you with essential information about the service you are using.", retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, @@ -95,7 +95,7 @@ const privacyData = [ categories: "Service usage data, API logs, user activity logs, device and browser information.", lawfulBasis: - 'Legitimate Interest (Article 6(1)(f)) - to maintain and improve the quality and functionality of our services.', + "Legitimate Interest (Article 6(1)(f)) - to maintain and improve the quality and functionality of our services.", retention: "For the duration of your account activity and up to 12 months thereafter for account reactivation and support purposes.", }, @@ -109,7 +109,7 @@ const privacyData = [ categories: "Name, email address, and any other information you provide in the content of your message.", lawfulBasis: - 'Legitimate Interest (Article 6(1)(f)) - to effectively respond to user inquiries and provide assistance.', + "Legitimate Interest (Article 6(1)(f)) - to effectively respond to user inquiries and provide assistance.", retention: "For the duration of the support interaction and up to 12 months thereafter for quality assurance purposes.", }, @@ -123,7 +123,7 @@ const privacyData = [ "To send you newsletters, product updates, and other marketing materials.", categories: "Name, email address.", lawfulBasis: - 'Consent (Article 6(1)(a)) - you provide your consent when you opt-in to receive these communications, and you can unsubscribe at any time.', + "Consent (Article 6(1)(a)) - you provide your consent when you opt-in to receive these communications, and you can unsubscribe at any time.", retention: "Until you unsubscribe.", }, ], diff --git a/src/sections/Company/Legal/sub-processors/index.js b/src/sections/Company/Legal/sub-processors/index.js index 89e4fd5d230f98..1d2262ba38df25 100644 --- a/src/sections/Company/Legal/sub-processors/index.js +++ b/src/sections/Company/Legal/sub-processors/index.js @@ -112,65 +112,65 @@ const subProcessorData = [ { name: "Amazon Web Services US", contactInfo: - 'https://aws.amazon.com/contact-us/', + "https://aws.amazon.com/contact-us/", dataProtectionAddendum: - 'https://aws.amazon.com/compliance/data-privacy-faq/', + "https://aws.amazon.com/compliance/data-privacy-faq/", }, { name: "Equinix", contactInfo: - 'https://www.equinix.com/contact-us/', + "https://www.equinix.com/contact-us/", dataProtectionAddendum: - 'https://www.equinix.com/legal/data-protection-addendum/', + "https://www.equinix.com/legal/data-protection-addendum/", }, { name: "GitHub, Inc.", contactInfo: - 'GitHub Privacy Office
88 Colin P. Kelly Jr. Street
San Francisco, California 94107 USA
dpo@github.com
https://support.github.com/contact', + "GitHub Privacy Office
88 Colin P. Kelly Jr. Street
San Francisco, California 94107 USA
dpo@github.com
https://support.github.com/contact", dataProtectionAddendum: - 'https://docs.github.com/en/site-policy/privacy-policies', + "https://docs.github.com/en/site-policy/privacy-policies", }, { name: "Cloudflare, Inc.", contactInfo: - '101 Townsend Street
San Francisco, CA 94107
USA
Emily Hancock
Data Protection Officer
legal@cloudflare.com
https://www.cloudflare.com/contact/', + "101 Townsend Street
San Francisco, CA 94107
USA
Emily Hancock
Data Protection Officer
legal@cloudflare.com
https://www.cloudflare.com/contact/", dataProtectionAddendum: - 'https://www.cloudflare.com/terms/cloudflare-dpa/', + "https://www.cloudflare.com/terms/cloudflare-dpa/", }, { name: "Stripe", contactInfo: - 'Stripe, Inc.
510 Townsend St.
San Francisco, CA 94103
USA
dpo@stripe.com
https://support.stripe.com/contact', + "Stripe, Inc.
510 Townsend St.
San Francisco, CA 94103
USA
dpo@stripe.com
https://support.stripe.com/contact", dataProtectionAddendum: - 'https://stripe.com/privacy', + "https://stripe.com/privacy", }, { name: "Slack", contactInfo: - 'Slack Technologies, LLC
500 Howard Street
San Francisco, CA 94105
USA
dpo@slack.com
https://slack.com/contact', + "Slack Technologies, LLC
500 Howard Street
San Francisco, CA 94105
USA
dpo@slack.com
https://slack.com/contact", dataProtectionAddendum: - 'https://slack.com/privacy', + "https://slack.com/privacy", }, { name: "Celonis, Inc. (make.com)", contactInfo: - 'Celonis, Inc.
800 W El Camino Real, Suite 180
Mountain View, CA 94040
USA
dpo@celonis.com
https://www.celonis.com/contact/', + "Celonis, Inc.
800 W El Camino Real, Suite 180
Mountain View, CA 94040
USA
dpo@celonis.com
https://www.celonis.com/contact/", dataProtectionAddendum: - 'https://www.make.com/en/privacy-and-gdpr', + "https://www.make.com/en/privacy-and-gdpr", }, { name: "ClickUp", contactInfo: - 'ClickUp, Inc.
55 Almaden Blvd, Suite 650
San Jose, CA 95113
USA
dpo@clickup.com
https://www.clickup.com/contact', + "ClickUp, Inc.
55 Almaden Blvd, Suite 650
San Jose, CA 95113
USA
dpo@clickup.com
https://www.clickup.com/contact", dataProtectionAddendum: - 'https://www.clickup.com/privacy', + "https://www.clickup.com/privacy", }, { name: "Atlassian StatusPage", contactInfo: - 'Atlassian, Inc.
350 Bush Street, 6th Floor
San Francisco, CA 94104
USA
dpo@atlassian.com
https://www.atlassian.com/contact', + "Atlassian, Inc.
350 Bush Street, 6th Floor
San Francisco, CA 94104
USA
dpo@atlassian.com
https://www.atlassian.com/contact", dataProtectionAddendum: - 'https://www.atlassian.com/legal/privacy-policy', + "https://www.atlassian.com/legal/privacy-policy", }, ], }, @@ -271,35 +271,35 @@ const SubProcessors = () => { {section.section === "Third-Party Sub-Processors" ? ( - <> - Name - + <> + Name + Applicable Services - - + + Type of Service - - + + Data Shared - - + + Sub-Processor Hosting Regions - - + + Additional Information - - - ) : ( - <> - Name - + + + ) : ( + <> + Name + Contact Information - - + + Data Protection Addendum - - - )} + + + )} @@ -321,37 +321,37 @@ const SubProcessors = () => { {row.name} {section.section === "Third-Party Sub-Processors" ? ( - <> - - {row.applicableServices} - - - {row.typeOfService} - - - {row.dataShared} - - - {row.hostingRegions} - - - {row.additionalInfo} - - - ) : ( - <> - - - - )} + <> + + {row.applicableServices} + + + {row.typeOfService} + + + {row.dataShared} + + + {row.hostingRegions} + + + {row.additionalInfo} + + + ) : ( + <> + + + + )} ))}