diff --git a/_tests_/components/search/result.test.tsx b/_tests_/components/search/result.test.tsx
new file mode 100644
index 0000000..c795025
--- /dev/null
+++ b/_tests_/components/search/result.test.tsx
@@ -0,0 +1,29 @@
+import { render, screen } from "@testing-library/react";
+import Result from "../../../src/components/search/result";
+
+describe("Result Component", () => {
+ const mockProps = {
+ transactionId: "123",
+ definition: "Test definition",
+ category: "Test category",
+ term: "Test term"
+ };
+
+ it("renders the result component with all props", () => {
+ render();
+
+ const container = screen.getByTitle("search-result-content");
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText("Test term")).toBeInTheDocument();
+ expect(screen.getByText("Test definition")).toBeInTheDocument();
+ expect(screen.getByText("Test category")).toBeInTheDocument();
+ });
+
+ it("renders with missing props", () => {
+ render();
+
+ const container = screen.getByTitle("search-result-content");
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText("Test term")).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/_tests_/components/search/unavailable-result.test.tsx b/_tests_/components/search/unavailable-result.test.tsx
new file mode 100644
index 0000000..5913eca
--- /dev/null
+++ b/_tests_/components/search/unavailable-result.test.tsx
@@ -0,0 +1,38 @@
+import { render, screen } from "@testing-library/react";
+import UnavailableResult from "../../../src/components/search/unavailable-result";
+import { useTranslation } from "next-i18next";
+
+// Mock next-i18next
+jest.mock("next-i18next", () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+describe("UnavailableResult Component", () => {
+ it("renders the unavailable result component", () => {
+ const term = "nonexistent-term";
+ render();
+
+ const container = screen.getByTitle("unavailable-search-result");
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText(term)).toBeInTheDocument();
+ expect(screen.getByText("twitter")).toBeInTheDocument();
+ expect(screen.getByText("gitHubIssueText")).toBeInTheDocument();
+ });
+
+ it("generates correct twitter link", () => {
+ const term = "test-term";
+ render();
+
+ const twitterLink = screen.getByText("twitter").closest("a");
+ expect(twitterLink).toHaveAttribute(
+ "href",
+ expect.stringContaining("Glossetadotcom")
+ );
+ expect(twitterLink).toHaveAttribute(
+ "href",
+ expect.stringContaining(term)
+ );
+ });
+});
\ No newline at end of file
diff --git a/_tests_/pages/search/index.test.tsx b/_tests_/pages/search/index.test.tsx
new file mode 100644
index 0000000..4b652fd
--- /dev/null
+++ b/_tests_/pages/search/index.test.tsx
@@ -0,0 +1,50 @@
+import { render, screen } from "@testing-library/react";
+import SearchPage from "../../../src/pages/search";
+
+// Mock matchMedia
+beforeAll(() => {
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+ });
+});
+
+// Mock next/router
+jest.mock('next/router', () => ({
+ useRouter() {
+ return {
+ route: '/',
+ pathname: '',
+ query: '',
+ asPath: '',
+ push: jest.fn(),
+ };
+ },
+}));
+
+describe("Search Page", () => {
+ it("renders the search page", () => {
+ render();
+
+ // Test for main components that should be present
+ expect(screen.getByRole("main")).toBeInTheDocument();
+ expect(screen.getByTitle("glosseta-search-fallback-page")).toBeInTheDocument();
+ });
+
+ it("displays loading skeleton", () => {
+ render();
+
+ // Test for skeleton loading elements using class name
+ const skeletons = document.getElementsByClassName("chakra-skeleton");
+ expect(skeletons.length).toBeGreaterThan(0);
+ });
+});
\ No newline at end of file
diff --git a/_tests_/pages/search/term/[id].test.jsx b/_tests_/pages/search/term/[id].test.jsx
index c2eb16d..db9a18c 100644
--- a/_tests_/pages/search/term/[id].test.jsx
+++ b/_tests_/pages/search/term/[id].test.jsx
@@ -3,7 +3,15 @@ import { render, screen } from "@testing-library/react";
import SearchResults from "../../../../src/pages/search/term/[id]";
jest.mock("react-i18next", () => ({
- useTranslation: () => ({ t: (key) => key }),
+ useTranslation: () => ({
+ t: (key) => ({
+ definition: 'definition',
+ unavailableSearchResultDescription: 'unavailableSearchResultDescription',
+ apiFetchErrorText: 'apiFetchErrorText',
+ requestThisTerm: 'requestThisTerm',
+ // Add other keys as needed
+ }[key] || key),
+ }),
}));
jest.spyOn(require("next/router"), "useRouter").mockImplementation(() => ({
@@ -30,18 +38,24 @@ describe("Search Results - Success", () => {
/>
);
- const searchBarInput = screen.getByTitle("search-bar-input");
- const resultContainer = screen.getByTitle("search-result-content");
- const resultUnavailableContainer = screen.queryByTitle(
- "unavailable-search-result"
+ // Check for the term heading
+ const termHeading = screen.getByRole('heading', { name: term.toUpperCase() });
+ expect(termHeading).toBeInTheDocument();
+
+ // Check for definition section
+ const definitionHeading = screen.getByRole('heading', { name: 'definition' });
+ expect(definitionHeading).toBeInTheDocument();
+
+ // Use getAllByText and check the specific one we want
+ const definitionTexts = screen.getAllByText(definition);
+ const definitionContent = definitionTexts.find(
+ element => element.tagName.toLowerCase() === 'p'
);
- const apiErrorContainer = screen.queryByTitle("api-error-result");
+ expect(definitionContent).toBeInTheDocument();
- expect(searchBarInput).toBeInTheDocument();
- expect(resultUnavailableContainer).not.toBeInTheDocument();
- expect(apiErrorContainer).not.toBeInTheDocument();
-
- expect(resultContainer).toBeInTheDocument();
+ // Check that error and unavailable content is not present
+ expect(screen.queryByText('unavailableSearchResultDescription')).not.toBeInTheDocument();
+ expect(screen.queryByText('apiFetchErrorText')).not.toBeInTheDocument();
});
});
@@ -65,21 +79,15 @@ describe("Search Results - Not found", () => {
/>
);
- const searchBarInput = screen.getByTitle("search-bar-input");
- const resultContainer = screen.queryByTitle("search-result-content");
- const resultUnavailableContainer = screen.getByTitle(
- "unavailable-search-result"
- );
- const apiErrorContainer = screen.queryByTitle("api-error-result");
-
- expect(searchBarInput).toBeInTheDocument();
- expect(resultContainer).not.toBeInTheDocument();
- expect(apiErrorContainer).not.toBeInTheDocument();
+ // Check for unavailable content
+ const termHeading = screen.getByRole('heading', { name: term.toUpperCase() });
+ expect(termHeading).toBeInTheDocument();
+
+ expect(screen.getByText('unavailableSearchResultDescription')).toBeInTheDocument();
+ expect(screen.getByText('requestThisTerm')).toBeInTheDocument();
- expect(resultUnavailableContainer).toBeInTheDocument();
- expect(resultUnavailableContainer).toHaveTextContent(
- "unavailableSearchResultDescription"
- );
+ // Check that success content is not present
+ expect(screen.queryByRole('heading', { name: 'definition' })).not.toBeInTheDocument();
});
});
@@ -103,18 +111,11 @@ describe("Search Results - Error", () => {
/>
);
- const searchBarInput = screen.getByTitle("search-bar-input");
- const resultContainer = screen.queryByTitle("search-result-content");
- const resultUnavailableContainer = screen.queryByTitle(
- "unavailable-search-result"
- );
- const apiErrorContainer = screen.getByTitle("api-error-result");
-
- expect(searchBarInput).toBeInTheDocument();
- expect(resultContainer).not.toBeInTheDocument();
- expect(resultUnavailableContainer).not.toBeInTheDocument();
+ // Check for error content
+ expect(screen.getByText('apiFetchErrorText')).toBeInTheDocument();
- expect(apiErrorContainer).toBeInTheDocument();
- expect(apiErrorContainer).toHaveTextContent("apiFetchErrorText");
+ // Check that success and unavailable content is not present
+ expect(screen.queryByRole('heading', { name: term.toUpperCase() })).not.toBeInTheDocument();
+ expect(screen.queryByText('unavailableSearchResultDescription')).not.toBeInTheDocument();
});
});
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 5514be9..a554756 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -14,12 +14,21 @@
"searchIconAriaLabel" : "Magnifying glass image",
"searchInputAriaLabel" : "Search web3 terms here",
"searchTermNotFoundInFilter" : "Term not found",
+ "copy" : "Copy",
+ "share" : "Share",
+ "moreLearningMaterials" : "More Learning Materials",
+ "definition" : "Definition",
+ "comingSoon" : "Coming soon! We're working on bringing you additional learning resources and related terms to enhance your understanding.",
+ "unavailableSearchResultDescription" : "We couldn't find this term in our glossary yet. Would you like to request it be added?",
+ "back" : "Back",
+ "searchForAnotherTerm" : "Search for Another Term",
+ "requestThisTerm" : "Request This Term",
"searchResultContentSourceHeading" : "Content Source",
"searchResultContentSourceDescription" : "The definition you see above is stored on the Arweave network which is a protocol for storing data permanently in a decentralized manner among network users who have storage to spare. As a result, this definition will live forever on the Arweave network.",
"searchResultContentSourceTransactionLinkText" : "Click here to view the Arweave transaction for this definition",
-
"glossaryPageHeading" : "Glossary",
+ "glossaryPageDescription" : "Explore the comprehensive glossary of web3 terms, including definitions, explanations, and related concepts. Whether you're a beginner or an expert, our glossary provides a detailed overview of the terminology used in blockchain, NFTs, and cryptocurrency.",
"somethingMissingHeading" : "Something missing?",
"twitterTermRequestDescription" : "If there's a specific term you were looking for but didn't see above, please let us know and we'll get it added. Give us a shout on ",
"glossaryTermFetchErrorHeading": "Error",
diff --git a/src/components/glossary/scroll-to-top-button.tsx b/src/components/glossary/scroll-to-top-button.tsx
index 65d0b4a..a073c07 100644
--- a/src/components/glossary/scroll-to-top-button.tsx
+++ b/src/components/glossary/scroll-to-top-button.tsx
@@ -26,16 +26,25 @@ export const ScrollToTopButton = ({
title="glossary-scroll-to-top-button"
onClick={scrollToTheTop}
position="fixed"
- bottom="20px"
- right="30px"
+ bottom="6"
+ right="6"
zIndex="99"
- backgroundColor="#A3A3A3"
- fontSize="18px"
- textColor="black"
- cursor="pointer"
- _hover={{ backgroundColor: "#BABABA" }}
+ colorScheme="blackAlpha"
+ size="lg"
+ rounded="full"
+ shadow="lg"
+ aria-label={t("scrollToTheTopButton")}
+ _hover={{
+ transform: "translateY(-2px)",
+ shadow: "xl"
+ }}
+ _active={{
+ transform: "translateY(0)",
+ shadow: "md"
+ }}
+ transition="all 0.2s"
>
-
+
{t("scrollToTheTopButton")}
diff --git a/src/components/input/search-bar.tsx b/src/components/input/search-bar.tsx
index ba55954..aaf8ad0 100644
--- a/src/components/input/search-bar.tsx
+++ b/src/components/input/search-bar.tsx
@@ -16,16 +16,16 @@ import Trie from "../../filter/trie";
import AutocompleteFilter from "../../filter/autocomplete";
const SearchBar = ({
- baseWidth,
- smWidth,
- mdWidth,
- lgWidth,
+ baseWidth = "280px",
+ smWidth = "320px",
+ mdWidth = "400px",
+ lgWidth = "480px",
filterItems,
}: {
- baseWidth: string;
- smWidth: string;
- mdWidth: string;
- lgWidth: string;
+ baseWidth?: string;
+ smWidth?: string;
+ mdWidth?: string;
+ lgWidth?: string;
filterItems: any[];
}): JSX.Element => {
const [searchTerm, setSearchTerm] = useState("");
@@ -131,7 +131,7 @@ const SearchBar = ({
{
event.currentTarget.scrollIntoView(false);
}}
@@ -173,54 +184,63 @@ const SearchBar = ({
{filteredSuggestions.length > 0 &&
- filteredSuggestions.map((suggestion, index) => {
- return (
- <>
- {
- setSearchTerm(event.currentTarget.innerText);
- setFilteredSuggestions([]);
- setShowSuggestions(false);
- setActiveSuggestion(0);
- location.assign(
- `/search/term/${event.currentTarget.innerText
- .trim()
- .toLowerCase()}`
- );
- }}
- background={
- index === activeSuggestion ? "darkgray" : "white"
- }
- padding={1}
- >
-
- {suggestion}
-
- >
- );
- })}
+ filteredSuggestions.map((suggestion, index) => (
+ {
+ setSearchTerm(event.currentTarget.innerText);
+ setFilteredSuggestions([]);
+ setShowSuggestions(false);
+ setActiveSuggestion(0);
+ location.assign(
+ `/search/term/${event.currentTarget.innerText
+ .trim()
+ .toLowerCase()}`
+ );
+ }}
+ background={
+ index === activeSuggestion ? "blue.50" : "white"
+ }
+ color={index === activeSuggestion ? "blue.700" : "gray.700"}
+ padding={3}
+ rounded="md"
+ cursor="pointer"
+ _hover={{
+ background: "blue.50",
+ color: "blue.700"
+ }}
+ transition="all 0.2s"
+ >
+
+ {suggestion}
+
+ ))}
{filteredSuggestions.length === 0 && (
<>
-
-
+
+
{t("searchTermNotFoundInFilter")}
>
diff --git a/src/components/layout/page/index.tsx b/src/components/layout/page/index.tsx
index c2c11fe..beac365 100644
--- a/src/components/layout/page/index.tsx
+++ b/src/components/layout/page/index.tsx
@@ -8,13 +8,13 @@ const PageLayout = ({ children }: { children?: object }): JSX.Element => {
<>
{children}
diff --git a/src/components/search/result-box.tsx b/src/components/search/result-box.tsx
index 750b4a9..7191644 100644
--- a/src/components/search/result-box.tsx
+++ b/src/components/search/result-box.tsx
@@ -5,23 +5,45 @@ export const ResultBox = ({ definition, category, term }: any): JSX.Element => {
<>
-
+
{term}
-
- {category}
+
+ {category}
-
+
{definition}
diff --git a/src/components/search/result.tsx b/src/components/search/result.tsx
index 3b416e0..d0c0944 100644
--- a/src/components/search/result.tsx
+++ b/src/components/search/result.tsx
@@ -9,14 +9,12 @@ export const Result = ({
term,
}: any): JSX.Element => {
return (
- <>
-
-
-
-
-
-
- >
+
+
+
+
+
+
);
};
diff --git a/src/components/search/unavailable-result.tsx b/src/components/search/unavailable-result.tsx
index 35899a7..b2fafcc 100644
--- a/src/components/search/unavailable-result.tsx
+++ b/src/components/search/unavailable-result.tsx
@@ -9,46 +9,44 @@ export const UnavailableResult = ({ term }: any): JSX.Element => {
const github_issue_href = 'https://github.com/narbs91/glosseta/issues/new?assignees=&labels=definition&template=definition-request.yml&title=%5BDefinition+Request%5D%3A+';
return (
- <>
-
-
-
-
- {term}
-
-
- {t('unavailableSearchResultDescription')}{" "}
-
- {t('twitter')}
-
- {t('opensInANewWindow')}
-
-
- {t('or')}{" "}
-
- {t('gitHubIssueText')}
-
- {t('opensInANewWindow')}
-
-
-
-
-
-
-
- >
+
+
+
+
+ {term}
+
+
+ {t('unavailableSearchResultDescription')}{" "}
+
+ {t('twitter')}
+
+ {t('opensInANewWindow')}
+
+
+ {t('or')}{" "}
+
+ {t('gitHubIssueText')}
+
+ {t('opensInANewWindow')}
+
+
+
+
+
+
+
);
};
diff --git a/src/pages/glossary/index.tsx b/src/pages/glossary/index.tsx
index eabd888..8a3037f 100644
--- a/src/pages/glossary/index.tsx
+++ b/src/pages/glossary/index.tsx
@@ -7,8 +7,12 @@ import {
Heading,
VStack,
Container,
+ Box,
+ Text,
+ HStack,
+ Link,
+ Badge,
} from "@chakra-ui/react";
-import { ResultBox } from "../../components/search/result-box";
import { NewTermRequest } from "../../components/glossary/new-term-request";
import { ScrollToTopButton } from "../../components/glossary/scroll-to-top-button";
import { useTranslation } from "react-i18next";
@@ -20,51 +24,236 @@ const AllTerms = ({ terms }: any): JSX.Element => {
return (
<>
-
-
+ {/* Hero Section */}
+
- {t("glossaryPageHeading")}
-
-
+ {t("glossaryPageHeading")}
+
+
+ {/* Quick intro text */}
+
+ {t("glossaryPageDescription")}
+
+
+
+ {/* Alphabet Navigation */}
+
-
-
- {terms.length === 0 ? (
-
- ) : (
- <>
- {terms.map((termItem: any) => {
- return (
-
- );
- })}
-
+
+
+ {Object.keys(
+ terms.reduce((acc: Record, term: any) => {
+ const firstLetter = term.term[0].toUpperCase();
+ if (!acc[firstLetter]) {
+ acc[firstLetter] = [];
+ }
+ acc[firstLetter].push(term);
+ return acc;
+ }, {})
+ )
+ .sort()
+ .map((letter) => (
+
+ {letter}
+
+ ))}
+
+
+
+ {/* Terms Grid */}
+
+
+
+ {terms.length === 0 ? (
+
+ ) : (
+ <>
+ {/* Category filters */}
+
+
+
+
+ {/* Terms Grid */}
+
+ {/* Group and sort terms alphabetically */}
+ {Object.entries(
+ terms.reduce((acc: Record, term: any) => {
+ const firstLetter = term.term[0].toUpperCase();
+ if (!acc[firstLetter]) {
+ acc[firstLetter] = [];
+ }
+ acc[firstLetter].push(term);
+ return acc;
+ }, {})
+ )
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([letter, letterTerms]) => (
+
+
+ {letter}
+
+
+ {(letterTerms as any[]).sort((a,b) => a.term.localeCompare(b.term)).map((termItem: any) => (
+
+
+
+
+ {termItem.category.toUpperCase()}
+
+
+ {termItem.term.toUpperCase()}
+
+
+ {termItem.definition}
+
+
+
+
+ ))}
+
+
+ ))}
+
+
+ {/* Bottom Actions */}
+
- >
- )}
-
+
+
+ >
+ )}
-
+
>
diff --git a/src/pages/search/term/[id].tsx b/src/pages/search/term/[id].tsx
index 4d319e3..8d71c8c 100644
--- a/src/pages/search/term/[id].tsx
+++ b/src/pages/search/term/[id].tsx
@@ -1,15 +1,37 @@
import { GetStaticPaths, GetStaticProps } from "next";
-import { SimpleGrid, chakra } from "@chakra-ui/react";
-import { Result } from "../../../components/search/result";
-import { UnavailableResult } from "../../../components/search/unavailable-result";
+import {
+ Box,
+ Container,
+ Heading,
+ Text,
+ VStack,
+ HStack,
+ Badge,
+ Divider,
+ Icon,
+ useColorModeValue,
+ Button,
+ Flex,
+ Tooltip,
+ Image,
+ Link
+} from "@chakra-ui/react";
+import { motion } from "framer-motion";
+import { FiBook, FiShare2, FiCopy, FiArrowLeft } from "react-icons/fi";
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import { useRouter } from "next/router";
+import { useTranslation } from "next-i18next";
import PageLayout from "../../../components/layout/page";
import SearchBar from "../../../components/input/search-bar";
import ApiError from "../../../components/search/api-error";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { termFilter } from "../../../filter/termConfig";
import { fetchGlossaryTerm } from "../../../backend/service/glosseta.service";
-import { useRouter } from "next/router";
import FallBack from "../../../components/loading/fallback";
+import styles from "../../../../styles/Home.module.css";
+import { VIEWBLOCK_URL } from "../../../utils/glosseta-constants";
+
+const MotionBox = motion(Box);
const SearchResults = ({
term,
@@ -19,7 +41,12 @@ const SearchResults = ({
transactionId,
isError,
}: any): JSX.Element => {
+ const twitter_href = `https://twitter.com/intent/tweet?screen_name=Glossetadotcom&text=Please%20add%20${term}%20to%20the%20knowledge%20base`;
+
const router = useRouter();
+ const { t } = useTranslation();
+ const bgColor = useColorModeValue("white", "gray.800");
+ const borderColor = useColorModeValue("gray.200", "gray.700");
if (router.isFallback) {
return ;
@@ -27,64 +54,263 @@ const SearchResults = ({
if (isError) {
return (
-
-
-
-
-
-
-
-
+
+
+
);
}
+ const handleCopy = () => {
+ navigator.clipboard.writeText(term);
+ // Add toast notification here
+ };
+
+ const handleShare = () => {
+ navigator
+ .share({
+ title: term,
+ text: definition,
+ url: window.location.href,
+ })
+ .catch(() => {
+ navigator.clipboard.writeText(window.location.href);
+ });
+ };
+
return (
- <>
-
-
-
-
+
+ {isAvailable ? (
+
+ {/* Term Header Section */}
+
+
+
+
+ {category}
+
+
+ {term.toUpperCase()}
+
+
+
+
+ }
+ >
+ {t("copy")}
+
+
+
+ }
+ >
+ {t("share")}
+
+
+
+
+
+
+ {/* Definition Section */}
+
+
+
+
+
+ {t("definition")}
+
+
+
+ {definition}
+
+
+
+
+ {t("searchResultContentSourceTransactionLinkText")}
+
+
+ {t("opensInANewWindow")}
+
+
+
+
+
+
+
+
+
+ {t("moreLearningMaterials")}
+
+
+
+ {t("comingSoon")}
+
+
+
+
+ ) : (
+
- {isAvailable && (
-
- )}
- {!isAvailable && }
-
-
-
- >
+
+
+
+ {term.toUpperCase()}
+
+
+ {t("unavailableSearchResultDescription")}
+
+ }
+ _hover={{
+ transform: "translateY(-2px)",
+ boxShadow: "lg",
+ }}
+ transition="all 0.2s"
+ rounded="full"
+ px={6}
+ >
+ {t("requestThisTerm")}
+
+
+
+
+ )}
+
+ {/* Enhanced Search Section */}
+
+
+
+
+ }
+ variant="ghost"
+ size="sm"
+ onClick={() => router.back()}
+ >
+ {t("back")}
+
+
+
+
+ {t("searchForAnotherTerm")}
+
+
+
+
+
+
+
+
+
+
);
};