diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a00da81 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +'on': + pull_request: + branches: + - dev + - master + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + check-dist: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + with: + run_install: true + version: 9 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run ESLint + run: pnpm lint + + - name: Build app + run: pnpm build diff --git a/.gitignore b/.gitignore index 5b2777a..2f20ea3 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts +# webstorm +/.idea/ + /app/generated/prisma-client diff --git a/app/demo/page.tsx b/app/demo/page.tsx index bb2f3b3..6ba015d 100644 --- a/app/demo/page.tsx +++ b/app/demo/page.tsx @@ -55,7 +55,7 @@ export default function Page() { ]; // State for sorting - const [sorting, setSorting] = useState({ criteria: "name", order: "DESC" }); + const [sorting] = useState({ criteria: "name", order: "DESC" }); // const handleSave = (e: ProductCardOnSaveEvent) => { // console.log( @@ -355,7 +355,7 @@ export default function Page() {
- {/* */} +
diff --git a/app/page.tsx b/app/page.tsx index b532b1b..1c88647 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -133,7 +133,7 @@ export default function Home() { productLink="/shop/product/111" color="#32665A" promotionDeadline={ - new Date("2026-03-01T23:59:59Z") + new Date("2026-06-01T23:59:59Z") } /> @@ -218,11 +218,7 @@ function BannerCard(props: BannerCardProps) {

- "Until the end of the promotion:{" "} - {cd.isMounted - ? `${cd.d} days ${formatTime(cd.h)}:${formatTime(cd.m)}:${formatTime(cd.s)}` - : "0 days 00:00:00"} - " + "Until the end of the promotion: {cd.d} days {formatTime(cd.h)}:{formatTime(cd.m)}:{formatTime(cd.s)}"

diff --git a/app/shop/product/[id]/page.tsx b/app/shop/product/[id]/page.tsx index ed5dea2..d3d9ded 100644 --- a/app/shop/product/[id]/page.tsx +++ b/app/shop/product/[id]/page.tsx @@ -13,6 +13,7 @@ type ProductPageProps = { // Mimic a DB-request async function getProduct(id: string): Promise { await new Promise((resolve) => setTimeout(resolve, 0)); + console.log("Got product with id ", id); return PRODUCTS[0]; } diff --git a/components/cartDrawerItem/cartDrawerItem.tsx b/components/cartDrawerItem/cartDrawerItem.tsx index 910e5d8..adc77a1 100644 --- a/components/cartDrawerItem/cartDrawerItem.tsx +++ b/components/cartDrawerItem/cartDrawerItem.tsx @@ -3,7 +3,6 @@ import { AvailabilityType } from "@/lib/types"; import { getAvailability, - getAvailabilityClass, getEuro, } from "@/lib/utils"; import { Trash2 } from "lucide-react"; @@ -36,7 +35,7 @@ export default function CartDrawerItem({ onDelete, }: CartDrawerItemProps) { const [isDeleted, setIsDeleted] = useState(false); - const [availability, setAvailability] = useState( + const [availability] = useState( getAvailability(quantityInStock) ); const availabilityClass: string = diff --git a/components/cartItem.tsx b/components/cartItem.tsx index b4e6f39..42d36e9 100644 --- a/components/cartItem.tsx +++ b/components/cartItem.tsx @@ -36,7 +36,7 @@ export default function CartItem({ onDelete, }: CartItemProps) { const [isDeleted, setIsDeleted] = useState(false); - const [availability, setAvailability] = useState( + const [availability] = useState( getAvailability(quantityInStock) ); const availabilityClass: string = getAvailabilityClass(availability); diff --git a/components/commentCard/commentCard.tsx b/components/commentCard/commentCard.tsx index 134dba1..54fdd25 100644 --- a/components/commentCard/commentCard.tsx +++ b/components/commentCard/commentCard.tsx @@ -1,5 +1,4 @@ -import Image from "next/image"; -import Rating from "../ui/rating"; +import Rating from "../ui/rating"; import Avatar from "../ui/avatar"; export interface CommentCardProps { diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx index d614afd..1635baf 100644 --- a/components/layout/footer.tsx +++ b/components/layout/footer.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { Facebook, Instagram, Twitter, Send } from "lucide-react"; +import { Send } from "lucide-react"; import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { TelegramIcon } from "../icons/telegram"; diff --git a/components/layout/header.tsx b/components/layout/header.tsx index f93cb3b..820657d 100644 --- a/components/layout/header.tsx +++ b/components/layout/header.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState, useCallback } from "react"; import Link from "next/link"; import { Bookmark, Mail, Search, ShoppingCartIcon, X } from "lucide-react"; @@ -34,12 +34,12 @@ export default function Header() { const closeSearch = () => setIsSearchOpen(false); const handleSearchFocus = () => setIsSearchOpen(true); const clearSearch = () => setSearchQuery(""); - const handleBlur = (e: PointerEvent) => { + const handleBlur = useCallback((e: PointerEvent) => { const target = e.target as HTMLElement; if (!target.closest(".search-bar")) { closeSearch(); } - }; + }, []); const applySearchQuery = () => { router.push(`/shop?q=${encodeURIComponent(searchQuery.trim())}`); closeSearch(); @@ -59,7 +59,7 @@ export default function Header() { useEffect(() => { document.body.addEventListener("click", handleBlur); return () => document.body.removeEventListener("click", handleBlur); - }, []); + }, [handleBlur]); return ( <> diff --git a/components/pages/shop/activeFilters.tsx b/components/pages/shop/activeFilters.tsx index 36c83e0..0e50c92 100644 --- a/components/pages/shop/activeFilters.tsx +++ b/components/pages/shop/activeFilters.tsx @@ -23,7 +23,7 @@ export function ActiveFilters({ filters }: ActiveFiltersProps) { return (
- {tags.map((tag, idx) => ( + {tags.map((tag) => ( - {groupsToShow.map((b, i) => ( + {groupsToShow.map((b) => ( ))}
@@ -33,7 +33,7 @@ function BrandsGroup({ char, brands }: BrandGroupType) {
    - {brands.map((b, i) => ( + {brands.map((b) => (
  • {b.name}
  • diff --git a/components/pages/shop/product/productGallery.tsx b/components/pages/shop/product/productGallery.tsx index 7af53bb..31e1c02 100644 --- a/components/pages/shop/product/productGallery.tsx +++ b/components/pages/shop/product/productGallery.tsx @@ -1,7 +1,7 @@ "use client"; import Image from "next/image"; -import { KeyboardEventHandler, ReactNode, useState } from "react"; +import { ReactNode, useState } from "react"; import { ChevronDown, ChevronUp } from "lucide-react"; import { Swiper, SwiperSlide } from "swiper/react"; diff --git a/components/searchResultItem.tsx b/components/searchResultItem.tsx index 48cea3c..12bbf98 100644 --- a/components/searchResultItem.tsx +++ b/components/searchResultItem.tsx @@ -29,7 +29,7 @@ export default function SearchResultItem({ imageUrl, }: SearchResultItemProps) { const productLink = `/shop/product/${productId}`; - const [availability, setAvailability] = useState(getAvailability(quantityInStock)); + const [availability] = useState(getAvailability(quantityInStock)); const availabilityClass: string = getAvailabilityClass(availability); return ( diff --git a/components/ui/marker.tsx b/components/ui/marker.tsx index 3ecb480..6c4cb17 100644 --- a/components/ui/marker.tsx +++ b/components/ui/marker.tsx @@ -1,4 +1,5 @@ -import { FireIcon } from "../icons/fireIcon"; +import { cn } from "@/lib/utils"; +import { FireIcon } from "../icons/fireIcon"; export interface MarkerProps { name: 'hit' | undefined; @@ -7,7 +8,9 @@ export interface MarkerProps { export default function Marker({name, size}: MarkerProps) { return ( -
    +
    {name == 'hit' && ( )} diff --git a/hooks/use-countdown.ts b/hooks/use-countdown.ts index 5252014..cd0a242 100644 --- a/hooks/use-countdown.ts +++ b/hooks/use-countdown.ts @@ -2,23 +2,15 @@ import { useEffect, useState } from "react"; export function useCountdown(deadline: Date) { - const [isMounted, setIsMounted] = useState(false); - const [timeLeft, setTimeLeft] = useState({ d: 0, h: 0, m: 0, s: 0 }); + const [, forceUpdate] = useState(0); - useEffect(() => { - setIsMounted(true); + useEffect(() => { + const interval = setInterval(() => { + forceUpdate(t => t + 1); + }, 1000); - setTimeLeft(getTimeRemaining(deadline)); + return () => clearInterval(interval); + }, []); - const interval = setInterval(() => { - setTimeLeft(getTimeRemaining(deadline)); - }, 1000); - - return () => clearInterval(interval); - }, [deadline]) - - return { - ...timeLeft, - isMounted, - }; + return getTimeRemaining(deadline); } \ No newline at end of file diff --git a/lib/data.ts b/lib/data.ts index ff99709..1276318 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -303,6 +303,6 @@ const BRANDS: BrandType[] = [ "2million", "3million", "1hundred" - ].map((b, i) => ({id: b, name: b})); + ].map((b) => ({id: b, name: b})); export { PRODUCTS, MOCK_SHOP_FILTERS, HOME_COMMENTS, BRANDS };