Skip to content

Commit

Permalink
Merge pull request #137 from BacPacNet/university-filter-screen
Browse files Browse the repository at this point in the history
University filter screen
  • Loading branch information
bacpactech authored Dec 22, 2024
2 parents 84e56a0 + 04a6637 commit c62dab7
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 24 deletions.
13 changes: 11 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,18 @@ const withPWA = require("next-pwa")({

// Next.js configuration
const nextConfig = {
// Allow images from external domains

images: {
domains: ['cdn.pixabay.com', "res.cloudinary.com", "upload.wikimedia.org", 'www.servizisegreti.com',"saig.physics.ualberta.ca"],
remotePatterns: [
{
protocol: 'https',
hostname: '**', // Accepts all domains
},
{
protocol: 'http',
hostname: '**', // Accepts all domains
},
],
},

// Custom headers for API routes
Expand Down
3 changes: 2 additions & 1 deletion src/app/discover/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import DiscoverContainer from '@/components/organisms/DiscoverContainer'
import React from 'react'

function Discover() {
return <div>Discover</div>
return <DiscoverContainer />
}

export default Discover
142 changes: 129 additions & 13 deletions src/app/university/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,154 @@
'use client'
import Buttons from '@/components/atoms/Buttons'
import Loading from '@/components/atoms/Loading'
import { useUniversitySearch, useUniversitySearchByName } from '@/services/universitySearch'
import { UniversityInfo } from '@/types/University'
import ImagePlaceholder from '@assets/unibuzz-orange.png'
import { useUniversitySearchByName } from '@/services/universitySearch'
import Image from 'next/image'
import { useParams } from 'next/navigation'
import { MdEmail } from 'react-icons/md'
import React, { useState } from 'react'
import { FaPhoneAlt } from 'react-icons/fa'
import { MdFax } from 'react-icons/md'
import { IoIosLink } from 'react-icons/io'
import { PiBuildingsFill } from 'react-icons/pi'
import { BsClockFill } from 'react-icons/bs'
import universityPlaceholder from '@assets/university_placeholder.jpg'
import universityLogoPlaceholder from '@assets/unibuzz_rounded.svg'
import { IconType } from 'react-icons/lib'

import React, { useEffect } from 'react'
const UniversityCard = ({ icon: Icon, title, info }: { icon: IconType; title: string; info: string }) => (
<div>
<p className="text-primary-700 flex gap-1 items-center font-bold">
<Icon size={20} />
{title}
</p>
<p className={`text-neutral-700 text-xs line-clamp-6`}>{info || 'Not available'}</p>
</div>
)

export default function UniversityProfile() {
const params = useParams()
const { id: universityName } = params
const { data: university, isLoading: isUniversityLoading } = useUniversitySearchByName(universityName as string)

const [imageSrc, setImageSrc] = useState(university?.images[0] || universityPlaceholder)
const [logoSrc, setLogoSrc] = useState(university?.logos?.[0] || universityLogoPlaceholder)

if (isUniversityLoading) return <Loading />

const contactData = [
{
icon: MdEmail,
title: 'Email',
info: university.wikiInfoBox?.email,
},
{
icon: FaPhoneAlt,
title: 'Phone',
info: university.collegeBoardInfo?.['Phone number'],
},
{
icon: MdFax,
title: 'Fax',
info: university.wikiInfoBox?.fax,
},
]

const additionalData = [
{
icon: IoIosLink,
title: 'Link',
info: university.collegeBoardInfo?.website,
},
{
icon: PiBuildingsFill,
title: 'Address',
info: university.collegeBoardInfo?.Location,
},
{
icon: BsClockFill,
title: 'Office Hours',
info: university.wikiInfoBox?.['Office Hours'] || 'Monday to Friday 9:00 am - 12:00 p.m. and 1:00 p.m - 5:00 p.m',
},
]

return (
<div className="p-28 ">
<div className="flex gap-16">
<div className="flex-1">
<div className="p-28 flex flex-col gap-4 max-sm:px-4">
<div className="flex gap-16 max-lg:flex-col-reverse">
<div className="flex flex-col gap-5">
<div className="flex items-center gap-9 py-4">
<div className="flex justify-center items-center p-6 drop-shadow-lg rounded-full bg-white">
<img
<Image
width={46}
height={46}
src={university?.logos?.[0] || (ImagePlaceholder as unknown as string)}
src={logoSrc}
alt="logo"
className="max-w-full max-h-40 object-contain"
onError={() => setLogoSrc(universityLogoPlaceholder)}
/>
</div>

<p className="text-neutral-900 text-lg font-extrabold">{university?.name}</p>
<p className="text-neutral-900 text-lg font-extrabold w-96 max-xl:w-60 max-xl:text-[24px] max-lg:w-full">{university?.name}</p>
</div>
<p className={`text-neutral-500 text-xs line-clamp-6`}>{university?.topUniInfo?.about}</p>
<p className={`text-neutral-500 text-xs line-clamp-6 max-w-lg`}>{university?.topUniInfo?.about || 'Not Available'}</p>
{university?.isCommunityCreated ? <Buttons className="w-max">Join Community</Buttons> : <Buttons className="w-max">Endorse</Buttons>}
</div>
<div className=" flex justify-center w-8/12 max-lg:w-full max-sm:items-center">
<Image
width={400}
height={600}
className="rounded-lg h-96 object-cover w-10/12 max-lg:w-full"
src={imageSrc}
alt="university_image"
onError={() => setImageSrc(universityPlaceholder)}
/>
</div>
<div className="flex-1 flex justify-center">
<img className="rounded-lg max-w-full object-contain" src={university?.images[0]} alt="university_image" />
</div>
{/* //overview */}
<div className="flex flex-col gap-4">
<p className="text-neutral-900 text-base font-extrabold w-96">Overview</p>
<div className="flex flex-col gap-4">
<p className={`text-neutral-500 text-xs line-clamp-6 `}>
Loremium University boasts a world-class faculty, many of whom are leaders in their respective fields. The university’s commitment to
innovation and research has led to groundbreaking discoveries and advancements, particularly in the realms of biotechnology, environmental
science, and digital arts. The state-of-the-art laboratories and creative studios provide students with the resources and inspiration
needed to push the boundaries of knowledge and creativity.
</p>
<p className={`text-neutral-500 text-xs line-clamp-6 `}>
In addition to its scientific prowess, Loremium University is a thriving cultural hub. The university’s School of Arts is renowned for its
avant-garde approach to art and design, producing graduates who have gone on to achieve international acclaim. Regular exhibitions,
performances, and lectures by visiting artists and scholars enrich the campus life and foster a dynamic exchange of ideas.
</p>
<p className={`text-neutral-500 text-xs line-clamp-6 `}>
Loremium University is a magnet for students from around the globe, drawn by its stellar reputation and welcoming atmosphere. The
university’s diverse student body, hailing from over 80 countries, creates a rich tapestry of cultures and perspectives. Dedicated support
services ensure that international students feel at home, making their transition to life in Loremium seamless and enjoyable.
</p>
</div>
</div>
{/* //contact */}
<div className="flex flex-col gap-4 mt-4">
<p className="text-neutral-900 text-base font-extrabold w-96">Contact Info</p>
<div className="flex justify-between gap-20 max-sm:flex-col max-sm:gap-5">
<div className="bg-neutral-200 py-2 px-4 w-full h-44 rounded-lg flex flex-col justify-between">
{contactData.map((item, index) => (
<UniversityCard key={index} icon={item.icon} title={item.title} info={item.info} />
))}
</div>
<div className="bg-neutral-200 py-2 px-4 w-full h-44 rounded-lg flex flex-col justify-between">
{additionalData.map((item, index) => (
<UniversityCard key={index} icon={item.icon} title={item.title} info={item.info} />
))}
</div>
</div>
</div>
{/* //contact */}
<div className="flex flex-col gap-4 mt-4">
<p className="text-neutral-900 text-base font-extrabold w-96">Reviews</p>
<div className="flex flex-col gap-4 items-center justify-center h-60">
<p className="text-neutral-900 text-base font-extrabold ">Reviews are coming soon!</p>
<p className={`text-neutral-700 text-xs line-clamp-6 max-w-lg text-center `}>
This feature is under construction. If you would like to have it sooner send us some feedback!
</p>
</div>
</div>
</div>
Expand Down
20 changes: 14 additions & 6 deletions src/components/atoms/SelectDropdown/SelectDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const motionStyle = {
exit: { opacity: 0, y: '-10%', transition: { duration: '0.35' } },
transition: { type: 'spring', stiffness: '100', duration: '0.75' },
}

const SelectDropdown = ({ options, onChange, value, placeholder, icon, search = false, err, showIcon = false }: SelectDropdownProps) => {
const [show, setShow] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -55,17 +56,25 @@ const SelectDropdown = ({ options, onChange, value, placeholder, icon, search =
}
}, [])

const toggleDropdown = () => {
if (!show) {
setFilteredOptions(options)
if (searchRef.current) searchRef.current.value = ''
}
setShow((prevShow) => !prevShow)
}

return (
<motion.div ref={dropdownRef} className="relative">
<div
onClick={() => setShow(!show)}
onClick={toggleDropdown}
className={`${
err ? 'border-red-400' : 'border-neutral-200'
} flex justify-between items-center py-2 px-3 border focus:ring-2 rounded-lg drop-shadow-sm text-neutral-400 outline-none`}
>
<p className={`${value ? 'text-neutral-900' : 'text-neutral-400'} text-2xs`}> {value || placeholder}</p>
<div>
{icon == 'single' ? (
{icon === 'single' ? (
<IoIosArrowDown />
) : (
<div className="flex flex-col text-xs">
Expand All @@ -78,9 +87,9 @@ const SelectDropdown = ({ options, onChange, value, placeholder, icon, search =
<AnimatePresence>
{show && (
<motion.div
className={`flex flex-col custom-scrollbar ${
className={`flex flex-col custom-scrollbar ${
!showIcon ? 'gap-2 w-full p-2' : 'gap-1 p-1'
} absolute right-0 bg-white shadow-lg border border-neutral-200 rounded-lg z-10 max-h-52 w-52 overflow-y-auto`}
} absolute right-0 bg-white shadow-lg border border-neutral-200 rounded-lg z-10 max-h-52 w-52 overflow-y-auto`}
{...motionStyle}
>
{search && (
Expand All @@ -93,7 +102,7 @@ const SelectDropdown = ({ options, onChange, value, placeholder, icon, search =
/>
)}
{filteredOptions?.length > 0 ? (
filteredOptions?.map((item: string, key: number) => {
filteredOptions.map((item: string, key: number) => {
const IconComponent = icons[key % icons.length]
return (
<div
Expand All @@ -117,5 +126,4 @@ const SelectDropdown = ({ options, onChange, value, placeholder, icon, search =
</motion.div>
)
}

export default SelectDropdown
117 changes: 117 additions & 0 deletions src/components/molecules/Discover/DiscoverFilterComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use client'
import Buttons from '@/components/atoms/Buttons'
import SelectDropdown from '@/components/atoms/SelectDropdown/SelectDropdown'
import { useGetFilteredUniversity } from '@/services/universitySearch'
import { country_list } from '@/utils/countriesList'
import React, { useState } from 'react'
import { Controller, useForm } from 'react-hook-form'
import { IoIosSearch } from 'react-icons/io'

type Props = {
setQuery: (value: any) => void
}

const DiscoverFilterComponent = ({ setQuery }: Props) => {
const { register, control, handleSubmit: handleFormSubmit, watch, reset } = useForm()
const [cityOptions, setCityOptions] = useState<string[]>([])
const [isCityAvailable, setIsCityAvailable] = useState(true)
const currCountry = watch('country') || ''

const handleCountryChange = (selectedCountry: string, field: any) => {
field.onChange(selectedCountry)
const cities = country_list[selectedCountry] || []

setCityOptions(cities.length > 0 ? cities : ['Not available'])
setIsCityAvailable(cities.length > 0)
}

const handleFilterSubmit = (data: any) => {
setQuery(JSON.stringify(data))
}
return (
<div className="max-lg:w-60">
<form onSubmit={handleFormSubmit(handleFilterSubmit)} className=" border border-neutral-300 shadow-xl rounded-2xl">
<h3 className="border-b border-neutral-300 text-neutral-700 text-[24px] font-poppins p-4">Search Filter</h3>
<div className="p-4 flex flex-col gap-4">
<div className="w-full flex flex-col relative">
<IoIosSearch size={20} className="absolute left-2 z-30 top-1/2 -translate-y-1/2" />
<input
className=" py-2 ps-8 pe-3 border-2 border-neutral-200 focus:ring-2 rounded-full drop-shadow-sm text-neutral-400 outline-none "
placeholder="Search"
type="text"
{...register('Search', {})}
/>
</div>
<div className="w-full flex flex-col">
<Controller
name="region"
control={control}
render={({ field }) => (
<SelectDropdown options={['a', 'b']} value={field.value} onChange={field.onChange} placeholder="Region" icon={'single'} err={false} />
)}
/>
</div>
<div className="w-full flex flex-col">
<Controller
name="country"
control={control}
render={({ field }) => (
<SelectDropdown
options={Object.keys(country_list)}
value={field.value || ''}
onChange={(selectedCountry: string) => handleCountryChange(selectedCountry, field)}
placeholder="Country"
icon={'single'}
search={true}
err={false}
/>
)}
/>
</div>
<div className="w-full flex flex-col">
<Controller
name="city"
control={control}
render={({ field }) => (
<SelectDropdown
options={cityOptions}
key={cityOptions[0]}
value={field.value || ''}
onChange={field.onChange}
placeholder="City"
icon={'single'}
search={true}
err={false}
/>
)}
/>
</div>
<div className="w-full flex flex-col">
<Controller
name="type"
control={control}
render={({ field }) => (
<SelectDropdown
options={['Private', 'Public', 'Community']}
value={field.value}
onChange={field.onChange}
placeholder="Type"
icon={'single'}
err={false}
/>
)}
/>
</div>
</div>
<div className="p-4 flex items-center gap-4 justify-end">
<Buttons size="extra_small">Search</Buttons>
<Buttons type="button" onClick={() => reset()} size="extra_small">
Reset
</Buttons>
</div>
</form>
</div>
)
}

export default DiscoverFilterComponent
Loading

0 comments on commit c62dab7

Please sign in to comment.