From dded3e821b254af8bae25cdbed1cec0eb471aefa Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 00:11:55 +0200 Subject: [PATCH 1/6] added searchbar - no functionality as state management is next --- package.json | 14 ++++++++++---- src/app/_components/SearchBar.tsx | 5 +++++ src/app/layout.tsx | 6 +++++- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 src/app/_components/SearchBar.tsx diff --git a/package.json b/package.json index 35ab83b..861388d 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,17 @@ "react-dom": "^19.0.0-rc-935180c7e0-20240524", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", - "zod": "^3.23.8" + "zod": "^3.23.8", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" + }, + "overrides": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" }, "devDependencies": { "@types/eslint": "^8.56.2", "@types/node": "^20.11.20", - "@types/react": "^18.2.57", - "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "drizzle-kit": "^0.21.0", @@ -49,7 +53,9 @@ "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.4.1", - "typescript": "^5.4.2" + "typescript": "^5.4.2", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" }, "ct3aMetadata": { "initVersion": "7.33.1" diff --git a/src/app/_components/SearchBar.tsx b/src/app/_components/SearchBar.tsx new file mode 100644 index 0000000..815c8b3 --- /dev/null +++ b/src/app/_components/SearchBar.tsx @@ -0,0 +1,5 @@ +import { Input } from "~/components/ui/input"; + +export default function SearchBar() { + return ; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2aa5893..f430e6d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import { Inter as FontSans } from "next/font/google"; import { cn } from "~/lib/utils"; import TopNav from "./_components/FilterMenu"; import Filtermenu from "./_components/FilterMenu"; +import SearchBar from "./_components/SearchBar"; const fontSans = FontSans({ subsets: ["latin"], @@ -29,7 +30,10 @@ export default function RootLayout({ fontSans.variable, )} > - +
+ + +
{children} From 890595f24ce4d2fc9702f49d23fdd768cc819351 Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 00:16:27 +0200 Subject: [PATCH 2/6] added zustand --- package-lock.json | 44 +++++++++++++++++++++++++++++++++++++++++--- package.json | 11 ++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8edc41a..b4d28b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@t3-oss/env-nextjs": "^0.10.1", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "@vercel/postgres": "^0.8.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -27,13 +29,14 @@ "react-dom": "^19.0.0-rc-935180c7e0-20240524", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^4.5.2" }, "devDependencies": { "@types/eslint": "^8.56.2", "@types/node": "^20.11.20", - "@types/react": "^18.2.57", - "@types/react-dom": "^18.2.19", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "drizzle-kit": "^0.21.0", @@ -8027,6 +8030,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/utf-8-validate": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", @@ -8313,6 +8324,33 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 861388d..208c3ab 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@t3-oss/env-nextjs": "^0.10.1", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "@vercel/postgres": "^0.8.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -32,8 +34,7 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8", - "@types/react": "npm:types-react@rc", - "@types/react-dom": "npm:types-react-dom@rc" + "zustand": "^4.5.2" }, "overrides": { "@types/react": "npm:types-react@rc", @@ -42,6 +43,8 @@ "devDependencies": { "@types/eslint": "^8.56.2", "@types/node": "^20.11.20", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "drizzle-kit": "^0.21.0", @@ -53,9 +56,7 @@ "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.4.1", - "typescript": "^5.4.2", - "@types/react": "npm:types-react@rc", - "@types/react-dom": "npm:types-react-dom@rc" + "typescript": "^5.4.2" }, "ct3aMetadata": { "initVersion": "7.33.1" From d4e7ee8cf84b11e0031afba30fc891dd0dea2e4a Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 01:03:26 +0200 Subject: [PATCH 3/6] added store for all filter along with actions to add, remove and reset all. --- package-lock.json | 10 ++++ package.json | 1 + src/app/store.ts | 144 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/app/store.ts diff --git a/package-lock.json b/package-lock.json index b4d28b4..ce1c8d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "clsx": "^2.1.1", "drizzle-orm": "^0.29.4", "geist": "^1.3.0", + "immer": "^10.1.1", "lucide-react": "^0.379.0", "next": "^15.0.0-rc.0", "postgres": "^3.4.3", @@ -5229,6 +5230,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", diff --git a/package.json b/package.json index 208c3ab..7323307 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "clsx": "^2.1.1", "drizzle-orm": "^0.29.4", "geist": "^1.3.0", + "immer": "^10.1.1", "lucide-react": "^0.379.0", "next": "^15.0.0-rc.0", "postgres": "^3.4.3", diff --git a/src/app/store.ts b/src/app/store.ts new file mode 100644 index 0000000..04ea7f2 --- /dev/null +++ b/src/app/store.ts @@ -0,0 +1,144 @@ +import { create } from "zustand"; +import { produce } from "immer"; + +interface FilterState { + filters: Filters; +} + +interface Filters { + regions: string[]; + countries: string[]; + producers: string[]; + price: Price; + type: WineType; +} + +interface Price { + min: number; + max: number; +} + +interface WineType { + sparkling: boolean; + white: boolean; + red: boolean; + sweet: boolean; + other: boolean; +} + +type FilterActions = { + addRegion: (region: string) => void; + removeRegion: (region: string) => void; + addCountry: (country: string) => void; + removeCountry: (country: string) => void; + addProducer: (producer: string) => void; + removeProducer: (producer: string) => void; + setPrice: (min: number, max: number) => void; + setType: (type: keyof WineType, value: boolean) => void; + resetFilters: () => void; +}; + +const initialFilters: Filters = { + regions: [], + countries: [], + producers: [], + price: { + min: 0, + max: 9999, + }, + type: { + sparkling: false, + white: false, + red: false, + sweet: false, + other: false, + }, +}; + +const useFilterStore = create()((set) => ({ + filters: { + regions: [], + countries: [], + producers: [], + price: { + min: 0, + max: 9999, + }, + type: { + sparkling: false, + white: false, + red: false, + sweet: false, + other: false, + }, + }, + addRegion: (region) => + set( + produce((state: FilterState) => { + if (!state.filters.regions.includes(region)) { + state.filters.regions.push(region); + } + }), + ), + removeRegion: (region) => + set( + produce((state: FilterState) => { + state.filters.regions = state.filters.regions.filter( + (r) => r !== region, + ); + }), + ), + addCountry: (country) => + set( + produce((state: FilterState) => { + if (!state.filters.countries.includes(country)) { + state.filters.countries.push(country); + } + }), + ), + removeCountry: (country) => + set( + produce((state: FilterState) => { + state.filters.countries = state.filters.countries.filter( + (c) => c !== country, + ); + }), + ), + addProducer: (producer) => + set( + produce((state: FilterState) => { + if (!state.filters.producers.includes(producer)) { + state.filters.producers.push(producer); + } + }), + ), + removeProducer: (producer) => + set( + produce((state: FilterState) => { + state.filters.producers = state.filters.producers.filter( + (p) => p !== producer, + ); + }), + ), + setPrice: (min, max) => + set( + produce((state: FilterState) => { + state.filters.price.min = min; + state.filters.price.max = max; + }), + ), + setType: (type, value) => + set( + produce((state: FilterState) => { + state.filters.type[type] = value; + }), + ), + resetFilters: () => + set( + produce((state: FilterState) => { + state.filters = initialFilters; + }), + ), +})); + +export default useFilterStore; From 3faa9fe73be7edd5917399664715e893ca636b46 Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 01:30:26 +0200 Subject: [PATCH 4/6] added state persistance --- src/app/App.tsx | 17 +++++ src/app/store.ts | 177 +++++++++++++++++++++++++---------------------- 2 files changed, 110 insertions(+), 84 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 25215ab..540b3fb 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,9 +1,26 @@ +"use client"; +import { Button } from "~/components/ui/button"; import FormCard from "./_components/FormCard"; +import useFilterStore from "./store"; + +const AddRegionButton: React.FC = () => { + const addRegion = useFilterStore((state) => state.addRegion); + + const handleClick = () => { + addRegion("sverige"); + }; + + return ; +}; export default function App() { + const filters = useFilterStore((state) => state.filters); + return (
+ {JSON.stringify(filters)} +
); } diff --git a/src/app/store.ts b/src/app/store.ts index 04ea7f2..1197306 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -1,5 +1,6 @@ import { create } from "zustand"; import { produce } from "immer"; +import { persist, createJSONStorage } from "zustand/middleware"; interface FilterState { filters: Filters; @@ -55,90 +56,98 @@ const initialFilters: Filters = { }, }; -const useFilterStore = create()((set) => ({ - filters: { - regions: [], - countries: [], - producers: [], - price: { - min: 0, - max: 9999, +const useFilterStore = create()( + persist( + (set) => ({ + filters: { + regions: [], + countries: [], + producers: [], + price: { + min: 0, + max: 9999, + }, + type: { + sparkling: false, + white: false, + red: false, + sweet: false, + other: false, + }, + }, + addRegion: (region) => + set( + produce((state: FilterState) => { + if (!state.filters.regions.includes(region)) { + state.filters.regions.push(region); + } + }), + ), + removeRegion: (region) => + set( + produce((state: FilterState) => { + state.filters.regions = state.filters.regions.filter( + (r) => r !== region, + ); + }), + ), + addCountry: (country) => + set( + produce((state: FilterState) => { + if (!state.filters.countries.includes(country)) { + state.filters.countries.push(country); + } + }), + ), + removeCountry: (country) => + set( + produce((state: FilterState) => { + state.filters.countries = state.filters.countries.filter( + (c) => c !== country, + ); + }), + ), + addProducer: (producer) => + set( + produce((state: FilterState) => { + if (!state.filters.producers.includes(producer)) { + state.filters.producers.push(producer); + } + }), + ), + removeProducer: (producer) => + set( + produce((state: FilterState) => { + state.filters.producers = state.filters.producers.filter( + (p) => p !== producer, + ); + }), + ), + setPrice: (min, max) => + set( + produce((state: FilterState) => { + state.filters.price.min = min; + state.filters.price.max = max; + }), + ), + setType: (type, value) => + set( + produce((state: FilterState) => { + state.filters.type[type] = value; + }), + ), + resetFilters: () => + set( + produce((state: FilterState) => { + state.filters = initialFilters; + }), + ), + }), + { + name: "filter-storage", // name of the item in the storage (must be unique) + storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used }, - type: { - sparkling: false, - white: false, - red: false, - sweet: false, - other: false, - }, - }, - addRegion: (region) => - set( - produce((state: FilterState) => { - if (!state.filters.regions.includes(region)) { - state.filters.regions.push(region); - } - }), - ), - removeRegion: (region) => - set( - produce((state: FilterState) => { - state.filters.regions = state.filters.regions.filter( - (r) => r !== region, - ); - }), - ), - addCountry: (country) => - set( - produce((state: FilterState) => { - if (!state.filters.countries.includes(country)) { - state.filters.countries.push(country); - } - }), - ), - removeCountry: (country) => - set( - produce((state: FilterState) => { - state.filters.countries = state.filters.countries.filter( - (c) => c !== country, - ); - }), - ), - addProducer: (producer) => - set( - produce((state: FilterState) => { - if (!state.filters.producers.includes(producer)) { - state.filters.producers.push(producer); - } - }), - ), - removeProducer: (producer) => - set( - produce((state: FilterState) => { - state.filters.producers = state.filters.producers.filter( - (p) => p !== producer, - ); - }), - ), - setPrice: (min, max) => - set( - produce((state: FilterState) => { - state.filters.price.min = min; - state.filters.price.max = max; - }), - ), - setType: (type, value) => - set( - produce((state: FilterState) => { - state.filters.type[type] = value; - }), - ), - resetFilters: () => - set( - produce((state: FilterState) => { - state.filters = initialFilters; - }), - ), -})); + ), +); export default useFilterStore; From be98abb776abe532f1e4a82a6a43f0b1227f81f4 Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 01:41:28 +0200 Subject: [PATCH 5/6] price slider now updates store filter --- src/app/App.tsx | 12 ++++++++++++ src/app/_components/FilterMenu.tsx | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/app/App.tsx b/src/app/App.tsx index 540b3fb..dcd44ee 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -13,14 +13,26 @@ const AddRegionButton: React.FC = () => { return ; }; +const ResetFilters: React.FC = () => { + const resetFilters = useFilterStore((state) => state.resetFilters); + + const handleClick = () => { + resetFilters(); + }; + + return ; +}; + export default function App() { const filters = useFilterStore((state) => state.filters); return (
+

Filter state:

{JSON.stringify(filters)} +
); } diff --git a/src/app/_components/FilterMenu.tsx b/src/app/_components/FilterMenu.tsx index dc4afec..19bd7ce 100644 --- a/src/app/_components/FilterMenu.tsx +++ b/src/app/_components/FilterMenu.tsx @@ -10,16 +10,19 @@ import { import { Input } from "~/components/ui/input"; import { Slider } from "~/components/ui/slider"; import { ChangeEvent, useState } from "react"; +import useFilterStore from "../store"; export default function Filtermenu() { const [minPrice, setMinPrice] = useState(0); const [maxPrice, setMaxPrice] = useState(9999); const [sliderValues, setSliderValues] = useState<[number, number]>([0, 9999]); + const setStorePrice = useFilterStore((state) => state.setPrice); const handleMinPriceChange = (e: ChangeEvent): void => { const value = Math.min(Number(e.target.value), maxPrice - 1); setMinPrice(value); setSliderValues([value, sliderValues[1]]); + setStorePrice(minPrice, maxPrice); }; const handleMaxPriceChange = (e: ChangeEvent) => { @@ -32,6 +35,7 @@ export default function Filtermenu() { setSliderValues(values); setMinPrice(values[0]); setMaxPrice(values[1]); + setStorePrice(minPrice, maxPrice); }; return ( From 0d7b8a3db701e500cb9eab6176e2110439db8592 Mon Sep 17 00:00:00 2001 From: ChrQR Date: Sun, 26 May 2024 01:48:56 +0200 Subject: [PATCH 6/6] added filter update to maxPriceChange --- src/app/_components/FilterMenu.tsx | 1 + src/app/_components/WineName.tsx | 3 --- src/app/_components/WineProducer.tsx | 25 ------------------------- src/app/store.ts | 3 +++ src/types/types.ts | 8 -------- 5 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 src/app/_components/WineName.tsx delete mode 100644 src/app/_components/WineProducer.tsx delete mode 100644 src/types/types.ts diff --git a/src/app/_components/FilterMenu.tsx b/src/app/_components/FilterMenu.tsx index 19bd7ce..dc8e65a 100644 --- a/src/app/_components/FilterMenu.tsx +++ b/src/app/_components/FilterMenu.tsx @@ -29,6 +29,7 @@ export default function Filtermenu() { const value = Math.max(Number(e.target.value), minPrice + 1); setMaxPrice(value); setSliderValues([sliderValues[0], value]); + setStorePrice(minPrice, maxPrice); }; const handleSliderChange = (values: [number, number]) => { diff --git a/src/app/_components/WineName.tsx b/src/app/_components/WineName.tsx deleted file mode 100644 index 451294b..0000000 --- a/src/app/_components/WineName.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function WineName(props: { name: string }) { - return

{props.name}

; -} diff --git a/src/app/_components/WineProducer.tsx b/src/app/_components/WineProducer.tsx deleted file mode 100644 index 415d9c2..0000000 --- a/src/app/_components/WineProducer.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import getProducer from "~/server/actions/getProducer"; - -export default async function WineProducer(props: { id: string }) { - const { id } = props; - - // Validate the id before making the database call - if (!id) { - return

Invalid producer ID

; - } - - try { - const result = await getProducer(id); - return !result ?

Unable to retrieve producer

:

{result?.name}

; - } catch (error) { - console.error("Error fetching producer:", error); - return

Error fetching producer

; - } -} - -// Utility function to validate UUID -function isValidUUID(uuid: string) { - const uuidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - return uuidRegex.test(uuid); -} diff --git a/src/app/store.ts b/src/app/store.ts index 1197306..9fc1053 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -7,6 +7,7 @@ interface FilterState { } interface Filters { + searchQuery: string; regions: string[]; countries: string[]; producers: string[]; @@ -40,6 +41,7 @@ type FilterActions = { }; const initialFilters: Filters = { + searchQuery: "", regions: [], countries: [], producers: [], @@ -60,6 +62,7 @@ const useFilterStore = create()( persist( (set) => ({ filters: { + searchQuery: "", regions: [], countries: [], producers: [], diff --git a/src/types/types.ts b/src/types/types.ts deleted file mode 100644 index 1eddc71..0000000 --- a/src/types/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Producer { - id: string; - name: string; - createdAt: Date; - country: string; - region: string; - email: string; - } \ No newline at end of file