Skip to content

Commit

Permalink
Merge branch 'search_bar'
Browse files Browse the repository at this point in the history
  • Loading branch information
loloToster committed Aug 8, 2024
2 parents dc77ae4 + f1e592e commit e6bebaa
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 49 deletions.
9 changes: 7 additions & 2 deletions app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,15 @@ apiRouter.delete("/item/:id", async (req, res) => {
})

apiRouter.get("/items", async (req, res) => {
const trashed = req.query.trashed === "true" ? 1 : 0
const { trashed, q } = req.query

const textSearch = typeof q === "string" ?
` AND title LIKE '%' || ? || '%'` :
""

db.all(
`SELECT * FROM items WHERE trashed = ${trashed} ORDER BY created_at DESC`,
`SELECT * FROM items WHERE trashed = ${trashed === "true" ? 1 : 0}${textSearch} ORDER BY created_at DESC`,
[q],
(err, rows) => {
if (err) return res.status(500).send()
res.send(rows)
Expand Down
8 changes: 6 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Outlet, ScrollRestoration } from "react-router-dom"

import { ItemsCacheContextProvider } from "./contexts/itemsCacheContext"
import { SearchContextProvider } from "./contexts/searchContext"

import Header from "./components/Header/Header"

function App() {
return (
<div className="app">
<ItemsCacheContextProvider>
<Header />
<Outlet />
<SearchContextProvider>
<Header />
<Outlet />
</SearchContextProvider>
</ItemsCacheContextProvider>
<ScrollRestoration />
</div>
Expand Down
1 change: 1 addition & 0 deletions client/src/components/ActionBtn/ActionBtn.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
cursor: pointer;
color: white;
text-decoration: none;
text-align: center;

&:hover,
&:focus {
Expand Down
58 changes: 53 additions & 5 deletions client/src/components/Header/Header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
background-color: #{c.$bg}ee;
z-index: 1;

@media (max-width: 580px) {
flex-wrap: wrap;
}

@media (max-width: 800px) {
padding: 20px 10px;
}
Expand All @@ -19,7 +23,48 @@
font-size: 2rem;
color: c.$main;
text-decoration: none;
margin-right: auto;
margin-right: 16px;
}

&__search {
display: flex;
background-color: c.$grey;
margin: 0 auto;
padding: 6px;
gap: 6px;
border-radius: 4px;

&:has(input:focus) {
outline: 2px solid c.$main;
}

svg {
fill: c.$light-grey;
vertical-align: middle;
}

input {
width: 18vw;
font-size: 1rem;

&::placeholder {
color: c.$light-grey;
}
}

@media (max-width: 580px) {
order: 1;
width: 100%;
margin-top: 10px;

input {
width: 100%;
}
}
}

&__spacer {
flex-grow: 1;
}

--fab-size: min(15vw, 15vh);
Expand Down Expand Up @@ -47,7 +92,7 @@
font-weight: bold;
color: c.$light-grey;
text-decoration: none;
margin-right: 16px;
margin: 0 16px;

svg {
fill: currentColor;
Expand All @@ -65,10 +110,13 @@

&__secondary {
margin-right: 0;
margin-left: auto;
}
}

span {
display: none;
}
@media (max-width: 900px) {
&__secondary span {
display: none;
}
}
}
47 changes: 47 additions & 0 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,61 @@
import { useEffect, useRef } from "react"
import { Link, useLocation } from "react-router-dom"

import routes from "src/routes"
import { useSearch } from "src/contexts/searchContext"

import "./Header.scss"

function Header() {
const location = useLocation()

const { searchQuery, setSearchQuery } = useSearch()

const searchInput = useRef<HTMLInputElement>(null)

const handleSearchEnter = () => {
searchInput.current?.focus()
}

const handleSearchClear = () => {
setSearchQuery("")
searchInput.current?.focus()
}

// clear search on page change
useEffect(() => {
setSearchQuery("")
}, [location.pathname, setSearchQuery])

return (
<div className="header">
<Link to="/" className="header__logo">
Cloudia
</Link>
{routes.find(r => r.path === location.pathname)?.searchable ? (
<div className="header__search">
{searchQuery ? (
<button onClick={handleSearchClear}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" />
</svg>
</button>
) : (
<button onClick={handleSearchEnter}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" />
</svg>
</button>
)}
<input type="text"
ref={searchInput}
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
placeholder="Search Cloudia" />
</div>
) : (
<div className="header__spacer"></div>
)}
<Link to="/trash" className="header__secondary">
<span>Trash</span>
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24">
Expand Down
45 changes: 31 additions & 14 deletions client/src/components/ItemList/ItemList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useEffect, useRef, useState } from "react"
import { ClientItem } from "@backend-types/types"

import { useSearch } from "src/contexts/searchContext"

import "./ItemList.scss"

import QuickActions from "../QuickActions/QuickActions"
Expand Down Expand Up @@ -166,6 +168,8 @@ function ItemList(props: Props) {
handleItemRemoval(id, "delete")
}

const { searchQuery } = useSearch()

return (
<div className="items">
{loading && [...Array(5)].map((_, i) => (
Expand All @@ -175,20 +179,33 @@ function ItemList(props: Props) {
{uploads.map((up, i) => (
<UploadItem key={i} {...up} />
))}
{!loading && items.map(item => {
const itemProps = {
key: item.id,
onRestore: handleRestore,
onDelete: item.trashed ? handleDelete : handleTrash,
onSelect: handleSelect,
onRangeSelect: handleRangeSelect
}

if (item.type === "text")
return <TextItem textItem={item} {...itemProps} />
else
return <FileItem fileItem={item} {...itemProps} />
})}
{!loading && items.filter(
i => {
if (!searchQuery.length) return true

const sq = searchQuery.toLowerCase()

if (i.title.toLowerCase().includes(sq))
return true

if (i.type === "text" && i.text.toLowerCase().includes(sq))
return true

return false
}).map(item => {
const itemProps = {
key: item.id,
onRestore: handleRestore,
onDelete: item.trashed ? handleDelete : handleTrash,
onSelect: handleSelect,
onRangeSelect: handleRangeSelect
}

if (item.type === "text")
return <TextItem textItem={item} {...itemProps} />
else
return <FileItem fileItem={item} {...itemProps} />
})}
</div>
)
}
Expand Down
35 changes: 35 additions & 0 deletions client/src/contexts/searchContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
createContext,
Dispatch,
SetStateAction,
useContext,
useState
} from "react"

export interface SearchContextI {
searchQuery: string,
setSearchQuery: Dispatch<SetStateAction<string>>
}

export const SearchContext = createContext<SearchContextI>({
searchQuery: "",
setSearchQuery: () => null
})

export const SearchContextProvider = (props: {
children: React.ReactNode
}) => {
const [searchQuery, setSearchQuery] = useState("")

return (
<SearchContext.Provider value={{
searchQuery, setSearchQuery
}}>
{props.children}
</SearchContext.Provider>
)
}

export const useSearch = () => {
return useContext(SearchContext)
}
28 changes: 2 additions & 26 deletions client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,11 @@ import App from "./App"
import reportWebVitals from "./reportWebVitals"
import "./sass/style.scss"

import ItemListPage from "./pages/ItemListPage/ItemListPage"
import AddFilePage from "./pages/AddFilesPage/AddFilesPage"
import TrashItemListPage from "./pages/TrashItemListPage/TrashItemListPage"
import FileDetailsPage from "./pages/FileDetailsPage/FileDetailsPage"
import routes from "./routes"

const router = createBrowserRouter([{
element: <App />,
children: [
{
path: "/",
element: <ItemListPage />
},
{
path: "/add",
element: <AddFilePage />
},
{
path: "/trash",
element: <TrashItemListPage />
},
{
path: "/file/:id",
element: <FileDetailsPage />
},
{
path: "/*",
element: <span>404</span>
}
]
children: routes
}])

ReactDOM.createRoot(
Expand Down
37 changes: 37 additions & 0 deletions client/src/routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RouteObject } from "react-router-dom"

import ItemListPage from "./pages/ItemListPage/ItemListPage"
import AddFilePage from "./pages/AddFilesPage/AddFilesPage"
import TrashItemListPage from "./pages/TrashItemListPage/TrashItemListPage"
import FileDetailsPage from "./pages/FileDetailsPage/FileDetailsPage"

export type RouteWithMeta = RouteObject & {
searchable?: boolean
}

const routes: RouteWithMeta[] = [
{
path: "/",
element: <ItemListPage />,
searchable: true
},
{
path: "/trash",
element: <TrashItemListPage />,
searchable: true
},
{
path: "/add",
element: <AddFilePage />
},
{
path: "/file/:id",
element: <FileDetailsPage />
},
{
path: "/*",
element: <span>404</span>
}
]

export default routes

0 comments on commit e6bebaa

Please sign in to comment.