-
Notifications
You must be signed in to change notification settings - Fork 2
[feat] Home 상품 탭 및 Popup 공컴 구현 #515
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
Merged
Merged
Changes from 39 commits
Commits
Show all changes
79 commits
Select commit
Hold shift + click to select a range
a9afa48
chore: 상품 탭 섹션 컴포넌트 순서 변경
earl9rey e10948d
feat: 상품 탭 검색 섹션 스켈레톤 UI 구현
earl9rey a74d4ff
style: 상품 탭 전체 css 적용
earl9rey dfef559
feat: 상품 배너 fallback 이미지 추가
earl9rey 1801299
feat: 상품 인트로 배너 영역 UI 구현
earl9rey 37c269b
chore: Chip 컴포넌트 SuffixIcon을 아이콘 공컴으로 변경
earl9rey b98aee9
chore: 상품 카드 공컴 변경 To-Do 주석 추가
earl9rey 12427c0
feat: 상품 탭에서 메뉴 탭 Sticky 해제
earl9rey 4c545dc
style: 검색창 및 필터 스티키 추가
earl9rey 8901583
feat: 상품 카드 목업 데이터로 변경
earl9rey 442d7bc
refactor: TitleNavBar에 아이콘 공컴 적용
earl9rey caf92b3
refactor: BottomSheetBase에 Icon 공컴 적용
earl9rey 500f00b
feat: 상품 탭에 상품 선택 바텀시트 추가
earl9rey 546d762
feat: DragHandleBottomSheet에 확장 여부 props 추가
earl9rey fc0ec84
feat: 선택한 가구 바텀시트 내부 콘텐츠 컴포넌트 구현
earl9rey dfd6566
style: 디자인 토큰 변경사항 적용
earl9rey ff1e400
Merge branch 'develop' of https://github.com/TEAM-HOUME/HOUME-CLIENT …
earl9rey ffd12a4
refactor: Chip 아이콘 reactNode로 변경
earl9rey e34d3d3
style: 색상 토큰 변경사항 적용
earl9rey c7d15db
feat: 상품 탭 바텀시트 기본 적용
earl9rey 6e5d773
feat: 필터칩 선택 옵션 추가
earl9rey 8475dd7
feat: 필터칩 선택시 closeBottomSheet 연결
earl9rey 81c8ae0
refactor: 상품 탭 바텀시트 라이팅 변경
earl9rey 530cb32
feat: 상품 탭 필터칩에 필터 바텀시트 연결
earl9rey ac48c27
style: 필터 바텀시트 타이틀 스타일 추가
earl9rey 05888c4
feat: 상품 탭 필터 바텀시트 내부 컴포넌트 구현
earl9rey e9c934f
chore: 상품 탭 라이팅 변경
earl9rey a477462
Merge branch 'develop' of https://github.com/TEAM-HOUME/HOUME-CLIENT …
earl9rey b6d2000
Merge branch 'develop' of https://github.com/TEAM-HOUME/HOUME-CLIENT …
earl9rey edd8071
feat: ProductCard v2 컴포넌트 적용
earl9rey 8702714
feat: 필터 적용하기 로직 구현
earl9rey 4904c92
style: 하단바 높이만큼 padding 추가
earl9rey d63856a
feat: product Card에 이미지 목 추가
earl9rey 2c448ba
refactor: 상품 바텀시트 내부 컴포넌트 네이밍 변경
earl9rey f83c236
feat: 상품 선택 로직 추가
earl9rey c9da247
feat: 상품 선택 해제 ui 추가
earl9rey 1571fd5
feat: 상품 선택 해제 로직 추가
earl9rey 566c90d
feat: 상품 선택 시 UI 적용
earl9rey 81f93a9
style: 바텀시트 스크롤바 제거
earl9rey e817e43
feat: 상품 필터칩 대표값 선택 로직 변경
earl9rey e71a9c9
style: ProductCard 높이 minHeight 적용
earl9rey 0d8fa58
style: 선택 상품 바텀시트 스타일 조정
earl9rey 735ca6b
feat: 상품 선택 바텀시트 선택값 유지 로직 추가
earl9rey 1297573
refactor: 인라인 함수를 별도 함수로 분리
earl9rey ca23546
style: 상품 선택 해제 관련 스타일에 recipe 적용
earl9rey 8854984
feat: 상품 선택 개수에 따른 토스트 표시 로직 추가
earl9rey 9e3f103
style: 상품 선택 해제 버튼 위치 조정
earl9rey 2bbcb3d
feat: ActionButton에 visuallyDisabled 추가
earl9rey f11bc6f
feat: 팝업 모달 공컴 구현
earl9rey 75a77dd
style: 팝업 모달 공컴 구현
earl9rey 014c961
feat: 돋보기 아이콘에 onToggle 추가
earl9rey 0688907
feat: ProductCard 상세보기 모달 적용
earl9rey b464aec
style: 상품 상세보기 모달 내부 컴포넌트 스타일 적용
earl9rey 0ab10d4
style: 팝업 모달 zindex 조정
earl9rey c7fcf4d
style: ProductCard middleInfo 박스 minHeight 조정
earl9rey 907560a
feat: 상품 상세 모달에 사이트 링크 버튼 추가
earl9rey 17c6486
refactor: 상품명 및 스타일 조정
earl9rey c198ec0
refactor: 상품 탭 컴포넌트 내부에서 목데이터 제거
earl9rey d46b96f
feat: 상품 탭 메인 API 목데이터 분리 및 구조화
earl9rey 841d754
refactor: 목데이터 파일 위치 변경
earl9rey 3ae91c7
test: 상품 탭 메인 API 기반 테스트 추가
earl9rey e0609ed
feat: 상품 탭 메인 API 연동 기본 파일 생성
earl9rey 72c0d7b
refactor: API 스펙과 목데이터 구조 통일
earl9rey a39d61c
refactor: 상품 선택 로직을 useProductTabState 훅으로 분리
earl9rey 002dc20
fix: 필터 시트 오픈 시 상태 복원 로직 보완
earl9rey 9b69f4b
chore: 불필요한 주석 삭제
earl9rey 0869223
feat: 상품 필터 리스트 및 검색바 스크롤 여부에 따른 Sticky/노출 구현
earl9rey b11dcc7
refactor: 상품 검색/필터 헤더 Sticky 노출 로직 훅으로 분리
earl9rey 882e9df
style: 검색바 노출 애니메이션 적용
earl9rey 81068f3
chore: 불필요한 주석 제거
earl9rey 43a5ff0
feat: forwardRef 컴포넌트에 displayName 추가
earl9rey 0e1ec62
chore: aria 접근성 강화
earl9rey 721ffca
fix: validateDOMNesting 해결
earl9rey 69bced2
feat: 상품 모달에서 상품 선택 여부에 따른 버튼 비활성화 로직 추가
earl9rey 76e62c7
style: 스티키 헤더 배경색 토큰으로 변경
earl9rey 53ca8f5
refactor: 클래스네임 가독성 개선
earl9rey 759c3b2
refactor: 상품 탭 스크롤 리스너 의존성 배열 최적화 및 초기 상태 동기화
earl9rey 4e3e927
refactor: setValues 동기화 시점 간소화
earl9rey 41e97e8
refactor: 불필요한 색상값 역조회 제거
earl9rey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 19 additions & 3 deletions
22
src/pages/home/components/product/IntroSection/IntroSection.css.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,24 @@ | ||
| import { style } from '@vanilla-extract/css'; | ||
|
|
||
| import { unitVars } from '@styles/tokensV2/unit.css'; | ||
|
|
||
| /** 비율 박스 — introBanner가 inset으로 프레임에 맞게 채움 */ | ||
| export const section = style({ | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| alignItems: 'center', | ||
| boxSizing: 'border-box', | ||
| aspectRatio: '37.5 / 22', | ||
| position: 'relative', | ||
| width: '100%', | ||
| minWidth: unitVars.unit.dimension.wMin, | ||
| maxWidth: unitVars.unit.dimension.wMax, | ||
| overflow: 'hidden', | ||
| }); | ||
|
|
||
| export const introBanner = style({ | ||
| position: 'absolute', | ||
| inset: 0, | ||
| display: 'block', | ||
| objectFit: 'cover', | ||
| objectPosition: 'center', | ||
| width: '100%', | ||
| height: '100%', | ||
| }); |
39 changes: 37 additions & 2 deletions
39
src/pages/home/components/product/IntroSection/IntroSection.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,42 @@ | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| import bannerFallback from '@assets/v2/images/bannerFallback.svg'; | ||
|
|
||
| import * as styles from './IntroSection.css'; | ||
|
|
||
| const IntroSection = () => { | ||
| return <section className={styles.section}>IntroSection</section>; | ||
| export type IntroSectionProps = { | ||
| /** API 등에서 받은 인트로 이미지·GIF URL. 없으면 bannerFallback부터 표시 */ | ||
| bannerUrl?: string | null; | ||
| alt?: string; | ||
| }; | ||
|
|
||
| const resolveInitialSrc = (url: string | null | undefined) => | ||
| url ? url : bannerFallback; | ||
|
|
||
| const IntroSection = ({ bannerUrl, alt = '상품 배너' }: IntroSectionProps) => { | ||
| const initialImageSrc = resolveInitialSrc(bannerUrl); | ||
|
|
||
| // 이미지 로드 실패 시 fallback src를 유지하고, | ||
| // 부모가 새 mediaUrl을 내려주면 그 값으로 다시 동기화 (RoomTypeCard와 동일 패턴) | ||
| const [imageSrc, setImageSrc] = useState(initialImageSrc); | ||
|
|
||
| useEffect(() => { | ||
| setImageSrc(resolveInitialSrc(bannerUrl)); | ||
| }, [bannerUrl]); | ||
|
|
||
| return ( | ||
| <section className={styles.section} aria-label={alt}> | ||
| <img | ||
| className={styles.introBanner} | ||
| src={imageSrc} | ||
| alt={alt} | ||
| draggable={false} | ||
| loading="eager" | ||
| decoding="async" | ||
| onError={() => setImageSrc(bannerFallback)} | ||
|
earl9rey marked this conversation as resolved.
Outdated
|
||
| /> | ||
| </section> | ||
| ); | ||
| }; | ||
|
|
||
| export default IntroSection; | ||
45 changes: 45 additions & 0 deletions
45
src/pages/home/components/product/ProductFilterSheet/ProductFilterSheet.css.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { style } from '@vanilla-extract/css'; | ||
|
|
||
| import { colorVars } from '@styles/tokensV2/color.css'; | ||
| import { fontVars } from '@styles/tokensV2/font.css'; | ||
| import { unitVars } from '@styles/tokensV2/unit.css'; | ||
|
|
||
| export const root = style({ | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| gap: unitVars.unit.gapPadding['200'], | ||
| }); | ||
|
|
||
| export const section = style({ | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| gap: unitVars.unit.gapPadding['300'], | ||
| }); | ||
|
|
||
| export const sectionTitle = style({ | ||
| textAlign: 'left', | ||
| color: colorVars.color.text.secondary, | ||
| ...fontVars.font.title_m_15, | ||
| }); | ||
|
|
||
| export const chipGroup = style({ | ||
| display: 'flex', | ||
| flexWrap: 'wrap', | ||
| gap: unitVars.unit.gapPadding['100'], | ||
| }); | ||
|
|
||
| export const colorChipInner = style({ | ||
| display: 'inline-flex', | ||
| alignItems: 'center', | ||
| gap: unitVars.unit.gapPadding['100'], | ||
| }); | ||
|
|
||
| export const colorDot = style({ | ||
| flexShrink: 0, | ||
| borderWidth: '0.1rem', | ||
| borderStyle: 'solid', | ||
| borderRadius: unitVars.unit.radius.full, | ||
| borderColor: colorVars.color.border.secondary, | ||
| width: '1.2rem', | ||
| height: '1.2rem', | ||
| }); |
215 changes: 215 additions & 0 deletions
215
src/pages/home/components/product/ProductFilterSheet/ProductFilterSheet.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| import { forwardRef, useCallback, useImperativeHandle, useState } from 'react'; | ||
|
|
||
| import Chip from '@components/v2/chip/Chip'; | ||
|
|
||
| import * as styles from './ProductFilterSheet.css'; | ||
|
|
||
| const FURNITURE_OPTIONS: { id: string; label: string }[] = [ | ||
| { id: 'ALL', label: '전체' }, | ||
| { id: 'bed', label: '침대/프레임' }, | ||
| { id: 'desk', label: '업무용 책상' }, | ||
| { id: 'dining', label: '식탁' }, | ||
| { id: 'floorTable', label: '좌식 테이블' }, | ||
| { id: 'wardrobe', label: '옷장' }, | ||
| { id: 'storage', label: '수납/장식장' }, | ||
| { id: 'sofa', label: '소파' }, | ||
| { id: 'chair', label: '의자/스툴' }, | ||
| { id: 'vanity', label: '화장대/협탁' }, | ||
| { id: 'light', label: '조명' }, | ||
| { id: 'other', label: '그 외' }, | ||
| ]; | ||
|
|
||
| const PRICE_OPTIONS: { id: string; label: string }[] = [ | ||
| { id: 'ALL', label: '전체' }, | ||
| { id: 'under50k', label: '5만 원 이하' }, | ||
| { id: '50to100k', label: '5-10만 원' }, | ||
| { id: '10man', label: '10만 원대' }, | ||
| { id: '20man', label: '20만 원대' }, | ||
| { id: '30man', label: '30만 원대' }, | ||
| { id: '40man', label: '40만 원대' }, | ||
| { id: 'over50man', label: '50만 원 이상' }, | ||
| ]; | ||
|
|
||
| const COLOR_OPTIONS: { id: string; label: string; dot?: string }[] = [ | ||
| { id: 'ALL', label: '전체' }, | ||
| { id: 'white', label: '화이트', dot: '#FFFFFF' }, | ||
| { id: 'gray', label: '그레이', dot: '#8E959E' }, | ||
| { id: 'black', label: '블랙', dot: '#1B1E22' }, | ||
| { id: 'silver', label: '실버', dot: '#C8CDD2' }, | ||
| { id: 'gold', label: '골드', dot: '#D4AF37' }, | ||
| { id: 'beige', label: '베이지', dot: '#D4C4B0' }, | ||
| { id: 'brown', label: '브라운', dot: '#5C4033' }, | ||
| { id: 'red', label: '레드', dot: '#E53935' }, | ||
| { id: 'orange', label: '오렌지', dot: '#FB8C00' }, | ||
| { id: 'yellow', label: '옐로우', dot: '#FDD835' }, | ||
| { id: 'green', label: '그린', dot: '#43A047' }, | ||
| { id: 'blue', label: '블루', dot: '#1E88E5' }, | ||
| { id: 'navy', label: '네이비', dot: '#1A237E' }, | ||
| { id: 'violet', label: '바이올렛', dot: '#7E57C2' }, | ||
| { id: 'pink', label: '핑크', dot: '#EC407A' }, | ||
| ]; | ||
|
earl9rey marked this conversation as resolved.
|
||
|
|
||
| const ALL = 'ALL'; | ||
|
|
||
| const INITIAL_SELECTION: string[] = [ALL]; | ||
|
|
||
| /** 섹션 내 다중 선택: ALL만 있으면 ‘전체’, 그 외는 선택된 id 목록 */ | ||
| function toggleSectionSelection(current: string[], id: string): string[] { | ||
| if (id === ALL) { | ||
| return [ALL]; | ||
| } | ||
| const withoutAll = current.filter((x) => x !== ALL); | ||
| const has = withoutAll.includes(id); | ||
| const next = has ? withoutAll.filter((x) => x !== id) : [...withoutAll, id]; | ||
| return next.length === 0 ? [ALL] : next; | ||
| } | ||
|
|
||
| export interface ProductFilterValues { | ||
| furnitureTypeIds: string[]; | ||
| priceRangeIds: string[]; | ||
| colorIds: string[]; | ||
| } | ||
|
|
||
| export interface ProductFilterSheetRef { | ||
| reset: () => void; | ||
| getValues: () => ProductFilterValues; | ||
| setValues: (values: ProductFilterValues) => void; | ||
| } | ||
|
|
||
| const ProductFilterSheet = forwardRef<ProductFilterSheetRef>( | ||
| function ProductFilterSheet(_props, ref) { | ||
|
earl9rey marked this conversation as resolved.
|
||
| const [furnitureTypeIds, setFurnitureTypeIds] = | ||
| useState<string[]>(INITIAL_SELECTION); | ||
| const [priceRangeIds, setPriceRangeIds] = | ||
| useState<string[]>(INITIAL_SELECTION); | ||
| const [colorIds, setColorIds] = useState<string[]>(INITIAL_SELECTION); | ||
|
|
||
| const reset = useCallback(() => { | ||
| setFurnitureTypeIds([...INITIAL_SELECTION]); | ||
| setPriceRangeIds([...INITIAL_SELECTION]); | ||
| setColorIds([...INITIAL_SELECTION]); | ||
| }, []); | ||
|
|
||
| const getValues = useCallback((): ProductFilterValues => { | ||
| return { | ||
| furnitureTypeIds: [...furnitureTypeIds], | ||
| priceRangeIds: [...priceRangeIds], | ||
| colorIds: [...colorIds], | ||
| }; | ||
| }, [furnitureTypeIds, priceRangeIds, colorIds]); | ||
|
|
||
| const setValues = useCallback((values: ProductFilterValues) => { | ||
| setFurnitureTypeIds( | ||
| values.furnitureTypeIds.length > 0 | ||
| ? [...values.furnitureTypeIds] | ||
| : [...INITIAL_SELECTION] | ||
| ); | ||
| setPriceRangeIds( | ||
| values.priceRangeIds.length > 0 | ||
| ? [...values.priceRangeIds] | ||
| : [...INITIAL_SELECTION] | ||
| ); | ||
| setColorIds( | ||
| values.colorIds.length > 0 | ||
| ? [...values.colorIds] | ||
| : [...INITIAL_SELECTION] | ||
| ); | ||
| }, []); | ||
|
|
||
| useImperativeHandle( | ||
| ref, | ||
| () => ({ | ||
| reset, | ||
| getValues, | ||
| setValues, | ||
| }), | ||
| [reset, getValues, setValues] | ||
| ); | ||
|
|
||
| return ( | ||
| <div className={styles.root}> | ||
| <section | ||
| className={styles.section} | ||
| aria-labelledby="filter-furniture-heading" | ||
| > | ||
| <h2 id="filter-furniture-heading" className={styles.sectionTitle}> | ||
| 카테고리 | ||
| </h2> | ||
| <div className={styles.chipGroup} role="group" aria-label="카테고리"> | ||
| {FURNITURE_OPTIONS.map(({ id, label }) => ( | ||
| <Chip | ||
| key={id} | ||
| selected={furnitureTypeIds.includes(id)} | ||
| onClick={() => | ||
| setFurnitureTypeIds((prev) => | ||
| toggleSectionSelection(prev, id) | ||
| ) | ||
| } | ||
| > | ||
| {label} | ||
| </Chip> | ||
| ))} | ||
| </div> | ||
| </section> | ||
|
|
||
| <section | ||
| className={styles.section} | ||
| aria-labelledby="filter-price-heading" | ||
| > | ||
| <h2 id="filter-price-heading" className={styles.sectionTitle}> | ||
| 가격대 | ||
| </h2> | ||
| <div className={styles.chipGroup} role="group" aria-label="가격대"> | ||
| {PRICE_OPTIONS.map(({ id, label }) => ( | ||
| <Chip | ||
| key={id} | ||
| selected={priceRangeIds.includes(id)} | ||
| onClick={() => | ||
| setPriceRangeIds((prev) => toggleSectionSelection(prev, id)) | ||
| } | ||
| > | ||
| {label} | ||
| </Chip> | ||
| ))} | ||
| </div> | ||
| </section> | ||
|
|
||
| <section | ||
| className={styles.section} | ||
| aria-labelledby="filter-color-heading" | ||
| > | ||
| <h2 id="filter-color-heading" className={styles.sectionTitle}> | ||
| 색상 | ||
| </h2> | ||
| <div className={styles.chipGroup} role="group" aria-label="색상"> | ||
| {COLOR_OPTIONS.map(({ id, label, dot }) => ( | ||
| <Chip | ||
| key={id} | ||
| selected={colorIds.includes(id)} | ||
| onClick={() => | ||
| setColorIds((prev) => toggleSectionSelection(prev, id)) | ||
| } | ||
| > | ||
| {dot ? ( | ||
| <span className={styles.colorChipInner}> | ||
| <span | ||
| className={styles.colorDot} | ||
| style={{ | ||
| backgroundColor: dot, | ||
| }} | ||
| /> | ||
| {label} | ||
| </span> | ||
| ) : ( | ||
| label | ||
| )} | ||
| </Chip> | ||
| ))} | ||
| </div> | ||
| </section> | ||
| </div> | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| export default ProductFilterSheet; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,21 @@ | ||
| import { style } from '@vanilla-extract/css'; | ||
|
|
||
| import { colorVars } from '@styles/tokensV2/color.css'; | ||
| import { fontVars } from '@styles/tokensV2/font.css'; | ||
| import { unitVars } from '@styles/tokensV2/unit.css'; | ||
|
|
||
| export const container = style({ | ||
| boxSizing: 'border-box', | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| alignItems: 'center', | ||
| alignItems: 'stretch', | ||
| alignSelf: 'stretch', | ||
| width: '100%', | ||
| minWidth: unitVars.unit.dimension.wMin, | ||
| maxWidth: unitVars.unit.dimension.wMax, | ||
| }); | ||
|
|
||
| export const filterSheetTitle = style({ | ||
| color: colorVars.color.text.primary, | ||
| ...fontVars.font.title_m_16, | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.