diff --git a/src/components/CatalogueContent.tsx b/src/components/CatalogueContent.tsx index bf83bf3..017da6a 100644 --- a/src/components/CatalogueContent.tsx +++ b/src/components/CatalogueContent.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import axios from "axios"; import { Button } from "@/components/ui/button"; -import { type IPaper, type Filters, type StoredSubjects, type ApiResponse } from "@/interface"; +import { type IPaper, type Filters, type ApiResponse } from "@/interface"; import Card from "./Card"; import Loader from "./ui/loader"; import SideBar from "../components/SideBar"; @@ -20,6 +20,7 @@ import posthog from "posthog-js"; import EmptyState from "./ui/EmptyState"; import SelectionToolbar from "./SelectionToolbar"; import SortComponent from "./ui/sorting"; +import { readPinnedSubjects } from "@/lib/utils/pinned-subjects"; const CatalogueContentInner = ({ subject }: { subject: string | null }) => { const [isMounted, setIsMounted] = useState(false); @@ -94,9 +95,7 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { useEffect(() => { setIsMounted(true); if (searchParams) { - const currentPinnedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const currentPinnedSubjects = readPinnedSubjects(); const subjectName = searchParams.get("subject"); setSelectedExams( searchParams.get("exams")?.split(",").filter(Boolean) ?? [], @@ -136,9 +135,7 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { const current = !pinned; setPinned(current); - const saved = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as string[]; + const saved = readPinnedSubjects(); const updated = current ? [...new Set([...saved, subject])] : saved.filter((s) => s !== subject); diff --git a/src/components/PinnedPapersCarousel.tsx b/src/components/PinnedPapersCarousel.tsx index be9f72e..1783159 100644 --- a/src/components/PinnedPapersCarousel.tsx +++ b/src/components/PinnedPapersCarousel.tsx @@ -13,10 +13,10 @@ import { } from "@/components/ui/carousel"; import Autoplay from "embla-carousel-autoplay"; import { chunkArray } from "@/lib/utils/array"; -import { type StoredSubjects } from "@/interface"; import SkeletonPaperCard from "./SkeletonPaperCard"; import PinnedModal from "./ui/PinnedModal"; import { type ApiResponse } from "@/interface" +import { readPinnedSubjects } from "@/lib/utils/pinned-subjects"; interface userPaperResponse { subject: string; @@ -63,9 +63,7 @@ function PinnedPapersCarousel() { try { setIsLoading(true); - const storedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const storedSubjects = readPinnedSubjects(); const response = await axios.post>( "/api/user-papers", @@ -116,9 +114,7 @@ function PinnedPapersCarousel() { const handleSubjectsChange = () => { void (async () => { try { - const storedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const storedSubjects = readPinnedSubjects(); const response = await axios.post>("/api/user-papers", storedSubjects); diff --git a/src/components/Searchbar/pinned-searchbar.tsx b/src/components/Searchbar/pinned-searchbar.tsx index f70f3b4..4421fe6 100644 --- a/src/components/Searchbar/pinned-searchbar.tsx +++ b/src/components/Searchbar/pinned-searchbar.tsx @@ -5,7 +5,8 @@ import { Search } from "lucide-react"; import { Input } from "@/components/ui/input"; import PinButton from "../PinButton"; import Fuse from "fuse.js"; -import { type StoredSubjects, type ICourseWithCount, type IUpcomingPaper } from "@/interface"; +import { type ICourseWithCount, type IUpcomingPaper } from "@/interface"; +import { readPinnedSubjects } from "@/lib/utils/pinned-subjects"; function PinnedSearchBar({ initialSubjects, @@ -53,9 +54,7 @@ function PinnedSearchBar({ const handleSelectSuggestion = (suggestion: string) => { setSearchText(suggestion); - const currentPinnedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const currentPinnedSubjects = readPinnedSubjects(); if (suggestion && Array.isArray(currentPinnedSubjects)) { setPinned(currentPinnedSubjects.includes(suggestion)); @@ -94,9 +93,7 @@ function PinnedSearchBar({ return; } - const saved = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as string[]; + const saved = readPinnedSubjects(); let updated: string[] = []; @@ -133,9 +130,7 @@ function PinnedSearchBar({ useEffect(() => { const handlePinsChange = () => { - const saved = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as string[]; + const saved = readPinnedSubjects(); if (saved.length === 0) { setShowControls(false); @@ -152,9 +147,7 @@ function PinnedSearchBar({ }, []); useEffect(() => { - const storedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as string[]; + const storedSubjects = readPinnedSubjects(); if (storedSubjects.length > 0) { setShowControls(true); diff --git a/src/components/UpcomingPaper.tsx b/src/components/UpcomingPaper.tsx index b5cba19..438ffa2 100644 --- a/src/components/UpcomingPaper.tsx +++ b/src/components/UpcomingPaper.tsx @@ -6,9 +6,9 @@ import { import { useRouter } from "next/navigation"; import { Pin } from "lucide-react"; import React, { useEffect, useState } from "react"; -import { type StoredSubjects } from "@/interface"; import { useCourses } from "@/context/courseContext"; import { Capsule } from "@/components/ui/capsule"; +import { readPinnedSubjects } from "@/lib/utils/pinned-subjects"; interface PaperCardProps { subject: string; @@ -26,9 +26,7 @@ export default function PaperCard({ subject, slots }: PaperCardProps) { const current = !pinned; setPinned(current); - const saved = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as string[]; + const saved = readPinnedSubjects(); const updated = current ? [...new Set([...saved, subject])] : saved.filter((s) => s !== subject); @@ -39,9 +37,7 @@ export default function PaperCard({ subject, slots }: PaperCardProps) { }; useEffect(() => { - const currentPinnedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const currentPinnedSubjects = readPinnedSubjects(); setPinned( Array.isArray(currentPinnedSubjects) && diff --git a/src/components/ui/PinnedModal.tsx b/src/components/ui/PinnedModal.tsx index d74110f..beabf6d 100644 --- a/src/components/ui/PinnedModal.tsx +++ b/src/components/ui/PinnedModal.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/dialog"; import SearchBar from "@/components/Searchbar/searchbar"; import { type IUpcomingPaper } from "@/interface"; -import { type StoredSubjects } from "@/interface"; import { useState, useEffect } from "react"; import { Pin, PinOff, Plus, GripVertical } from "lucide-react"; import { @@ -31,6 +30,7 @@ import { import { type ApiResponse } from '@/interface' import { CSS } from "@dnd-kit/utilities"; +import { readPinnedSubjects } from "@/lib/utils/pinned-subjects"; interface userPaperResponse { subject: string; @@ -102,9 +102,7 @@ const PinnedModal = ({ const fetchPapers = async () => { try { setIsLoading(true); - const storedSubjects = JSON.parse( - localStorage.getItem("userSubjects") ?? "[]", - ) as StoredSubjects; + const storedSubjects = readPinnedSubjects(); const response = await axios.post>( "/api/user-papers", storedSubjects, @@ -143,9 +141,7 @@ const PinnedModal = ({ }; const unpinSubject = (subjectToRemove: string) => { - const updatedSubjects = ( - JSON.parse(localStorage.getItem("userSubjects") ?? "[]") as string[] - ).filter((subj) => subj !== subjectToRemove); + const updatedSubjects = readPinnedSubjects().filter((subj) => subj !== subjectToRemove); localStorage.setItem("userSubjects", JSON.stringify(updatedSubjects)); setDisplayPapers((prev) => prev.filter((paper) => paper.subject !== subjectToRemove), diff --git a/src/lib/utils/pinned-subjects.ts b/src/lib/utils/pinned-subjects.ts new file mode 100644 index 0000000..de58d4b --- /dev/null +++ b/src/lib/utils/pinned-subjects.ts @@ -0,0 +1,22 @@ +const pinnedSubjectsKey = "userSubjects"; + +function parseSaved(saved: string): unknown { + try { + return JSON.parse(saved); + } catch { + return null; + } +} + +export function readPinnedSubjects(): string[] { + const saved = localStorage.getItem(pinnedSubjectsKey); + if (!saved) return []; + + const subjects = parseSaved(saved); + if (Array.isArray(subjects) && subjects.every((subject): subject is string => typeof subject === "string")) { + return subjects; + } + + localStorage.removeItem(pinnedSubjectsKey); + return []; +}