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

[#182] 디테일 모달 수정 #190

Merged
merged 21 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
23ff3e9
fix: remove not use code on pages/index.ts
chanwoo00106 Aug 3, 2023
d115838
feat: add BlurPortal
chanwoo00106 Aug 4, 2023
fea792e
feat: add StudentDetailModal component
chanwoo00106 Aug 4, 2023
feef481
feat: add ProfileDetail component
chanwoo00106 Aug 4, 2023
db15e48
feat: add StudentInfo component
chanwoo00106 Aug 4, 2023
0811e4f
feat: add PrizesDetail component
chanwoo00106 Aug 4, 2023
5f6cc9f
feat: add project component
chanwoo00106 Aug 9, 2023
6e68823
fix: add props on profile detail component
chanwoo00106 Aug 10, 2023
15cf25d
fix: remove not use modal hook
chanwoo00106 Aug 10, 2023
305c18a
fix: fix close button wrapper
chanwoo00106 Aug 11, 2023
0ac0d82
Merge branch 'master' of https://github.com/GSM-MSG/SMS-FrontEnd into…
chanwoo00106 Aug 22, 2023
9a314ba
fix: fix profileDetail responsive
chanwoo00106 Aug 22, 2023
de39be6
chore: remove mock data and add props on StudentInfo
chanwoo00106 Aug 22, 2023
3970715
feat: add profileImg load exception handling
chanwoo00106 Aug 22, 2023
df64d7e
fix: fix student detail type and add PrizeDetail props
chanwoo00106 Aug 22, 2023
ca0ba28
feat: add projectDetail props
chanwoo00106 Aug 22, 2023
29e0a46
fix: fix register prizes type
chanwoo00106 Aug 22, 2023
b531573
chore: add project description on ProjectDetail
chanwoo00106 Aug 22, 2023
c602c90
feat: add replace url feature
chanwoo00106 Aug 22, 2023
c7598f0
fix: move StudentDetailModal to StudentDetail
chanwoo00106 Aug 22, 2023
6987c18
feat: add portfolio button
chanwoo00106 Aug 22, 2023
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
1 change: 1 addition & 0 deletions packages/app/src/features/modal/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as useModal } from './useModal'
export { default as usePortal } from './usePortal'
3 changes: 3 additions & 0 deletions packages/app/src/features/modal/hooks/useModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { close, show } from '@store/modalSlice'
import { ReactElement } from 'react'
import { useDispatch } from 'react-redux'
import { useRouter } from 'next/router'

const useModal = () => {
const router = useRouter()
const dispatch = useDispatch()

const onClose = () => {
router.push('/', '/')
dispatch(close())
}

Expand Down
49 changes: 49 additions & 0 deletions packages/app/src/features/modal/hooks/usePortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useState, MouseEvent } from 'react'
import { useRouter } from 'next/router'
import { useDispatch } from 'react-redux'
import { close } from '@store/modalSlice'

const usePortal = () => {
const router = useRouter()
const [isCSR, setIsCSR] = useState(false)
const dispatch = useDispatch()

useEffect(() => {
setIsCSR(true)
}, [])

useEffect(() => {
document.body.style.cssText = `
position: fixed;
top: -${window.scrollY}px;
width: 100%;`
return () => {
const scrollY = document.body.style.top
document.body.style.cssText = ''
window.scrollTo(0, -parseInt(scrollY))
}
}, [])

if (typeof window === 'undefined') return null
if (!isCSR) return null

const portal = document.getElementById('modal')
if (!portal) throw new Error('Not found modal')

const onClick = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
}

const onClose = () => {
router.push('/', '/')
dispatch(close())
}

return {
onClick,
onClose,
portal,
}
}

export default usePortal
28 changes: 28 additions & 0 deletions packages/app/src/features/modal/portals/BlurPortal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { usePortal } from '@features/modal/hooks'
import ReactDOM from 'react-dom'
import { cloneElement, ReactElement } from 'react'
import * as S from './style'

interface Props {
children: ReactElement
}

/**
* 배경은 blur로 되어 있음
*/
const BlurPortal = ({ children }: Props) => {
const portalValue = usePortal()

if (!portalValue) return <></>

const { onClick, onClose, portal } = portalValue

return ReactDOM.createPortal(
<S.Wrapper onClick={onClose}>
{cloneElement(children, { onClick })}
</S.Wrapper>,
portal
)
}

export default BlurPortal
14 changes: 14 additions & 0 deletions packages/app/src/features/modal/portals/BlurPortal/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from '@emotion/styled'

export const Wrapper = styled.div`
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
background: rgba(0, 0, 0, 0.25);
backdrop-filter: blur(1.25rem);
overflow-y: scroll;
`
50 changes: 7 additions & 43 deletions packages/app/src/features/modal/portals/ModalPortal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,22 @@
import { close } from '@store/modalSlice'
import {
cloneElement,
useEffect,
useState,
MouseEvent,
ReactElement,
} from 'react'
import { usePortal } from '@features/modal/hooks'
import { cloneElement, ReactElement } from 'react'
import ReactDOM from 'react-dom'
import { useDispatch } from 'react-redux'
import * as S from './style'

interface Props {
children: ReactElement
}

const ModalPortal = ({ children }: Props) => {
const [isCSR, setIsCSR] = useState(false)
const dispatch = useDispatch()
const portal = usePortal()

useEffect(() => {
setIsCSR(true)
}, [])

useEffect(() => {
document.body.style.cssText = `
position: fixed;
top: -${window.scrollY}px;
width: 100%;`
return () => {
const scrollY = document.body.style.top
document.body.style.cssText = ''
window.scrollTo(0, -parseInt(scrollY))
}
}, [])

if (typeof window === 'undefined') return <></>
if (!isCSR) return <></>

const portal = document.getElementById('modal')
if (!portal) throw new Error('Not found modal')

const onClick = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
}

const onClose = () => {
dispatch(close())
}
if (!portal) return <></>

return ReactDOM.createPortal(
<S.Wrapper onClick={onClose}>
{cloneElement(children, { onClick })}
<S.Wrapper onClick={portal.onClose}>
{cloneElement(children, { onClick: portal.onClick })}
</S.Wrapper>,
portal
portal.portal
)
}

Expand Down
1 change: 1 addition & 0 deletions packages/app/src/features/modal/portals/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as ModalPortal } from './ModalPortal'
export { default as BlurPortal } from './BlurPortal'
2 changes: 1 addition & 1 deletion packages/app/src/features/register/type/PrizeType.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
interface PrizeType {
name: string
kind: string
type: string
date: string
}
6 changes: 6 additions & 0 deletions packages/app/src/features/register/type/RegisterFormType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ interface RegisterFormType {
prizes: PrizeType[]
}

interface PrizeType {
name: string
kind: string
date: string
}

interface LanguageCertificateType {
languageCertificateName: string
score: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import useModal from '@features/student/hooks/useModal'
import { useModal } from '@features/modal/hooks'
import * as Icon from '@sms/shared/src/icons'

import * as S from './style'
Expand All @@ -8,7 +8,7 @@ interface Props {
}

const FilterHeader = ({ reset }: Props) => {
const { onClose } = useModal('filter')
const { onClose } = useModal()

return (
<S.Wrapper>
Expand Down
26 changes: 26 additions & 0 deletions packages/app/src/features/student/atoms/PrizesDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as S from './style'

interface Props {
prizes?: PrizeType[]
}

const PrizesDetail = ({ prizes }: Props) => {
if (!prizes?.length) return null

return (
<S.Wrapper>
<S.Title>수상</S.Title>
{prizes.map((prize, idx) => (
<S.Prize key={idx}>
<S.PrizeTop>
<S.PrizeTitle>{prize.name}</S.PrizeTitle>
<S.PrizeDate>{prize.date}</S.PrizeDate>
</S.PrizeTop>
<S.PrizeInfo>{prize.type}</S.PrizeInfo>
</S.Prize>
))}
</S.Wrapper>
)
}

export default PrizesDetail
41 changes: 41 additions & 0 deletions packages/app/src/features/student/atoms/PrizesDetail/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import styled from '@emotion/styled'

export const Wrapper = styled.div`
padding: 1.5rem 2.5rem;
display: flex;
flex-direction: column;
gap: 1rem;

@media (max-width: 34rem) {
padding: 1.25rem 1.5rem;
}
`

export const Title = styled.h3`
${({ theme }) => theme.title1}
`

export const Prize = styled.section`
background: var(--N10);
padding: 0.5rem;
border-radius: 0.5rem;
`

export const PrizeTop = styled.div`
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
`

export const PrizeTitle = styled.h3`
${({ theme }) => theme.body1}
`

export const PrizeDate = styled.p`
${({ theme }) => theme.caption2}
`

export const PrizeInfo = styled.p`
${({ theme }) => theme.caption2}
color: var(--N40);
`
70 changes: 70 additions & 0 deletions packages/app/src/features/student/atoms/ProfileDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState } from 'react'
import * as SVG from '@sms/shared/src/assets/svg'
import * as S from './style'

interface Props {
major?: string
name?: string
grade?: number
classNum?: number
number?: number
techStack?: string[]
introduce?: string
profileImg?: string
}

const ProfileDetail = ({
major,
name,
grade,
number,
profileImg,
classNum,
introduce,
techStack,
}: Props) => {
const [isError, setIsError] = useState<boolean>(false)

return (
<S.Wrapper>
<S.Left>
<S.Major>{major}</S.Major>

<S.Name>{name?.replace('**', '소금')}</S.Name>

{grade && classNum && number && (
<S.SchoolInfo>
{grade}학년 {classNum}반 {number}번 • 소프트웨어 개발과
</S.SchoolInfo>
)}

<S.Tags>
{techStack?.map((stack, idx) => (
<S.Tag key={idx}>{stack}</S.Tag>
))}
</S.Tags>

<S.Introduce>
<S.IntroduceLabel>자기소개</S.IntroduceLabel>
{introduce}
</S.Introduce>
</S.Left>

<S.Right>
{!isError && profileImg ? (
<S.ProfileImage
src={profileImg}
alt='profile image'
onError={() => setIsError(true)}
/>
) : (
<S.TemeporaryImage>
<SVG.Person />
</S.TemeporaryImage>
)}
</S.Right>
</S.Wrapper>
)
}

export default ProfileDetail
Loading