mirror of
https://github.com/Dokploy/templates
synced 2025-06-26 18:16:07 +00:00
Implement routing and search functionality enhancements
- Wrapped the main application in a BrowserRouter to enable routing. - Integrated react-router-dom for managing search parameters in the Search component. - Updated search functionality to initialize from URL parameters and reflect changes in the URL when the search query is modified. - Added logic to filter templates based on search query and selected tags, improving user experience.
This commit is contained in:
parent
a22c638f82
commit
fa3a75a9ba
@ -10,14 +10,13 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"yaml":"2.7.1",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-yaml": "^6.1.1",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/view": "6.29.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
@ -35,11 +34,13 @@
|
||||
"next-themes": "^0.4.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.4.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.12",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vite-plugin-static-copy": "2.3.0",
|
||||
"yaml": "2.7.1",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -80,6 +80,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-router-dom:
|
||||
specifier: ^7.4.1
|
||||
version: 7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
sonner:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -1121,6 +1124,9 @@ packages:
|
||||
'@types/babel__traverse@7.20.6':
|
||||
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
|
||||
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
@ -1220,6 +1226,10 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
|
||||
|
||||
@ -1498,6 +1508,23 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-router-dom@7.4.1:
|
||||
resolution: {integrity: sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
react-router@7.4.1:
|
||||
resolution: {integrity: sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -1538,6 +1565,9 @@ packages:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||
|
||||
sonner@2.0.1:
|
||||
resolution: {integrity: sha512-FRBphaehZ5tLdLcQ8g2WOIRE+Y7BCfWi5Zyd8bCvBjiW8TxxAyoWZIxS661Yz6TGPqFQ4VLzOF89WEYhfynSFQ==}
|
||||
peerDependencies:
|
||||
@ -1576,6 +1606,9 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
turbo-stream@2.4.0:
|
||||
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||
|
||||
typescript@5.7.3:
|
||||
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@ -2626,6 +2659,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.26.9
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/node@20.17.24':
|
||||
@ -2756,6 +2791,8 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@1.0.2: {}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
dependencies:
|
||||
toggle-selection: 1.0.6
|
||||
@ -2998,6 +3035,22 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
react-router-dom@7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
react-router: 7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
|
||||
react-router@7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@types/cookie': 0.6.0
|
||||
cookie: 1.0.2
|
||||
react: 19.0.0
|
||||
set-cookie-parser: 2.7.1
|
||||
turbo-stream: 2.4.0
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
@ -3049,6 +3102,8 @@ snapshots:
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
|
||||
sonner@2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
@ -3076,6 +3131,8 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
turbo-stream@2.4.0: {}
|
||||
|
||||
typescript@5.7.3: {}
|
||||
|
||||
undici-types@6.19.8: {}
|
||||
|
@ -3,16 +3,19 @@ import Navigation from "./components/Navigation";
|
||||
import Search from "./components/Search";
|
||||
import { useStore } from "@/store";
|
||||
import "./App.css";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
const view = useStore((state) => state.view);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Navigation />
|
||||
<Search />
|
||||
<TemplateGrid view={view} />
|
||||
</div>
|
||||
<BrowserRouter>
|
||||
<div className="min-h-screen">
|
||||
<Navigation />
|
||||
<Search />
|
||||
<TemplateGrid view={view} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,10 @@ import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import React from "react";
|
||||
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import SelectedTags from "./SelectedTags";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
const Search = () => {
|
||||
const { templates, searchQuery, setSearchQuery, setView, templatesCount } =
|
||||
const { templates, searchQuery, setSearchQuery, setView, templatesCount, setFilteredTemplates, setTemplatesCount } =
|
||||
useStore();
|
||||
const selectedTags = useStore((state) => state.selectedTags);
|
||||
const addSelectedTag = useStore((state) => state.addSelectedTag);
|
||||
@ -26,6 +27,7 @@ const Search = () => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [tagSearch, setTagSearch] = React.useState("");
|
||||
const view = useStore((state) => state.view);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Get all unique tags, safely handle empty templates
|
||||
const uniqueTags = React.useMemo(() => {
|
||||
@ -43,6 +45,53 @@ const Search = () => {
|
||||
);
|
||||
}, [uniqueTags, tagSearch]);
|
||||
|
||||
// Initialize search query from URL params and apply filters
|
||||
React.useEffect(() => {
|
||||
const queryFromUrl = searchParams.get("q") || "";
|
||||
if (queryFromUrl !== searchQuery) {
|
||||
setSearchQuery(queryFromUrl);
|
||||
}
|
||||
|
||||
// Apply filters whenever templates, search query or selected tags change
|
||||
if (templates) {
|
||||
const filtered = templates.filter((template) => {
|
||||
// Filter by search query
|
||||
const matchesSearch = template.name
|
||||
.toLowerCase()
|
||||
.includes(queryFromUrl.toLowerCase());
|
||||
|
||||
// Filter by selected tags
|
||||
const matchesTags =
|
||||
selectedTags.length === 0 ||
|
||||
selectedTags.every((tag) => template.tags.includes(tag));
|
||||
|
||||
return matchesSearch && matchesTags;
|
||||
});
|
||||
|
||||
setFilteredTemplates(filtered);
|
||||
setTemplatesCount(filtered.length);
|
||||
}
|
||||
}, [searchParams, templates, selectedTags, setSearchQuery, setFilteredTemplates, setTemplatesCount]);
|
||||
|
||||
// Update URL params when search query changes
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newQuery = e.target.value;
|
||||
setSearchQuery(newQuery);
|
||||
if (newQuery) {
|
||||
setSearchParams({ q: newQuery });
|
||||
} else {
|
||||
searchParams.delete("q");
|
||||
setSearchParams(searchParams);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear search and URL params
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery("");
|
||||
searchParams.delete("q");
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=" mx-auto p-4 lg:p-12 border-b w-full">
|
||||
{/* <h1 className="text-2xl md:text-3xl xl:text-4xl font-bold text-center mb-8">
|
||||
@ -63,11 +112,11 @@ const Search = () => {
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={handleSearchChange}
|
||||
className="w-full p-6"
|
||||
/>
|
||||
{searchQuery.length > 0 ? (
|
||||
<div className="cursor-pointer" onClick={() => setSearchQuery("")}>
|
||||
<div className="cursor-pointer" onClick={handleClearSearch}>
|
||||
<XIcon className="absolute end-3 translate-y-3.5 top-1/2 h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user