added search history

This commit is contained in:
2025-07-21 19:48:08 +03:00
parent 825f1c7a15
commit 8519a831cb
22 changed files with 311 additions and 148 deletions

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -8,6 +8,8 @@
</head>
<body>
<div id="root"></div>
<div id="dropdown-portal-root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -640,6 +640,10 @@ export const PublishVideo = ({
<>
{editId ? null : (
<StyledButton
sx={{
width: '100%',
justifyContent: 'flex-start',
}}
color="primary"
startIcon={
<VideoLibraryIcon

View File

@@ -219,8 +219,6 @@ export const CommentEditor = ({
}
};
console.log('onCloseReply', onCloseReply);
return (
<CommentInputContainer
sx={{

View File

@@ -28,7 +28,6 @@ export const AddToBookmarks = ({ metadataReference }) => {
'bookmarks-v1',
{}
);
console.log('metadataReference', metadataReference);
const [bookmarkList, setBookmarkList] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [mode, setMode] = useState(1);
@@ -134,10 +133,8 @@ export const AddToBookmarks = ({ metadataReference }) => {
service: string;
}
) => {
console.log('hello');
setBookmarks((prev) => {
const list = prev[listId];
console.log('list', list);
if (!list || list.type !== 'list') return prev;
const updatedVideos = list.videos.filter(
@@ -148,7 +145,6 @@ export const AddToBookmarks = ({ metadataReference }) => {
v.service === video.service
)
);
console.log('updatedVideos', updatedVideos);
// If no change, avoid unnecessary update
if (updatedVideos.length === list.videos.length) return prev;
@@ -203,7 +199,6 @@ export const AddToBookmarks = ({ metadataReference }) => {
.filter((bookmark) => bookmark?.type === 'list' && !!bookmark?.title)
.sort((a, b) => a.title.localeCompare(b.title));
console.log('lists', lists, bookmarks);
return (
<>
<ButtonBase onClick={() => setIsOpen(true)}>
@@ -311,7 +306,6 @@ export const AddToBookmarks = ({ metadataReference }) => {
>
{lists?.map((list) => {
const isInList = isVideoInList(list.id, metadataReference);
console.log('isInList', isInList);
return (
<ButtonBase
sx={{

View File

@@ -112,6 +112,7 @@ export const DownloadTaskManager: React.FC = () => {
}}
onClick={() => {
navigate(download?.path);
handleCloseDownload();
}}
>
<Box

View File

@@ -25,7 +25,6 @@ export default function ListSuperLikes({ superlikes }) {
const theme = useTheme();
const navigate = useNavigate();
console.log('superlikes', superlikes);
return (
<List
sx={{

View File

@@ -48,8 +48,6 @@ export const VideoPlayer = ({ ...props }: VideoPlayerProps) => {
watchedAt: Date.now(),
};
console.log('playing');
setWatchedHistory((prev) => {
const exists = prev.some(
(v) =>

View File

@@ -71,6 +71,10 @@ export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
<DropdownContainer onClick={popMenuRef?.current?.closePopover}>
<StyledButton
color="primary"
sx={{
justifyContent: 'flex-start',
width: '100%',
}}
startIcon={
<PlaylistAddIcon
sx={{

View File

@@ -1,11 +1,10 @@
import {
Box,
ButtonBase,
Dialog,
DialogContent,
DialogTitle,
ClickAwayListener,
IconButton,
InputBase,
Portal,
styled,
useTheme,
} from '@mui/material';
@@ -15,9 +14,12 @@ import { useSidebarState } from '../../../pages/Home/Components/SearchSidebar-St
import { useNavigate } from 'react-router-dom';
import SearchIcon from '@mui/icons-material/Search';
import CloseIcon from '@mui/icons-material/Close';
import HistoryIcon from '@mui/icons-material/History';
import ClearAllIcon from '@mui/icons-material/ClearAll';
import { usePersistedState } from '../../../state/persist/persist';
import { useIsSmall } from '../../../hooks/useIsSmall';
import { AnimatePresence, motion } from 'framer-motion';
const SearchParent = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
@@ -26,17 +28,11 @@ const SearchParent = styled('div')(({ theme }) => ({
marginLeft: 0,
width: '100%',
outline: `1px ${theme.palette.action.active} solid`,
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
height: '40px',
}));
const SearchIconWrapper = styled('div')(({ theme }) => ({
// padding: theme.spacing(0, 2),
// height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -49,12 +45,8 @@ const StyledInputBase = styled(InputBase)(({ theme }) => ({
width: '100%',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingRight: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
[theme.breakpoints.up('sm')]: {
width: '30ch',
},
},
padding: '8px 12px',
}));
@@ -62,14 +54,23 @@ const StyledInputBase = styled(InputBase)(({ theme }) => ({
export const Search = () => {
const isSmall = useIsSmall();
const navigate = useNavigate();
const searchWrapperRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const theme = useTheme();
const [showPopover, setShowPopover] = useState(false);
const [popoverPosition, setPopoverPosition] = useState<DOMRect | null>(null);
const [isOpenSearch, setIsOpenSearch] = useState(false);
const [isFocused, setIsFocused] = useState(false);
const [filterMode, setFilterMode, isHydratedFilterMode] = usePersistedState(
'filterMode',
'recent'
);
const inputRef = useRef();
const [searchHistory, setSearchHistory] = usePersistedState(
'search-history-v1',
[]
);
const {
filterSearch,
setFilterSearch,
@@ -80,11 +81,14 @@ export const Search = () => {
const handleInputKeyDown = (event: any) => {
if (event.key === 'Enter') {
onSearch();
if (isHydratedFilterMode) {
setFilterMode('all');
if (filterSearch) {
setSearchHistory((prev) => {
const filtered = prev.filter((term) => term !== filterSearch);
return [filterSearch, ...filtered].slice(0, 200);
});
}
// setIsOpenSearch(false);
onSearch();
if (isHydratedFilterMode) setFilterMode('all');
navigate(`/`);
setIsFocused(false);
inputRef?.current?.blur();
@@ -97,23 +101,141 @@ export const Search = () => {
}
}, [filterSearchGlobal, isSmall]);
const handleFocus = () => {
setIsFocused(true);
setTimeout(() => {
setShowPopover(true);
}, 200);
if (searchWrapperRef.current) {
const rect = searchWrapperRef.current.getBoundingClientRect();
setPopoverPosition(rect);
}
};
const handleClickAway = (event: MouseEvent) => {
const target = event.target as Node;
if (!searchWrapperRef.current?.contains(target)) {
setShowPopover(false);
}
};
const renderPopover = () => {
if (
!showPopover ||
filterSearch ||
!popoverPosition ||
searchHistory.length === 0
)
return null;
return (
<Portal container={document.getElementById('dropdown-portal-root')}>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 10 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2 }}
style={{
position: 'absolute',
top: popoverPosition.bottom + window.scrollY,
left: popoverPosition.left + window.scrollX,
width: popoverPosition.width,
zIndex: 1000,
}}
>
<Box
sx={{
width: '100%',
zIndex: 1500,
maxHeight: 300,
overflowY: 'auto',
bgcolor: 'background.paper',
border: `1px solid ${theme.palette.divider}`,
borderRadius: 1,
boxShadow: 3,
'::-webkit-scrollbar-track': {
backgroundColor: 'transparent',
},
'::-webkit-scrollbar': {
width: '16px',
height: '10px',
},
'::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(63, 67, 80, 0.24)',
borderRadius: '8px',
backgroundClip: 'content-box',
border: '4px solid transparent',
transition: '0.3s background-color',
},
'::-webkit-scrollbar-thumb:hover': {
backgroundColor: 'rgba(63, 67, 80, 0.50)',
},
}}
>
<Box
onClick={() => {
setSearchHistory([]);
setShowPopover(false);
}}
sx={{
px: 2,
py: 1,
display: 'flex',
alignItems: 'center',
gap: 1,
cursor: 'pointer',
'&:hover': { backgroundColor: 'rgba(0,0,0,0.05)' },
}}
>
<ClearAllIcon fontSize="small" />
Clear search history
</Box>
{searchHistory.map((term, index) => (
<Box
key={index}
onClick={() => {
setFilterSearch(term);
setShowPopover(false);
if (isHydratedFilterMode) setFilterMode('all');
navigate(`/`);
onSearch(term);
}}
sx={{
px: 2,
py: 1,
display: 'flex',
alignItems: 'center',
gap: 1,
wordBreak: 'break-word',
cursor: 'pointer',
'&:hover': { backgroundColor: 'rgba(0,0,0,0.05)' },
}}
>
<HistoryIcon fontSize="small" />
{term}
</Box>
))}
</Box>
</motion.div>
</Portal>
);
};
return (
<>
{isSmall && (
{isSmall ? (
<>
<ButtonBase
onClick={() => {
setIsOpenSearch(true);
setTimeout(() => {
inputRef.current?.focus();
}, 250);
setTimeout(() => inputRef.current?.focus(), 250);
}}
>
<SearchIcon
sx={{
fontSize: '35px',
color: theme.palette.action.active,
}}
sx={{ fontSize: '35px', color: theme.palette.action.active }}
/>
</ButtonBase>
<AnimatePresence>
@@ -126,11 +248,12 @@ export const Search = () => {
style={{
display: 'flex',
position: 'fixed',
flexDirection: 'column',
zIndex: 1001,
top: '0px',
left: '0px',
right: '0px',
bottom: isFocused ? '0px' : 'unset',
top: 0,
left: 0,
right: 0,
bottom: isFocused ? 0 : 'unset',
height: isFocused ? 'unset' : '65px',
backgroundColor: theme.palette.background.paper2,
}}
@@ -144,6 +267,7 @@ export const Search = () => {
borderRadius: '0px',
height: '65px',
padding: '3px',
width: '100%',
}}
>
<ButtonBase
@@ -152,8 +276,7 @@ export const Search = () => {
setIsFocused(false);
if (filterSearchGlobal) {
setFilterSearch(filterSearchGlobal);
}
if (!filterSearchGlobal) {
} else {
setIsOpenSearch(false);
setFilterSearch('');
}
@@ -163,20 +286,16 @@ export const Search = () => {
setFilterSearchGlobal('');
setFilterSearch('');
}}
// sx={{
// visibility: filterSearchGlobal ? 'visible' : 'hidden',
// }}
>
<ArrowBackIcon
sx={{
color: theme.palette.action.active,
fontSize: '30px',
}}
/>
</ButtonBase>
<StyledInputBase
inputRef={inputRef}
// autoFocus
size="small"
placeholder="Search…"
inputProps={{
'aria-label': 'search',
@@ -186,89 +305,165 @@ export const Search = () => {
value={filterSearch}
onChange={(e) => setFilterSearch(e.target.value)}
onKeyDown={handleInputKeyDown}
onFocus={() => setIsFocused(true)}
onFocus={handleFocus}
sx={{
height: '100%',
'& .MuiInputBase-input': {
padding: '0px',
height: '100%',
},
}}
/>
<SearchIconWrapper>
<ButtonBase
onClick={() => {
// setFilterSearchGlobal('');
setFilterSearch('');
inputRef?.current?.focus();
}}
sx={{
visibility: filterSearch ? 'visible' : 'hidden',
}}
sx={{ visibility: filterSearch ? 'visible' : 'hidden' }}
>
<CloseIcon
sx={{
color: theme.palette.action.active,
fontSize: '30px',
}}
/>
</ButtonBase>
</SearchIconWrapper>
</SearchParent>
{searchHistory?.length > 0 && isFocused && (
<Box
sx={{
width: '100%',
flexGrow: 1,
overflowY: 'auto',
bgcolor: 'background.paper',
border: `1px solid ${theme.palette.divider}`,
boxShadow: 3,
'::-webkit-scrollbar-track': {
backgroundColor: 'transparent',
},
'::-webkit-scrollbar': {
width: '16px',
height: '10px',
},
'::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(63, 67, 80, 0.24)',
borderRadius: '8px',
backgroundClip: 'content-box',
border: '4px solid transparent',
transition: '0.3s background-color',
},
'::-webkit-scrollbar-thumb:hover': {
backgroundColor: 'rgba(63, 67, 80, 0.50)',
},
}}
>
<Box
onClick={() => {
setSearchHistory([]);
setShowPopover(false);
}}
sx={{
px: 2,
py: 1,
display: 'flex',
alignItems: 'center',
gap: 1,
cursor: 'pointer',
'&:hover': { backgroundColor: 'rgba(0,0,0,0.05)' },
}}
>
<ClearAllIcon fontSize="small" />
Clear search history
</Box>
{searchHistory.map((term, index) => (
<Box
key={index}
onClick={() => {
setFilterSearch(term);
setShowPopover(false);
if (isHydratedFilterMode) setFilterMode('all');
navigate(`/`);
onSearch(term);
setIsFocused(false);
}}
sx={{
px: 2,
py: 1,
display: 'flex',
alignItems: 'center',
gap: 1,
wordBreak: 'break-word',
cursor: 'pointer',
'&:hover': { backgroundColor: 'rgba(0,0,0,0.05)' },
}}
>
<HistoryIcon fontSize="small" />
{term}
</Box>
))}
</Box>
)}
</motion.div>
)}
</AnimatePresence>
</>
)}
{!isSmall && (
<SearchParent>
<StyledInputBase
size="small"
placeholder="Search…"
inputProps={{ 'aria-label': 'search' }}
value={filterSearch}
onChange={(e) => setFilterSearch(e.target.value)}
onKeyDown={handleInputKeyDown}
/>
<SearchIconWrapper>
<ButtonBase
onClick={() => {
setFilterSearchGlobal('');
setFilterSearch('');
}}
sx={{
visibility: filterSearchGlobal ? 'visible' : 'hidden',
}}
>
<CloseIcon
sx={{
color: theme.palette.action.active,
}}
) : (
<ClickAwayListener onClickAway={handleClickAway}>
<Box sx={{ position: 'relative' }}>
<SearchParent ref={searchWrapperRef}>
<StyledInputBase
inputRef={inputRef}
size="small"
placeholder="Search…"
inputProps={{ 'aria-label': 'search' }}
value={filterSearch}
onChange={(e) => setFilterSearch(e.target.value)}
onKeyDown={handleInputKeyDown}
onFocus={handleFocus}
/>
</ButtonBase>
<ButtonBase
sx={{
height: '100%',
}}
onClick={() => {
onSearch();
if (isHydratedFilterMode) {
setFilterMode('all');
}
navigate(`/`);
}}
>
<Box
sx={{
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
bgcolor: 'primary.main',
width: '40px',
}}
>
<SearchIcon
sx={{
color: 'background.paper2',
<SearchIconWrapper>
<ButtonBase
onClick={() => {
setFilterSearchGlobal('');
setFilterSearch('');
inputRef?.current?.focus();
}}
/>
</Box>
</ButtonBase>
</SearchIconWrapper>
</SearchParent>
sx={{ visibility: filterSearchGlobal ? 'visible' : 'hidden' }}
>
<CloseIcon sx={{ color: theme.palette.action.active }} />
</ButtonBase>
<ButtonBase
sx={{ height: '100%' }}
onClick={() => {
onSearch();
if (isHydratedFilterMode) setFilterMode('all');
navigate(`/`);
}}
>
<Box
sx={{
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
bgcolor: 'primary.main',
width: '40px',
}}
>
<SearchIcon sx={{ color: 'background.paper2' }} />
</Box>
</ButtonBase>
</SearchIconWrapper>
</SearchParent>
{renderPopover()}
</Box>
</ClickAwayListener>
)}
</>
);

View File

@@ -62,8 +62,6 @@ export const Bookmarks = () => {
.sort((a, b) => a.title.localeCompare(b.title));
}, [bookmarks]);
console.log('sortedLists', sortedLists);
const handleChange = (event) => {
const listId = event.target.value;
if (!listId) {
@@ -123,7 +121,6 @@ export const Bookmarks = () => {
) => {
setBookmarks((prev) => {
const list = prev[listId];
console.log('list', list);
if (!list || list.type !== 'list') return prev;
const updatedVideos = list.videos.filter(
@@ -134,7 +131,6 @@ export const Bookmarks = () => {
v.service === video.service
)
);
console.log('updatedVideos', updatedVideos);
// If no change, avoid unnecessary update
if (updatedVideos.length === list.videos.length) return prev;

View File

@@ -56,7 +56,6 @@ export const PlaylistContent = () => {
const isSmall = useIsSmall();
const isScreenSmall = !useMediaQuery(`(min-width:950px)`);
const { s, n, i } = useParams();
console.log({ s, n, i });
const playlistsSX: SxProps<Theme> = isScreenSmall
? { width: '100%', marginTop: '10px' }
: { width: '35%', position: 'absolute', right: '20px' };

View File

@@ -34,7 +34,6 @@ export const VideoActionsBar = ({
setSuperLikeList,
sx,
}: VideoActionsBarProps) => {
console.log('videoData', videoData);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superLikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))

View File

@@ -25,7 +25,6 @@ export const useVideoContentState = () => {
};
const { resource } = usePublish(2, 'JSON', metadata);
console.log('resource', resource);
const [superLikeversion, setSuperLikeVersion] = useState<null | number>(null);
const [isExpandedDescription, setIsExpandedDescription] =
useState<boolean>(false);

View File

@@ -40,9 +40,9 @@ export const useSidebarState = () => {
const [selectedSubCategoryVideosState, setSelectedSubCategoryVideosState] =
useState(null);
const onSearch = () => {
const onSearch = (term?: string) => {
setFilterType(filterStateType);
setFilterSearch(filterStateSearch);
setFilterSearch(term || filterStateSearch);
setFilterName(filterStateName);
setFilterCategory(selectedCategoryVideosState);
setFilterSubCategory(selectedSubCategoryVideosState);

View File

@@ -50,6 +50,7 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
width: '100%',
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
}}
>
<Typography>No results</Typography>
@@ -58,7 +59,6 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
}
return <VideoLoaderItem status={status} />;
}, []);
console.log('rendering');
const renderListItem = useCallback(
(item, index) => {

View File

@@ -38,7 +38,6 @@ export const VideoListPreloaded = ({
listId,
handleRemoveVideoFromList,
}: VideoListProps) => {
console.log('videoList', videoList);
const { name: username } = useAuth();
const setEditVideo = useSetAtom(editVideoAtom);
const { addToBlockedList } = useBlockedNames();
@@ -60,7 +59,6 @@ export const VideoListPreloaded = ({
}, []);
const renderLoaderList = useCallback((status: LoaderListStatus) => {
console.log('status', status);
if (status === 'NO_RESULTS') {
return (
<Box

View File

@@ -92,7 +92,6 @@ export const FilterOptions = () => {
setFilterMode(query.filterMode);
}
if (query?.filterCategory) {
console.log('filterCat');
setFilterCategory({ id: query.filterCategory });
} else {
setFilterCategory('');
@@ -147,8 +146,6 @@ export const FilterOptions = () => {
];
}, [filterMode, filterCategory]);
console.log('filterType', filterType);
const handleClose = () => {
setIsOpen(false);
};
@@ -165,7 +162,7 @@ export const FilterOptions = () => {
'& .MuiTabs-indicator': {
backgroundColor: 'white',
},
width: `calc(100vw)`, // Ensure the tabs container fits within the available space
width: `100%`, // Ensure the tabs container fits within the available space
overflow: 'hidden', // Prevents overflow on small screens
backgroundColor: theme.palette.background.paper,
marginBottom: '5px',

View File

@@ -8,7 +8,6 @@ export const useHomeState = () => {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('query'); // "example"
// const page = searchParams.get('page'); // "2"
console.log('query', query);
const [videoListTab, setVideoListTab, isHydratedVideoListTab] =
usePersistedState<VideoListType>('videoListTab', 'all');
const [filterName, setFilterName, isHydratedFilterName] = usePersistedState(
@@ -27,21 +26,11 @@ export const useHomeState = () => {
);
const [filterSearch, setFilterSearch, isHydratedFilterSearch] =
usePersistedState('filterSearch', '');
console.log('filterSearch', filterSearch);
const [filterCategory, setFilterCategory, isHydratedFilterCategory] =
usePersistedState<any>('filterCategory', '');
const [filterSubCategory, setFilterSubCategory, isHydratedFilterSubCategory] =
usePersistedState<any>('filterSubCategory', '');
console.log(
'hydration',
isHydratedFilterState,
isHydratedFilterSearch,
isHydratedFilterName,
isHydratedFilterSubCategory,
isHydratedFilterCategory,
isHydratedFilterMode,
!isLoadingUser
);
const isHydrated =
isHydratedFilterState &&
isHydratedFilterSearch &&

View File

@@ -21,8 +21,6 @@ export const Home = () => {
const scrollRef = useAtomValue(scrollRefAtom);
const query = searchParams.get('query'); // "example"
// const page = searchParams.get('page'); // "2"
console.log('query', query);
const {
tabValue,
changeTab,
@@ -83,8 +81,6 @@ export const Home = () => {
subscriptions,
]);
console.log('searchParameters', searchParameters, isHydrated);
return (
<PageTransition>
<Box>

View File

@@ -17,7 +17,6 @@ export const Search = () => {
const mode = searchParams.get('mode'); // "example"
// const page = searchParams.get('page'); // "2"
console.log('query', query);
const {
tabValue,
changeTab,

View File

@@ -22,7 +22,6 @@ export const Subscriptions = () => {
const query = searchParams.get('query'); // "example"
// const page = searchParams.get('page'); // "2"
const { isHydrated, subscriptions } = useHomeState();
console.log('subscriptions', subscriptions);
const searchParameters: QortalSearchParams | null = useMemo(() => {
if (!subscriptions || subscriptions?.length === 0) return [];
@@ -47,8 +46,6 @@ export const Subscriptions = () => {
};
}, [isHydrated, subscriptions]);
console.log('subs');
return (
<PageTransition>
<Box

View File

@@ -14,7 +14,6 @@ const hydrationStatusCache = new Map<
export function usePersistAtom<T>(key: string, initialValue: T) {
const { address: authAddress } = useAuth();
const address = authAddress || 'anonymous';
console.log('address', address);
const scopedKey = `${address}/${key}`;
return useMemo(() => {