Skip to content

Commit

Permalink
Merge pull request #22 from daodaoedu/feature/2.1_partner_add_api
Browse files Browse the repository at this point in the history
feat(partner): Deal with Fetching Search Partners results
  • Loading branch information
hsuifang authored Jan 2, 2024
2 parents 0e7240f + 9991c64 commit 47c4b64
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 51 deletions.
3 changes: 2 additions & 1 deletion components/Partner/Parnter.styled.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Box } from '@mui/material';
export const StyledWrapper = styled.div`
position: relative;
margin: 70px auto 0;
padding-bottom: 14px;
width: 100%;
max-width: 1024px;
min-height: 100vh;
margin-top: -80px;
@media (max-width: 900px) {
padding: 0 16px;
padding: 0 16px 44px;
margin-top: -50px;
}
`;
Expand Down
1 change: 0 additions & 1 deletion components/Partner/PartnerList/PartnerCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function PartnerCard({

return (
<StyledCard>
{/* TODO: href redirect */}
<StyledCardContainer>
<FlexAlignCenter mb="8px">
<PartnerCardAvator image={image} />
Expand Down
26 changes: 12 additions & 14 deletions components/Partner/PartnerList/index.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { useEffect, Fragment } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchAllPartners } from '@/redux/actions/partners';
import { Fragment } from 'react';
import { useRouter } from 'next/router';
import { useSelector } from 'react-redux';
import useMediaQuery from '@mui/material/useMediaQuery';
import { Grid, Box } from '@mui/material';
import PartnerCard from './PartnerCard';

function PartnerList() {
const partners = useSelector((state) => state.partners);
const dispatch = useDispatch();

// TODO: ADD PAGE
const handleFetchData = () => {
dispatch(fetchAllPartners());
};
const router = useRouter();

useEffect(() => {
handleFetchData();
}, []);
const partners = useSelector((state) => state.partners);

const lists = partners.items || [];
const mobileScreen = useMediaQuery('(max-width: 900px)');
Expand All @@ -34,7 +26,13 @@ function PartnerList() {
>
{lists.map((item, idx) => (
<Fragment key={`${item._id}`}>
<Grid item width="100%" md={6} mb={mobileScreen && '12px'}>
<Grid
onClick={() => router.push(`partner/${item._id}`)}
item
width="100%"
md={6}
mb={mobileScreen && '12px'}
>
<PartnerCard
image={item.photoURL}
date={item.date}
Expand Down
17 changes: 6 additions & 11 deletions components/Partner/SearchParamsList/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ import {
StyledClosed,
} from './SearchParamsList.styles';

const SearchParamsList = ({ paramsKey = [], keySelections = {} }) => {
const [getSearchParams, pushState] = useSearchParamsManager();
const SearchParamsList = ({ paramsKey = [], paramsKeyOptions = {} }) => {
const [getSearchParams, pushState, genParamsItems] = useSearchParamsManager();

const handleGenerateParams = (params) => {
return params.reduce((acc, param) => {
const values = getSearchParams(param).filter((value) =>
keySelections[param]?.includes(value),
);
return [...acc, { key: param, values }];
}, []);
};
const params = useMemo(
() => (Array.isArray(paramsKey) ? handleGenerateParams(paramsKey) : []),
() =>
Array.isArray(paramsKey)
? genParamsItems(paramsKey, paramsKeyOptions)
: [],
[getSearchParams],
);

Expand Down
90 changes: 80 additions & 10 deletions components/Partner/index.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useSelector } from 'react-redux';
import { useEffect, useMemo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Box, Button } from '@mui/material';
import useMediaQuery from '@mui/material/useMediaQuery';
import { AREAS } from '@/constants/areas';
import { fetchPartners } from '@/redux/actions/partners';
import { EDUCATION_STEP, ROLE } from '@/constants/member';
import { SEARCH_TAGS } from '@/constants/category';
import useSearchParamsManager from '@/hooks/useSearchParamsManager';

import PartnerList from './PartnerList';
import SearchField from './SearchField';
import SearchParamsList from './SearchParamsList';
Expand All @@ -13,11 +18,79 @@ import {
StyledSearchWrapper,
} from './Parnter.styled';

// utils
const _compose =
(...fns) =>
(x) =>
fns.reduceRight((v, f) => f(v), x);
const _map = (arr, key) => arr.map((item) => item[key]);
const mapValues = (values, mapFn) => values.map(mapFn).join(',');

const createObjFromArrary = (arr, keyProp = 'label', valueProp = 'label') => {
return arr.reduce(
(obj, item) => ({
...obj,
[item[keyProp]]: item[valueProp],
}),
{},
);
};

// constants
const keySelections = {
area: _map(AREAS, 'name'),
edu: _map(EDUCATION_STEP, 'label'),
role: _map(ROLE, 'label'),
tag: SEARCH_TAGS['全部'],
q: 'PASS_STRING',
};

const eduObj = createObjFromArrary(EDUCATION_STEP);
const roleObj = createObjFromArrary(ROLE);

function Partner() {
const dispatch = useDispatch();
const mobileScreen = useMediaQuery('(max-width: 767px)');

// main data
const partners = useSelector((state) => state.partners);
const { page: current = 1, totalPages } = partners.pagination;

// queryStr
const [getSearchParams, _, generateParamsItems] = useSearchParamsManager();
const searchParamsItems = useMemo(
() =>
generateParamsItems(['area', 'role', 'edu', 'tag', 'q'], keySelections),
[getSearchParams],
);

// fetch api - params
const findValues = (params, key) =>
params.find((item) => item.key === key)?.values;
const prepareData = _compose(
([location, educationStage, roleList, tag, search]) => ({
location,
educationStage,
roleList,
tag,
search,
}),
(arg) => [
findValues(arg, 'area').join(','),
mapValues(findValues(arg, 'edu'), (item) => eduObj[item]),
mapValues(findValues(arg, 'role'), (item) => roleObj[item]),
findValues(arg, 'tag').join(','),
findValues(arg, 'q').join(','),
],
);

const handleFetchData = (page = 1) => {
dispatch(fetchPartners({ page, ...prepareData(searchParamsItems) }));
};

useEffect(() => {
handleFetchData();
}, [getSearchParams]);

return (
<>
Expand All @@ -28,24 +101,21 @@ function Partner() {
</StyledSearchWrapper>
<StyledContent>
<SearchParamsList
paramsKey={['area', 'role', 'edu']}
keySelections={{
area: _map(AREAS, 'name'),
edu: _map(EDUCATION_STEP, 'label'),
role: _map(ROLE, 'label'),
}}
paramsKey={['area', 'role', 'edu', 'tag', 'q']}
keySelections={keySelections}
/>
<PartnerList />
</StyledContent>
{partners.items.length > 0 && (
{partners.items.length > 0 && current < totalPages && (
<Box
sx={
mobileScreen
? { textAlign: 'center', padding: '32px 0 80px' }
: { textAlign: 'center', padding: '72px 0 100px' }
? { textAlign: 'center', padding: '32px 0' }
: { textAlign: 'center', padding: '72px 0' }
}
>
<Button
onClick={() => handleFetchData(current + 1)}
variant="outlined"
sx={{
fontSize: '16px',
Expand Down
17 changes: 16 additions & 1 deletion hooks/useSearchParamsManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,20 @@ export default function useSearchParamsManager() {
[push, searchParams],
);

return [getSearchParams, pushState];
const generateParamsItems = useCallback(
(arr, keyObj = {}) => {
if (!Array.isArray(arr)) return [];
return arr.reduce((acc, param) => {
const values = getSearchParams(param).filter((value) =>
keyObj[param] === 'PASS_STRING'
? value
: keyObj[param]?.includes(value),
);
return [...acc, { key: param, values }];
}, []);
},
[searchParams],
);

return [getSearchParams, pushState, generateParamsItems];
}
9 changes: 7 additions & 2 deletions redux/actions/partners.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export function fetchAllPartners() {
export function fetchPartners({ pageSize = 10, page = 1, ...rest } = {}) {
return {
type: 'FETCH_ALL_PARTNERS',
type: 'FETCH_PARTNERS',
payload: {
page,
pageSize,
...rest,
},
};
}
21 changes: 17 additions & 4 deletions redux/reducers/partners.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
const initialState = {
items: [],
pagination: {
pageSize: 10,
page: 1,
totalCount: 0,
totalPages: 0,
},
};

const reducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_ALL_PARTNERS_SUCCESS': {
case 'FETCH_PARTNERS_MORE_SUCCESS': {
return {
...state,
items: action.payload,
items: [...state.items, ...action.payload.data],
pagination: action.payload.pagination,
};
}
case 'FETCH_ALL_PARTNERS_FAILURE': {
case 'FETCH_PARTNERS_SUCCESS': {
return {
...initialState,
items: action.payload.data,
pagination: action.payload.pagination,
};
}
case 'FETCH_PARTNERS_FAILURE': {
return {
...initialState,
};
Expand Down
35 changes: 28 additions & 7 deletions redux/sagas/partnersSaga.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { put, takeEvery } from 'redux-saga/effects';

function* fetchAllPartners() {
function* fetchPartnersResource(action) {
const { pageSize = 10, page = 1, ...rest } = action.payload;

const startParams = `page=${page}&pageSize=${pageSize}`;
const searchKey = ['educationStage', 'roleList', 'location', 'tag', 'search'];

const queryStr = Object.entries(rest).reduce((acc, [key, val]) => {
return val && searchKey.includes(key) ? `${acc}&${key}=${val}` : acc;
}, startParams);

try {
const baseUrl =
process.env.NEXT_PUBLIC_API_URL || 'https://daodao-server.onrender.com';
const URL = `${baseUrl}/user/all_User`;
const URL = `${baseUrl}/user?${queryStr}`;
const result = yield fetch(URL).then((res) => res.json());
yield put({ type: 'FETCH_ALL_PARTNERS_SUCCESS', payload: result });
yield put({
type:
page !== 1 ? 'FETCH_PARTNERS_MORE_SUCCESS' : 'FETCH_PARTNERS_SUCCESS',
payload: {
data: result.data,
pagination: {
pageSize: +result.pageSize || 10,
page: +result.page,
totalPages: +result.totalPages,
totalCount: +result.totalCount,
},
},
});
} catch (error) {
yield put({ type: 'FETCH_ALL_PARTNERS_FAILURE' });
yield put({ type: 'FETCH_PARTNERS_FAILURE' });
}
}

function* userSaga() {
yield takeEvery('FETCH_ALL_PARTNERS', fetchAllPartners);
function* partnerSaga() {
yield takeEvery('FETCH_PARTNERS', fetchPartnersResource);
}

export default userSaga;
export default partnerSaga;

1 comment on commit 47c4b64

@vercel
Copy link

@vercel vercel bot commented on 47c4b64 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

daodao-f2e – ./

daodao-f2e-git-dev-daodaoedu.vercel.app
daodao-f2e.vercel.app
daodao-f2e-daodaoedu.vercel.app

Please sign in to comment.