From 28a3b7fd9adf8d415ba496b0c045f19155069276 Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 17:42:24 -0700
Subject: [PATCH 01/27] gen by ai
---
.gitignore | 1 +
README.md | 50 ++
components.json | 21 +
eslint.config.js | 28 +
index.html | 13 +
package.json | 79 +++
postcss.config.js | 6 +
public/anomaly-detection.jpg | 26 +
public/customer-360.jpg | 26 +
public/fraud-prevention.jpg | 26 +
public/log-analytics.jpg | 26 +
public/stream-dashboard.jpg | 26 +
public/supply-chain.jpg | 26 +
public/vite.svg | 1 +
src/App.css | 0
src/App.tsx | 83 +++
src/assets/react.svg | 1 +
src/components/demos/DemoCard.tsx | 52 ++
src/components/demos/DemoDetail.tsx | 94 ++++
src/components/demos/DemoGrid.tsx | 46 ++
src/components/layout/Footer.tsx | 30 +
src/components/layout/Header.tsx | 35 ++
src/components/ui/accordion.tsx | 55 ++
src/components/ui/alert-dialog.tsx | 139 +++++
src/components/ui/alert.tsx | 59 ++
src/components/ui/aspect-ratio.tsx | 5 +
src/components/ui/avatar.tsx | 50 ++
src/components/ui/badge.tsx | 36 ++
src/components/ui/breadcrumb.tsx | 115 ++++
src/components/ui/button.tsx | 57 ++
src/components/ui/calendar.tsx | 74 +++
src/components/ui/card.tsx | 76 +++
src/components/ui/carousel.tsx | 260 +++++++++
src/components/ui/chart.tsx | 365 ++++++++++++
src/components/ui/checkbox.tsx | 28 +
src/components/ui/collapsible.tsx | 11 +
src/components/ui/command.tsx | 151 +++++
src/components/ui/context-menu.tsx | 198 +++++++
src/components/ui/dialog.tsx | 122 +++++
src/components/ui/drawer.tsx | 118 ++++
src/components/ui/dropdown-menu.tsx | 199 +++++++
src/components/ui/form.tsx | 178 ++++++
src/components/ui/hover-card.tsx | 29 +
src/components/ui/input-otp.tsx | 69 +++
src/components/ui/input.tsx | 22 +
src/components/ui/label.tsx | 24 +
src/components/ui/menubar.tsx | 236 ++++++++
src/components/ui/navigation-menu.tsx | 128 +++++
src/components/ui/pagination.tsx | 117 ++++
src/components/ui/popover.tsx | 31 ++
src/components/ui/progress.tsx | 28 +
src/components/ui/radio-group.tsx | 42 ++
src/components/ui/resizable.tsx | 45 ++
src/components/ui/scroll-area.tsx | 46 ++
src/components/ui/select.tsx | 159 ++++++
src/components/ui/separator.tsx | 29 +
src/components/ui/sheet.tsx | 140 +++++
src/components/ui/sidebar.tsx | 761 ++++++++++++++++++++++++++
src/components/ui/skeleton.tsx | 15 +
src/components/ui/slider.tsx | 26 +
src/components/ui/sonner.tsx | 31 ++
src/components/ui/switch.tsx | 27 +
src/components/ui/table.tsx | 120 ++++
src/components/ui/tabs.tsx | 53 ++
src/components/ui/textarea.tsx | 22 +
src/components/ui/toast.tsx | 127 +++++
src/components/ui/toaster.tsx | 33 ++
src/components/ui/toggle-group.tsx | 59 ++
src/components/ui/toggle.tsx | 45 ++
src/components/ui/tooltip.tsx | 32 ++
src/data/demos.ts | 194 +++++++
src/hooks/use-mobile.tsx | 19 +
src/hooks/use-toast.ts | 194 +++++++
src/index.css | 106 ++++
src/lib/utils.ts | 6 +
src/main.tsx | 10 +
src/vite-env.d.ts | 1 +
tailwind.config.js | 94 ++++
tsconfig.app.json | 33 ++
tsconfig.json | 18 +
tsconfig.node.json | 24 +
vite.config.ts | 13 +
82 files changed, 6200 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 components.json
create mode 100644 eslint.config.js
create mode 100644 index.html
create mode 100644 package.json
create mode 100644 postcss.config.js
create mode 100644 public/anomaly-detection.jpg
create mode 100644 public/customer-360.jpg
create mode 100644 public/fraud-prevention.jpg
create mode 100644 public/log-analytics.jpg
create mode 100644 public/stream-dashboard.jpg
create mode 100644 public/supply-chain.jpg
create mode 100644 public/vite.svg
create mode 100644 src/App.css
create mode 100644 src/App.tsx
create mode 100644 src/assets/react.svg
create mode 100644 src/components/demos/DemoCard.tsx
create mode 100644 src/components/demos/DemoDetail.tsx
create mode 100644 src/components/demos/DemoGrid.tsx
create mode 100644 src/components/layout/Footer.tsx
create mode 100644 src/components/layout/Header.tsx
create mode 100644 src/components/ui/accordion.tsx
create mode 100644 src/components/ui/alert-dialog.tsx
create mode 100644 src/components/ui/alert.tsx
create mode 100644 src/components/ui/aspect-ratio.tsx
create mode 100644 src/components/ui/avatar.tsx
create mode 100644 src/components/ui/badge.tsx
create mode 100644 src/components/ui/breadcrumb.tsx
create mode 100644 src/components/ui/button.tsx
create mode 100644 src/components/ui/calendar.tsx
create mode 100644 src/components/ui/card.tsx
create mode 100644 src/components/ui/carousel.tsx
create mode 100644 src/components/ui/chart.tsx
create mode 100644 src/components/ui/checkbox.tsx
create mode 100644 src/components/ui/collapsible.tsx
create mode 100644 src/components/ui/command.tsx
create mode 100644 src/components/ui/context-menu.tsx
create mode 100644 src/components/ui/dialog.tsx
create mode 100644 src/components/ui/drawer.tsx
create mode 100644 src/components/ui/dropdown-menu.tsx
create mode 100644 src/components/ui/form.tsx
create mode 100644 src/components/ui/hover-card.tsx
create mode 100644 src/components/ui/input-otp.tsx
create mode 100644 src/components/ui/input.tsx
create mode 100644 src/components/ui/label.tsx
create mode 100644 src/components/ui/menubar.tsx
create mode 100644 src/components/ui/navigation-menu.tsx
create mode 100644 src/components/ui/pagination.tsx
create mode 100644 src/components/ui/popover.tsx
create mode 100644 src/components/ui/progress.tsx
create mode 100644 src/components/ui/radio-group.tsx
create mode 100644 src/components/ui/resizable.tsx
create mode 100644 src/components/ui/scroll-area.tsx
create mode 100644 src/components/ui/select.tsx
create mode 100644 src/components/ui/separator.tsx
create mode 100644 src/components/ui/sheet.tsx
create mode 100644 src/components/ui/sidebar.tsx
create mode 100644 src/components/ui/skeleton.tsx
create mode 100644 src/components/ui/slider.tsx
create mode 100644 src/components/ui/sonner.tsx
create mode 100644 src/components/ui/switch.tsx
create mode 100644 src/components/ui/table.tsx
create mode 100644 src/components/ui/tabs.tsx
create mode 100644 src/components/ui/textarea.tsx
create mode 100644 src/components/ui/toast.tsx
create mode 100644 src/components/ui/toaster.tsx
create mode 100644 src/components/ui/toggle-group.tsx
create mode 100644 src/components/ui/toggle.tsx
create mode 100644 src/components/ui/tooltip.tsx
create mode 100644 src/data/demos.ts
create mode 100644 src/hooks/use-mobile.tsx
create mode 100644 src/hooks/use-toast.ts
create mode 100644 src/index.css
create mode 100644 src/lib/utils.ts
create mode 100644 src/main.tsx
create mode 100644 src/vite-env.d.ts
create mode 100644 tailwind.config.js
create mode 100644 tsconfig.app.json
create mode 100644 tsconfig.json
create mode 100644 tsconfig.node.json
create mode 100644 vite.config.ts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74872fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+export default tseslint.config({
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
+- Optionally add `...tseslint.configs.stylisticTypeChecked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
+
+```js
+// eslint.config.js
+import react from 'eslint-plugin-react'
+
+export default tseslint.config({
+ // Set the react version
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ // Add the react plugin
+ react,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended rules
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ },
+})
+```
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..78cd18f
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/index.css",
+ "baseColor": "zinc",
+ "cssVariables": false,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..092408a
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..63d3894
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ timeplus-demo-site
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2b870c8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,79 @@
+{
+ "name": "react_repo",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "^1.2.2",
+ "@radix-ui/react-alert-dialog": "^1.1.4",
+ "@radix-ui/react-aspect-ratio": "^1.1.1",
+ "@radix-ui/react-avatar": "^1.1.2",
+ "@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-collapsible": "^1.1.2",
+ "@radix-ui/react-context-menu": "^2.2.4",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
+ "@radix-ui/react-hover-card": "^1.1.4",
+ "@radix-ui/react-label": "^2.1.1",
+ "@radix-ui/react-menubar": "^1.1.4",
+ "@radix-ui/react-navigation-menu": "^1.2.3",
+ "@radix-ui/react-popover": "^1.1.4",
+ "@radix-ui/react-progress": "^1.1.1",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-scroll-area": "^1.2.2",
+ "@radix-ui/react-select": "^2.1.4",
+ "@radix-ui/react-separator": "^1.1.1",
+ "@radix-ui/react-slider": "^1.2.2",
+ "@radix-ui/react-slot": "^1.1.1",
+ "@radix-ui/react-switch": "^1.1.2",
+ "@radix-ui/react-tabs": "^1.1.2",
+ "@radix-ui/react-toast": "^1.2.4",
+ "@radix-ui/react-toggle": "^1.1.1",
+ "@radix-ui/react-toggle-group": "^1.1.1",
+ "@radix-ui/react-tooltip": "^1.1.6",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "1.0.0",
+ "date-fns": "^4.1.0",
+ "embla-carousel-react": "^8.5.2",
+ "input-otp": "^1.4.2",
+ "lucide-react": "^0.364.0",
+ "next-themes": "^0.4.4",
+ "react": "^18.3.1",
+ "react-day-picker": "8.10.1",
+ "react-dom": "^18.3.1",
+ "react-hook-form": "^7.54.2",
+ "react-markdown": "^10.1.0",
+ "react-resizable-panels": "^2.1.7",
+ "recharts": "^2.12.4",
+ "sonner": "^1.7.2",
+ "tailwind-merge": "^2.6.0",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.2",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.15.0",
+ "@types/node": "^22.10.7",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "10.4.20",
+ "eslint": "^9.15.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.14",
+ "globals": "^15.12.0",
+ "postcss": "8.4.49",
+ "tailwindcss": "v3.4.16",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.15.0",
+ "vite": "^6.0.1"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/anomaly-detection.jpg b/public/anomaly-detection.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/anomaly-detection.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/customer-360.jpg b/public/customer-360.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/customer-360.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/fraud-prevention.jpg b/public/fraud-prevention.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/fraud-prevention.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/log-analytics.jpg b/public/log-analytics.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/log-analytics.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/stream-dashboard.jpg b/public/stream-dashboard.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/stream-dashboard.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/supply-chain.jpg b/public/supply-chain.jpg
new file mode 100644
index 0000000..6cd9750
--- /dev/null
+++ b/public/supply-chain.jpg
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Application Error
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..2da1e01
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,83 @@
+import React, { useState } from 'react';
+import Header from './components/layout/Header';
+import Footer from './components/layout/Footer';
+import DemoGrid from './components/demos/DemoGrid';
+import DemoDetail from './components/demos/DemoDetail';
+import { demos } from './data/demos';
+
+const App: React.FC = () => {
+ const [selectedDemoId, setSelectedDemoId] = useState(null);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedCategory, setSelectedCategory] = useState(undefined);
+
+ const selectedDemo = selectedDemoId ? demos.find(demo => demo.id === selectedDemoId) : null;
+
+ // Get unique categories from demos
+ const categories = Array.from(new Set(demos.map(demo => demo.category)));
+
+ const handleDemoClick = (id: string) => {
+ setSelectedDemoId(id);
+ window.scrollTo(0, 0);
+ };
+
+ const handleBackClick = () => {
+ setSelectedDemoId(null);
+ };
+
+ return (
+
+
+
+
+ {selectedDemo ? (
+
+ ) : (
+ <>
+
+
Timeplus Product Demos
+
+ Explore our interactive demos showcasing real-time data processing, analytics, and streaming solutions.
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+ setSelectedCategory(e.target.value || undefined)}
+ >
+ All Categories
+ {categories.map((category) => (
+ {category}
+ ))}
+
+
+
+
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/src/assets/react.svg b/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/demos/DemoCard.tsx b/src/components/demos/DemoCard.tsx
new file mode 100644
index 0000000..19b3fb0
--- /dev/null
+++ b/src/components/demos/DemoCard.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { Demo } from '../../data/demos';
+
+interface DemoCardProps {
+ demo: Demo;
+ onClick: (id: string) => void;
+}
+
+const DemoCard: React.FC = ({ demo, onClick }) => {
+ return (
+ onClick(demo.id)}
+ >
+
+
+
+
+ {demo.category}
+
+
+
+
+
+
{demo.title}
+
{demo.subtitle}
+
+
+ {demo.keywords.slice(0, 3).map((keyword, index) => (
+
+ {keyword}
+
+ ))}
+ {demo.keywords.length > 3 && (
+
+ +{demo.keywords.length - 3} more
+
+ )}
+
+
+
+ );
+};
+
+export default DemoCard;
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
new file mode 100644
index 0000000..49610e3
--- /dev/null
+++ b/src/components/demos/DemoDetail.tsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import ReactMarkdown from 'react-markdown';
+import { Demo } from '../../data/demos';
+
+interface DemoDetailProps {
+ demo: Demo;
+ onBack: () => void;
+}
+
+const DemoDetail: React.FC = ({ demo, onBack }) => {
+ return (
+
+
+
+
+
+
+ ← Back to demos
+
+
+ {demo.category}
+
+
{demo.title}
+
{demo.subtitle}
+
+
+
+
+
+ {demo.keywords.map((keyword, index) => (
+
+ {keyword}
+
+ ))}
+
+
+
+
Problem Statement
+
{demo.problemStatement}
+
+
+
+
Context
+
+
+ {demo.context}
+
+
+
+
+
+
Key Steps
+
+ {demo.steps.map((step, index) => (
+ {step}
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default DemoDetail;
diff --git a/src/components/demos/DemoGrid.tsx b/src/components/demos/DemoGrid.tsx
new file mode 100644
index 0000000..88b57e8
--- /dev/null
+++ b/src/components/demos/DemoGrid.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import DemoCard from './DemoCard';
+import { Demo } from '../../data/demos';
+
+interface DemoGridProps {
+ demos: Demo[];
+ onDemoClick: (id: string) => void;
+ selectedCategory?: string;
+ searchQuery?: string;
+}
+
+const DemoGrid: React.FC = ({
+ demos,
+ onDemoClick,
+ selectedCategory,
+ searchQuery
+}) => {
+ // Filter demos based on category and search query
+ const filteredDemos = demos.filter(demo => {
+ const matchesCategory = !selectedCategory || demo.category === selectedCategory;
+ const matchesSearch = !searchQuery ||
+ demo.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ demo.subtitle.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ demo.keywords.some(keyword => keyword.toLowerCase().includes(searchQuery.toLowerCase()));
+
+ return matchesCategory && matchesSearch;
+ });
+
+ return (
+
+ {filteredDemos.length > 0 ? (
+ filteredDemos.map((demo) => (
+
+ ))
+ ) : (
+
+
+ No demos found matching your criteria. Try adjusting your filters.
+
+
+ )}
+
+ );
+};
+
+export default DemoGrid;
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..f219ea2
--- /dev/null
+++ b/src/components/layout/Footer.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+
+const Footer: React.FC = () => {
+ return (
+
+
+
+
+
+ © {new Date().getFullYear()} Timeplus. All rights reserved.
+
+
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
new file mode 100644
index 0000000..7aeaeee
--- /dev/null
+++ b/src/components/layout/Header.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+
+const Header: React.FC = () => {
+ return (
+
+ );
+};
+
+export default Header;
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..5f67b5c
--- /dev/null
+++ b/src/components/ui/accordion.tsx
@@ -0,0 +1,55 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..4224e8a
--- /dev/null
+++ b/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..218f631
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border border-zinc-200 px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-zinc-950 [&>svg~*]:pl-7 dark:border-zinc-800 dark:[&>svg]:text-zinc-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50",
+ destructive:
+ "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..c4abbf3
--- /dev/null
+++ b/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..5e76ffe
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000..120c5b5
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-md border border-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-950 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-300",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
+ secondary:
+ "border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
+ destructive:
+ "border-transparent bg-red-500 text-zinc-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/80",
+ outline: "text-zinc-950 dark:text-zinc-50",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..a00c8e0
--- /dev/null
+++ b/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => )
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..4f52e23
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-zinc-300",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90",
+ destructive:
+ "bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90",
+ outline:
+ "border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
+ secondary:
+ "bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
+ ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
+ link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..58fe354
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md"
+ ),
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
+ ),
+ day_range_start: "day-range-start",
+ day_range_end: "day-range-end",
+ day_selected:
+ "bg-zinc-900 text-zinc-50 hover:bg-zinc-900 hover:text-zinc-50 focus:bg-zinc-900 focus:text-zinc-50 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50 dark:hover:text-zinc-900 dark:focus:bg-zinc-50 dark:focus:text-zinc-900",
+ day_today: "bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-50",
+ day_outside:
+ "day-outside text-zinc-500 aria-selected:bg-zinc-100/50 aria-selected:text-zinc-500 dark:text-zinc-400 dark:aria-selected:bg-zinc-800/50 dark:aria-selected:text-zinc-400",
+ day_disabled: "text-zinc-500 opacity-50 dark:text-zinc-400",
+ day_range_middle:
+ "aria-selected:bg-zinc-100 aria-selected:text-zinc-900 dark:aria-selected:bg-zinc-800 dark:aria-selected:text-zinc-50",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..1b43b8c
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,76 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
new file mode 100644
index 0000000..9c2b9bf
--- /dev/null
+++ b/src/components/ui/carousel.tsx
@@ -0,0 +1,260 @@
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx
new file mode 100644
index 0000000..6934c08
--- /dev/null
+++ b/src/components/ui/chart.tsx
@@ -0,0 +1,365 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+
\ No newline at end of file
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
index f219ea2..493898f 100644
--- a/src/components/layout/Footer.tsx
+++ b/src/components/layout/Footer.tsx
@@ -11,13 +11,13 @@ const Footer: React.FC = () => {
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index 7aeaeee..864172e 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -6,8 +6,8 @@ const Header: React.FC = () => {
-
-
T+
+
+
Timeplus Demos
@@ -17,7 +17,7 @@ const Header: React.FC = () => {
Main Site
-
+
Documentation
Date: Tue, 20 May 2025 19:31:07 -0700
Subject: [PATCH 04/27] reduce 6 demos to 2
---
src/data/demos.ts | 170 +++++++++-------------------------------------
1 file changed, 31 insertions(+), 139 deletions(-)
diff --git a/src/data/demos.ts b/src/data/demos.ts
index 18c1df7..b40dee2 100644
--- a/src/data/demos.ts
+++ b/src/data/demos.ts
@@ -19,176 +19,68 @@ export interface Demo {
export const demos: Demo[] = [
{
id: "demo-1",
- title: "Real-time Stream Processing Dashboard",
- subtitle: "Monitor and analyze streaming data with interactive visualizations",
- category: "Analytics",
- keywords: ["streaming", "dashboard", "real-time", "visualization"],
- coverImage: "/stream-dashboard.jpg",
- description: "A comprehensive dashboard for monitoring and analyzing streaming data in real-time.",
- problemStatement: "Organizations struggle to gain immediate insights from high-velocity streaming data, leading to delayed decision making.",
- context: "This demo showcases how Timeplus enables real-time monitoring and analysis of streaming data through an intuitive dashboard interface.",
+ title: "OLTP to OLAP Pipeline",
+ subtitle:
+ "Denormalize N+1 records in MySQL to a JSON doc, and Save in S3/GCS",
+ category: "Pipeline",
+ keywords: ["cdc", "debezium", "kafka", "pipeline", "s3", "oltp", "olap"],
+ coverImage: "/cdc.jpg",
+ description:
+ "A comprehensive dashboard for monitoring and analyzing streaming data in real-time.",
+ problemStatement:
+ "Organizations struggle to gain immediate insights from high-velocity streaming data, leading to delayed decision making.",
+ context:
+ "This demo showcases how Timeplus enables real-time monitoring and analysis of streaming data through an intuitive dashboard interface.",
steps: [
"Connect to streaming data sources like Kafka or Redpanda",
"Configure real-time processing rules and transformations",
"Set up interactive visualizations and alerts",
- "Monitor system performance and data throughput"
+ "Monitor system performance and data throughput",
],
demoLinks: [
{
title: "Dashboard Demo",
url: "https://demo.timeplus.com/dashboard",
- description: "Interactive dashboard with live data streams"
+ description: "Interactive dashboard with live data streams",
},
{
title: "Redpanda Console",
url: "https://demo.timeplus.com/redpanda",
- description: "View raw Kafka topics and messages"
- }
- ]
+ description: "View raw Kafka topics and messages",
+ },
+ ],
},
{
id: "demo-2",
- title: "Anomaly Detection in IoT Sensor Data",
- subtitle: "Identify unusual patterns in sensor readings automatically",
- category: "IoT",
- keywords: ["anomaly detection", "IoT", "sensors", "machine learning"],
+ title: "OpenTelemetry + SQL",
+ subtitle:
+ "Collect logs, metrics and tracing via OpenTelemetry, export to Kafka, filter and aggregated by streaming SQL and build dashboards in Timeplus or Grafana",
+ category: "Observability",
+ keywords: ["kafka", "opentelemetry", "grafana", "sql"],
coverImage: "/anomaly-detection.jpg",
- description: "Detect anomalies in IoT sensor data streams using machine learning algorithms.",
- problemStatement: "IoT deployments generate massive amounts of sensor data, making it difficult to manually identify anomalies or issues that require attention.",
- context: "This demo shows how Timeplus can automatically detect anomalies in streaming sensor data using built-in machine learning capabilities.",
+ description:
+ "Detect anomalies in IoT sensor data streams using machine learning algorithms.",
+ problemStatement:
+ "IoT deployments generate massive amounts of sensor data, making it difficult to manually identify anomalies or issues that require attention.",
+ context:
+ "This demo shows how Timeplus can automatically detect anomalies in streaming sensor data using built-in machine learning capabilities.",
steps: [
"Ingest streaming sensor data from IoT devices",
"Apply anomaly detection algorithms in real-time",
"Visualize normal vs. anomalous patterns",
- "Configure alerts for detected anomalies"
+ "Configure alerts for detected anomalies",
],
demoLinks: [
{
title: "Anomaly Detection Demo",
url: "https://demo.timeplus.com/anomaly-detection",
- description: "Interactive anomaly detection visualization"
+ description: "Interactive anomaly detection visualization",
},
{
title: "OpenSearch Dashboard",
url: "https://demo.timeplus.com/opensearch",
- description: "Detailed logs and analytics"
- }
- ]
- },
- {
- id: "demo-3",
- title: "E-commerce Fraud Prevention",
- subtitle: "Detect and prevent fraudulent transactions in real-time",
- category: "Security",
- keywords: ["fraud detection", "e-commerce", "security", "transactions"],
- coverImage: "/fraud-prevention.jpg",
- description: "Real-time fraud detection system for e-commerce transactions.",
- problemStatement: "E-commerce platforms need to identify and block fraudulent transactions instantly, without disrupting legitimate customer purchases.",
- context: "This demo illustrates how Timeplus can analyze transaction patterns in real-time to identify potentially fraudulent activities.",
- steps: [
- "Process streaming transaction data",
- "Apply fraud detection rules and ML models",
- "Score transactions for fraud probability",
- "Automatically flag or block suspicious activities"
- ],
- demoLinks: [
- {
- title: "Fraud Detection Dashboard",
- url: "https://demo.timeplus.com/fraud-detection",
- description: "Live fraud detection system"
+ description: "Detailed logs and analytics",
},
- {
- title: "Transaction Simulator",
- url: "https://demo.timeplus.com/transaction-simulator",
- description: "Generate test transactions to see the system in action"
- }
- ]
- },
- {
- id: "demo-4",
- title: "Log Analytics Pipeline",
- subtitle: "Process and analyze log data from multiple sources",
- category: "DevOps",
- keywords: ["log analytics", "observability", "monitoring", "troubleshooting"],
- coverImage: "/log-analytics.jpg",
- description: "Centralized log processing and analysis platform for DevOps teams.",
- problemStatement: "Modern applications generate massive volumes of logs across distributed systems, making it challenging to troubleshoot issues efficiently.",
- context: "This demo shows how Timeplus can ingest, process, and analyze logs from multiple sources to provide real-time observability.",
- steps: [
- "Collect logs from various sources (applications, servers, containers)",
- "Parse and normalize log formats",
- "Apply real-time filtering and pattern detection",
- "Create dashboards for monitoring and troubleshooting"
],
- demoLinks: [
- {
- title: "Log Analytics Dashboard",
- url: "https://demo.timeplus.com/log-analytics",
- description: "Interactive log analysis interface"
- },
- {
- title: "Alert Configuration",
- url: "https://demo.timeplus.com/alert-config",
- description: "Set up automated alerts based on log patterns"
- }
- ]
},
- {
- id: "demo-5",
- title: "Real-time Customer 360",
- subtitle: "Build comprehensive customer profiles with streaming data",
- category: "Marketing",
- keywords: ["customer 360", "personalization", "user behavior", "marketing"],
- coverImage: "/customer-360.jpg",
- description: "Create and update customer profiles in real-time based on streaming interaction data.",
- problemStatement: "Businesses struggle to maintain up-to-date customer profiles across multiple touchpoints, leading to fragmented experiences.",
- context: "This demo illustrates how Timeplus can aggregate customer data from various sources to create comprehensive, real-time customer profiles.",
- steps: [
- "Ingest customer interaction data from multiple channels",
- "Enrich profiles with historical and contextual data",
- "Apply real-time segmentation and scoring",
- "Trigger personalized experiences based on profile updates"
- ],
- demoLinks: [
- {
- title: "Customer Profile Dashboard",
- url: "https://demo.timeplus.com/customer-profiles",
- description: "Interactive customer profile explorer"
- },
- {
- title: "Segmentation Engine",
- url: "https://demo.timeplus.com/segmentation",
- description: "Real-time customer segmentation tool"
- }
- ]
- },
- {
- id: "demo-6",
- title: "Supply Chain Monitoring",
- subtitle: "Track inventory and logistics in real-time",
- category: "Operations",
- keywords: ["supply chain", "logistics", "inventory", "tracking"],
- coverImage: "/supply-chain.jpg",
- description: "End-to-end visibility into supply chain operations with real-time monitoring.",
- problemStatement: "Supply chain managers lack real-time visibility into inventory levels, shipment status, and potential disruptions.",
- context: "This demo showcases how Timeplus can provide real-time monitoring of supply chain operations, from inventory to logistics.",
- steps: [
- "Integrate data from inventory, logistics, and supplier systems",
- "Track key metrics and KPIs in real-time",
- "Detect potential disruptions and bottlenecks",
- "Optimize inventory levels and logistics routes"
- ],
- demoLinks: [
- {
- title: "Supply Chain Dashboard",
- url: "https://demo.timeplus.com/supply-chain",
- description: "Real-time supply chain monitoring"
- },
- {
- title: "Logistics Tracker",
- url: "https://demo.timeplus.com/logistics",
- description: "Shipment and logistics monitoring tool"
- }
- ]
- }
];
From 7ea17533958b0b2ac754ff2f12c19d885baa4e2a Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 19:46:04 -0700
Subject: [PATCH 05/27] Update DemoDetail.tsx
how desc and problem statement in ReactMarkdown
---
src/components/demos/DemoDetail.tsx | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
index 49610e3..b5ea0ff 100644
--- a/src/components/demos/DemoDetail.tsx
+++ b/src/components/demos/DemoDetail.tsx
@@ -43,10 +43,19 @@ const DemoDetail: React.FC = ({ demo, onBack }) => {
))}
+
+
+
Overview
+
+ {demo.description}
+
+
Problem Statement
-
{demo.problemStatement}
+
+ {demo.problemStatement}
+
From 5c6e13e8a9f3db61b256d9ab97b813c31a656108 Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 20:52:12 -0700
Subject: [PATCH 06/27] basic info for the demo
---
src/components/demos/DemoDetail.tsx | 6 ++--
src/data/demos.ts | 50 ++++++++++++++++++-----------
2 files changed, 34 insertions(+), 22 deletions(-)
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
index b5ea0ff..d741499 100644
--- a/src/components/demos/DemoDetail.tsx
+++ b/src/components/demos/DemoDetail.tsx
@@ -45,21 +45,21 @@ const DemoDetail: React.FC = ({ demo, onBack }) => {
-
Overview
+
Introduction
{demo.description}
-
Problem Statement
+
Challenges
{demo.problemStatement}
-
Context
+
Timeplus Solution
{demo.context}
diff --git a/src/data/demos.ts b/src/data/demos.ts
index b40dee2..a865c10 100644
--- a/src/data/demos.ts
+++ b/src/data/demos.ts
@@ -25,28 +25,40 @@ export const demos: Demo[] = [
category: "Pipeline",
keywords: ["cdc", "debezium", "kafka", "pipeline", "s3", "oltp", "olap"],
coverImage: "/cdc.jpg",
- description:
- "A comprehensive dashboard for monitoring and analyzing streaming data in real-time.",
- problemStatement:
- "Organizations struggle to gain immediate insights from high-velocity streaming data, leading to delayed decision making.",
- context:
- "This demo showcases how Timeplus enables real-time monitoring and analysis of streaming data through an intuitive dashboard interface.",
+ description: `It's common to design your OLTP database schema in the normalized way. For example, when a customer places an order with multiple line items, one row will be added to the "orders" MySQL table with a "orderNumber", and multiple rows to add in the "order_details" table with the same "orderNumber".
+
+The goal is to create a JSON document for each order, with all line items aggregated, with the subtotal calculated, so that BigQuery can run SQL for those JSON files in GCS and no need to perform expensive JOINs.`,
+ problemStatement: `1️⃣ You can capture database changes via tools like Debezium and make the change feed available in Apache Kafka. However since each order include 1 new row in "orders" and multiple rows in "order_details", the time stamp for those CDC messages are not exactly same. A naive JOIN by "orderNumber" and timestamp won't catch all changes.
+
+2️⃣ Tumble window aggregations (say every 10s) can split one order across two windows if the customer adds items right at the edge
+
+3️⃣ Sliding windows (hop) solve the split but now you get duplicate events
+
+4️⃣ Global aggregation keeps everything in memory—great until your state grows to 100GB in a week…`,
+ context: `✨ Timeplus can seamlessly connect OLTP systems like MySQL, using technologies such as Debezium for Change Data Capture (CDC) and Kafka for streaming.
+
+✨ Use a range join first, assuming all events for a single transaction arrive within 10 seconds. Join "orders" and "order_details" for the same ID within that time range, then use the order timestamp (not the event timestamp) to run your tumble window aggregation.
+
+✨ This approach ensures data is ready for immediate analysis, without order splitting, no duplicate and small JOIN state.`,
steps: [
- "Connect to streaming data sources like Kafka or Redpanda",
- "Configure real-time processing rules and transformations",
- "Set up interactive visualizations and alerts",
- "Monitor system performance and data throughput",
+ "Create 2 tables in MySQL",
+ "Set up Kafka Connect and Debezium MySQL Connector to load changes from MySQL",
+ "Create External Streams in Timeplus to read data from those 2 Kafka topics",
+ "Creaet an External Table in S3 type to write data to S3 or GCS",
+ "Create a Materialized View to apply range join to enrich each order_details message with the order event, then apply tumble window join to aggregate line items for same order and send to S3/GCS ",
+ "Optionally, you can set up BigQuery to scan the JSON files in GCS or use Looker to visualize them",
],
demoLinks: [
{
- title: "Dashboard Demo",
- url: "https://demo.timeplus.com/dashboard",
- description: "Interactive dashboard with live data streams",
+ title: "Kafka UI",
+ url: "http://kafka.demo.timeplus.com:8080/topics/demo.cdc.mysql.retailer.orders",
+ description: "View raw Kafka topics and messages",
},
{
- title: "Redpanda Console",
- url: "https://demo.timeplus.com/redpanda",
- description: "View raw Kafka topics and messages",
+ title: "Timeplus Enterprise",
+ url: "https://timeplus.demo.timeplus.com",
+ description:
+ "Login with demo/demo123. Check data lineage for retailer_etl namespace",
},
],
},
@@ -59,11 +71,11 @@ export const demos: Demo[] = [
keywords: ["kafka", "opentelemetry", "grafana", "sql"],
coverImage: "/anomaly-detection.jpg",
description:
- "Detect anomalies in IoT sensor data streams using machine learning algorithms.",
+ "A powerful observability solution combining OpenTelemetry with streaming SQL.\nKey features include:\n- Comprehensive data collection\n- Real-time filtering and aggregation\n- Flexible visualization options",
problemStatement:
- "IoT deployments generate massive amounts of sensor data, making it difficult to manually identify anomalies or issues that require attention.",
+ "Modern distributed systems generate vast amounts of observability data including logs, metrics, and traces.\nKey challenges include:\n- Data silos across different tools\n- Complexity in correlating different data types\n- Delays in identifying and resolving issues\nThis results in slower incident response and reduced system reliability.",
context:
- "This demo shows how Timeplus can automatically detect anomalies in streaming sensor data using built-in machine learning capabilities.",
+ "This demo shows how Timeplus integrates with OpenTelemetry to provide a unified observability platform.\nBy collecting data via OpenTelemetry standards and exporting to Kafka, we enable:\n- Real-time processing with streaming SQL\n- Custom filtering and aggregation\n- Visualization through Timeplus dashboards or integration with Grafana\nThis provides teams with immediate insights into system health and performance.",
steps: [
"Ingest streaming sensor data from IoT devices",
"Apply anomaly detection algorithms in real-time",
From 6dcb9ed8709d8c04966c202a6d2353d04dbeee42 Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 21:14:01 -0700
Subject: [PATCH 07/27] add demo video and rename metadata
---
src/components/demos/DemoDetail.tsx | 123 ++++++++++++++++++++--------
src/data/demos.ts | 49 +++++++++--
2 files changed, 128 insertions(+), 44 deletions(-)
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
index d741499..3258187 100644
--- a/src/components/demos/DemoDetail.tsx
+++ b/src/components/demos/DemoDetail.tsx
@@ -1,6 +1,6 @@
-import React from 'react';
-import ReactMarkdown from 'react-markdown';
-import { Demo } from '../../data/demos';
+import React from "react";
+import ReactMarkdown from "react-markdown";
+import { Demo } from "../../data/demos";
interface DemoDetailProps {
demo: Demo;
@@ -11,31 +11,39 @@ const DemoDetail: React.FC = ({ demo, onBack }) => {
return (
-
-
← Back to demos
-
+
{demo.category}
- {demo.title}
- {demo.subtitle}
+
+ {demo.title}
+
+
+ {demo.subtitle}
+
-
+
{demo.keywords.map((keyword, index) => (
-
@@ -43,53 +51,98 @@ const DemoDetail: React.FC = ({ demo, onBack }) => {
))}
-
-
-
Introduction
-
- {demo.description}
-
-
-
+
+
+
Introduction
+
+ {demo.introduction}
+
+
+
Challenges
-
-
{demo.problemStatement}
+
+ {demo.challenges}
-
+
-
Timeplus Solution
-
-
- {demo.context}
-
+
Solution
+
+ {demo.solution}
-
+
Key Steps
{demo.steps.map((step, index) => (
- {step}
+
+ {step}
+
))}
-
+
+
+
Data Flow
+
+
+
+
+
+
+
SQL Example
+
+
+ {demo.sqlExample}
+
+
+
+
+ {demo.youtubeVideoLink && (
+
+ )}
+
Demo Links
{demo.demoLinks.map((link, index) => (
-
- {link.title}
+
+ {link.title}
+
{link.description && (
- {link.description}
+
+ {link.description}
+
)}
))}
diff --git a/src/data/demos.ts b/src/data/demos.ts
index a865c10..fae4085 100644
--- a/src/data/demos.ts
+++ b/src/data/demos.ts
@@ -5,10 +5,13 @@ export interface Demo {
category: string;
keywords: string[];
coverImage: string;
- description: string;
- problemStatement: string;
- context: string;
+ introduction: string;
+ challenges: string;
+ solution: string;
steps: string[];
+ dataFlowImage: string;
+ sqlExample: string;
+ youtubeVideoLink?: string;
demoLinks: {
title: string;
url: string;
@@ -25,17 +28,17 @@ export const demos: Demo[] = [
category: "Pipeline",
keywords: ["cdc", "debezium", "kafka", "pipeline", "s3", "oltp", "olap"],
coverImage: "/cdc.jpg",
- description: `It's common to design your OLTP database schema in the normalized way. For example, when a customer places an order with multiple line items, one row will be added to the "orders" MySQL table with a "orderNumber", and multiple rows to add in the "order_details" table with the same "orderNumber".
+ introduction: `It's common to design your OLTP database schema in the normalized way. For example, when a customer places an order with multiple line items, one row will be added to the "orders" MySQL table with a "orderNumber", and multiple rows to add in the "order_details" table with the same "orderNumber".
The goal is to create a JSON document for each order, with all line items aggregated, with the subtotal calculated, so that BigQuery can run SQL for those JSON files in GCS and no need to perform expensive JOINs.`,
- problemStatement: `1️⃣ You can capture database changes via tools like Debezium and make the change feed available in Apache Kafka. However since each order include 1 new row in "orders" and multiple rows in "order_details", the time stamp for those CDC messages are not exactly same. A naive JOIN by "orderNumber" and timestamp won't catch all changes.
+ challenges: `1️⃣ You can capture database changes via tools like Debezium and make the change feed available in Apache Kafka. However since each order include 1 new row in "orders" and multiple rows in "order_details", the time stamp for those CDC messages are not exactly same. A naive JOIN by "orderNumber" and timestamp won't catch all changes.
2️⃣ Tumble window aggregations (say every 10s) can split one order across two windows if the customer adds items right at the edge
3️⃣ Sliding windows (hop) solve the split but now you get duplicate events
4️⃣ Global aggregation keeps everything in memory—great until your state grows to 100GB in a week…`,
- context: `✨ Timeplus can seamlessly connect OLTP systems like MySQL, using technologies such as Debezium for Change Data Capture (CDC) and Kafka for streaming.
+ solution: `✨ Timeplus can seamlessly connect OLTP systems like MySQL, using technologies such as Debezium for Change Data Capture (CDC) and Kafka for streaming.
✨ Use a range join first, assuming all events for a single transaction arrive within 10 seconds. Join "orders" and "order_details" for the same ID within that time range, then use the order timestamp (not the event timestamp) to run your tumble window aggregation.
@@ -48,6 +51,31 @@ The goal is to create a JSON document for each order, with all line items aggreg
"Create a Materialized View to apply range join to enrich each order_details message with the order event, then apply tumble window join to aggregate line items for same order and send to S3/GCS ",
"Optionally, you can set up BigQuery to scan the JSON files in GCS or use Looker to visualize them",
],
+ dataFlowImage: "/oltp-to-olap-flow.jpg",
+ sqlExample: `create materialized view retailer_etl.mv_mysql_gcs_pipeline
+ into retailer_etl.gcs as
+ with
+ details as (select _tp_time, * from retailer_etl.mv_orderdetails settings seek_to='earliest'),
+ orders as (select _tp_time, * from retailer_etl.mv_orders settings seek_to='earliest'),
+ enriched_orderdetails as (
+ select orders._tp_time as timestamp,*
+ from details inner join orders
+ on (details.orderNumber = orders.orderNumber)
+ and date_diff_within(10s)--assume N+1 rows added within 10 seconds
+ settings join_max_buffered_bytes = 1048576000
+ )
+ select
+ orderNumber,any(customerNumber) as customerNumber,any(orderDate) as orderDate,any(status) as status,
+ sum(priceEach*quantityOrdered)::decimal(10,2) as orderTotal,
+ count() as itemCount,
+ group_uniq_array(productCode) as productCodes,
+ group_uniq_array(dict_get('retailer_etl.dict_products','productName',productCode)) as productNames,
+ group_uniq_array(dict_get('retailer_etl.dict_products','productLine',productCode)) as productLines
+ from tumble(enriched_orderdetails,timestamp,30s)
+ group by window_start,orderNumber
+ comment 'Combine details for same order & create JSON in GCS, batch every 30s';`,
+ youtubeVideoLink:
+ "https://www.youtube.com/embed/lTzSm2pMs-E?si=7NR8qbqq7m383OSS",
demoLinks: [
{
title: "Kafka UI",
@@ -70,11 +98,11 @@ The goal is to create a JSON document for each order, with all line items aggreg
category: "Observability",
keywords: ["kafka", "opentelemetry", "grafana", "sql"],
coverImage: "/anomaly-detection.jpg",
- description:
+ introduction:
"A powerful observability solution combining OpenTelemetry with streaming SQL.\nKey features include:\n- Comprehensive data collection\n- Real-time filtering and aggregation\n- Flexible visualization options",
- problemStatement:
+ challenges:
"Modern distributed systems generate vast amounts of observability data including logs, metrics, and traces.\nKey challenges include:\n- Data silos across different tools\n- Complexity in correlating different data types\n- Delays in identifying and resolving issues\nThis results in slower incident response and reduced system reliability.",
- context:
+ solution:
"This demo shows how Timeplus integrates with OpenTelemetry to provide a unified observability platform.\nBy collecting data via OpenTelemetry standards and exporting to Kafka, we enable:\n- Real-time processing with streaming SQL\n- Custom filtering and aggregation\n- Visualization through Timeplus dashboards or integration with Grafana\nThis provides teams with immediate insights into system health and performance.",
steps: [
"Ingest streaming sensor data from IoT devices",
@@ -82,6 +110,9 @@ The goal is to create a JSON document for each order, with all line items aggreg
"Visualize normal vs. anomalous patterns",
"Configure alerts for detected anomalies",
],
+ dataFlowImage: "/observability-flow.jpg",
+ sqlExample: "..",
+ youtubeVideoLink: "https://www.youtube.com/embed/example2",
demoLinks: [
{
title: "Anomaly Detection Demo",
From dd7a09ae9d0a602887cc6a5a51fcc01d5af33e02 Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 21:19:46 -0700
Subject: [PATCH 08/27] add syntax highlighted sql block
---
package.json | 1 +
pnpm-lock.yaml | 166 ++++++++++++++++++++++++++++
src/components/demos/DemoDetail.tsx | 19 ++--
3 files changed, 179 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index b4ce3b0..7d3a9b3 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"react-hook-form": "^7.54.2",
"react-markdown": "^10.1.0",
"react-resizable-panels": "^2.1.7",
+ "react-syntax-highlighter": "^15.6.1",
"recharts": "^2.12.4",
"sonner": "^1.7.2",
"tailwind-merge": "^2.6.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7fb740e..6f44b62 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -134,6 +134,9 @@ importers:
react-resizable-panels:
specifier: ^2.1.7
version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-syntax-highlighter:
+ specifier: ^15.6.1
+ version: 15.6.1(react@18.3.1)
recharts:
specifier: ^2.12.4
version: 2.15.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1493,6 +1496,9 @@ packages:
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+ '@types/hast@2.3.10':
+ resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -1680,12 +1686,21 @@ packages:
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+ character-entities-legacy@1.1.4:
+ resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
+
character-entities-legacy@3.0.0:
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+ character-entities@1.2.4:
+ resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==}
+
character-entities@2.0.2:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+ character-reference-invalid@1.1.4:
+ resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
+
character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
@@ -1713,6 +1728,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ comma-separated-tokens@1.0.8:
+ resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
+
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@@ -1942,6 +1960,9 @@ packages:
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+ fault@1.0.4:
+ resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
+
fdir@6.4.4:
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
peerDependencies:
@@ -1973,6 +1994,10 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
+ format@0.2.2:
+ resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
+ engines: {node: '>=0.4.x'}
+
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -2027,12 +2052,24 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-parse-selector@2.2.5:
+ resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
+
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+ hastscript@6.0.0:
+ resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==}
+
+ highlight.js@10.7.3:
+ resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
+
+ highlightjs-vue@1.0.0:
+ resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
+
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
@@ -2065,9 +2102,15 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ is-alphabetical@1.0.4:
+ resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
+
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+ is-alphanumerical@1.0.4:
+ resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}
+
is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
@@ -2079,6 +2122,9 @@ packages:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
+ is-decimal@1.0.4:
+ resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
+
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
@@ -2094,6 +2140,9 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-hexadecimal@1.0.4:
+ resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
+
is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
@@ -2172,6 +2221,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lowlight@1.20.0:
+ resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
+
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -2347,6 +2399,9 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse-entities@2.0.0:
+ resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
+
parse-entities@4.0.2:
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
@@ -2433,9 +2488,20 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ prismjs@1.27.0:
+ resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
+ engines: {node: '>=6'}
+
+ prismjs@1.30.0:
+ resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
+ engines: {node: '>=6'}
+
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@5.6.0:
+ resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
+
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
@@ -2531,6 +2597,11 @@ packages:
'@types/react':
optional: true
+ react-syntax-highlighter@15.6.1:
+ resolution: {integrity: sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==}
+ peerDependencies:
+ react: '>= 0.14.0'
+
react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
@@ -2558,6 +2629,9 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ refractor@3.6.0:
+ resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
+
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
@@ -2619,6 +2693,9 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ space-separated-tokens@1.1.5:
+ resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
+
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@@ -2859,6 +2936,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4131,6 +4212,10 @@ snapshots:
'@types/estree@1.0.7': {}
+ '@types/hast@2.3.10':
+ dependencies:
+ '@types/unist': 2.0.11
+
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -4341,10 +4426,16 @@ snapshots:
character-entities-html4@2.1.0: {}
+ character-entities-legacy@1.1.4: {}
+
character-entities-legacy@3.0.0: {}
+ character-entities@1.2.4: {}
+
character-entities@2.0.2: {}
+ character-reference-invalid@1.1.4: {}
+
character-reference-invalid@2.0.1: {}
chokidar@3.6.0:
@@ -4381,6 +4472,8 @@ snapshots:
color-name@1.1.4: {}
+ comma-separated-tokens@1.0.8: {}
+
comma-separated-tokens@2.0.3: {}
commander@4.1.1: {}
@@ -4623,6 +4716,10 @@ snapshots:
dependencies:
reusify: 1.1.0
+ fault@1.0.4:
+ dependencies:
+ format: 0.2.2
+
fdir@6.4.4(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
@@ -4652,6 +4749,8 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ format@0.2.2: {}
+
fraction.js@4.3.7: {}
fsevents@2.3.3:
@@ -4694,6 +4793,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-parse-selector@2.2.5: {}
+
hast-util-to-jsx-runtime@2.3.6:
dependencies:
'@types/estree': 1.0.7
@@ -4718,6 +4819,18 @@ snapshots:
dependencies:
'@types/hast': 3.0.4
+ hastscript@6.0.0:
+ dependencies:
+ '@types/hast': 2.3.10
+ comma-separated-tokens: 1.0.8
+ hast-util-parse-selector: 2.2.5
+ property-information: 5.6.0
+ space-separated-tokens: 1.1.5
+
+ highlight.js@10.7.3: {}
+
+ highlightjs-vue@1.0.0: {}
+
html-url-attributes@3.0.1: {}
ignore@5.3.2: {}
@@ -4740,8 +4853,15 @@ snapshots:
internmap@2.0.3: {}
+ is-alphabetical@1.0.4: {}
+
is-alphabetical@2.0.1: {}
+ is-alphanumerical@1.0.4:
+ dependencies:
+ is-alphabetical: 1.0.4
+ is-decimal: 1.0.4
+
is-alphanumerical@2.0.1:
dependencies:
is-alphabetical: 2.0.1
@@ -4755,6 +4875,8 @@ snapshots:
dependencies:
hasown: 2.0.2
+ is-decimal@1.0.4: {}
+
is-decimal@2.0.1: {}
is-extglob@2.1.1: {}
@@ -4765,6 +4887,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
+ is-hexadecimal@1.0.4: {}
+
is-hexadecimal@2.0.1: {}
is-number@7.0.0: {}
@@ -4824,6 +4948,11 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lowlight@1.20.0:
+ dependencies:
+ fault: 1.0.4
+ highlight.js: 10.7.3
+
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@@ -5123,6 +5252,15 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse-entities@2.0.0:
+ dependencies:
+ character-entities: 1.2.4
+ character-entities-legacy: 1.1.4
+ character-reference-invalid: 1.1.4
+ is-alphanumerical: 1.0.4
+ is-decimal: 1.0.4
+ is-hexadecimal: 1.0.4
+
parse-entities@4.0.2:
dependencies:
'@types/unist': 2.0.11
@@ -5199,12 +5337,20 @@ snapshots:
prelude-ls@1.2.1: {}
+ prismjs@1.27.0: {}
+
+ prismjs@1.30.0: {}
+
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@5.6.0:
+ dependencies:
+ xtend: 4.0.2
+
property-information@7.1.0: {}
punycode@2.3.1: {}
@@ -5301,6 +5447,16 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.21
+ react-syntax-highlighter@15.6.1(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.27.1
+ highlight.js: 10.7.3
+ highlightjs-vue: 1.0.0
+ lowlight: 1.20.0
+ prismjs: 1.30.0
+ react: 18.3.1
+ refractor: 3.6.0
+
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.27.1
@@ -5339,6 +5495,12 @@ snapshots:
tiny-invariant: 1.3.3
victory-vendor: 36.9.2
+ refractor@3.6.0:
+ dependencies:
+ hastscript: 6.0.0
+ parse-entities: 2.0.0
+ prismjs: 1.27.0
+
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
@@ -5419,6 +5581,8 @@ snapshots:
source-map-js@1.2.1: {}
+ space-separated-tokens@1.1.5: {}
+
space-separated-tokens@2.0.2: {}
string-width@4.2.3:
@@ -5686,6 +5850,8 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
+ xtend@4.0.2: {}
+
yallist@3.1.1: {}
yaml@2.8.0: {}
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
index 3258187..2deb89c 100644
--- a/src/components/demos/DemoDetail.tsx
+++ b/src/components/demos/DemoDetail.tsx
@@ -1,5 +1,7 @@
import React from "react";
import ReactMarkdown from "react-markdown";
+import SyntaxHighlighter from "react-syntax-highlighter";
+import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs"; // Choose a theme
import { Demo } from "../../data/demos";
interface DemoDetailProps {
@@ -97,13 +99,16 @@ inline-block"
SQL Example
-
-
- {demo.sqlExample}
-
+
+
+ {demo.sqlExample}
+
From 7ac3da07dcd3b0a6bca958cd6b8a03efc98d6f43 Mon Sep 17 00:00:00 2001
From: Jove Zhong
Date: Tue, 20 May 2025 21:45:10 -0700
Subject: [PATCH 09/27] change code theme from docco to pojoaque
---
src/components/demos/DemoDetail.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/demos/DemoDetail.tsx b/src/components/demos/DemoDetail.tsx
index 2deb89c..3052fb6 100644
--- a/src/components/demos/DemoDetail.tsx
+++ b/src/components/demos/DemoDetail.tsx
@@ -1,7 +1,7 @@
import React from "react";
import ReactMarkdown from "react-markdown";
import SyntaxHighlighter from "react-syntax-highlighter";
-import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs"; // Choose a theme
+import { pojoaque } from "react-syntax-highlighter/dist/esm/styles/hljs"; // Choose a theme
import { Demo } from "../../data/demos";
interface DemoDetailProps {
@@ -102,7 +102,7 @@ inline-block"
Date: Tue, 20 May 2025 21:57:59 -0700
Subject: [PATCH 10/27] update cover image and data flow image
---
src/assets/images/cdc_cover.png | Bin 0 -> 193365 bytes
src/assets/images/cdc_data_flow.png | Bin 0 -> 204810 bytes
src/data/demos.ts | 77 +++++++++++++++++++---------
3 files changed, 53 insertions(+), 24 deletions(-)
create mode 100644 src/assets/images/cdc_cover.png
create mode 100644 src/assets/images/cdc_data_flow.png
diff --git a/src/assets/images/cdc_cover.png b/src/assets/images/cdc_cover.png
new file mode 100644
index 0000000000000000000000000000000000000000..59a91b24eca6f72e7ccbce891d4326e355cc3321
GIT binary patch
literal 193365
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