From ad39852af2ae4b39aee4c6b4d2fe63a24e04b376 Mon Sep 17 00:00:00 2001 From: Kim Min-gyu <99083803+cobocho@users.noreply.github.com> Date: Thu, 18 Jul 2024 04:44:31 -0700 Subject: [PATCH] =?UTF-8?q?[Feature]=20=ED=99=95=EC=9E=A5=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EB=9E=A8=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 확장 프로그램 로그인 연결 * feat: 확장 프로그램 새창 로그인 통신 구현 * fix: 검색창 에러 수정 * fix: 온보딩을 항상 실행하도록 수정 * feat: 상황별 unAuthorizedHandler 로직 구현 * feat: 디자인 변경사항 반영 * feat: 토큰 동기화용 컴포넌트 추가 * fix: 로그인 관련 에러 수정 --- .../components/PopupBox/PopupBox.styles.tsx | 20 +++ .../components/PopupBox/PopupBox.tsx | 127 ++++++++++++++++ apps/extension/components/PopupBox/index.ts | 1 + .../components/SearchBox/SearchBox.styles.tsx | 76 ++++++++++ .../components/SearchBox/SearchBox.tsx | 77 ++++++++++ apps/extension/components/SearchBox/index.ts | 1 + .../SearchWindow/SearchWindow.styles.tsx | 11 +- .../components/SearchWindow/SearchWindow.tsx | 135 ++++++++++++++++-- .../components/TermItem/TermItem.styles.tsx | 16 +-- .../components/TermItem/TermItem.tsx | 61 ++++---- .../components/TermList/TermList.styles.tsx | 8 ++ .../components/TermList/TermList.tsx | 77 +++++----- apps/extension/content.tsx | 64 +++++++++ apps/extension/hooks/useSearch/useSearch.ts | 54 ++++--- apps/extension/package.json | 3 + apps/extension/popup.css | 3 + apps/extension/popup.tsx | 13 +- apps/extension/tsconfig.json | 21 ++- apps/extension/utils/storage.ts | 17 +++ apps/web/package.json | 1 + .../src/app/(beforeLogin)/auth/token/page.tsx | 44 ++++++ .../app/(onboarding)/onboarding/job/page.tsx | 40 +++--- apps/web/src/app/layout.tsx | 8 ++ .../InitialSetting/InitialSetting.tsx | 3 + .../src/components/SearchBox/SearchBox.css.ts | 13 +- .../src/components/SearchBox/SearchBox.tsx | 66 +++++---- .../SilentRefresh/SilentRefresh.tsx | 1 + .../components/TokenSender/TokenSender.tsx | 31 ++++ apps/web/src/middleware.ts | 1 + apps/web/tsconfig.json | 3 +- packages/api/src/lib/fetcher.ts | 29 +++- packages/design-system/src/tokens/colors.ts | 1 + pnpm-lock.yaml | 101 +++++-------- 33 files changed, 883 insertions(+), 244 deletions(-) create mode 100644 apps/extension/components/PopupBox/PopupBox.styles.tsx create mode 100644 apps/extension/components/PopupBox/PopupBox.tsx create mode 100644 apps/extension/components/PopupBox/index.ts create mode 100644 apps/extension/components/SearchBox/SearchBox.styles.tsx create mode 100644 apps/extension/components/SearchBox/SearchBox.tsx create mode 100644 apps/extension/components/SearchBox/index.ts create mode 100644 apps/extension/popup.css create mode 100644 apps/extension/utils/storage.ts create mode 100644 apps/web/src/components/TokenSender/TokenSender.tsx diff --git a/apps/extension/components/PopupBox/PopupBox.styles.tsx b/apps/extension/components/PopupBox/PopupBox.styles.tsx new file mode 100644 index 0000000..edd03f9 --- /dev/null +++ b/apps/extension/components/PopupBox/PopupBox.styles.tsx @@ -0,0 +1,20 @@ +import styled from '@emotion/styled' + +export const PopupBoxContainer = styled.div` + min-width: 450px; + max-width: 700px; + margin: 20px; + padding: 20px; + + display: flex; + flex-direction: column; + gap: 20px; + + justify-content: center; + align-items: center; + + .logo { + display: flex; + align-items: center; + } +` diff --git a/apps/extension/components/PopupBox/PopupBox.tsx b/apps/extension/components/PopupBox/PopupBox.tsx new file mode 100644 index 0000000..d7d0842 --- /dev/null +++ b/apps/extension/components/PopupBox/PopupBox.tsx @@ -0,0 +1,127 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import { Button, SymbolLogo, Text, TypoLogo } from '@vook-client/design-system' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useLayoutEffect, useState } from 'react' +import { baseFetcher, userOptions, vocabularyOptions } from '@vook-client/api' + +import { getStorage, removeStorage, setStorage } from '../../utils/storage' + +import { PopupBoxContainer } from './PopupBox.styles' + +import { SearchBox } from 'components/SearchBox' + +export const PopupBox = () => { + const [login, setLogin] = useState(false) + const [tokenDone, setTokenDone] = useState(false) + const [hasResult, setHasResult] = useState(false) + + const client = useQueryClient() + + const userInfo = useQuery({ + ...userOptions.userInfo(client), + enabled: tokenDone, + }) + const vocabularyQuery = useQuery({ + ...vocabularyOptions.vocabularyInfo(client), + enabled: tokenDone, + }) + + useLayoutEffect(() => { + baseFetcher.setUnAuthorizedHandler(() => { + removeStorage('vook-access') + removeStorage('vook-refresh') + setTokenDone(false) + }) + }, []) + + useLayoutEffect(() => { + const setToken = async () => { + const access = await getStorage('vook-access') + const refresh = await getStorage('vook-refresh') + const vookLogin = await getStorage('vook-login') + + if (!access || !refresh) { + setTokenDone(false) + return + } + + if (!vookLogin) { + setTokenDone(false) + setLogin(false) + return + } + + client.setQueryData(['access'], access) + client.setQueryData(['refresh'], refresh) + setTokenDone(true) + } + + setToken() + }, [client]) + + useLayoutEffect(() => { + if (userInfo.isSuccess && userInfo.data) { + setStorage('vook-user', userInfo.data) + setStorage('vook-vocabulary', vocabularyQuery.data) + setLogin(true) + } + }, [userInfo.data, userInfo.isSuccess, vocabularyQuery.data]) + + const onClickLogin = () => { + window.open( + `${process.env.PLASMO_PUBLIC_WEB_DOMAIN}/login`, + 'popup', + 'width=600,height=600', + ) + } + + return ( + + +
+ + +
+ {!login && ( + <> + + 주제별로 용어집을 관리하고, 간편하게 용어를 검색하세요 + + + + 이미 계정이 있으신가요?{' '} + + 로그인 + + + + )} + {login && } +
+ ) +} diff --git a/apps/extension/components/PopupBox/index.ts b/apps/extension/components/PopupBox/index.ts new file mode 100644 index 0000000..13719fc --- /dev/null +++ b/apps/extension/components/PopupBox/index.ts @@ -0,0 +1 @@ +export { PopupBox } from './PopupBox' diff --git a/apps/extension/components/SearchBox/SearchBox.styles.tsx b/apps/extension/components/SearchBox/SearchBox.styles.tsx new file mode 100644 index 0000000..876d9b3 --- /dev/null +++ b/apps/extension/components/SearchBox/SearchBox.styles.tsx @@ -0,0 +1,76 @@ +import styled from '@emotion/styled' + +export const SearchBoxContainer = styled.form` + position: relative; + + min-width: 400px; + width: fit-content; + width: 500px; +` + +export const SearchBoxInputBox = styled.div` + position: relative; + box-sizing: border-box; + + width: 100%; + + padding: 0px; +` + +export const SearchBoxInput = styled.input` + width: 100%; + height: 58px; + + background: #ffffff; + border: 1px solid rgba(112, 115, 124, 0.22); + border-radius: 6px; + + font-family: 'Pretendard'; + font-style: normal; + font-weight: 500; + font-size: 16px; + line-height: 150%; + letter-spacing: -0.01em; + font-feature-settings: 'ss10' on; + + padding-left: 70px; + box-sizing: border-box; + + ::placeholder { + color: rgba(22, 23, 25, 0.25); + } +` + +export const SearchBoxIcon = styled.div` + display: flex; + justify-content: center; + align-items: center; + + position: absolute; + + top: 0; + left: 0; + + height: 58px; + width: 70px; + z-index: 20; +` + +export const SearchButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + + border: none; + background-color: transparent; + + position: absolute; + top: 0; + right: 0; + + height: 58px; + width: 70px; + + cursor: pointer; + z-index: 20; +` diff --git a/apps/extension/components/SearchBox/SearchBox.tsx b/apps/extension/components/SearchBox/SearchBox.tsx new file mode 100644 index 0000000..7b6508a --- /dev/null +++ b/apps/extension/components/SearchBox/SearchBox.tsx @@ -0,0 +1,77 @@ +import { Icon, SymbolLogo } from '@vook-client/design-system' +import { useEffect, useState } from 'react' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { searchQueryOptions, vocabularyOptions } from '@vook-client/api' + +import * as S from './SearchBox.styles' + +import { TermList } from 'components/TermList' + +interface SearchBoxProps { + hasResult: boolean + setHasResult: (value: boolean) => void +} + +export const SearchBox = ({ hasResult, setHasResult }: SearchBoxProps) => { + const [text, setText] = useState('') + const client = useQueryClient() + + const vocabularyQuery = useQuery({ + ...vocabularyOptions.vocabularyInfo(client), + }) + + const query = useQuery({ + ...searchQueryOptions.search( + { + query: text, + highlightPostTag: '', + highlightPreTag: '', + withFormat: true, + vocabularyUids: + vocabularyQuery.data?.result.map((item) => item.uid) || [], + }, + client, + ), + enabled: false, + }) + + const onSubmit = () => { + query.refetch() + } + + useEffect(() => { + if (query.isSuccess && query.data.result.records.length > 0) { + setHasResult(true) + } else { + setHasResult(false) + } + }, [query?.data?.result.records, query.isSuccess, setHasResult]) + + return ( + { + e.preventDefault() + onSubmit() + }} + > + + + + + + + + setText(e.target.value)} + /> + {query.isSuccess && ( + + )} + + + ) +} diff --git a/apps/extension/components/SearchBox/index.ts b/apps/extension/components/SearchBox/index.ts new file mode 100644 index 0000000..943c6c6 --- /dev/null +++ b/apps/extension/components/SearchBox/index.ts @@ -0,0 +1 @@ +export { SearchBox } from './SearchBox' diff --git a/apps/extension/components/SearchWindow/SearchWindow.styles.tsx b/apps/extension/components/SearchWindow/SearchWindow.styles.tsx index ee3e3e4..9005aaf 100644 --- a/apps/extension/components/SearchWindow/SearchWindow.styles.tsx +++ b/apps/extension/components/SearchWindow/SearchWindow.styles.tsx @@ -10,7 +10,8 @@ export const SearchWindowBox = styled.div<{ position: Position }>` padding: 16px; - width: 500px; + width: fit-content; + min-width: 415px; border-radius: 6px; @@ -61,3 +62,11 @@ export const SearchWindowLink = styled.div` transform: translateY(1px); } ` + +export const SearchOverMaxLength = styled.div` + display: flex; + justify-content: center; + align-items: center; + + padding: 20px; +` diff --git a/apps/extension/components/SearchWindow/SearchWindow.tsx b/apps/extension/components/SearchWindow/SearchWindow.tsx index fef74e0..646a392 100644 --- a/apps/extension/components/SearchWindow/SearchWindow.tsx +++ b/apps/extension/components/SearchWindow/SearchWindow.tsx @@ -1,11 +1,33 @@ -import { isEmpty } from '@fxts/core' +import { Text } from '@vook-client/design-system' import { useSelectedText, useToggle } from '../../store/toggle' -import { TermList } from '../TermList' -import { useSearch } from '../../hooks/useSearch' -import * as S from './SearchWindow.styles' import { SearchWindowHeader } from './SearchWindowHeader' +import * as S from './SearchWindow.styles' + +import { useSearch } from 'hooks/useSearch' +import { TermList } from 'components/TermList' +import { + BlankTermList, + SearchWindowBlankButton, +} from 'components/TermList/TermList.styles' + +const PlusIcon = () => ( + + + +) const LinkExternalIcon = () => ( ( ) export const SearchWindow = () => { - const { position } = useToggle() const { selectedText } = useSelectedText() + const { position } = useToggle() - const { query, hitsTerms, headerText, tailText } = useSearch({ + const { query, headerText, tailText } = useSearch({ selectedText, }) + if (selectedText.length > 30) { + return ( + + + + + 용어 검색은 30자 이내로만 가능합니다. + + + + + Vook 바로가기 + + + + + ) + } + if (query.isLoading) { return ( + + + Vook 바로가기 + + + ) } @@ -44,7 +99,59 @@ export const SearchWindow = () => { if (query.isError) { return ( - + + + + 검색 중 에러가 발생하였습니다! + + + + Vook 바로가기 + + + + + + ) + } + + if ( + query.isSuccess && + query.data.result.records.reduce( + (acc, record) => record.hits.length + acc, + 0, + ) === 0 + ) { + return ( + + + + + 등록된 용어가 없습니다. + + { + window.open(process.env.PLASMO_PUBLIC_WEB_DOMAIN, '_blank') + }} + > + + 용어 추가하기 + + + + + Vook 바로가기 + + + ) } @@ -52,9 +159,19 @@ export const SearchWindow = () => { return ( - +
+ +
- + Vook 바로가기 diff --git a/apps/extension/components/TermItem/TermItem.styles.tsx b/apps/extension/components/TermItem/TermItem.styles.tsx index 7d2ce66..b434fdb 100644 --- a/apps/extension/components/TermItem/TermItem.styles.tsx +++ b/apps/extension/components/TermItem/TermItem.styles.tsx @@ -1,16 +1,13 @@ import styled from '@emotion/styled' +import { tokens } from '@vook-client/design-system' export const TermBox = styled.div` display: grid; - grid-template-columns: 120px 120px 1fr; + grid-template-columns: 160px 160px 1fr; + transition: background-color 0.5s; - border-bottom: 1px solid rgba(112, 115, 124, 0.16); - - font-size: 13px; - - &.header > div { - background-color: #efeffc; - color: #161719; + :hover { + background-color: ${tokens.colors['component-alternative']}; } ` @@ -23,16 +20,13 @@ export const TermSqaure = styled.div` } &.synonyms { - color: rgba(22, 23, 25, 0.6); } &.meaning { - color: #161719; word-break: keep-all; } strong { - font-weight: 400; background-color: #fff2b2; word-break: keep-all; } diff --git a/apps/extension/components/TermItem/TermItem.tsx b/apps/extension/components/TermItem/TermItem.tsx index 63c4e2f..b22f6f3 100644 --- a/apps/extension/components/TermItem/TermItem.tsx +++ b/apps/extension/components/TermItem/TermItem.tsx @@ -1,46 +1,39 @@ import type { SearchResponse } from '@vook-client/api' +import { Text } from '@vook-client/design-system' import * as S from './TermItem.styles' -interface TermHeaderItemProps { - term?: never -} - interface TermItemProps { - term: SearchResponse['result']['hits'][0] + term: SearchResponse['result']['records'][0]['hits'][0] } -export const TermItem = ({ term }: TermItemProps | TermHeaderItemProps) => { - if (!term) { - return ( - - 용어 - 동의어 - 의미 - - ) - } - +export const TermItem = ({ term }: TermItemProps) => { return ( - - - + + + + + + + + + ) } diff --git a/apps/extension/components/TermList/TermList.styles.tsx b/apps/extension/components/TermList/TermList.styles.tsx index e9fee96..28a0177 100644 --- a/apps/extension/components/TermList/TermList.styles.tsx +++ b/apps/extension/components/TermList/TermList.styles.tsx @@ -5,6 +5,8 @@ export const TermListBox = styled.article` border: 1px solid rgba(112, 115, 124, 0.16); max-height: 200px; + width: 100%; + min-width: 500px; overflow-y: scroll; ` @@ -54,3 +56,9 @@ export const SearchWindowBlankButton = styled.a` margin-right: 7px; } ` + +export const TermListVocabularyTitle = styled.div` + width: 100%; + padding: 12px 24px; + box-sizing: border-box; +` diff --git a/apps/extension/components/TermList/TermList.tsx b/apps/extension/components/TermList/TermList.tsx index 29689f3..1a0915e 100644 --- a/apps/extension/components/TermList/TermList.tsx +++ b/apps/extension/components/TermList/TermList.tsx @@ -1,52 +1,51 @@ -import { type SearchResponse } from '@vook-client/api' -import { isEmpty } from '@fxts/core' - -import { TermItem } from '../TermItem' +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import { vocabularyOptions, type SearchResponse } from '@vook-client/api' +import { useQueryClient } from '@tanstack/react-query' +import { Text } from '@vook-client/design-system' +import { Fragment } from 'react/jsx-runtime' import * as S from './TermList.styles' -const PlusIcon = () => ( - - - -) +import { TermItem } from 'components/TermItem' interface TermListProps { - hits: SearchResponse['result']['hits'] + records: SearchResponse['result']['records'] } -export const TermList = ({ hits }: TermListProps) => { - if (isEmpty(hits)) { - return ( - -

등록된 용어가 없습니다.

- - - 용어 추가하기 - -
- ) - } +export const TermList = ({ records }: TermListProps) => { + const client = useQueryClient() + const vocabularyUids = client.getQueryData<{ + result: { + name: string + uid: string + }[] + }>(vocabularyOptions.vocabularyInfo(client).queryKey)! return ( - - {hits.map((hit) => { - return + {records.map((record) => { + if (record.hits.length === 0) { + return null + } + return ( + + + + { + vocabularyUids.result.find( + (vocabulary) => vocabulary.uid === record.vocabularyUid, + )!.name + } + + + {record.hits.map((term) => ( +
+ +
+ ))} +
+ ) })}
) diff --git a/apps/extension/content.tsx b/apps/extension/content.tsx index cc0a565..49a9934 100644 --- a/apps/extension/content.tsx +++ b/apps/extension/content.tsx @@ -3,7 +3,10 @@ import styleText from 'data-text:@vook-client/design-system/style.css' import createCache from '@emotion/cache' import { CacheProvider, Global } from '@emotion/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useEffect } from 'react' +import { baseFetcher } from '@vook-client/api' +import { getStorage, removeStorage, setStorage } from './utils/storage' import { ToggleButton } from './components/ToggleButton' import { SearchWindow } from './components/SearchWindow' import { useDomRect } from './hooks/useDomRect' @@ -35,6 +38,67 @@ function VookContentScript() { useDomRect() const { isSelected, isOpenSearchWindow, position } = useToggle() + useEffect(() => { + baseFetcher.setUnAuthorizedHandler(async () => { + queryClient.removeQueries({ + queryKey: ['access'], + }) + queryClient.removeQueries({ + queryKey: ['refresh'], + }) + await removeStorage('vook-access') + await removeStorage('vook-refresh') + }) + }, []) + + useEffect(() => { + const setToken = async () => { + const access = await getStorage('vook-access') + const refresh = await getStorage('vook-refresh') + + queryClient.setQueryData(['access'], access) + queryClient.setQueryData(['refresh'], refresh) + } + + setToken() + }, []) + + useEffect(() => { + const postSuccessMessage = () => { + window.postMessage( + { + from: 'vook-extension', + content: 'success', + }, + '*', + ) + } + + window.addEventListener( + 'message', + async (event: { + data: { + from: string + access: string + refresh: string + } + }) => { + if ( + event.data.from === 'vook-web' && + event.data.access && + event.data.refresh + ) { + await setStorage('vook-access', event.data.access) + await setStorage('vook-refresh', event.data.refresh) + await setStorage('vook-login', true) + queryClient.setQueryData(['access'], event.data.access) + queryClient.setQueryData(['refresh'], event.data.refresh) + postSuccessMessage() + } + }, + ) + }, []) + return ( diff --git a/apps/extension/hooks/useSearch/useSearch.ts b/apps/extension/hooks/useSearch/useSearch.ts index 11deee5..1003f33 100644 --- a/apps/extension/hooks/useSearch/useSearch.ts +++ b/apps/extension/hooks/useSearch/useSearch.ts @@ -1,26 +1,35 @@ -import { useQuery } from '@tanstack/react-query' -import { searchQueryOptions, type SearchResponse } from '@vook-client/api' +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { + searchQueryOptions, + vocabularyOptions, + type SearchHit, + type SearchResponse, +} from '@vook-client/api' import { pipe, take, pluck, - map, toArray, filter, isEmpty, join, + flat, } from '@fxts/core' -import { stripHtmlTags } from '../../utils/parser' - interface UseSearchProps { selectedText: string } const MAX_HITS_QUANTITY = 3 +export const countTotalHits = ( + records: SearchResponse['result']['records'], +) => { + return pipe(records, pluck('hits'), flat, toArray) +} + export const getSearchTerms = (hits: SearchResponse['result']['records']) => { - return pipe(hits, pluck('term'), map(stripHtmlTags), take(4), toArray) + return pipe(hits, pluck('hits'), flat, take(4), pluck('term'), toArray) } export const isSubstring = (v1: string, v2: string) => @@ -38,28 +47,41 @@ export const getHeaderText = (terms: string[], selectedText: string) => ? selectedText : pipe(terms, take(MAX_HITS_QUANTITY), join(', ')) -export const getTailText = (terms: string[]) => +export const getTailText = (terms: SearchHit[]) => isEmpty(terms) ? '에 대한 검색 결과가 없습니다.' : `${terms.length > MAX_HITS_QUANTITY ? ' 등의' : ''} 용어를 찾았습니다.` export const useSearch = ({ selectedText }: UseSearchProps) => { + const client = useQueryClient() + + const vocabularyQuery = useQuery({ + ...vocabularyOptions.vocabularyInfo(client), + }) + const query = useQuery({ - ...searchQueryOptions.search({ - query: `${selectedText}`, - withFormat: true, - highlightPreTag: '', - highlightPostTag: '', - }), - select: (data) => data.result.hits, + ...searchQueryOptions.search( + { + query: selectedText, + highlightPostTag: '
', + highlightPreTag: '', + withFormat: true, + vocabularyUids: + vocabularyQuery.data?.result.map((item) => item.uid) || [], + }, + client, + ), + enabled: vocabularyQuery.isSuccess && selectedText.length <= 30, }) - const searchedTerms = getSearchTerms(query.data || []) + const totalCount = countTotalHits(query.data?.result.records || []) + const searchedTerms = getSearchTerms(query.data?.result.records || []) const hitsTerms = getHitsTerms(searchedTerms, selectedText) const headerText = getHeaderText(hitsTerms, selectedText) - const tailText = getTailText(hitsTerms) + const tailText = getTailText(totalCount) return { + totalCount, query, hitsTerms, headerText, diff --git a/apps/extension/package.json b/apps/extension/package.json index 4409c3d..4566de2 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -18,8 +18,11 @@ "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", "@fxts/core": "^1.0.0", + "@plasmohq/storage": "^1.11.0", "@tanstack/react-query": "^5.32.0", "@vook-client/api": "*", + "@vook-client/design-system": "*", + "hook": "link:@plasmohq/storage/hook", "plasmo": "^0.86.3", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/apps/extension/popup.css b/apps/extension/popup.css new file mode 100644 index 0000000..022306f --- /dev/null +++ b/apps/extension/popup.css @@ -0,0 +1,3 @@ +body { + overflow: hidden; +} diff --git a/apps/extension/popup.tsx b/apps/extension/popup.tsx index b216d45..c1e1827 100644 --- a/apps/extension/popup.tsx +++ b/apps/extension/popup.tsx @@ -1,5 +1,16 @@ +import '@vook-client/design-system/style.css' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +import { PopupBox } from './components/PopupBox' + +const queryClient = new QueryClient() + function IndexPopup() { - return
Pop up
+ return ( + + + + ) } export default IndexPopup diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json index 512d1d6..3c2dc22 100644 --- a/apps/extension/tsconfig.json +++ b/apps/extension/tsconfig.json @@ -1,12 +1,21 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "extends": "plasmo/templates/tsconfig.base", - "exclude": ["node_modules"], - "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"], - + "Display": "vite", "compilerOptions": { + "allowJs": false, + "esModuleInterop": false, + "jsx": "react-jsx", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": false, + "strict": true, + "target": "ESNext", + "types": ["vitest/globals", "@testing-library/jest-dom"], "baseUrl": ".", - "types": ["vitest/globals"], - "strictFunctionTypes": true, - "strictNullChecks": true, }, + "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"], } diff --git a/apps/extension/utils/storage.ts b/apps/extension/utils/storage.ts new file mode 100644 index 0000000..8beffa2 --- /dev/null +++ b/apps/extension/utils/storage.ts @@ -0,0 +1,17 @@ +import { Storage } from '@plasmohq/storage' + +const storage = new Storage({ + area: 'local', +}) + +export const setStorage = async (key: string, value: unknown) => { + await storage.set(key, value) +} + +export const getStorage = async (key: string): Promise => { + return storage.get(key) +} + +export const removeStorage = async (key: string) => { + await storage.remove(key) +} diff --git a/apps/web/package.json b/apps/web/package.json index a8fd613..3952183 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,7 @@ "dependencies": { "@hookform/resolvers": "^3.6.0", "@tanstack/react-query": "^5.32.0", + "@types/chrome": "0.0.258", "@types/js-cookie": "^3.0.6", "@vanilla-extract/css": "^1.14.2", "@vanilla-extract/css-utils": "^0.1.3", diff --git a/apps/web/src/app/(beforeLogin)/auth/token/page.tsx b/apps/web/src/app/(beforeLogin)/auth/token/page.tsx index 395e2ba..973ff81 100644 --- a/apps/web/src/app/(beforeLogin)/auth/token/page.tsx +++ b/apps/web/src/app/(beforeLogin)/auth/token/page.tsx @@ -26,6 +26,14 @@ const AuthCallbackPage = ({ Cookies.set('refresh', refresh, { secure: true, }) + window.postMessage( + { + from: 'vook-web', + access, + refresh, + }, + '*', + ) queryClient.setQueryData(['access'], access) queryClient.setQueryData(['refresh'], refresh) @@ -35,6 +43,22 @@ const AuthCallbackPage = ({ if (isRegistered) { router.push('/') + + const sendToken = () => { + window.postMessage( + { + from: 'vook-web', + access, + refresh, + }, + '*', + ) + } + + if (window.opener) { + sendToken() + } + return } router.push('/signup') @@ -43,6 +67,26 @@ const AuthCallbackPage = ({ checkUserRegistered() }, [access, queryClient, refresh, router]) + useEffect(() => { + window.addEventListener( + 'message', + (event: { + data: { + from: string + content: string + } + }) => { + if ( + event.data.from === 'vook-extension' && + event.data.content === 'success' && + window.opener + ) { + window.close() + } + }, + ) + }, []) + return null } diff --git a/apps/web/src/app/(onboarding)/onboarding/job/page.tsx b/apps/web/src/app/(onboarding)/onboarding/job/page.tsx index 1240b3a..42a510e 100644 --- a/apps/web/src/app/(onboarding)/onboarding/job/page.tsx +++ b/apps/web/src/app/(onboarding)/onboarding/job/page.tsx @@ -10,8 +10,6 @@ import React from 'react' import { OnboardingJob, useOnboardingMutation } from '@vook-client/api' import { useRouter } from 'next/navigation' -import { Link } from '@/components/Link' - import { SelectBoxGroup } from '../_components/SelectBoxGroup' import { useOnBoarding } from '../_context/useOnboarding' import { OnboardingHeader } from '../_components/OnboardingHeader' @@ -117,27 +115,23 @@ const OnboardingJobPage = () => {
- - - 건너뛰기 - - - - - + + 건너뛰기 + +
) diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 3a9fffa..c54a14a 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -8,6 +8,7 @@ import { pretendard } from '@/styles/fonts' import { MSWComponent } from '@/mock/MSWComponent' import { ToastContextProvider } from '@/hooks/useToast' import { InitialSetting } from '@/components/InitialSetting' +import TokenSender from '@/components/TokenSender/TokenSender' import { ModalContextProvider } from 'src/hooks/useModal/useModal' @@ -26,6 +27,7 @@ const RootLayout = ({ +
{children}