Skip to content

Commit

Permalink
feat(apps/web): faq (#1744)
Browse files Browse the repository at this point in the history
  • Loading branch information
LufyCZ authored Nov 13, 2024
1 parent b4d3db4 commit 6ec271d
Show file tree
Hide file tree
Showing 36 changed files with 1,631 additions and 37 deletions.
56 changes: 56 additions & 0 deletions apps/web/src/app/(cms)/faq/(root)/components/get-in-touch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
ChatBubbleOvalLeftIcon /*, TicketIcon*/,
} from '@heroicons/react/24/solid'
import { Button } from '@sushiswap/ui'
import React, { FC } from 'react'

interface Block {
title: string
button: { text: string; link: string }
icon: FC<React.ComponentProps<'svg'>>
}

function Block({ title, button, icon: Icon }: Block) {
return (
<div className="px-7 py-5 flex flex-row justify-between w-full space-x-4 bg-white dark:bg-opacity-5 bg-opacity-[0.12] rounded-xl border border-black border-opacity-30 dark:border-opacity-10 dark:border-white">
<div className="flex-col flex space-y-5">
<div>{title}</div>
<a target="_blank" rel="noreferrer" href={button.link}>
<Button size="xs" className="py-2 px-3">
{button.text}
</Button>
</a>
</div>
<div className="relative w-[55px] aspect-1">
<div className="absolute opacity-20 rounded-full bg-blue-500 w-[55px] aspect-1 bottom-0 right-0" />
<div className="absolute opacity-20 rounded-full bg-[#F338C3] w-[52px] aspect-1 bottom-0 right-0" />
<div className="absolute w-[55px] aspect-1 bottom-0 right-0 flex justify-center items-center pl-px text-white">
<Icon width={26} height={26} className="w-[26px] h-[26px]" />
</div>
</div>
</div>
)
}

export function GetInTouch() {
return (
<div className="grid md:grid-cols-1 gap-6 w-full max-w-md">
<Block
title={`Can’t find what you are looking for?`}
button={{
text: `Chat with us`,
link: 'https://discord.com/channels/748031363935895552/1141943831898636410',
}}
icon={ChatBubbleOvalLeftIcon}
/>
{/* <Block
title={`Looking for more support on a specific issue?`}
button={{
text: `Raise a ticket`,
link: '',
}}
icon={TicketIcon}
/> */}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { classNames } from '@sushiswap/ui'
import Link from 'next/link'

import {
type FaqCategories,
getFaqCategories,
} from '@sushiswap/graph-client/strapi'

function Block({ name, url }: FaqCategories[number]) {
return (
<Link
href={url}
className={classNames(
'md:text-lg md:py-3 md:px-5 px-4 py-2 rounded-lg text-sm whitespace-nowrap border',
'bg-black bg-opacity-5 border-black border-opacity-30',
'dark:bg-white dark:bg-opacity-5 dark:border-slate-500 dark:border-opacity-20',
)}
>
{name}
</Link>
)
}

export async function HelpByCategories() {
const categories = await getFaqCategories({ sort: ['id'] })

return (
<div className="md:space-y-12 space-y-8">
<div className="text-2xl font-medium">Help By Categories</div>
<div className="flex flex-wrap gap-x-3 md:gap-x-6 gap-y-3 md:gap-y-4">
{categories.map((topic) => (
<Block key={topic.slug} {...topic} />
))}
</div>
</div>
)
}
51 changes: 51 additions & 0 deletions apps/web/src/app/(cms)/faq/(root)/components/help-by-products.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
type FaqProducts,
getFaqProducts,
} from '@sushiswap/graph-client/strapi'
import { CloudinaryImage, classNames } from '@sushiswap/ui'
import Link from 'next/link'

function Block({ name, description, url, image }: FaqProducts[number]) {
return (
<Link
href={url}
className={classNames(
'flex flex-col justify-center md:text-lg md:py-6 px-3 py-4 rounded-lg text-sm border items-center space-y-6',
'bg-black bg-opacity-[0.02] border-black border-opacity-30',
'dark:bg-white dark:bg-opacity-5 dark:border-slate-500 dark:border-opacity-20',
)}
>
{image ? (
<CloudinaryImage
alt={image.alternativeText}
src={image.provider_metadata.public_id as string}
width={image.width}
height={image.height}
sizes="100vw"
className="h-14 w-min"
/>
) : null}
<div className="space-y-3 flex flex-col justify-center text-center">
<div className="text-lg font-bold">{name}</div>
<div className="text-sm dark:text-white text-opacity-70">
{description}
</div>
</div>
</Link>
)
}

export async function HelpByProducts() {
const products = await getFaqProducts()

return (
<div className="md:space-y-12 space-y-8">
<div className="text-2xl font-medium">Help By Products</div>
<div className="lg:grid-cols-4 xs:grid-cols-2 md:grid-cols-3 grid gap-x-5 md:gap-y-8 gap-y-4">
{products.map((product, i) => (
<Block key={i} {...product} />
))}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ChevronRightIcon } from '@heroicons/react/24/solid'
import {
FaqMostSearched,
getFaqMostSearched,
} from '@sushiswap/graph-client/strapi'
import Link from 'next/link'

function Question({ question, url }: FaqMostSearched[number]) {
return (
<div className="">
<Link
href={url}
className="flex flex-row items-center justify-between space-x-4"
prefetch={true}
>
<div className="dark:hover:text-slate-300 hover:text-neutral-600">
{question}
</div>
<div>
<ChevronRightIcon width={28} height={28} />
</div>
</Link>
</div>
)
}

function MostSearchedQuestionsDesktop({
questions,
}: { questions: FaqMostSearched }) {
const firstHalf = questions.slice(0, Math.ceil(questions.length / 2))
const secondHalf = questions.slice(Math.ceil(questions.length / 2))

return (
<div className="grid grid-cols-2 gap-x-16 lg:gap-x-24">
<div className="divide-y divide-slate-500 divide-opacity-50">
{firstHalf.map((topic, i) => (
<div key={i} className="pt-2.5 pb-2.5 first:pt-0 last:pb-0">
<Question {...topic} />
</div>
))}
</div>
<div className="divide-y divide-slate-500 divide-opacity-50">
{secondHalf.map((topic, i) => (
<div key={i} className="pt-2.5 pb-2.5 first:pt-0 last:pb-0">
<Question {...topic} />
</div>
))}
</div>
</div>
)
}

function MostSearchedQuestionsMobile({
questions,
}: { questions: FaqMostSearched }) {
return (
<div className="divide-y divide-slate-500 divide-opacity-50 gap-y-4">
{questions.map((topic, i) => (
<div key={i} className="first:pt-0 py-3 last:pb-0">
<Question {...topic} />
</div>
))}
</div>
)
}

export async function MostSearchedQuestions() {
const questions = await getFaqMostSearched()

return (
<div className="flex flex-col space-y-8 md:space-y-12">
<div className="text-2xl font-medium">Most Searched Questions</div>

<div>
<div className="md:block hidden">
<MostSearchedQuestionsDesktop questions={questions} />
</div>
<div className="md:hidden block">
<MostSearchedQuestionsMobile questions={questions} />
</div>
</div>
</div>
)
}
146 changes: 146 additions & 0 deletions apps/web/src/app/(cms)/faq/(root)/components/search-box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'use client'

import { useDebounce, useOnClickOutside } from '@sushiswap/hooks'
import { LinkInternal, SkeletonText, classNames } from '@sushiswap/ui'
import { useRef, useState } from 'react'

import { SearchIcon } from '@heroicons/react-v1/outline'
import { XIcon } from '@heroicons/react-v1/solid'
import { getFaqAnswerSearch } from '@sushiswap/graph-client/strapi'
import { useQuery } from '@tanstack/react-query'

export function SearchBox() {
const ref = useRef<HTMLDivElement>(null)
const [query, setQuery] = useState<string>('')
const debouncedQuery = useDebounce(query, 300)
const [open, setOpen] = useState(false)

useOnClickOutside(ref, () => {
setOpen(false)
})

const { data, isLoading, isError } = useQuery({
queryKey: ['faq-answers', debouncedQuery],
queryFn: () => getFaqAnswerSearch({ search: debouncedQuery }),
})

return (
<div className="flex flex-col gap-3 relative">
<div className="z-10 flex w-full gap-4">
<div
ref={ref}
onFocus={() => setOpen(true)}
className={classNames(
'rounded-xl w-full border',
'border-black border-opacity-30 bg-neutral-100',
'dark:bg-[#1F2535] dark:border-opacity-20 dark:border-slate-500',
)}
>
<div className="flex items-center gap-2 pl-4 pr-3 h-14">
<div className="flex gap-2 items-center w-full">
<div className="w-6 h-6">
<SearchIcon
width={24}
height={24}
className="dark:text-slate-500 text-neutral-950"
/>
</div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search questions, keyword, articles..."
className={classNames(
'w-full dark:placeholder:text-slate-500 placeholder:text-neutral-950',
'p-0 bg-transparent border-none focus:outline-none focus:ring-0 w-full truncate font-medium text-left text-base md:text-sm placeholder:font-normal',
)}
/>
</div>
{query && (
<XIcon
onClick={() => setQuery('')}
className="w-6 h-6 cursor-pointer dark:text-slate-500 text-neutral-950"
/>
)}
</div>
<div
className={classNames(
open
? 'max-h-[335px] py-2 border-b border-l border-r -ml-px scroll'
: 'max-h-[0px]',
'z-[100] rounded-b-xl flex flex-col gap-2 overflow-hidden transition-all absolute w-full -mt-4 ',
'dark:bg-[#1F2535] dark:border-opacity-20 dark:border-slate-500',
'border-black border-opacity-30 bg-neutral-100',
)}
>
{isError ? (
<div className="px-4 pt-4 pb-2 gap-2 flex justify-center w-full text-sm">
An unexpected error has occured.
</div>
) : (
<>
<p className="text-sm font-semibold mt-4 px-4 text-slate-400">
Questions
</p>
<RowGroup entries={data?.answers || []} isLoading={isLoading} />
<p className="text-sm font-semibold mt-4 px-4 text-slate-400">
Question Groups
</p>
<RowGroup
entries={data?.answerGroups || []}
isLoading={isLoading}
/>
</>
)}
</div>
</div>
</div>
</div>
)
}

function RowGroup({
entries,
isLoading,
}: {
entries: { name: string; slug: string }[]
isLoading: boolean
}) {
if (isLoading) {
return (
<div className="px-4 py-2 gap-2">
<SkeletonText />
<SkeletonText />
<SkeletonText />
</div>
)
}

if (entries.length === 0) {
return (
<div className="px-4 py-2 gap-2 flex w-full text-sm">
No results found.
</div>
)
}

return (
<>
{entries.map(({ name, slug }) => (
<Row key={slug} name={name} slug={slug} />
))}
</>
)
}

function Row({ name, slug }: { name: string; slug: string }) {
const content = (
<div
className="flex items-center gap-2 px-4 py-2 cursor-pointer dark:hover:bg-slate-700 hover:bg-neutral-200"
key={slug}
>
<p className="font-medium">{name}</p>
</div>
)

return <LinkInternal href={`/faq/${slug}`}>{content}</LinkInternal>
}
Loading

0 comments on commit 6ec271d

Please sign in to comment.