Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge dev branch to prod #85

Merged
merged 9 commits into from
Oct 2, 2024
34 changes: 26 additions & 8 deletions components/Group/Form/Fields/TagsField.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useRef } from 'react';
import IconButton from '@mui/material/IconButton';
import FormHelperText from '@mui/material/FormHelperText';
import ClearIcon from '@mui/icons-material/Clear';
Expand All @@ -8,19 +8,20 @@ import { StyledChip, StyledTagsField } from '../Form.styled';
function TagsField({ name, helperText, control, value = [] }) {
const [input, setInput] = useState('');
const [error, setError] = useState('');
const isComposing = useRef(false);

const handleInput = (e) => {
const handleChange = (e) => {
const _value = e.target.value;
if (_value.length > 8) setError('標籤最多 8 個字');
else setError('');
setInput(_value);
};

const handleKeyDown = (e) => {
if (error) return;
const handleAddTag = () => {
const tag = input.trim();
if (e.key !== 'Enter' || !tag) return;
if (value.indexOf(tag) > -1) return;
if (!tag) return;
if (error) return;
if (value.includes(tag)) return;
setInput('');
control.onChange({
target: {
Expand All @@ -30,6 +31,12 @@ function TagsField({ name, helperText, control, value = [] }) {
});
};

const handleKeyDown = (e) => {
if (e.keyCode !== 13) return;
if (isComposing.current) return;
handleAddTag();
};

const handleDelete = (tag) => () => {
control.onChange({
target: {
Expand All @@ -54,12 +61,23 @@ function TagsField({ name, helperText, control, value = [] }) {
{value.length < 8 && (
<input
value={input}
onChange={handleInput}
onCompositionStart={() => {
isComposing.current = true;
}}
onCompositionEnd={() => {
isComposing.current = false;
}}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
)}
{input.trim() && (
<IconButton sx={{ textTransform: 'none' }} size="small" edge="end">
<IconButton
sx={{ textTransform: 'none' }}
size="small"
edge="end"
onClick={handleAddTag}
>
<AddCircleOutlineIcon />
</IconButton>
)}
Expand Down
71 changes: 71 additions & 0 deletions components/Group/GroupList/SkeletonGroupCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Skeleton } from '@mui/material';
import {
StyledContainer,
StyledFooter,
StyledGroupCard,
StyledInfo,
StyledText,
} from './GroupCard.styled';

function SkeletonGroupCard() {
return (
<StyledGroupCard href="#" sx={{ cursor: 'default' }}>
<Skeleton variant="rounded" width="100%" height={122} animation="wave" />
<StyledContainer>
<Skeleton
variant="text"
width="100%"
fontSize="14px"
animation="wave"
/>
<StyledInfo>
<StyledText>
<Skeleton
variant="text"
width="60%"
fontSize="64px"
animation="wave"
/>
</StyledText>
<StyledText>
<Skeleton
variant="text"
width="52.8%"
fontSize="12px"
animation="wave"
/>
</StyledText>
</StyledInfo>
<StyledText lineClamp="2" fontSize="14px" style={{ minHeight: '28px' }}>
<Skeleton
variant="text"
fontSize="12px"
width="100%"
animation="wave"
/>
<Skeleton
variant="text"
fontSize="12px"
width="100%"
animation="wave"
/>
</StyledText>
<StyledFooter>
<Skeleton
sx={{
height: '32px',
width: '52.8%',
marginLeft: 'auto',
}}
variant="text"
fontSize="12px"
width="27.2%"
animation="wave"
/>
</StyledFooter>
</StyledContainer>
</StyledGroupCard>
);
}

export default SkeletonGroupCard;
21 changes: 20 additions & 1 deletion components/Group/GroupList/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Box } from '@mui/material';
import useSearchParamsManager from '@/hooks/useSearchParamsManager';
import { setQuery } from '@/redux/actions/group';
import GroupCard from './GroupCard';
import SkeletonGroupCard from './SkeletonGroupCard';

export const StyledGroupItem = styled.li`
position: relative;
Expand Down Expand Up @@ -56,7 +57,25 @@ function GroupList() {
return (
<>
<StyledGroupList>
{items?.length || isLoading ? (
{isLoading ? (
// always show 3 || 6 skeleton cards in mobile || desktop screen
Array.from({ length: isMobileScreen ? 3 : 6 }, (_, index) => {
const isLast = index === (isMobileScreen ? 2 : 5);
const shouldRenderDivider =
(isMobileScreen && !isLast) ||
(isPadScreen && !isLast && index % 2 === 1) ||
(isDeskTopScreen && !isLast && index % 3 === 2);

return (
<Fragment key={index}>
<StyledGroupItem>
<SkeletonGroupCard />
</StyledGroupItem>
{shouldRenderDivider && <StyledDivider />}
</Fragment>
);
})
) : items?.length ? (
items.map((data, index) => {
const isLast = index === items.length - 1;
const shouldRenderDivider =
Expand Down
55 changes: 29 additions & 26 deletions components/Group/detail/OrganizerCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EDUCATION_STEP, ROLE } from '@/constants/member';
import locationSvg from '@/public/assets/icons/location.svg';
import Chip from '@/shared/components/Chip';
import { timeDuration } from '@/utils/date';
import Link from 'next/link';

const StyledHeader = styled.div`
display: flex;
Expand Down Expand Up @@ -82,33 +83,35 @@ function OrganizerCard({ data = {}, isLoading }) {
return (
<>
<StyledHeader>
<StyledFlex style={{ marginBottom: '10px', gap: 12 }}>
<Avatar
src={data?.user?.photoURL}
alt={`${data?.user?.name} avatar`}
/>
<div>
<StyledFlex style={{ gap: 10 }}>
<StyledText>
{isLoading ? (
<Skeleton width={80} animation="wave" />
) : (
data?.user?.name
)}
<Link href={`/partner/detail?id=${data?.userId}`}>
<StyledFlex style={{ marginBottom: '10px', gap: 12 }}>
<Avatar
src={data?.user?.photoURL}
alt={`${data?.user?.name} avatar`}
/>
<div>
<StyledFlex style={{ gap: 10 }}>
<StyledText>
{isLoading ? (
<Skeleton width={80} animation="wave" />
) : (
data?.user?.name
)}
</StyledText>
<StyledTag>
{isLoading ? (
<Skeleton width={80} animation="wave" />
) : (
educationStage
)}
</StyledTag>
</StyledFlex>
<StyledText style={{ color: '#92989A' }}>
{isLoading ? <Skeleton width={88} animation="wave" /> : role}
</StyledText>
<StyledTag>
{isLoading ? (
<Skeleton width={80} animation="wave" />
) : (
educationStage
)}
</StyledTag>
</StyledFlex>
<StyledText style={{ color: '#92989A' }}>
{isLoading ? <Skeleton width={88} animation="wave" /> : role}
</StyledText>
</div>
</StyledFlex>
</div>
</StyledFlex>
</Link>
<StyledText style={{ alignSelf: 'flex-start', gap: 1 }}>
<img src={locationSvg.src} alt="location icon" />
{isLoading ? <Skeleton width={48} animation="wave" /> : location}
Expand Down
60 changes: 60 additions & 0 deletions components/Group/detail/ShareButtonGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import getShareApi from '@/utils/share';
import styled from '@emotion/styled';
import IconButton from '@mui/material/IconButton';
import {
FaSquareFacebook,
FaLine,
FaLinkedin,
FaSquareThreads,
FaSquareXTwitter,
FaShareFromSquare,
} from 'react-icons/fa6';

const StyledShareButtonGroup = styled.div`
display: flex;
gap: 0.25rem;
align-items: center;

.share-text {
font-size: 14px;
color: #536166;
}
`;

export default function ShareButtonGroup({ title, text, url, hashtag }) {
const {
hasNativeShare,
nativeShare,
facebookShare,
lineShare,
linkedinShare,
threadsShare,
xShare,
} = getShareApi({ title, text, url, hashtag });

return (
<StyledShareButtonGroup>
<span className="share-text">分享至</span>
<IconButton size="small" onClick={facebookShare}>
<FaSquareFacebook fill="#1877F2" />
</IconButton>
<IconButton size="small" onClick={threadsShare}>
<FaSquareThreads fill="#000" />
</IconButton>
<IconButton size="small" onClick={lineShare}>
<FaLine size={16} fill="#00B900" />
</IconButton>
<IconButton size="small" onClick={linkedinShare}>
<FaLinkedin fill="#0A66C2" />
</IconButton>
<IconButton size="small" onClick={xShare}>
<FaSquareXTwitter fill="#000" />
</IconButton>
{hasNativeShare && (
<IconButton size="small" onClick={nativeShare}>
<FaShareFromSquare size={16} />
</IconButton>
)}
</StyledShareButtonGroup>
);
}
33 changes: 20 additions & 13 deletions components/Group/detail/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useRouter } from 'next/navigation';
import { useSelector } from 'react-redux';
import styled from '@emotion/styled';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Skeleton from '@mui/material/Skeleton';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
Expand All @@ -19,6 +17,7 @@ import {
StyledMobileEditButton,
} from './Detail.styled';
import ContactButton from './Contact';
import ShareButtonGroup from './ShareButtonGroup';

function GroupDetail({ id, source, isLoading }) {
const router = useRouter();
Expand All @@ -38,18 +37,26 @@ function GroupDetail({ id, source, isLoading }) {
<Image height="300px" src={source?.photoURL} alt={source?.photoAlt} />
)}
<Box sx={{ position: 'relative', p: '10px' }}>
{isLoading ? (
<Skeleton
variant="rounded"
height={26}
width={68}
animation="wave"
<Box sx={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{isLoading ? (
<Skeleton
variant="rounded"
height={26}
width={68}
animation="wave"
/>
) : source?.isGrouping ? (
<StyledStatus>揪團中</StyledStatus>
) : (
<StyledStatus className="finished">已結束</StyledStatus>
)}
<ShareButtonGroup
title={source?.title}
text={source?.description}
url={window.location.href}
hashtag={source?.hashtag}
/>
) : source?.isGrouping ? (
<StyledStatus>揪團中</StyledStatus>
) : (
<StyledStatus className="finished">已結束</StyledStatus>
)}
</Box>
{isMyGroup ? (
<StyledDesktopEditButton
variant="outlined"
Expand Down
Loading
Loading