Skip to content

Commit

Permalink
✨feat: snackBar 생성 (재커밋)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkdcodus committed Sep 15, 2024
1 parent c6e19f6 commit 48c95bd
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 4,226 deletions.
2 changes: 1 addition & 1 deletion grass-diary/src/assets/svg/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions grass-diary/src/components/Feed/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useAuth } from '@state/auth/useAuth';
import { useModal } from '@state/modal/useModal';
import { INTERACTION } from '@styles/interaction';
import { MODAL } from '@constants/message';
import { useUser } from '@state/user/useUser';

interface IFeedProps {
feed: Feed;
Expand All @@ -18,7 +19,7 @@ interface IFeedProps {

const Feed = ({ feed, isTop }: IFeedProps) => {
const navigate = useNavigate();
const { isAuthenticated } = useAuth();
const memberId = useUser();
const { data: writer } = useWriterProfile(feed.memberId);
const { modal } = useModal();

Expand Down Expand Up @@ -60,7 +61,7 @@ const Feed = ({ feed, isTop }: IFeedProps) => {
interaction: INTERACTION.accent.subtle(),
};

if (!isAuthenticated) {
if (!memberId) {
modal(setting, button1, button2);
return;
}
Expand Down
2 changes: 2 additions & 0 deletions grass-diary/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Outlet } from 'react-router-dom';
import Header from './Header';
import Footer from './Footer';
import SnackBar from './SnackBar';
import { Toast, Modal } from '@components/index';

const Layout = () => {
Expand All @@ -10,6 +11,7 @@ const Layout = () => {
<Outlet />
<Toast />
<Modal />
<SnackBar />
<Footer />
</>
);
Expand Down
115 changes: 115 additions & 0 deletions grass-diary/src/components/Layout/SnackBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { semantic } from '@styles/semantic';
import { TYPO } from '@styles/typo';
import styled, { keyframes } from 'styled-components';
import { ReactComponent as CloseIcon } from '@svg/close.svg';
import {
useSnackBarActions,
useSnackBarActive,
useSnackBarHighlight,
useSnackBarLinkText,
useSnackBarPage,
useSnackBarText,
} from '@state/toast/SnackBarStore';
import { useNavigate } from 'react-router-dom';

const SnackBar = () => {
const navigate = useNavigate();
const active = useSnackBarActive();
const text = useSnackBarText();
const highlight = useSnackBarHighlight();
const linkText = useSnackBarLinkText();
const page = useSnackBarPage();
const { setActive } = useSnackBarActions();

const textArr = text.split(highlight);

const clickHandler = () => {
navigate(page);
setActive(false);
};

return (
<ToastContainer $active={active}>
<MainText>
{highlight !== '' ? (
<>
{textArr[0]}
<HighlightText>{highlight}</HighlightText>
{textArr[1]}
</>
) : (
text
)}
</MainText>
{linkText !== '' && (
<NavigateButton onClick={clickHandler}>{linkText}</NavigateButton>
)}
<CloseButton onClick={() => setActive(false)}>
<CloseIcon
width={16}
height={16}
fill={semantic.light.inverse.solid.normal}
/>
</CloseButton>
</ToastContainer>
);
};

export default SnackBar;

const toastFadeIn = keyframes`
100% {
opacity: 1;
top: 90%;
}
`;

const ToastContainer = styled.div<{ $active: boolean }>`
display: flex;
padding: var(--gap-sm, 0.75rem) var(--gap-lg, 1.25rem) var(--gap-sm, 0.75rem)
var(--gap-xl, 1.5rem);
justify-content: center;
align-items: center;
gap: var(--gap-sm, 0.75rem);
position: fixed;
top: 100%;
left: 50%;
transform: translate(-50%);
border-radius: var(--radius-round, 6rem);
background: ${semantic.light.inverse.solid.bg};
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.06),
0px 2px 4px 0px rgba(0, 0, 0, 0.06), 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
color: ${semantic.light.inverse.solid.hero};
text-align: center;
animation: ${props => props.$active && toastFadeIn} 1s 1s ease forwards;
`;

const MainText = styled.p`
color: ${semantic.light.inverse.solid.hero};
text-align: center;
${TYPO.label2}
`;

const HighlightText = styled.span`
color: ${semantic.light.inverse.solid.accent};
`;

const NavigateButton = styled.button`
display: flex;
padding: var(--gap-4xs, 0.25rem) var(--gap-2xs, 0.5rem);
border-radius: var(--radius-xs, 0.5rem);
color: ${semantic.light.inverse.solid.normal};
text-align: center;
${TYPO.label2}
`;

const CloseButton = styled.button`
display: flex;
padding: var(--gap-4xs, 0.25rem);
`;
134 changes: 134 additions & 0 deletions grass-diary/src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import styled from 'styled-components';
import { semantic } from '@styles/semantic';
import { TYPO } from '@styles/typo';
import { ReactComponent as Close } from '@svg/close.svg';
import CustomButton from './CustomButton';
import {
useModalActions,
useModalActive,
useModalSetting,
} from '@state/modal/ModalStore';
import {
useModalButton1,
useModalButton2,
} from '@state/modal/ModalButtonStore';

const Modal = () => {
const setting = useModalSetting();
const active = useModalActive();
const button1 = useModalButton1();
const button2 = useModalButton2();
const { setActive } = useModalActions();

return (
<Background $active={active}>
<ModalContainer>
<TopSection>
<Title>{setting.title}</Title>
<CloseBtn onClick={() => setActive(false)} />
</TopSection>
<Content>{setting.content}</Content>
{(button1.active || button2.active) && <Divider />}
<BottomSection>
{button1.active && (
<CustomButton
text={button1.text}
onClick={() => setActive(false)}
color={button1.color}
interaction={button1.interaction}
/>
)}
{button2.active && (
<CustomButton
text={button2.text}
onClick={() => {
console.log(button2.clickHandler);
if (button2.clickHandler) button2.clickHandler();
setActive(false);
}}
color={button2.color}
interaction={button2.interaction}
/>
)}
</BottomSection>
</ModalContainer>
</Background>
);
};

export default Modal;

const Background = styled.div<{ $active: boolean }>`
display: ${props => (props.$active ? 'flex' : 'none')};
justify-content: center;
align-items: center;
z-index: 1;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
padding: 0rem 1rem;
background: ${semantic.light.bg.transparent.dimmed};
cursor: auto;
`;

const ModalContainer = styled.div`
display: flex;
width: 22.5rem;
flex-direction: column;
align-items: flex-start;
border-radius: var(--radius-md, 1rem);
background: ${semantic.light.bg.solid.normal};
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08),
0px 3px 8px 0px rgba(0, 0, 0, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.16);
`;

const TopSection = styled.div`
display: flex;
padding: var(--gap-md, 1rem) var(--gap-lg, 1.25rem) var(--gap-md, 1rem)
var(--gap-xl, 1.5rem);
align-items: center;
gap: var(--gap-md, 1rem);
align-self: stretch;
`;

const Title = styled.p`
flex: 1 0 0;
${TYPO.title1}
color: ${semantic.light.object.solid.normal};
`;

const CloseBtn = styled(Close)`
width: 20px;
height: 20px;
cursor: pointer;
`;

const Content = styled.p`
padding: var(--gap-md, 1rem) var(--gap-xl, 1.5rem) var(--gap-xl, 1.5rem)
var(--gap-xl, 1.5rem);
text-align: center;
align-self: stretch;
${TYPO.body1}
color: ${semantic.light.object.transparent.neutral};
`;

const Divider = styled.div`
align-self: stretch;
height: 0.0625rem;
background: ${semantic.light.border.transparent.assistive};
`;

const BottomSection = styled.div`
display: flex;
padding: var(--gap-2xs, 0.5rem);
align-items: flex-start;
gap: var(--gap-2xs, 0.5rem);
align-self: stretch;
`;
8 changes: 8 additions & 0 deletions grass-diary/src/hooks/api/useCreateDiary.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import API from '@services/index';
import { END_POINT } from '@constants/api';
import { useSnackBar } from '@state/toast/useSnackBar';

export const useCreateDiary = (memberId: number) => {
const { snackBar } = useSnackBar();
const queryClient = useQueryClient();
return useMutation({
mutationFn: (request: DiaryRequest) =>
API.post(END_POINT.diary(memberId), request),
onSuccess: () => {
snackBar(
'일기를 작성해서 10 리워드를 받았어요.',
'10 리워드',
'리워드 내역 보기',
'/rewardpage',
);
queryClient.invalidateQueries({ queryKey: ['diaries'] });
},
});
Expand Down
46 changes: 46 additions & 0 deletions grass-diary/src/state/toast/SnackBarStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { create } from 'zustand';

type Actions = {
setReset: () => void;
setActive: (active: boolean) => void;
setText: (text: string) => void;
setHighlight: (highlight: string) => void;
setLinkText: (linkText: string) => void;
setPage: (page: string) => void;
};

interface SnackBarState {
active: boolean;
text: string;
highlight: string;
linkText: string;
page: string;
actions: Actions;
}

const useSnackBarStore = create<SnackBarState>(set => ({
active: false,
text: '',
highlight: '',
linkText: '',
page: '',
actions: {
setReset: () =>
set({ active: false, text: '', highlight: '', linkText: '', page: '' }),
setActive: active => set({ active }),
setText: text => set({ text }),
setHighlight: highlight => set({ highlight }),
setLinkText: linkText => set({ linkText }),
setPage: page => set({ page }),
},
}));

export const useSnackBarText = () => useSnackBarStore(state => state.text);
export const useSnackBarActive = () => useSnackBarStore(state => state.active);
export const useSnackBarHighlight = () =>
useSnackBarStore(state => state.highlight);
export const useSnackBarLinkText = () =>
useSnackBarStore(state => state.linkText);
export const useSnackBarPage = () => useSnackBarStore(state => state.page);
export const useSnackBarActions = () =>
useSnackBarStore(state => state.actions);
23 changes: 23 additions & 0 deletions grass-diary/src/state/toast/useSnackBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useSnackBarActions } from './SnackBarStore';

export const useSnackBar = () => {
const { setReset, setActive, setText, setHighlight, setPage, setLinkText } =
useSnackBarActions();

const snackBar = (
text: string,
highlight?: string,
linkText?: string,
page?: string,
) => {
setReset();
setActive(true);
setText(text);

if (highlight) setHighlight(highlight);
if (linkText) setLinkText(linkText);
if (page) setPage(page);
};

return { snackBar };
};
Loading

0 comments on commit 48c95bd

Please sign in to comment.