Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cde8813c77 | ||
|
|
1f74a55dff | ||
|
|
531dc6e9aa | ||
|
|
130e988143 | ||
|
|
45f8d0ff38 | ||
|
|
b7a772dc47 | ||
|
|
66f97ee491 | ||
|
|
5db1b5a836 | ||
|
|
624fd61de8 | ||
|
|
e3a33403d0 | ||
|
|
25d95df8f7 | ||
|
|
605087f0a8 | ||
|
|
c1ec1a000e | ||
|
|
94227f79ae | ||
|
|
b339995084
|
||
|
|
8226b6fdff
|
||
|
|
4209e4ad0f | ||
|
|
aed0f63429 |
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# SEO Site URL
|
||||||
|
VITE_SEO_SITE_URL=http://localhost:5173
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Deploy to Vercel
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Deploy to Vercel
|
||||||
|
uses: amondnet/vercel-action@v42.3.0
|
||||||
|
with:
|
||||||
|
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
vercel-args: '--prod'
|
||||||
|
working-directory: ./
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "chore: "
|
|
||||||
|
|
||||||
groups:
|
|
||||||
dependency-updates:
|
|
||||||
patterns: ["*"]
|
|
||||||
update-types: ["minor", "patch"]
|
|
||||||
|
|
||||||
ignore:
|
|
||||||
- dependency-name: "@types/node"
|
|
||||||
update-types: ["version-update:semver-major"]
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-03-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Refined sample datasets to better demonstrate JSON tree navigation and edge cases.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed the integrated Changelog page from the system UI.
|
||||||
|
|
||||||
|
## [1.1.1] - 2026-02-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added syntax highlighting for JSONPath expressions to improve visual readability.
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-02-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Implemented sortable table views for JSON arrays with CSV export functionality.
|
||||||
|
- Introduced a dedicated tool layout featuring a categorised sidebar menu for efficient switching.
|
||||||
|
- Added one-click functionality to copy JSONPath-selected data as raw JSON.
|
||||||
|
- Categorised tools into "JSON Processing" (Viewer, Grid) and "Daily Tools" (BMI Calculator).
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-01-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial release.
|
||||||
|
- Full-featured JSON viewer with JSONPath query support and CSV export.
|
||||||
|
- BMI calculator with category guidance and health advice.
|
||||||
|
- Internationalisation support for British English and Simplified Chinese.
|
||||||
+23
-31
@@ -12,41 +12,33 @@
|
|||||||
"predeploy": "pnpm build"
|
"predeploy": "pnpm build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.12.0",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.17.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.21",
|
||||||
"i18next": "^25.7.4",
|
"i18next": "^26.3.1",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.18.1",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.7",
|
||||||
"react-i18next": "^16.5.3",
|
"react-i18next": "^17.0.8",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.3.0",
|
||||||
"react-router": "^7.12.0",
|
"react-router": "^7.17.0",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.17.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jsonpath": "^0.2.4",
|
"@types/jsonpath": "^0.2.4",
|
||||||
"@types/lodash": "^4.17.23",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/node": "^22.19.7",
|
"@types/node": "^24.13.1",
|
||||||
"@types/react": "^19.2.8",
|
"@types/react": "^19.2.17",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"globals": "^16.5.0",
|
"globals": "^17.6.0",
|
||||||
"prettier": "^3.8.0",
|
"prettier": "^3.8.3",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~6.0.3",
|
||||||
"vite": "^7.3.1"
|
"vite": "^8.0.16"
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"ignoredBuiltDependencies": [
|
|
||||||
"esbuild"
|
|
||||||
],
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@tailwindcss/oxide"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+642
-1031
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const today = useMemo(() => dayjs(), [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="bg-white border-t shrink-0">
|
||||||
|
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
||||||
|
<p className="text-center text-sm text-gray-500">
|
||||||
|
{t("app.copyright", { year: today.year() })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import LanguageSwitcher from "@/components/language-switcher"
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="bg-white shadow-sm border-b">
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<h1 className="text-xl font-semibold text-gray-900">{t("app.title")}</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<nav className="flex space-x-8">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
{t("navigation.home")}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/about"
|
||||||
|
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
{t("navigation.about")}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/contact"
|
||||||
|
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
{t("navigation.contact")}
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useRef } from "react"
|
import React, { useMemo, useRef } from "react"
|
||||||
|
|
||||||
type JsonCodeEditorProps = {
|
type JsonCodeEditorProps = {
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type SeoProps = {
|
|||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SITE_URL = "https://dev-lab.onixbyte.dev"
|
const SITE_URL = import.meta.env.VITE_SEO_SITE_URL
|
||||||
const DEFAULT_IMAGE = `${SITE_URL}/onixbyte.svg`
|
const DEFAULT_IMAGE = `${SITE_URL}/onixbyte.svg`
|
||||||
|
|
||||||
function setMetaTag(selector: string, attr: string, value: string) {
|
function setMetaTag(selector: string, attr: string, value: string) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ void i18n
|
|||||||
.init({
|
.init({
|
||||||
resources,
|
resources,
|
||||||
fallbackLng: "en-GB",
|
fallbackLng: "en-GB",
|
||||||
debug: process.env.NODE_ENV === "development",
|
debug: import.meta.env.DEV,
|
||||||
|
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // React already does escaping
|
escapeValue: false, // React already does escaping
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { useMemo } from "react"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import LanguageSwitcher from "@/components/language-switcher"
|
import LanguageSwitcher from "@/components/language-switcher"
|
||||||
|
import Header from "@/components/header"
|
||||||
|
import Footer from "@/components/footer"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application component that serves as the root layout.
|
* Main application component that serves as the root layout.
|
||||||
@@ -15,46 +17,7 @@ export default function HeroLayout() {
|
|||||||
return (
|
return (
|
||||||
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
|
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
|
||||||
{/* Navigation Header */}
|
{/* Navigation Header */}
|
||||||
<header className="bg-white shadow-sm border-b">
|
<Header></Header>
|
||||||
<div className="px-4">
|
|
||||||
<div className="flex justify-between items-center h-16">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<h1 className="text-xl font-semibold text-gray-900">
|
|
||||||
{t("app.title")}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<nav className="flex space-x-8">
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
{t("navigation.home")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/about"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
{t("navigation.about")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/contact"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
{t("navigation.contact")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/changelog"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
{t("navigation.changelog")}
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<main className="flex-1 p-4 overflow-hidden min-h-0">
|
<main className="flex-1 p-4 overflow-hidden min-h-0">
|
||||||
@@ -62,13 +25,7 @@ export default function HeroLayout() {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-white border-t shrink-0">
|
<Footer></Footer>
|
||||||
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
|
||||||
<p className="text-center text-sm text-gray-500">
|
|
||||||
{t("app.copyright", { year: today.year() })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@ import { Link, NavLink, Outlet } from "react-router-dom"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import LanguageSwitcher from "@/components/language-switcher"
|
import LanguageSwitcher from "@/components/language-switcher"
|
||||||
|
import Header from "@/components/header"
|
||||||
|
import Footer from "@/components/footer"
|
||||||
|
|
||||||
export default function ToolsLayout() {
|
export default function ToolsLayout() {
|
||||||
const today = useMemo(() => dayjs(), [])
|
const today = useMemo(() => dayjs(), [])
|
||||||
@@ -28,40 +30,7 @@ export default function ToolsLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
|
<div className="h-screen bg-gray-50 flex flex-col overflow-hidden">
|
||||||
<header className="bg-white shadow-sm border-b">
|
<Header></Header>
|
||||||
<div className="px-4">
|
|
||||||
<div className="flex justify-between items-center h-16">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<h1 className="text-xl font-semibold text-gray-900">{t("app.title")}</h1>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<nav className="flex space-x-8">
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
|
||||||
{t("navigation.home")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/about"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
|
||||||
{t("navigation.about")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/contact"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
|
||||||
{t("navigation.contact")}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/changelog"
|
|
||||||
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
|
||||||
{t("navigation.changelog")}
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main className="flex-1 p-4 overflow-hidden min-h-0">
|
<main className="flex-1 p-4 overflow-hidden min-h-0">
|
||||||
<div className="h-full flex gap-4 overflow-hidden">
|
<div className="h-full flex gap-4 overflow-hidden">
|
||||||
@@ -120,13 +89,7 @@ export default function ToolsLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="bg-white border-t shrink-0">
|
<Footer></Footer>
|
||||||
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
|
||||||
<p className="text-center text-sm text-gray-500">
|
|
||||||
{t("app.copyright", { year: today.year() })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import { useMemo } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import Seo from "@/components/seo"
|
|
||||||
|
|
||||||
interface ChangeEntry {
|
|
||||||
type: "feat" | "fix" | "refactor" | "chore"
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
date?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChangelogVersion {
|
|
||||||
version: string
|
|
||||||
date: string
|
|
||||||
entries: ChangeEntry[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const CHANGELOG_DATA: ChangelogVersion[] = [
|
|
||||||
{
|
|
||||||
version: "1.1.1",
|
|
||||||
date: "2026-02-24",
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "JSON Path Highlighting",
|
|
||||||
description: "Add highlighting for JSON Path expression."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
version: "1.1.0",
|
|
||||||
date: "2026-02-24",
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "JSON Grid",
|
|
||||||
description: "Convert JSON arrays into sortable table view with CSV export functionality"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "Tools Layout with Collapsible Menu",
|
|
||||||
description: "Added a dedicated tools layout with categorised sidebar menu for quick tool switching",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "Copy Selected JSON Feature",
|
|
||||||
description: "Added ability to copy JSONPath selected data as raw JSON with one-click action",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "Tool Categories",
|
|
||||||
description: "Organised tools into JSON Processing (JSON Viewer, JSON Grid) and Daily Tools (BMI Calculator)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "fix",
|
|
||||||
title: "Live Application Link",
|
|
||||||
description: "Updated live application link in README",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
version: "1.0.0",
|
|
||||||
date: "2026-01-19",
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "JSON Viewer with JSONPath",
|
|
||||||
description: "Full-featured JSON viewer with JSONPath query support and CSV export",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "BMI Calculator",
|
|
||||||
description: "Calculate Body Mass Index with category guidance and health advice",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "feat",
|
|
||||||
title: "Internationalization",
|
|
||||||
description: "Support for English (GB) and Simplified Chinese with language switcher",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
type ChangeTypeKey = "feat" | "fix" | "refactor" | "chore"
|
|
||||||
|
|
||||||
export default function Changelog() {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const changeTypeLabels = useMemo(() => {
|
|
||||||
const labels: Record<ChangeTypeKey, { label: string; colour: string }> = {
|
|
||||||
feat: { label: t("changelog.featureType"), colour: "bg-emerald-100 text-emerald-700" },
|
|
||||||
fix: { label: t("changelog.fixType"), colour: "bg-blue-100 text-blue-700" },
|
|
||||||
refactor: { label: t("changelog.refactorType"), colour: "bg-purple-100 text-purple-700" },
|
|
||||||
chore: { label: t("changelog.choreType"), colour: "bg-slate-100 text-slate-700" },
|
|
||||||
}
|
|
||||||
return labels
|
|
||||||
}, [t])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-50">
|
|
||||||
<Seo
|
|
||||||
title={t("seo.changelog.title")}
|
|
||||||
description={t("seo.changelog.description")}
|
|
||||||
path="/changelog"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Header */}
|
|
||||||
<div className="bg-white shadow-sm border-b">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
|
||||||
<h1 className="text-4xl font-bold text-gray-900">{t("changelog.title")}</h1>
|
|
||||||
<p className="mt-2 text-lg text-gray-600">{t("changelog.description")}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Timeline */}
|
|
||||||
<div className="max-w-4xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
|
||||||
<div className="space-y-8">
|
|
||||||
{CHANGELOG_DATA.map((changelog) => (
|
|
||||||
<div key={changelog.version} className="relative">
|
|
||||||
{/* Version header */}
|
|
||||||
<div className="flex items-baseline gap-4 mb-6">
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900">v{changelog.version}</h2>
|
|
||||||
<time className="text-sm text-gray-500">{changelog.date}</time>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Changes list */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
{changelog.entries.map((entry, idx) => {
|
|
||||||
const typeInfo = changeTypeLabels[entry.type as ChangeTypeKey]
|
|
||||||
return (
|
|
||||||
<div key={`${changelog.version}-${idx}`} className="bg-white rounded-lg border border-slate-200 p-4">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<span className={`inline-block px-2.5 py-0.5 rounded text-xs font-semibold ${typeInfo.colour}`}>
|
|
||||||
{typeInfo.label}
|
|
||||||
</span>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3 className="text-base font-semibold text-gray-900">{entry.title}</h3>
|
|
||||||
{entry.description && (
|
|
||||||
<p className="mt-1 text-sm text-gray-600">{entry.description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ export default function JsonGrid() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const initialData = [
|
const initialData = [
|
||||||
{ id: 0, name: "TTY", role: "CEO", active: true },
|
|
||||||
{ id: 1, name: "Alice", role: "Developer", active: true },
|
{ id: 1, name: "Alice", role: "Developer", active: true },
|
||||||
{ id: 2, name: "Bob", role: "Designer", active: false },
|
{ id: 2, name: "Bob", role: "Designer", active: false },
|
||||||
{ id: 3, name: "Charlie", role: "Product Manager", active: true },
|
{ id: 3, name: "Charlie", role: "Product Manager", active: true },
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export default function JsonViewer() {
|
|||||||
location: "London",
|
location: "London",
|
||||||
is_active: true,
|
is_active: true,
|
||||||
staff_members: [
|
staff_members: [
|
||||||
{ id: 100, name: "TTY", roles: ["CEO"] },
|
|
||||||
{ id: 101, name: "Alice", roles: ["Admin", "Manager"] },
|
{ id: 101, name: "Alice", roles: ["Admin", "Manager"] },
|
||||||
{ id: 102, name: "Bob", roles: ["Developer"] },
|
{ id: 102, name: "Bob", roles: ["Developer"] },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ const router = createBrowserRouter(
|
|||||||
{
|
{
|
||||||
path: "contact",
|
path: "contact",
|
||||||
lazy: lazy(() => import("@/page/contact")),
|
lazy: lazy(() => import("@/page/contact")),
|
||||||
},
|
}
|
||||||
{
|
|
||||||
path: "changelog",
|
|
||||||
lazy: lazy(() => import("@/page/changelog")),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-2
@@ -10,10 +10,11 @@ import {
|
|||||||
PURGE,
|
PURGE,
|
||||||
REGISTER,
|
REGISTER,
|
||||||
} from "redux-persist"
|
} from "redux-persist"
|
||||||
import storage from "redux-persist/lib/storage/session" // use session storage
|
import createWebStorage from "redux-persist/es/storage/createWebStorage"
|
||||||
// import storage from "redux-persist/lib/storage" // use local storage
|
|
||||||
import authReducer from "./auth-slice"
|
import authReducer from "./auth-slice"
|
||||||
|
|
||||||
|
const storage = createWebStorage(import.meta.env.VITE_REDUX_STORAGE ?? "local")
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: "root",
|
key: "root",
|
||||||
storage,
|
storage,
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
// todo add env properties here
|
readonly VITE_SEO_SITE_URL: string
|
||||||
|
readonly VITE_REDUX_STORAGE: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"outputDirectory": "dist",
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "/(.*)",
|
"source": "/(.*)",
|
||||||
|
|||||||
Reference in new issue
Block a user