chore: Enhancement + New Feature (#185)

* wip

* wip page

* chore: style

* wip pages

* wip pages

* chore: toggle

* chore: link

* feat: topic search

* chore: page section

* refactor: apply tailwind class ordering

* fix: handle loggedIn user for guest route

* feat: folder & image schema

* chore: move utils to shared

* refactor: tailwind class ordering

* feat: img ext for editor

* refactor: remove qa

* fix: tanstack start

* fix: wrong import

* chore: use toast

* chore: schema
This commit is contained in:
Aslam
2024-10-18 21:18:20 +07:00
committed by GitHub
parent c93c634a77
commit a440828f8c
158 changed files with 2808 additions and 1064 deletions

View File

@@ -16,7 +16,6 @@ export const Route = createFileRoute("/_layout/_pages/(topic)/$")({
export const openPopoverForIdAtom = atom<string | null>(null)
export function TopicDetailComponent() {
console.log("TopicDetailComponent")
const params = useParams({ from: "/_layout/_pages/$" })
const { me } = useAccountOrGuest({ root: { personalLinks: [] } })
@@ -30,34 +29,65 @@ export function TopicDetailComponent() {
latestGlobalGuide: { sections: [] },
})
const [activeIndex, setActiveIndex] = React.useState(-1)
const [searchQuery, setSearchQuery] = React.useState("")
const topicExists = GraphData.find((node) => {
return node.name === params._splat
})
const topicExists = React.useMemo(
() => GraphData.find((node) => node.name === params._splat),
[params._splat],
)
const latestGlobalGuide = React.useMemo(
() => topic?.latestGlobalGuide,
[topic?.latestGlobalGuide],
)
const flattenedItems = React.useMemo(
() =>
latestGlobalGuide?.sections.flatMap((section) => [
{ type: "section" as const, data: section },
...(section?.links?.map((link) => ({
type: "link" as const,
data: link,
})) || []),
]) || [],
[latestGlobalGuide],
)
const filteredItems = React.useMemo(() => {
if (!searchQuery) return flattenedItems
return flattenedItems.filter((item) => {
if (item.type === "section") {
return item.data?.title
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
if (item.type === "link") {
return (
item.data?.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.data?.url.toLowerCase().includes(searchQuery.toLowerCase())
)
}
return false
})
}, [flattenedItems, searchQuery])
if (!topicExists) {
return <NotFoundPlaceholder />
}
const flattenedItems = topic?.latestGlobalGuide?.sections.flatMap(
(section) => [
{ type: "section" as const, data: section },
...(section?.links?.map((link) => ({
type: "link" as const,
data: link,
})) || []),
],
)
if (!topic || !me || !flattenedItems) {
if (!topic || !me) {
return <TopicDetailSkeleton />
}
return (
<>
<TopicDetailHeader topic={topic} />
<TopicDetailHeader
topic={topic}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
/>
<TopicDetailList
items={flattenedItems}
items={filteredItems}
topic={topic}
activeIndex={activeIndex}
setActiveIndex={setActiveIndex}
@@ -88,7 +118,7 @@ function TopicDetailSkeleton() {
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-6 w-48" />
</div>
<Skeleton className="h-9 w-36" />
<Skeleton className="h-7 w-28" />
</div>
<div className="space-y-4 p-6 max-lg:px-4">

View File

@@ -10,13 +10,19 @@ import { LearningStateValue } from "@/lib/constants"
import { useMedia } from "@/hooks/use-media"
import { useClerk } from "@clerk/tanstack-start"
import { useLocation } from "@tanstack/react-router"
import { Input } from "~/components/ui/input"
import { LaIcon } from "~/components/custom/la-icon"
interface TopicDetailHeaderProps {
topic: Topic
searchQuery: string
setSearchQuery: (query: string) => void
}
export const TopicDetailHeader = React.memo(function TopicDetailHeader({
topic,
searchQuery,
setSearchQuery,
}: TopicDetailHeaderProps) {
const clerk = useClerk()
const { pathname } = useLocation()
@@ -111,28 +117,51 @@ export const TopicDetailHeader = React.memo(function TopicDetailHeader({
topicLists[learningState]?.push(topic)
}
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value)
}
return (
<ContentHeader className="px-6 py-5 max-lg:px-4">
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 min-w-0 flex-1 items-center">
<h1 className="truncate text-left font-bold lg:text-xl">
{topic.prettyName}
</h1>
<>
<ContentHeader>
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 min-w-0 flex-1 items-center">
<h1 className="truncate text-left font-semibold lg:text-lg">
{topic.prettyName}
</h1>
</div>
</div>
<div className="flex flex-auto"></div>
{/* <GuideCommunityToggle topicName={topic.name} /> */}
<LearningStateSelector
showSearch={false}
value={p?.learningState || ""}
onChange={handleAddToProfile}
defaultLabel={isMobile ? "" : "Add to profile"}
defaultIcon="Circle"
/>
</ContentHeader>
<div className="flex min-h-10 flex-row items-center justify-between border-b border-b-[var(--la-border-new)] px-6 py-2 max-lg:px-4">
<div className="flex flex-1 flex-row items-center gap-2">
<span className="text-tertiary flex h-5 w-5 items-center justify-center">
<LaIcon name="Search" className="text-muted-foreground" />
</span>
<Input
className="h-6 flex-1 border-none bg-transparent p-0 focus-visible:ring-0"
placeholder="Search..."
role="searchbox"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
value={searchQuery}
onChange={handleSearchChange}
/>
</div>
</div>
<div className="flex flex-auto"></div>
{/* <GuideCommunityToggle topicName={topic.name} /> */}
<LearningStateSelector
showSearch={false}
value={p?.learningState || ""}
onChange={handleAddToProfile}
defaultLabel={isMobile ? "" : "Add to profile"}
defaultIcon="Circle"
/>
</ContentHeader>
</>
)
})

View File

@@ -178,11 +178,8 @@ export const LinkItem = React.memo(
size="sm"
type="button"
role="combobox"
variant="secondary"
className={cn(
"size-7 shrink-0 p-0",
"hover:bg-accent-foreground/10",
)}
variant="ghost"
className="h-auto shrink-0 cursor-default p-0 text-muted-foreground/75 hover:bg-inherit hover:text-foreground"
onClick={(e) => e.stopPropagation()}
>
{selectedLearningState?.icon ? (
@@ -215,7 +212,7 @@ export const LinkItem = React.memo(
<div className="gap-x-2 space-y-0.5 xl:flex xl:flex-row">
<p
className={cn(
"text-primary hover:text-primary line-clamp-1 text-sm font-medium",
"line-clamp-1 text-sm font-medium text-primary hover:text-primary",
isActive && "font-bold",
)}
>
@@ -226,14 +223,14 @@ export const LinkItem = React.memo(
<LaIcon
name="Link"
aria-hidden="true"
className="text-muted-foreground group-hover:text-primary size-3.5 flex-none"
className="flex-none text-muted-foreground group-hover:text-primary"
/>
<Link
to={ensureUrlProtocol(link.url)}
target="_blank"
onClick={(e) => e.stopPropagation()}
className="text-muted-foreground hover:text-primary text-xs"
className="text-xs text-muted-foreground hover:text-primary"
>
<span className="line-clamp-1">{link.url}</span>
</Link>

View File

@@ -51,10 +51,10 @@ export function TopicDetailList({
className="flex flex-col"
>
<div className="flex items-center gap-4 px-6 py-2 max-lg:px-4">
<p className="text-foreground text-sm font-medium">
<p className="text-[13px] font-medium text-muted-foreground">
{item.data?.title}
</p>
<div className="flex-1 border-b" />
<div className="flex-1 border-b border-[var(--la-border-new)]" />
</div>
</div>
)
@@ -82,7 +82,7 @@ export function TopicDetailList({
)
return (
<div ref={parentRef} className="flex-1 overflow-auto">
<div ref={parentRef} className="flex-1 overflow-auto py-4">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,