diff --git a/next.config.js b/next.config.js index 1b19f329..e9ec4232 100644 --- a/next.config.js +++ b/next.config.js @@ -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 diff --git a/src/app/discover/page.tsx b/src/app/discover/page.tsx index bd71455a..550d5aea 100644 --- a/src/app/discover/page.tsx +++ b/src/app/discover/page.tsx @@ -1,7 +1,8 @@ +import DiscoverContainer from '@/components/organisms/DiscoverContainer' import React from 'react' function Discover() { - return
Discover
+ return } export default Discover diff --git a/src/app/university/[id]/page.tsx b/src/app/university/[id]/page.tsx index fa2a57c8..53ada4eb 100644 --- a/src/app/university/[id]/page.tsx +++ b/src/app/university/[id]/page.tsx @@ -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 }) => ( +
+

+ + {title} +

+

{info || 'Not available'}

+
+) 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 + + 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 ( -
-
-
+
+
+
- logo setLogoSrc(universityLogoPlaceholder)} />
-

{university?.name}

+

{university?.name}

-

{university?.topUniInfo?.about}

+

{university?.topUniInfo?.about || 'Not Available'}

+ {university?.isCommunityCreated ? Join Community : Endorse} +
+
+ university_image setImageSrc(universityPlaceholder)} + />
-
- university_image +
+ {/* //overview */} +
+

Overview

+
+

+ 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. +

+

+ 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. +

+

+ 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. +

+
+
+ {/* //contact */} +
+

Contact Info

+
+
+ {contactData.map((item, index) => ( + + ))} +
+
+ {additionalData.map((item, index) => ( + + ))} +
+
+
+ {/* //contact */} +
+

Reviews

+
+

Reviews are coming soon!

+

+ This feature is under construction. If you would like to have it sooner send us some feedback! +

diff --git a/src/components/atoms/SelectDropdown/SelectDropdown.tsx b/src/components/atoms/SelectDropdown/SelectDropdown.tsx index 618fd98a..4def7983 100644 --- a/src/components/atoms/SelectDropdown/SelectDropdown.tsx +++ b/src/components/atoms/SelectDropdown/SelectDropdown.tsx @@ -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(null) @@ -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 (
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`} >

{value || placeholder}

- {icon == 'single' ? ( + {icon === 'single' ? ( ) : (
@@ -78,9 +87,9 @@ const SelectDropdown = ({ options, onChange, value, placeholder, icon, search = {show && ( {search && ( @@ -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 (
) } - export default SelectDropdown diff --git a/src/components/molecules/Discover/DiscoverFilterComponent.tsx b/src/components/molecules/Discover/DiscoverFilterComponent.tsx new file mode 100644 index 00000000..664f3434 --- /dev/null +++ b/src/components/molecules/Discover/DiscoverFilterComponent.tsx @@ -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([]) + 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 ( +
+
+

Search Filter

+
+
+ + +
+
+ ( + + )} + /> +
+
+ ( + handleCountryChange(selectedCountry, field)} + placeholder="Country" + icon={'single'} + search={true} + err={false} + /> + )} + /> +
+
+ ( + + )} + /> +
+
+ ( + + )} + /> +
+
+
+ Search + reset()} size="extra_small"> + Reset + +
+
+
+ ) +} + +export default DiscoverFilterComponent diff --git a/src/components/molecules/Discover/DiscoverUniversityCard.tsx b/src/components/molecules/Discover/DiscoverUniversityCard.tsx new file mode 100644 index 00000000..d817e0d4 --- /dev/null +++ b/src/components/molecules/Discover/DiscoverUniversityCard.tsx @@ -0,0 +1,50 @@ +'use client' +import Image from 'next/image' +import React, { useState } from 'react' +import universityPlaceholder from '@assets/university_placeholder.jpg' +import universityLogoPlaceholder from '@assets/unibuzz_rounded.svg' +import { useRouter } from 'next/navigation' + +type Props = { + data: { + images: string[] + logos: string[] + name: string + pathUrl: string + } +} +const DiscoverUniversityCard = ({ data }: Props) => { + const [imageSrc, setImageSrc] = useState(data?.images[0] || universityPlaceholder) + const [logoSrc, setLogoSrc] = useState(data?.logos[0] || universityLogoPlaceholder) + + const router = useRouter() + return ( +
router.push(`/university/${data.pathUrl}`)} + className="w-96 max-xl:w-80 max-lg:w-60 max-md:w-80 relative rounded-2xl cursor-pointer" + > + {'university'} setImageSrc(universityPlaceholder)} + /> +
+ {'logo'} setLogoSrc(universityLogoPlaceholder)} + /> +

{data?.name}

+
+
+ ) +} + +export default DiscoverUniversityCard diff --git a/src/components/molecules/Discover/DiscoverUniversityCardSkeleton.tsx b/src/components/molecules/Discover/DiscoverUniversityCardSkeleton.tsx new file mode 100644 index 00000000..cdd6bc06 --- /dev/null +++ b/src/components/molecules/Discover/DiscoverUniversityCardSkeleton.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +const DiscoverUniversityCardSkeleton = () => { + return ( +
+
+ +
+
+

+
+
+ ) +} + +export default DiscoverUniversityCardSkeleton diff --git a/src/components/organisms/DiscoverContainer/index.tsx b/src/components/organisms/DiscoverContainer/index.tsx new file mode 100644 index 00000000..53344726 --- /dev/null +++ b/src/components/organisms/DiscoverContainer/index.tsx @@ -0,0 +1,58 @@ +'use client' +import DiscoverFilterComponent from '@/components/molecules/Discover/DiscoverFilterComponent' +import DiscoverUniversityCard from '@/components/molecules/Discover/DiscoverUniversityCard' +import DiscoverUniversityCardSkeleton from '@/components/molecules/Discover/DiscoverUniversityCardSkeleton' +import { useGetFilteredUniversity } from '@/services/universitySearch' +import React, { useEffect, useRef, useState } from 'react' + +const DiscoverContainer = () => { + const [query, setQuery] = useState('') + const { data, fetchNextPage, isFetchingNextPage, hasNextPage, isLoading } = useGetFilteredUniversity(5, query) + + const universities = data?.pages.flatMap((page) => page.Universities) || [] + + const containerRef = useRef(null) + + useEffect(() => { + const handleScroll = () => { + if (containerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = containerRef.current + + if (scrollTop + clientHeight >= scrollHeight - 10 && hasNextPage && !isFetchingNextPage) { + fetchNextPage() + } + } + } + + const container = containerRef.current + container?.addEventListener('scroll', handleScroll) + + return () => { + container?.removeEventListener('scroll', handleScroll) + } + }, [fetchNextPage, hasNextPage, isFetchingNextPage]) + + return ( +
+ + +
+ {isLoading ? ( + <> + + + + + + + ) : !isLoading && !universities.length ? ( +
No Result
+ ) : ( + universities?.map((item) => ) + )} +
+
+ ) +} + +export default DiscoverContainer diff --git a/src/services/universitySearch.tsx b/src/services/universitySearch.tsx index 4a6279ca..6928d262 100644 --- a/src/services/universitySearch.tsx +++ b/src/services/universitySearch.tsx @@ -1,5 +1,5 @@ import useDebounce from '@/hooks/useDebounce' -import { useQuery } from '@tanstack/react-query' +import { useInfiniteQuery, useQuery } from '@tanstack/react-query' import { client } from './api-Client' import { ServerResponse } from '@/models/common/api-client' @@ -44,3 +44,29 @@ export async function getUniversityByName(universityName: string): Promise getFilteredUniversity(pageParam, limit, query), + getNextPageParam: (lastPage) => { + if (lastPage.currentPage < lastPage.totalPages) { + return lastPage.currentPage + 1 + } + return undefined + }, + initialPageParam: 1, + }) +} diff --git a/src/utils/countriesList.ts b/src/utils/countriesList.ts index 06603aee..08b98cd5 100644 --- a/src/utils/countriesList.ts +++ b/src/utils/countriesList.ts @@ -10,7 +10,7 @@ export const country_list: Record = { Armenia: [], Aruba: [], Australia: [], - Austria: [], + Austria: ['Styria', 'Vienna'], Azerbaijan: [], Bahamas: [], Bahrain: [],