Compare commits

..

No commits in common. "b2ffd46a79c431f166cc549c85ccf7555a4aa7bf" and "cb54c9829d39874ba4aabefebd9ef15087f7ffc1" have entirely different histories.

2 changed files with 84 additions and 222 deletions

View File

@ -2,18 +2,60 @@
import Callout from "@/components/resource/Callout"; import Callout from "@/components/resource/Callout";
import Card from "@/components/resource/Card"; import Card from "@/components/resource/Card";
import { LandingSearchBar } from "@/components/resource/LandingSearchBar"; import { LandingSearchBar } from "@/components/resource/LandingSearchBar";
import {
BookOpenIcon,
BookmarkIcon,
ClipboardIcon,
} from "@heroicons/react/24/solid";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export default function Page() { export default function Page() {
return ( return (
<div className="min-h-screen flex flex-col items-center"> <div className="min-h-screen flex flex-col">
<div className="flex justify-center p-14"> {/* icon + title */}
<h1 className="font-bold text-4xl text-purple-800"> <div className="pt-16 px-8 pb-4 flex-row">
Good evening! <div className="mb-4 flex items-center space-x-4">
</h1> <Image
src="/logo.png"
alt="Compass Center logo."
width={25}
height={25}
/>
<h1 className="font-bold text-2xl text-purple-800">
Compass Center Advocate Landing Page
</h1>
</div>
<Callout>
Welcome! Below you will find a list of resources for the
Compass Center&apos;s trained advocates. These materials
serve to virtually provide a collection of advocacy,
resource, and hotline manuals and information.
<b>
{" "}
If you are an advocate looking for the contact
information of a particular Compass Center employee,
please directly contact your staff back-up or the person
in charge of your training.
</b>
</Callout>
</div>
<div className="p-8 flex-grow border-t border-gray-200 bg-gray-50">
{/* link to different pages */}
<div className="grid grid-cols-3 gap-6 pb-6">
<Link href="/resource">
<Card icon={<BookmarkIcon />} text="Resources" />
</Link>
<Link href="/service">
<Card icon={<ClipboardIcon />} text="Services" />
</Link>
<Link href="/training-manual">
<Card icon={<BookOpenIcon />} text="Training Manuals" />
</Link>
</div>
{/* search bar */}
<LandingSearchBar />
</div> </div>
<LandingSearchBar />
</div> </div>
); );
} }

View File

@ -1,38 +1,17 @@
import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline";
import { import {
ArchiveBoxIcon,
ChevronDownIcon, ChevronDownIcon,
FunnelIcon,
MagnifyingGlassIcon, MagnifyingGlassIcon,
TagIcon,
XMarkIcon, XMarkIcon,
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import React, { import React, { useState } from "react";
ReactNode,
SetStateAction,
useState,
useRef,
useEffect,
} from "react";
import Image from "next/image"; import Image from "next/image";
import { FilterBox } from "../FilterBox"; import { FilterBox } from "../FilterBox";
export const LandingSearchBar: React.FC = () => { export const LandingSearchBar: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [showFilters, setShowFilters] = useState(false);
const [showFilterBox, setShowFilterBox] = useState(false); const [showFilterBox, setShowFilterBox] = useState(false);
const toggleFilterBox = () => setShowFilterBox((prev) => !prev); const toggleFilterBox = () => setShowFilterBox((prev) => !prev);
const collections = ["Resources", "Services"];
const [selectedCollections, setSelectedCollections] = useState(
new Array(collections.length).fill(false)
);
const tags = ["Food Relief", "Period Poverty", "Nutrition Education"];
const [selectedTags, setSelectedTags] = useState(
new Array(tags.length).fill(false)
);
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value); setSearchTerm(event.target.value);
}; };
@ -42,74 +21,41 @@ export const LandingSearchBar: React.FC = () => {
}; };
return ( return (
<div className="w-9/12"> <div className="max-w mx-auto">
{/* searchbar */} {/* searchbar */}
<div className="flex items-center bg-white border border-gray-200 shadow rounded-md px-4 py-2"> <div className="flex items-center bg-white border border-gray-200 shadow rounded-md">
{/* Left side: magnifying glass icon and input */} <div className="flex-grow">
<MagnifyingGlassIcon <input
className="h-5 w-5 text-gray-500" className="sm:text-sm text-gray-800 w-full px-6 py-3 rounded-md focus:outline-none"
aria-hidden="true" type="text"
/> placeholder="Search..."
value={searchTerm}
<input onChange={handleSearchChange}
className="sm:text-sm flex-grow text-gray-800 w-full px-3 rounded-md focus:outline-none" />
type="text" </div>
placeholder="Search…" {/* input */}
value={searchTerm} {searchTerm && (
onChange={handleSearchChange} <button onClick={clearSearch}>
/> <XMarkIcon
className="h-5 w-5 text-gray-500"
{/* Right side icons */} aria-hidden="true"
<div className="flex gap-1"> />
{/* If search bar is not empty, include clear icon */}
{searchTerm && (
<button
onClick={clearSearch}
className="hover:bg-purple-100 rounded-md p-1"
>
<XMarkIcon
className="h-5 w-5 text-gray-500"
aria-hidden="true"
/>
</button>
)}
{/* Filter button */}
<button
className={
"hover:bg-purple-100 rounded-md p-1 " +
(showFilters ? "bg-purple-100" : "")
}
onClick={() => setShowFilters(!showFilters)}
>
{!showFilters && (
<FunnelIconOutline className="h-5 w-5 text-gray-500" />
)}
{showFilters && (
<FunnelIcon className="h-5 w-5 text-purple-800" />
)}
</button> </button>
)}
<div className="flex flex-row space-x-1 p-3">
<span>
<ChevronDownIcon
className="h-5 w-5 text-gray-500"
onClick={toggleFilterBox}
/>
</span>
{showFilterBox && <FilterBox className="relative top-50" />}
<MagnifyingGlassIcon
className="h-5 w-5 text-gray-500"
aria-hidden="true"
/>
</div> </div>
</div> </div>
{/* Search filters */}
<div className={"mt-2 flex " + (showFilters ? "" : "hidden")}>
<FilterPill
icon={ArchiveBoxIcon}
name="In"
options={collections}
selectedOptions={selectedCollections}
setSelectedOptions={setSelectedCollections}
/>
<FilterPill
icon={TagIcon}
name="Tags"
options={tags}
selectedOptions={selectedTags}
setSelectedOptions={setSelectedTags}
searchBar
/>
</div>
{/* search results, for now since it's empty this is the default screen */} {/* search results, for now since it's empty this is the default screen */}
<div className="flex flex-col pt-16 space-y-2 justify-center items-center"> <div className="flex flex-col pt-16 space-y-2 justify-center items-center">
<Image <Image
@ -118,136 +64,10 @@ export const LandingSearchBar: React.FC = () => {
width={250} width={250}
height={250} height={250}
/> />
<p className="font-medium text-medium text-gray-800"> <h2 className="font-medium text-medium text-gray-800">
Need to find something? Use the search bar above to get your Need to find something? Use the links or the search bar
results. above to get your results.
</p> </h2>
</div>
</div>
);
};
// Closes the filter dropdown when the user clicks outside the dropdown.
const useFilterPillDropdown = (
ref: React.RefObject<HTMLDivElement>,
setShowDropdown: Function
) => {
// Close on outside click
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
if (setShowDropdown) setShowDropdown(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () =>
// Unbind the event listener on cleanup.
document.removeEventListener("mousedown", handleClickOutside);
}, [ref, setShowDropdown]);
};
interface FilterPillProps {
icon: React.ForwardRefExoticComponent<
Omit<React.SVGProps<SVGSVGElement>, "ref">
>;
name: string;
searchBar?: boolean;
options: string[];
selectedOptions: boolean[];
setSelectedOptions: React.Dispatch<SetStateAction<boolean[]>>;
}
const FilterPill: React.FC<FilterPillProps> = ({
icon,
name,
options,
selectedOptions,
setSelectedOptions,
searchBar = false,
}) => {
const Icon = icon;
const [showDropdown, setShowDropdown] = useState(false);
const [isActive, setIsActive] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const handleCheck = (
e: React.ChangeEvent<HTMLInputElement>,
item: string
) => {
const selected = selectedOptions.map((o, i) => {
if (i == options.indexOf(item)) {
return e.target.checked;
} else {
return o;
}
});
setSelectedOptions(selected);
setIsActive(selected.includes(true));
};
// Closes dropdown when clicked outside
useFilterPillDropdown(dropdownRef, setShowDropdown);
return (
<div
className={
"border rounded-2xl w-max px-2 py-0.5 text-sm mr-2 relative " +
(isActive
? "border-purple-800 text-purple-800 bg-purple-100"
: "border-gray-400 text-gray-400 hover:bg-gray-100")
}
>
{/* The filter pill */}
<button
className="flex gap-1 items-center"
onClick={() => setShowDropdown(!showDropdown)}
>
<Icon className="h-4 w-4" />
<span className="max-w-36 overflow-hidden whitespace-nowrap text-ellipsis">
{name}
{/* Displaying the selected options */}
{options.reduce(
(a, b, i) =>
a +
(selectedOptions[i] // If selected, append option
? (a === "" ? ": " : ", ") + b // (prepend ": " if first element, ", " if not)
: ""),
""
)}
</span>
<ChevronDownIcon className="h-4 w-4" />
</button>
{/* The filter option selection dropdown */}
<div
ref={dropdownRef}
className={
"absolute top-full mt-0.5 left-0 border border-gray-200 bg-white shadow-lg rounded-md p-1.5 w-48 " +
(showDropdown ? "flex flex-col" : "hidden")
}
>
<input
className="border w-full rounded-md mb-1 text-gray-600 p-1"
type="text"
placeholder={"Search " + name.toLowerCase()}
hidden={!searchBar}
/>
{options.map((item, index) => {
return (
<label className="text-gray-800" key={index}>
<input
type="checkbox"
name="name"
className="mr-1"
checked={selectedOptions[index]}
onChange={(e) => handleCheck(e, item)}
/>
{item}
</label>
);
})}
</div> </div>
</div> </div>
); );