Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions content/learning-center/metadata-management.md

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/components/NavbarDev/NavbarDev.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ const NavbarDev = ({ onClick }: { onClick: () => void }) => {
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
/>
</li>
<li>
<ParamLink
href="/learning-center"
name="Learning Center"
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
/>
</li>
</ul>
</li>
<li className="mb-4 lg:mb-0">
Expand Down
128 changes: 128 additions & 0 deletions src/components/learning-center/DetailsPageRightPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useState } from 'react'
import Image from 'next/image'
import ParamLink from '@/components/ParamLink'
import { LEARNING_CENTER_DATA } from '@/constants/learningCenter.constant'

interface Article {
slug: string
title: string
cluster: string
}

interface Props {
article: Article
link: string
}

const DetailsPageRightPanel = ({ article, link }: Props) => {
const [showAllArticles, setShowAllArticles] = useState(false)

const currentItem = LEARNING_CENTER_DATA.find((item) => item.slug === article.slug)
const currentClusters = (currentItem?.cluster || article.cluster)
.split(' & ')
.map((c) => c.trim())

const relatedArticles = LEARNING_CENTER_DATA.filter((item) => {
if (item.slug === article.slug) return false
const itemClusters = item.cluster.split(' & ').map((c) => c.trim())
return itemClusters.some((ic) => currentClusters.includes(ic))
})

const siteUrl = 'https://open-metadata.org'

return (
<>
{relatedArticles.length > 0 && (
<aside className="bg-gradient-to-t from-gray-200 to-white border border-white rounded-3xl learning-center-card p-8 lg:block hidden mb-6">
<h2 className="text-2xl font-semibold mb-4 text-black">
Related Articles
</h2>
<div>
{(showAllArticles ? relatedArticles : relatedArticles.slice(0, 3)).map(
(item, index) => (
<div
key={item.id}
className="py-4 border-b border-gray-300 flex justify-between gap-5 items-center"
>
<ParamLink
href={
item.slug
? `/learning-center/${item.slug}`
: item.link ?? '#'
}
className="text-black font-bold hover:underline"
>
{item.title}
</ParamLink>
</div>
)
)}
</div>
{relatedArticles.length > 3 ? (
<button
onClick={() => setShowAllArticles(!showAllArticles)}
className="text-[#7147E8] font-semibold pt-5 block text-lg hover:underline"
>
{showAllArticles ? 'Collapse' : (
<span className="flex gap-2 items-center">
See More
<Image
src="/assets/icons/next-arrow.svg"
alt="arrow"
width={16}
height={16}
/>
</span>
)}
</button>
) : (
<ParamLink
href="/learning-center"
className="text-[#7147E8] font-semibold pt-5 flex gap-2 items-center text-lg hover:underline"
>
See More
<Image
src="/assets/icons/next-arrow.svg"
alt="arrow"
width={16}
height={16}
/>
</ParamLink>
)}
</aside>
)}

<div>
<h2 className="text-xl text-black font-medium mb-4">
Share this article
</h2>
<div className="flex gap-3">
<a
href={`https://twitter.com/intent/post?url=${encodeURIComponent(`${siteUrl}${link}`)}`}
target="_blank"
rel="noreferrer"
className="cursor-pointer flex justify-center items-center bg-gray-200 px-[6px] py-1 rounded-[6px] hover:bg-gray-300 transition-colors"
aria-label="Share on X (Twitter)"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.724-8.835L2.25 2.25h6.844l4.258 5.622 4.892-5.622zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77z" fill="currentColor"/>
</svg>
</a>
<a
href={`https://www.linkedin.com/feed/?linkOrigin=LI_BADGE&shareActive=true&shareUrl=${encodeURIComponent(`${siteUrl}${link}`)}`}
target="_blank"
rel="noreferrer"
className="cursor-pointer flex justify-center items-center bg-gray-200 px-[6px] py-1 rounded-[6px] hover:bg-gray-300 transition-colors"
aria-label="Share on LinkedIn"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" fill="currentColor"/>
</svg>
</a>
</div>
</div>
</>
)
}

export default DetailsPageRightPanel
183 changes: 183 additions & 0 deletions src/components/learning-center/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useState, useRef, useEffect } from 'react'
import Image from 'next/image'
import { CATEGORY_LIST } from '../../constants/learningCenter.constant'
import { buildLearningCenterPath } from '../../utils/filterUtils'
import { ArrowLeft } from '../Icons/ArrowLeft'
import { ArrowRight } from '../Icons/ArrowRight'

const topicItems = CATEGORY_LIST.find((c) => c.header === 'Topics')?.items || []
const resourceTypeItems = CATEGORY_LIST.find((c) => c.header === 'Resource Types')?.items || []

interface CustomDropdownProps {
label: string
placeholder: string
items: string[]
selected: string
onSelect: (value: string) => void
buildHref: (value: string) => string
}

const CustomDropdown = ({
label,
placeholder,
items,
selected,
onSelect,
buildHref,
}: CustomDropdownProps) => {
const [isOpen, setIsOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setIsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])

const displayValue = selected === 'All' ? placeholder : selected

return (
<div ref={dropdownRef}>
<label className="block text-base font-medium text-gray-700 mb-2">
{label}
</label>
<div className="relative">
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="border border-gray-400 w-full p-[10px] outline-none rounded-[6px] bg-white text-base text-left cursor-pointer text-black"
>
{displayValue}
</button>
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none rotate-90 transition-all">
{isOpen ? <ArrowLeft/> : <ArrowRight />}
</div>
{isOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-white border border-gray-400 rounded-[6px] shadow-lg z-30 max-h-60 overflow-y-auto">
<a
href={buildHref('All')}
onClick={(e) => {
e.preventDefault()
onSelect('All')
setIsOpen(false)
}}
className={`block w-full text-left px-4 py-2.5 text-base hover:bg-gray-50 transition-colors ${
selected === 'All'
? 'text-[#7147E8] font-medium'
: 'text-gray-600'
}`}
>
{placeholder}
</a>
{items.map((item) => (
<a
key={item}
href={buildHref(item)}
onClick={(e) => {
e.preventDefault()
onSelect(item)
setIsOpen(false)
}}
className={`block w-full text-left px-4 py-2.5 text-base hover:bg-gray-50 transition-colors ${
selected === item
? 'text-[#7147E8] font-medium'
: 'text-gray-600'
}`}
>
{item}
</a>
))}
</div>
)}
</div>
</div>
)
}

interface FiltersProps {
selectedTopic: string
selectedResourceType: string
handleCategoryFilter: (category: string, categoryType: string) => void
handleInputChange: (value: string) => void
inputValue: string
}

const Filters = ({
selectedTopic,
selectedResourceType,
handleCategoryFilter,
handleInputChange,
inputValue,
}: FiltersProps) => {
return (
<div className="flex flex-col gap-6 w-full lg:w-[280px] lg:flex-shrink-0 rounded-xl md:p-6">
<div className="sr-only" aria-hidden="true">
{topicItems.map((topic) => (
<a
key={`seo-topic-${topic}`}
href={buildLearningCenterPath(topic, 'All')}
>
{topic}
</a>
))}
{resourceTypeItems.map((resource) => (
<a
key={`seo-resource-${resource}`}
href={buildLearningCenterPath('All', resource)}
>
{resource}
</a>
))}
</div>
<div>
<label className="block text-base font-medium text-gray-700 mb-2">
Search
</label>
<div className="relative">
<input
className="border border-gray-400 w-full p-[10px] outline-none rounded-[6px] bg-white text-base placeholder-gray-400"
placeholder="Enter Keywords"
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
<Image
src="/assets/customer-gallery/search.svg"
alt="search"
width={18}
height={18}
/>
</div>
</div>
</div>

<CustomDropdown
label="Topics"
placeholder="All"
items={topicItems}
selected={selectedTopic}
onSelect={(value) => handleCategoryFilter(value, 'topic')}
buildHref={(topic) =>
buildLearningCenterPath(topic, selectedResourceType)
}
/>

<CustomDropdown
label="Resource Types"
placeholder="All"
items={resourceTypeItems}
selected={selectedResourceType}
onSelect={(value) => handleCategoryFilter(value, 'resourceType')}
buildHref={(resource) =>
buildLearningCenterPath(selectedTopic, resource)
}
/>
</div>
)
}

export default Filters
34 changes: 34 additions & 0 deletions src/constants/learningCenter.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { LearningCenterItem } from '../utils/filterUtils'

const IMAGE_PATH = '/assets/learning-center/banner'

export interface CategoryItem {
header: string
items?: string[]
}

export const LEARNING_CENTER_DATA: LearningCenterItem[] = [
{
id: 0,
title: 'Metadata Management in 2026: Processes, Use Cases & Technologies',
image: `${IMAGE_PATH}/metadata-management.png`,
slug: 'metadata-management',
cluster: 'Metadata Management',
resourceType: 'Articles',
},

]

export const CATEGORY_LIST: CategoryItem[] = [
{
header: 'All',
},
{
header: 'Topics',
items: ['Metadata Management'],
},
{
header: 'Resource Types',
items: ['Articles'],
},
]
Loading
Loading