Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions client/src/assets/styles/search.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.search-sort-container {
width: 100%;
display: grid;
grid-auto-flow: column;
justify-content: start;
}

.search-container {

}

.search-input {
display: block;
width: 400px;
margin-top: 10px;
margin-left: 10px;
padding: 10px 50px 10px 20px;
font-size: 14px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
color: gray;
border-radius: 50px;
border: 1px solid gray;
}

.sort-button {
display: grid;
padding: 12px 15px;
margin: 10px;
border-radius: 50px;
border: 1px solid gray;
color: var(--primary-600);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 12px;
font-weight: bolder;
}

.sort-list {
padding: 5px;
padding-right: 30px;
background-color: white;
border: 1px solid gray;
border-radius: 5px;
font-size: 12px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
:hover {
color: #ffa630;
cursor: pointer;
}
}

.active {
color: #ffa630;
}

.search {
position: relative;

button {
position: absolute;
top:0px;
right:5px;
width: 40px;
height: 100%;
cursor: pointer;
background-color: transparent;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
border-radius: 20px;
border: 0;
}
}
38 changes: 38 additions & 0 deletions client/src/components/Search.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState } from 'react';
import '../assets/styles/search.scss';
import { useAppContext } from '../context/appContext';

const Search = (props) => {
const [search, setSearch] = useState({
value: ''
});

const { getSubscriptions } = useAppContext();

const handleSubmit = () => {
event.preventDefault();
getSubscriptions({type: props.type, sort: null, search: search.value})
};
const handleChange = event => {
setSearch({value: event.target.value});
};

return (
<div className='search-container'>
<form className='search' onSubmit={handleSubmit}>
<input
className='search-input'
type='text'
value={search.value}
placeholder='Search...'
onChange={handleChange}
/>
<button type='submit'>
<i className='fa-search'></i>
</button>
</form>
</div>
);
};

export default Search;
69 changes: 69 additions & 0 deletions client/src/components/Sort.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useState } from 'react';
import { useAppContext } from '../context/appContext';
import '../assets/styles/search.scss';

const Sort = (props) => {
//Initialize the sort button's open state to false
//Otherwise, the dropdown will be open upon initial render
const [sortStatus, setSortStatus] = useState({
isOpen: false,
activeSortOpt: '',
});

const { activeSortOpt } = sortStatus;
const { getSubscriptions } = useAppContext();

//Sort dropdown options
const sortOptions = ['Alphabetical', 'Cost', 'Payment Due'];

const handleSortOptClick = (e) => {
e.preventDefault();
getSubscriptions({type: props.type, sort: e.target.innerText.toLowerCase()});
setSortStatus({ isOpen: false, activeSortOpt: e.target.innerText });
};

//handler that is invoked upon clicking on sort button, toggling its open status to the opposite boolean
const handleSortClick = () => {
//invoke funtion in appContext here
//dispatch action?
setSortStatus((sortStatus) => {
return {
...sortStatus,
isOpen: !sortStatus.isOpen,
};
});
};

//Map dropdown options to li component
const sortListItems = sortOptions.map(el => {
//If the list item was selected, provide dynamic className for dynamic styling
//Will let user know which sort option is currently active
let activeClass = activeSortOpt === el ? 'active sort-list-item' : 'sort-list-item';

return (
<li
key={el}
className={activeClass}
//Handle click to update state to show this sort option is the active selection
onClick={handleSortOptClick}
>
{el}
</li>
);
});

return (
<div className='sort-container'>
<button type='button' className='sort-button' onClick={handleSortClick}>
Sort
</button>
{sortStatus.isOpen && (
<div className='sort-dropdown'>
<ul className='sort-list'>{sortListItems}</ul>
</div>
)}
</div>
);
};

export default Sort;
4 changes: 3 additions & 1 deletion client/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ import Alert from './Alert';
import Navbar from './Navbar';
import LargeSidebar from './LargeSidebar';
import SmallSidebar from './SmallSidebar';
import Search from './Search';
import Sort from './Sort';

export { Logo, FormRow, Alert, Navbar, LargeSidebar, SmallSidebar };
export { Logo, FormRow, Alert, Navbar, LargeSidebar, SmallSidebar, Search, Sort };
8 changes: 8 additions & 0 deletions client/src/context/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const GET_CURRENT_USER_SUCCESS = 'GET_CURRENT_USER_SUCCESS';

const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR';

//Subscriptions
const GET_SUBSCRIPTIONS_SUCCESS = 'GET_SUBSCRIPTIONS_SUCCESS';
const GET_SUBSCRIPTIONS_ERROR = 'GET_SUBSCRIPTIONS_ERROR';
const GET_SUBSCRIPTIONS_BEGIN = 'GET_SUBSCRIPTIONS_BEGIN';

export {
DISPLAY_ALERT,
REMOVE_ALERT,
Expand All @@ -36,4 +41,7 @@ export {
GET_CURRENT_USER_BEGIN,
GET_CURRENT_USER_SUCCESS,
TOGGLE_SIDEBAR,
GET_SUBSCRIPTIONS_SUCCESS,
GET_SUBSCRIPTIONS_ERROR,
GET_SUBSCRIPTIONS_BEGIN,
};
39 changes: 38 additions & 1 deletion client/src/context/appContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
GET_CURRENT_USER_BEGIN,
GET_CURRENT_USER_SUCCESS,
TOGGLE_SIDEBAR,
GET_SUBSCRIPTIONS_BEGIN,
GET_SUBSCRIPTIONS_SUCCESS,
GET_SUBSCRIPTIONS_ERROR,
} from './actions';

const initialState = {
Expand All @@ -36,7 +39,7 @@ const AppProvider = ({ children }) => {
// Axios config
// TODO move to separate file
const authFetch = axios.create({
baseURL: '/api/v1/',
baseURL: '/api/v1',
});

// alternative way to add token to header
Expand Down Expand Up @@ -196,6 +199,39 @@ const AppProvider = ({ children }) => {
getCurrentUser();
}, []);

//do we want to consider optional parameters at all?
const getSubscriptions = async ({ type, sort, search }) => {
const url = `/subscriptions?status=${type}&sort=${sort}&search=${search}`;

try{
dispatch({ type: GET_SUBSCRIPTIONS_BEGIN });

const { data } = await authFetch.get(url);
if (!data) {
throw new Error('Subscriptions not found');
}
const { subscriptions } = data;
if (!subscriptions) {
throw new Error('Subscriptions not found');
}

dispatch({
type: GET_SUBSCRIPTIONS_SUCCESS,
payload: { subscriptions }
});
}
catch(error){
dispatch( {
type: GET_SUBSCRIPTIONS_ERROR,
payload: {
message:
error.response?.data?.message || error.message || 'Getting subscriptions failed'
}
});
}
clearAlert();
};

return (
<AppContext.Provider
value={{
Expand All @@ -207,6 +243,7 @@ const AppProvider = ({ children }) => {
toggleSidebar,
logoutUser,
updateUser,
getSubscriptions,
}}
>
{children}
Expand Down
29 changes: 29 additions & 0 deletions client/src/context/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
GET_CURRENT_USER_BEGIN,
GET_CURRENT_USER_SUCCESS,
TOGGLE_SIDEBAR,
GET_SUBSCRIPTIONS_BEGIN,
GET_SUBSCRIPTIONS_ERROR,
GET_SUBSCRIPTIONS_SUCCESS,
} from './actions';

import { initialState } from './appContext';
Expand Down Expand Up @@ -151,6 +154,32 @@ const reducer = (state, action) => {
};
}

if (action.type === GET_SUBSCRIPTIONS_BEGIN) {
return { ...state, isLoading: true };
}

if (action.type === GET_SUBSCRIPTIONS_SUCCESS) {
return {
...state,
isLoading: false,
subscriptions: action.payload.subscriptions,
}
}

if (action.type === GET_SUBSCRIPTIONS_ERROR) {
return {
...state,
isLoading: false,
showAlert: true,
alert: {
type: 'danger',
message:
action.payload.message ||
'Unexpected Error. Subscriptions could not be retrieved.',
}
}
}

throw new Error(`Unhandled action type: ${action.type}`);
};

Expand Down
21 changes: 18 additions & 3 deletions client/src/pages/dashboard/Active.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
const Active = () => {
return <div>Active Subscription</div>;
import { Search, Sort } from '../../components';
import { useAppContext } from '../../context/appContext';
import '../../assets/styles/search.scss';

const Active = () => {
return (
<div>
<div>Active Subscriptions</div>
{/* add a div container to contain the search filter and sort components w/ className for styling*/}
<div className='search-sort-container'>
<Search type='active'/>
<Sort type='active'/>
</div>

{/* render all cards that have been mapped to an array and/or retrieved from state/contexAPI */}
</div>
);
};

export default Active;
export default Active;
14 changes: 13 additions & 1 deletion client/src/pages/dashboard/Past.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { Search, Sort } from '../../components';
import { useAppContext } from '../../context/appContext';
import '../../assets/styles/search.scss';

const Past = () => {
return <div>Past</div>;
return (
<div>
<div>Past Subscriptions</div>
<div className="search-sort-container">
<Search type='past'/>
<Sort type='past'/>
</div>
</div>
);
};

export default Past;
14 changes: 13 additions & 1 deletion client/src/pages/dashboard/Trial.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { Search, Sort } from '../../components';
import { useAppContext } from '../../context/appContext';
import '../../assets/styles/search.scss';

const Trial = () => {
return <div>Trial</div>;
return (
<div>
<div>Trial Subscriptions</div>
<div className="search-sort-container">
<Search type='trial'/>
<Sort type='trial'/>
</div>
</div>
);
};

export default Trial;
Loading