Skip to content

Commit

Permalink
Merge pull request #87 from vatsalsinghkv/feat--local-storage
Browse files Browse the repository at this point in the history
feat: filters save in local storage
  • Loading branch information
vatsalsinghkv authored Oct 31, 2024
2 parents 0157cdd + db2ffdd commit 228bfc2
Show file tree
Hide file tree
Showing 16 changed files with 412 additions and 262 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
"settings": { "react": { "version": "18.2" } },
"plugins": ["react-refresh", "unused-imports", "vitest"],
"parser": "@babel/eslint-parser",
"rules": {
"react-refresh/only-export-components": [
"warn",
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.18.0
v20.6.0
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
// Tailwind CSS Intellisense
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"aaron-bond.better-comments"
]
}
2 changes: 1 addition & 1 deletion src/__tests__/hooks/useUrlValues.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
DEFAULT_PAGE,
DEFAULT_SORTING_TAG,
} from '@/lib/utils/config';
import UrlProvider, { useUrlValues } from '@/providers/urlProvider';
import UrlProvider, { useUrlValues } from '@/providers/urlProvider/reducer';
import { act, render } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest';

Expand Down
62 changes: 28 additions & 34 deletions src/containers/Filter.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
import { MiniContainer, Select } from '@/components';
import SortingTagFilter from '@/components/SortingTagFilter';
import { useFilter } from '@/lib/hooks/use-filter';
import { toId } from '@/lib/utils';
import { Label, sortedLabels } from '@/models/Label';
import { Language, sortedLanguages } from '@/models/Language';
import { SortingTag, sortedSortingTags } from '@/models/SortingTag';
import { useUrlValues } from '@/providers/urlProvider';
import { FormEvent, useState } from 'react';
import { sortedLanguages } from '@/models/Language';
import { sortedSortingTags } from '@/models/SortingTag';

const Filter = () => {
const [customLabel, setCustomLabel] = useState("");

const [labels, setLabels] = useState(sortedLabels);

const customLabelHandler = (e: FormEvent) => {
e.preventDefault();
setLabels([...labels, customLabel]);
onLabelChange(customLabel);
setCustomLabel("");
}

const { dispatch, language, ordering, sortingTag, label } = useUrlValues();

const onLanguageChange = (payload: Language) => {
return () => dispatch({ type: 'update-language', payload });
};

const onLabelChange = (payload: Label) => {
return () => dispatch({ type: 'update-label', payload });
};

const onSortingTagClick = (payload: SortingTag) => {
return () => dispatch({ type: 'update-sorting-tag', payload });
};
const {
language,
label,
labels,
ordering,
sortingTag,
customLabel,
customLabelHandler,
languageChangeHandler,
labelsChangeHandler,
sortingTagClickHandler,
setCustomLabel,
} = useFilter();

return (
<>
Expand All @@ -43,7 +30,7 @@ const Filter = () => {
value={lang}
checked={lang === language}
name={lang}
onChange={onLanguageChange(lang)}
onChange={languageChangeHandler(lang)}
/>
</li>
))}
Expand All @@ -58,16 +45,23 @@ const Filter = () => {
value={l}
checked={l === label}
name={l}
onChange={onLabelChange(l)}
onChange={labelsChangeHandler(l)}
/>
</li>
))}
</ul>
<form className='mt-2' onSubmit={(e) => { customLabelHandler(e) }}>
<form
className='mt-2'
onSubmit={(e) => {
customLabelHandler(e);
}}
>
<input
value={customLabel}
type='input'
onChange={(e) => { setCustomLabel(e.target.value) }}
onChange={(e) => {
setCustomLabel(e.target.value);
}}
placeholder='+ add custom label'
className='block bg-transparent p-3 py-1.5 font-mono text-xs capitalize transition-all border rounded cursor-pointer hover:text-accent hover:border-accent focus:text-accent focus:border-accent border-dark-2 peer-hover:border-accent peer-focus:border-accent peer-focus:text-accent peer-checked:text-accent peer-checked:border-accent peer-checked:bg-accent-light peer-focus:outline-none'
/>
Expand All @@ -80,7 +74,7 @@ const Filter = () => {
<li key={tag}>
<SortingTagFilter
isSelected={tag === sortingTag}
onClick={onSortingTagClick(tag)}
onClick={sortingTagClickHandler(tag)}
ordering={ordering}
value={tag}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Issues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getTotalPages, toId } from '@/lib/utils';
import httpGateway from '@/lib/utils/HttpGateway';
import { MAX_ISSUES_ALLOWED } from '@/lib/utils/config';
import { githubIssueSearchResponse } from '@/models/GithubIssueSearch';
import { useUrlValues } from '@/providers/urlProvider';
import { useUrlValues } from '@/providers/urlProvider/reducer';
import { useEffect } from 'react';

const Issues = () => {
Expand Down
3 changes: 3 additions & 0 deletions src/containers/Provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Provider({ children }: { children: React.ReactNode }) {
return;
}
Empty file added src/lib/api/requrest.ts
Empty file.
90 changes: 90 additions & 0 deletions src/lib/hooks/use-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Label, sortedLabels } from '@/models/Label';
import { Language } from '@/models/Language';
import { Ordering } from '@/models/Ordering';
import { SortingTag } from '@/models/SortingTag';
import { useUrlValues } from '@/providers/urlProvider/reducer';
import { FormEvent, createContext, useContext, useState } from 'react';

type FilterContextType = {
customLabel: string;
language: Language;
label: string;
ordering: Ordering;
sortingTag: SortingTag;
labels: Label[];
customLabelHandler: (e: FormEvent) => void;
setCustomLabel: (label: string) => void;
languageChangeHandler: (payload: Language) => () => void;
labelsChangeHandler: (payload: Label) => () => void;
sortingTagClickHandler: (payload: SortingTag) => () => void;
};

const initialState: FilterContextType = {
customLabel: '',
language: 'all',
label: 'hacktoberfest',
ordering: 'asc',
sortingTag: 'best-match',
labels: sortedLabels,
customLabelHandler: () => {},
setCustomLabel: () => {},
languageChangeHandler: () => () => {},
labelsChangeHandler: () => () => {},
sortingTagClickHandler: () => () => {},
};

const FilterContext = createContext<FilterContextType>(initialState);

export default function FilterProvider({
children,
}: {
children: React.ReactNode;
}) {
const [customLabel, setCustomLabel] = useState<string>('');
const [labels, setLabels] = useState<Label[]>(sortedLabels);

const customLabelHandler = (e: FormEvent) => {
e.preventDefault();
setLabels([...labels, customLabel]);
labelsChangeHandler(customLabel as Label)();
setCustomLabel('');
};

const { dispatch, language, ordering, sortingTag, label } = useUrlValues();

const languageChangeHandler = (payload: Language) => {
return () => dispatch({ type: 'update-language', payload });
};

const labelsChangeHandler = (payload: Label) => {
return () => dispatch({ type: 'update-label', payload });
};

const sortingTagClickHandler = (payload: SortingTag) => {
return () => dispatch({ type: 'update-sorting-tag', payload });
};

return (
<FilterContext.Provider
value={{
customLabel,
label,
language,
ordering,
sortingTag,
labels,
customLabelHandler,
languageChangeHandler,
labelsChangeHandler,
sortingTagClickHandler,
setCustomLabel,
}}
>
{children}
</FilterContext.Provider>
);
}

export function useFilter() {
return useContext(FilterContext);
}
31 changes: 31 additions & 0 deletions src/lib/hooks/use-local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from 'react';

export default function useLocalStorage<T>(
key: string,
initialValue: T
): readonly [T, (val: T | ((val: T) => T)) => void, () => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
const value = window.localStorage.getItem(key);

if (value === 'true' || value === 'false') {
return JSON.parse(value) as T;
}

return value ? (value as T) : initialValue;
});

useEffect(() => {
if (typeof storedValue === 'boolean') {
window.localStorage.setItem(key, JSON.stringify(storedValue));
} else {
window.localStorage.setItem(key, storedValue as string);
}
}, [storedValue, key]);

const removeValue = () => {
window.localStorage.removeItem(key);
setStoredValue(initialValue);
};

return [storedValue, setStoredValue, removeValue] as const;
}
21 changes: 11 additions & 10 deletions src/lib/hooks/use-theme.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import { createContext, useCallback, useContext, useEffect } from 'react';

import useLocalStorage from './use-local-storage';

const initialState = {
isDarkMode: false,
Expand All @@ -26,10 +22,15 @@ export default function ThemeProvider({
}: {
children: React.ReactNode;
}) {
const [isDarkMode, setIsDarkMode] = useState<boolean>(
JSON.parse(localStorage.getItem('darkMode') ?? 'true')
const [isDarkMode, setIsDarkMode] = useLocalStorage<boolean>(
'darkMode',
true
);

/* const [isDarkMode, setIsDarkMode] = useState<boolean>(
JSON.parse(localStorage.getItem('darkMode') ?? 'true')
); */

const toggle = useCallback(() => {
setIsDarkMode((prev) => !prev);
}, []);
Expand All @@ -43,7 +44,7 @@ export default function ThemeProvider({
}, []);

useEffect(() => {
localStorage.setItem('darkMode', JSON.stringify(isDarkMode));
setIsDarkMode(isDarkMode);
if (isDarkMode) {
document.documentElement.classList.add('dark');
} else {
Expand Down
7 changes: 5 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import '@/index.css';
import React from 'react';
import ReactDOM from 'react-dom/client';

import FilterProvider from './lib/hooks/use-filter';
import ThemeProvider from './lib/hooks/use-theme';
import UrlProvider from './providers/urlProvider';
import UrlProvider from './providers/urlProvider/reducer';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<UrlProvider>
<ThemeProvider>
<App />
<FilterProvider>
<App />
</FilterProvider>
</ThemeProvider>
</UrlProvider>
</React.StrictMode>
Expand Down
58 changes: 0 additions & 58 deletions src/providers/urlProvider/index.tsx

This file was deleted.

Loading

0 comments on commit 228bfc2

Please sign in to comment.