diff --git a/src/components/domains/ActivityItem/index.tsx b/src/components/domains/ActivityItem/index.tsx index 426a1fc..d4b6fbb 100644 --- a/src/components/domains/ActivityItem/index.tsx +++ b/src/components/domains/ActivityItem/index.tsx @@ -1,33 +1,33 @@ -import { captureException } from '@sentry/react'; +import { captureException } from '@sentry/react' -import type { ActivityType } from '@/types'; +import type { ActivityType } from '@/types' -import { ReactComponent as CheckIcon } from '@/assets/circle_check.svg'; -import { ReactComponent as XMarkIcon } from '@/assets/circle_x.svg'; -import { ReactComponent as StopwatchIcon } from '@/assets/stopwatch.svg'; -import ActivityTag from '@/components/domains/ActivityTag'; -import { convertDateTime, timeFormat } from '@/utils'; +import { ReactComponent as CheckIcon } from '@/assets/circle_check.svg' +import { ReactComponent as XMarkIcon } from '@/assets/circle_x.svg' +import { ReactComponent as StopwatchIcon } from '@/assets/stopwatch.svg' +import ActivityTag from '@/components/domains/ActivityTag' +import { convertDateTime, timeFormat } from '@/utils' type Props = { - activity: ActivityType; - courseName: string; -}; + activity: ActivityType + courseName: string +} const Icon = ({ type }: { type: 'check' | 'x' | 'stopwatch' }) => ({ check: , x: , stopwatch: , - }[type]); + }[type]) const ActivityItem = ({ activity, courseName }: Props) => { - const { type, title, endAt, id } = activity; + const { type, title, endAt, id } = activity - if (!title) captureException(new Error('[Warning] Activity title is empty')); - if (!endAt) captureException(new Error('[Warning] Activity endAt is empty')); - if (!id) captureException(new Error('[Warning] Activity id is empty')); + if (!title) captureException(new Error('[Warning] Activity title is empty')) + if (!endAt) captureException(new Error('[Warning] Activity endAt is empty')) + if (!id) captureException(new Error('[Warning] Activity id is empty')) - const isAssignment = type === 'assignment'; + const isAssignment = type === 'assignment' return ( {
{timeFormat(endAt)}
- ); -}; + ) +} -export default ActivityItem; +export default ActivityItem diff --git a/src/components/domains/ActivityList/index.tsx b/src/components/domains/ActivityList/index.tsx index b1ad88a..1bef976 100644 --- a/src/components/domains/ActivityList/index.tsx +++ b/src/components/domains/ActivityList/index.tsx @@ -1,12 +1,12 @@ -import type { ActivityType, Course } from '@/types'; +import type { ActivityType, Course } from '@/types' -import ActivityItem from '@/components/domains/ActivityItem'; -import FlexCenterDiv from '@/components/uis/FlexCenterDiv'; +import ActivityItem from '@/components/domains/ActivityItem' +import FlexCenterDiv from '@/components/uis/FlexCenterDiv' type Props = { - filteredActivityList: ActivityType[]; - courseList: Course[]; -}; + filteredActivityList: ActivityType[] + courseList: Course[] +} const ActivityList = ({ filteredActivityList, courseList }: Props) => { if (!filteredActivityList.length) @@ -14,7 +14,7 @@ const ActivityList = ({ filteredActivityList, courseList }: Props) => {

과제가 없습니다.

- ); + ) return (
{ /> ))}
- ); -}; + ) +} -export default ActivityList; +export default ActivityList diff --git a/src/components/domains/ActivityTag/index.tsx b/src/components/domains/ActivityTag/index.tsx index 9bdcd33..d489fbd 100644 --- a/src/components/domains/ActivityTag/index.tsx +++ b/src/components/domains/ActivityTag/index.tsx @@ -1,5 +1,5 @@ interface ActivityTagProps { - type: 'assignment' | 'video'; + type: 'assignment' | 'video' } const ActivityTag = ({ type }: ActivityTagProps) => { const status = { @@ -11,7 +11,7 @@ const ActivityTag = ({ type }: ActivityTagProps) => { color: 'bg-black', text: '녹화강의', }, - }[type]; + }[type] return ( { > {status.text} - ); -}; + ) +} -export default ActivityTag; +export default ActivityTag diff --git a/src/components/domains/ContentModal/hooks/useFetchData.ts b/src/components/domains/ContentModal/hooks/useFetchData.ts index 6770765..1b9fb32 100644 --- a/src/components/domains/ContentModal/hooks/useFetchData.ts +++ b/src/components/domains/ContentModal/hooks/useFetchData.ts @@ -1,47 +1,47 @@ -import { useState } from 'react'; +import { useState } from 'react' -import type { ActivityType, Course } from '@/types'; +import type { ActivityType, Course } from '@/types' -import { getActivities, getCourses } from '@/services'; -import { allProgress } from '@/utils'; +import { getActivities, getCourses } from '@/services' +import { allProgress } from '@/utils' type dataType = { - courseList: Course[]; - activityList: ActivityType[]; - updateAt: number; -}; + courseList: Course[] + activityList: ActivityType[] + updateAt: number +} const useFetchData = () => { - const [pos, setPos] = useState(0); + const [pos, setPos] = useState(0) const [data, setData] = useState({ courseList: [{ id: '-1', title: '전체' }], activityList: [], updateAt: 0, - }); + }) const getData = async () => { - const courses = await getCourses(); + const courses = await getCourses() const activities = await allProgress( courses.map(course => getActivities(course.id)), progress => setPos(progress), - ).then(activities => activities.flat()); + ).then(activities => activities.flat()) - const updateAt = new Date().getTime(); + const updateAt = new Date().getTime() setData({ courseList: [{ id: '-1', title: '전체' }, ...courses], activityList: activities, updateAt, - }); + }) - setPos(0); + setPos(0) chrome.storage.local.set({ courses, activities, updateAt, - }); - }; + }) + } const getLocalData = () => { chrome.storage.local.get(({ updateAt, courses, activities }) => { @@ -49,11 +49,11 @@ const useFetchData = () => { courseList: [{ id: '-1', title: '전체' }, ...courses], activityList: activities, updateAt, - }); - }); - }; + }) + }) + } - return [getData, getLocalData, data, pos] as const; -}; + return [getData, getLocalData, data, pos] as const +} -export default useFetchData; +export default useFetchData diff --git a/src/components/domains/ContentModal/index.tsx b/src/components/domains/ContentModal/index.tsx index 297c0bb..9551f3f 100644 --- a/src/components/domains/ContentModal/index.tsx +++ b/src/components/domains/ContentModal/index.tsx @@ -1,75 +1,75 @@ -import { forwardRef, useEffect, useState } from 'react'; -import { Tooltip } from 'react-tooltip'; +import { forwardRef, useEffect, useState } from 'react' +import { Tooltip } from 'react-tooltip' -import type { Course } from '@/types'; +import type { Course } from '@/types' -import { ReactComponent as RefreshIcon } from '@/assets/refresh.svg'; -import { ReactComponent as SettingIcon } from '@/assets/setting.svg'; -import ActivityList from '@/components/domains/ActivityList'; -import useFetchData from '@/components/domains/ContentModal/hooks/useFetchData'; -import Filter from '@/components/uis/Filter'; -import FlexCenterDiv from '@/components/uis/FlexCenterDiv'; -import Modal from '@/components/uis/Modal'; -import ProgressBar from '@/components/uis/ProgressBar'; -import { REFRESH_TIME } from '@/constants'; -import useError from '@/hooks/useError'; -import useScrollLock from '@/hooks/useScrollLock'; -import filteredActivities from '@/utils/filteredActivityList'; +import { ReactComponent as RefreshIcon } from '@/assets/refresh.svg' +import { ReactComponent as SettingIcon } from '@/assets/setting.svg' +import ActivityList from '@/components/domains/ActivityList' +import useFetchData from '@/components/domains/ContentModal/hooks/useFetchData' +import Filter from '@/components/uis/Filter' +import FlexCenterDiv from '@/components/uis/FlexCenterDiv' +import Modal from '@/components/uis/Modal' +import ProgressBar from '@/components/uis/ProgressBar' +import { REFRESH_TIME } from '@/constants' +import useError from '@/hooks/useError' +import useScrollLock from '@/hooks/useScrollLock' +import filteredActivities from '@/utils/filteredActivityList' const status = [ { id: 1, title: '진행중인 과제' }, { id: 2, title: '모든 과제' }, -]; +] type Props = { - isOpen: boolean; - onClick: (event: React.MouseEvent) => void; -}; + isOpen: boolean + onClick: (event: React.MouseEvent) => void +} const ContentModal = ({ isOpen, onClick }: Props, ref: React.Ref) => { - const [selectedCourse, setSelectedCourse] = useState({ id: '-1', title: '전체' }); - const [statusType, setStatusType] = useState<{ id: number; title: string }>(status[0]); - const [isRefresh, setIsRefresh] = useState(false); - const [isChecked, setIsChecked] = useState(false); + const [selectedCourse, setSelectedCourse] = useState({ id: '-1', title: '전체' }) + const [statusType, setStatusType] = useState<{ id: number; title: string }>(status[0]) + const [isRefresh, setIsRefresh] = useState(false) + const [isChecked, setIsChecked] = useState(false) - const { catchAsyncError } = useError(); - const { scrollLock, scrollUnlock } = useScrollLock(); - const [getData, getLocalData, data, pos] = useFetchData(); + const { catchAsyncError } = useError() + const { scrollLock, scrollUnlock } = useScrollLock() + const [getData, getLocalData, data, pos] = useFetchData() - const { courseList, activityList, updateAt } = data; + const { courseList, activityList, updateAt } = data const filteredActivityList = filteredActivities( activityList, selectedCourse.id, statusType.title, isChecked, - ); + ) useEffect(() => { - if (!isRefresh) return; + if (!isRefresh) return getData() .then(() => setIsRefresh(false)) - .catch(error => catchAsyncError(error)); - }, [isRefresh]); + .catch(error => catchAsyncError(error)) + }, [isRefresh]) useEffect(() => { - if (!isOpen) return; - scrollLock(); + if (!isOpen) return + scrollLock() if (!isRefresh) chrome.storage.local.get(['updateAt'], ({ updateAt }) => { - if (!updateAt) return setIsRefresh(true); + if (!updateAt) return setIsRefresh(true) - const diff = new Date().getTime() - updateAt; - const isOverRefreshTime = diff > REFRESH_TIME; + const diff = new Date().getTime() - updateAt + const isOverRefreshTime = diff > REFRESH_TIME if (!isOverRefreshTime) { - getLocalData(); + getLocalData() } else { - setIsRefresh(true); + setIsRefresh(true) } - }); + }) - return scrollUnlock; - }, [isOpen]); + return scrollUnlock + }, [isOpen]) return ( checked={isChecked} onChange={() => setIsChecked(!isChecked)} /> -

미제출 과제만 보기

+

미제출 과제만

@@ -144,7 +144,7 @@ const ContentModal = ({ isOpen, onClick }: Props, ref: React.Ref
- ); -}; + ) +} -export default forwardRef(ContentModal); +export default forwardRef(ContentModal) diff --git a/src/components/domains/OptionItem/index.tsx b/src/components/domains/OptionItem/index.tsx index f4a8c31..1966ef2 100644 --- a/src/components/domains/OptionItem/index.tsx +++ b/src/components/domains/OptionItem/index.tsx @@ -1,25 +1,25 @@ -import { useState } from 'react'; +import { useState } from 'react' -import Toggle from '@/components/uis/Toggle'; +import Toggle from '@/components/uis/Toggle' interface OptionItemProps { - text: string; + text: string } const OptionItem = ({ text }: OptionItemProps) => { - const [isOn, setIsOn] = useState(false); + const [isOn, setIsOn] = useState(false) const toggleSwitch = () => { - setIsOn(prev => !prev); + setIsOn(prev => !prev) // TODO: Save the option to the local storage - }; + } return (
  • {text}

  • - ); -}; + ) +} -export default OptionItem; +export default OptionItem diff --git a/src/components/uis/Filter/index.tsx b/src/components/uis/Filter/index.tsx index 6079594..ed738fd 100644 --- a/src/components/uis/Filter/index.tsx +++ b/src/components/uis/Filter/index.tsx @@ -1,25 +1,25 @@ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useState } from 'react' -import { ReactComponent as CheckIcon } from '@/assets/check.svg'; -import { ReactComponent as DropdownIcon } from '@/assets/dropdown.svg'; -import { ReactComponent as DropupIcon } from '@/assets/dropup.svg'; -import Modal from '@/components/uis/Modal'; +import { ReactComponent as CheckIcon } from '@/assets/check.svg' +import { ReactComponent as DropdownIcon } from '@/assets/dropdown.svg' +import { ReactComponent as DropupIcon } from '@/assets/dropup.svg' +import Modal from '@/components/uis/Modal' -type valueType = { title: string; [key: string]: any }; +type valueType = { title: string; [key: string]: any } type FilterProps = { - value: valueType; - onChange: React.Dispatch>; - children: React.ReactNode; - maxWidth?: string; - hasBorder?: boolean; -}; + value: valueType + onChange: React.Dispatch> + children: React.ReactNode + maxWidth?: string + hasBorder?: boolean +} -const FilterDataContext = createContext | null>(null); -const OpenClosedContext = createContext<{ isOpen: boolean } | null>(null); +const FilterDataContext = createContext | null>(null) +const OpenClosedContext = createContext<{ isOpen: boolean } | null>(null) const Filter = ({ value, maxWidth, onChange, children, hasBorder = true }: FilterProps) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false) return ( @@ -39,25 +39,25 @@ const Filter = ({ value, maxWidth, onChange, children, hasBorder = true }: Filte - ); -}; + ) +} type FilterHeaderProps = { - className?: string; -}; + className?: string +} const FilterHeader = ({ className }: FilterHeaderProps) => { - const { value } = useContext(FilterDataContext); - return

    {value.title}

    ; -}; + const { value } = useContext(FilterDataContext) + return

    {value.title}

    +} type FilterModalProps = { - children: React.ReactNode; - pos?: 'left' | 'right'; -}; + children: React.ReactNode + pos?: 'left' | 'right' +} const FilterModal = ({ children, pos = 'right' }: FilterModalProps) => { - const { isOpen } = useContext(OpenClosedContext); + const { isOpen } = useContext(OpenClosedContext) return ( { {children} - ); -}; + ) +} type FilterItemProps = { - item: valueType; -}; + item: valueType +} const FilterItem = ({ item }: FilterItemProps) => { - const { value, onChange } = useContext(FilterDataContext); - const isChecked = value === item; + const { value, onChange } = useContext(FilterDataContext) + const isChecked = value === item const handleClick = () => { - if (isChecked) return; - onChange(item); - }; + if (isChecked) return + onChange(item) + } return (
    { )}

    {item.title}

    - ); -}; + ) +} -Filter.Header = FilterHeader; -Filter.Modal = FilterModal; -Filter.Item = FilterItem; +Filter.Header = FilterHeader +Filter.Modal = FilterModal +Filter.Item = FilterItem -export default Filter; +export default Filter diff --git a/src/components/uis/FlexCenterDiv/index.tsx b/src/components/uis/FlexCenterDiv/index.tsx index 10649a2..d59274c 100644 --- a/src/components/uis/FlexCenterDiv/index.tsx +++ b/src/components/uis/FlexCenterDiv/index.tsx @@ -1,6 +1,6 @@ interface Props extends React.HTMLAttributes { - className: string; - children: React.ReactNode; + className: string + children: React.ReactNode } const FlexCenterDiv = ({ className, children, ...props }: Props) => { @@ -8,7 +8,7 @@ const FlexCenterDiv = ({ className, children, ...props }: Props) => {
    {children}
    - ); -}; + ) +} -export default FlexCenterDiv; +export default FlexCenterDiv diff --git a/src/components/uis/Modal/index.tsx b/src/components/uis/Modal/index.tsx index 04e9424..fd2501f 100644 --- a/src/components/uis/Modal/index.tsx +++ b/src/components/uis/Modal/index.tsx @@ -1,21 +1,21 @@ -import { AnimatePresence, motion } from 'framer-motion'; -import { forwardRef } from 'react'; +import { AnimatePresence, motion } from 'framer-motion' +import { forwardRef } from 'react' type ModalProps = { - children: React.ReactNode; - className?: string; -}; + children: React.ReactNode + className?: string +} const Modal = ({ className, children }: ModalProps) => { - return
    {children}
    ; -}; + return
    {children}
    +} type ModalBackgroundProps = { - isOpen: boolean; - children: React.ReactNode; - className?: string; - onClick?: (e: React.MouseEvent) => void; -}; + isOpen: boolean + children: React.ReactNode + className?: string + onClick?: (e: React.MouseEvent) => void +} const ModalBackground = ( { isOpen, children, className, onClick }: ModalBackgroundProps, @@ -36,9 +36,9 @@ const ModalBackground = ( )} - ); -}; + ) +} -Modal.Background = forwardRef(ModalBackground); +Modal.Background = forwardRef(ModalBackground) -export default Modal; +export default Modal diff --git a/src/components/uis/ProgressBar/index.tsx b/src/components/uis/ProgressBar/index.tsx index 4d2dcdf..c79bc4f 100644 --- a/src/components/uis/ProgressBar/index.tsx +++ b/src/components/uis/ProgressBar/index.tsx @@ -1,16 +1,16 @@ type Props = { - pos: number; - max?: number; -}; + pos: number + max?: number +} const ProgressBar = ({ pos, max = 100 }: Props) => { - const percent = (pos / max) * 100; + const percent = (pos / max) * 100 return (
    - ); -}; + ) +} -export default ProgressBar; +export default ProgressBar diff --git a/src/components/uis/Toast/index.tsx b/src/components/uis/Toast/index.tsx index 929b335..823966d 100644 --- a/src/components/uis/Toast/index.tsx +++ b/src/components/uis/Toast/index.tsx @@ -1,26 +1,26 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react' -import Modal from '../Modal'; +import Modal from '../Modal' type Props = { - message: string; - type: 'success' | 'error'; - delay?: number; -}; + message: string + type: 'success' | 'error' + delay?: number +} const Toast = ({ message, type, delay = 3000 }: Props) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false) const toastColor = { success: 'bg-green-500', error: 'bg-red-500', - }; + } useEffect(() => { - setIsOpen(true); - const timer = setTimeout(() => setIsOpen(false), delay); - return () => clearTimeout(timer); - }, []); + setIsOpen(true) + const timer = setTimeout(() => setIsOpen(false), delay) + return () => clearTimeout(timer) + }, []) return ( @@ -30,7 +30,7 @@ const Toast = ({ message, type, delay = 3000 }: Props) => {

    {message}

    - ); -}; + ) +} -export default Toast; +export default Toast diff --git a/src/components/uis/Toggle/index.tsx b/src/components/uis/Toggle/index.tsx index 7dddc49..429b054 100644 --- a/src/components/uis/Toggle/index.tsx +++ b/src/components/uis/Toggle/index.tsx @@ -1,8 +1,8 @@ -import { motion } from 'framer-motion'; +import { motion } from 'framer-motion' interface ToggleProps { - isOn: boolean; - toggleSwitch: () => void; + isOn: boolean + toggleSwitch: () => void } const Toggle = ({ isOn, toggleSwitch }: ToggleProps) => { @@ -10,7 +10,7 @@ const Toggle = ({ isOn, toggleSwitch }: ToggleProps) => { type: 'spring', stiffness: 700, damping: 30, - }; + } return (
    { transition={spring} >
    - ); -}; + ) +} -export default Toggle; +export default Toggle diff --git a/src/constants/index.ts b/src/constants/index.ts index 14797b9..a2cad00 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1 +1 @@ -export const REFRESH_TIME = 1000 * 60 * 10; // 10분 +export const REFRESH_TIME = 1000 * 60 * 10 // 10분 diff --git a/src/content.tsx b/src/content.tsx index 084cf5a..b903107 100644 --- a/src/content.tsx +++ b/src/content.tsx @@ -1,12 +1,12 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import App from '@/pages/content/App'; +import App from '@/pages/content/App' -import '@/styles/globals.css'; -import 'react-tooltip/dist/react-tooltip.css'; +import '@/styles/globals.css' +import 'react-tooltip/dist/react-tooltip.css' -const root = document.createElement('div'); -root.id = 'crx-root'; -document.body.append(root); +const root = document.createElement('div') +root.id = 'crx-root' +document.body.append(root) -createRoot(root).render(); +createRoot(root).render() diff --git a/src/data/dummyData.ts b/src/data/dummyData.ts index 8d81397..4eae201 100644 --- a/src/data/dummyData.ts +++ b/src/data/dummyData.ts @@ -1,4 +1,4 @@ -import type { ActivityType, Course } from '@/types'; +import type { ActivityType, Course } from '@/types' export const courseData: Course[] = [ { @@ -17,7 +17,7 @@ export const courseData: Course[] = [ id: '76094', title: '고객가치', }, -]; +] export const ActivityData: ActivityType[] = [ { @@ -399,4 +399,4 @@ export const ActivityData: ActivityType[] = [ title: '성적제출', type: 'assignment', }, -]; +] diff --git a/src/global.d.ts b/src/global.d.ts index 4cc07c1..d68059d 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,5 +1,5 @@ -import Chrome from 'chrome'; +import Chrome from 'chrome' declare namespace chrome { - export default Chrome; + export default Chrome } diff --git a/src/helpers/portal.ts b/src/helpers/portal.ts index cce7e3e..81f1b5d 100644 --- a/src/helpers/portal.ts +++ b/src/helpers/portal.ts @@ -1,13 +1,13 @@ -import { createPortal } from 'react-dom'; +import { createPortal } from 'react-dom' type Props = { - elementId: string; - children: React.ReactNode; -}; + elementId: string + children: React.ReactNode +} const Portal = ({ elementId, children }: Props) => { - const el = document.getElementById(elementId) as HTMLElement; - return createPortal(children, el); -}; + const el = document.getElementById(elementId) as HTMLElement + return createPortal(children, el) +} -export default Portal; +export default Portal diff --git a/src/hooks/useError.ts b/src/hooks/useError.ts index c4865d7..077fadd 100644 --- a/src/hooks/useError.ts +++ b/src/hooks/useError.ts @@ -1,13 +1,13 @@ -import { useState } from 'react'; +import { useState } from 'react' const useError = () => { - const [error, setError] = useState(null); + const [error, setError] = useState(null) const catchAsyncError = (error: Error) => { - setError(error); - }; + setError(error) + } - return { error, catchAsyncError }; -}; + return { error, catchAsyncError } +} -export default useError; +export default useError diff --git a/src/hooks/useScrollLock.ts b/src/hooks/useScrollLock.ts index 8a653d0..25ae26e 100644 --- a/src/hooks/useScrollLock.ts +++ b/src/hooks/useScrollLock.ts @@ -4,16 +4,16 @@ const useScrollLock = () => { position: fixed; top: -${window.scrollY}px; overflow-y: scroll; - width: 100%;`; - }; + width: 100%;` + } const scrollUnlock = () => { - const scrollY = document.body.style.top; - document.body.style.cssText = ''; - window.scrollTo(0, parseInt(scrollY || '0', 10) * -1); - }; + const scrollY = document.body.style.top + document.body.style.cssText = '' + window.scrollTo(0, parseInt(scrollY || '0', 10) * -1) + } - return { scrollLock, scrollUnlock }; -}; + return { scrollLock, scrollUnlock } +} -export default useScrollLock; +export default useScrollLock diff --git a/src/pages/background/index.ts b/src/pages/background/index.ts index 0cc61dc..30092e6 100644 --- a/src/pages/background/index.ts +++ b/src/pages/background/index.ts @@ -4,23 +4,23 @@ chrome.runtime.onInstalled.addListener(async () => { chrome.scripting.executeScript({ target: { tabId: tab.id }, files: cs.js, - }); + }) cs.css?.forEach(css => { chrome.scripting.insertCSS({ target: { tabId: tab.id }, files: [css], - }); - }); + }) + }) } } -}); +}) chrome.storage.onChanged.addListener((changes, areaName) => { if (areaName === 'local') { for (const key of Object.keys(changes)) { - console.log(`storage.local.${key} changed`); + console.log(`storage.local.${key} changed`) } } -}); +}) -export {}; +export {} diff --git a/src/pages/content/App.tsx b/src/pages/content/App.tsx index 0e5f923..447c866 100644 --- a/src/pages/content/App.tsx +++ b/src/pages/content/App.tsx @@ -1,32 +1,32 @@ -import { ErrorBoundary } from '@sentry/react'; -import { motion } from 'framer-motion'; -import { useRef, useState } from 'react'; +import { Box, useDisclosure } from '@chakra-ui/react' +import { AnimatePresence } from 'framer-motion' -import ContentModal from '@/components/domains/ContentModal'; -import Toast from '@/components/uis/Toast'; -import Portal from '@/helpers/portal'; +import ChakraMotion from '@/components/ChakraMotion' +import ContentModal from '@/components/ContentModal' export default function App() { - const [isModalOpen, setIsModalOpen] = useState(false); - const modalRef = useRef(); - - const handleModalClick = (event: React.MouseEvent) => { - if (event.target === modalRef.current) setIsModalOpen(false); - }; + const { isOpen, onOpen, onClose } = useDisclosure() return ( -
    - setIsModalOpen(prev => !prev)} - > - - }> - - - -
    - ); + + + {!isOpen && ( + + )} + + + + ) } diff --git a/src/pages/content/main.tsx b/src/pages/content/main.tsx index 672847c..51caca1 100644 --- a/src/pages/content/main.tsx +++ b/src/pages/content/main.tsx @@ -1,4 +1,4 @@ -import { ChakraProvider } from '@chakra-ui/react' +import { ChakraProvider, extendTheme } from '@chakra-ui/react' import * as Sentry from '@sentry/react' import { createRoot } from 'react-dom/client' @@ -32,8 +32,19 @@ const modal = document.createElement('div') modal.id = 'modal' document.body.append(modal) +const theme = extendTheme({ + styles: { + global: { + p: { + margin: 0, + padding: 0, + }, + }, + }, +}) + createRoot(root).render( - + , ) diff --git a/src/pages/options/Options.tsx b/src/pages/options/Options.tsx index d1fb522..98e462e 100644 --- a/src/pages/options/Options.tsx +++ b/src/pages/options/Options.tsx @@ -1,6 +1,6 @@ -import packageJson from '../../../package.json'; +import packageJson from '../../../package.json' -const { version } = packageJson; +const { version } = packageJson function Options() { return ( @@ -17,7 +17,7 @@ function Options() {

    version: {version}

    - ); + ) } -export default Options; +export default Options diff --git a/src/pages/options/index.tsx b/src/pages/options/index.tsx index 5f5e727..da15a04 100644 --- a/src/pages/options/index.tsx +++ b/src/pages/options/index.tsx @@ -1,7 +1,7 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import Options from '@/pages/options/Options'; -import '@/styles/globals.css'; -import '@/styles/options.css'; +import Options from '@/pages/options/Options' +import '@/styles/globals.css' +import '@/styles/options.css' -createRoot(document.getElementById('app-container') as HTMLElement).render(); +createRoot(document.getElementById('app-container') as HTMLElement).render() diff --git a/src/pages/popup/Popup.tsx b/src/pages/popup/Popup.tsx index aec9ae2..23f3fb2 100644 --- a/src/pages/popup/Popup.tsx +++ b/src/pages/popup/Popup.tsx @@ -1,8 +1,8 @@ -import { Tooltip } from 'react-tooltip'; +import { Tooltip } from 'react-tooltip' -import { ReactComponent as FeedbackIcon } from '@/assets/feedback.svg'; -import { ReactComponent as GachonIcon } from '@/assets/gachon.svg'; -import { ReactComponent as GithubIcon } from '@/assets/github.svg'; +import { ReactComponent as FeedbackIcon } from '@/assets/feedback.svg' +import { ReactComponent as GachonIcon } from '@/assets/gachon.svg' +import { ReactComponent as GithubIcon } from '@/assets/github.svg' function App() { return ( @@ -36,7 +36,7 @@ function App() { - ); + ) } -export default App; +export default App diff --git a/src/pages/popup/index.tsx b/src/pages/popup/index.tsx index c514a59..238f6d5 100644 --- a/src/pages/popup/index.tsx +++ b/src/pages/popup/index.tsx @@ -1,7 +1,7 @@ -import { createRoot } from 'react-dom/client'; +import { createRoot } from 'react-dom/client' -import Popup from '@/pages/popup/Popup'; -import '@/styles/globals.css'; -import 'react-tooltip/dist/react-tooltip.css'; +import Popup from '@/pages/popup/Popup' +import '@/styles/globals.css' +import 'react-tooltip/dist/react-tooltip.css' -createRoot(document.getElementById('app-container') as HTMLElement).render(); +createRoot(document.getElementById('app-container') as HTMLElement).render() diff --git a/src/services/index.ts b/src/services/index.ts index 1e75a91..afff102 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,75 +1,75 @@ -import { captureException } from '@sentry/react'; -import axios from 'axios'; -import * as cheerio from 'cheerio'; +import { captureException } from '@sentry/react' +import axios from 'axios' +import * as cheerio from 'cheerio' -import type { Assignment, Video } from '@/types'; +import type { Assignment, Video } from '@/types' -import { getLinkId } from '@/utils'; +import { getLinkId } from '@/utils' const fetchDocument = async (url: string) => { try { - const response = await axios.get(url); - const html = response.data; - const $ = cheerio.load(html); - return $; + const response = await axios.get(url) + const html = response.data + const $ = cheerio.load(html) + return $ } catch (error) { - console.error(error); + console.error(error) } -}; +} /** * 모든 course를 가져온다. */ export const getCourses = async () => { - const $ = await fetchDocument('https://cyber.gachon.ac.kr/local/ubion/user'); + const $ = await fetchDocument('https://cyber.gachon.ac.kr/local/ubion/user') const courses = $('.coursefullname').map((i, el) => { - const id = getLinkId($(el).attr('href')); + const id = getLinkId($(el).attr('href')) const title = $(el) .text() - .replace(/ \((\d{5}_\d{3})\)/, ''); + .replace(/ \((\d{5}_\d{3})\)/, '') return { id, title, - }; - }); + } + }) - return courses.get(); -}; + return courses.get() +} /** * 강의의 activity들을 가져온다. * @param courseId course id */ export const getActivities = async (courseId: string): Promise<(Assignment | Video)[]> => { - const $ = await fetchDocument(`https://cyber.gachon.ac.kr/course/view.php?id=${courseId}`); - const assignmentAtCourseDocument = getAssignmentAtCourseDocument($, courseId); - const videoAtCourseDocument = getVideoAtCourseDocument($, courseId); + const $ = await fetchDocument(`https://cyber.gachon.ac.kr/course/view.php?id=${courseId}`) + const assignmentAtCourseDocument = getAssignmentAtCourseDocument($, courseId) + const videoAtCourseDocument = getVideoAtCourseDocument($, courseId) - const assignmentSubmittedArray = await getAssignmentSubmitted(courseId); - const videoSubmittedArray = await getVideoSubmitted(courseId); + const assignmentSubmittedArray = await getAssignmentSubmitted(courseId) + const videoSubmittedArray = await getVideoSubmitted(courseId) const assignment = assignmentAtCourseDocument.reduce((acc, cur) => { const findAssignment = assignmentSubmittedArray.find( a => a.sectionTitle === cur.sectionTitle && a.title === cur.title, - ); - if (findAssignment) return [...acc, Object.assign({}, cur, findAssignment)]; + ) + if (findAssignment) return [...acc, Object.assign({}, cur, findAssignment)] - return acc; - }, []); + return acc + }, []) const video = videoAtCourseDocument.reduce((acc, cur) => { const findVideo = videoSubmittedArray.find( v => v.sectionTitle === cur.sectionTitle && v.title === cur.title, - ); - if (findVideo) return [...acc, Object.assign({}, cur, findVideo)]; + ) + if (findVideo) return [...acc, Object.assign({}, cur, findVideo)] - return acc; - }, []); + return acc + }, []) - return [...assignment, ...video]; -}; + return [...assignment, ...video] +} /** * 강의 페이지의 document에서 과제를 가져온다. @@ -78,10 +78,10 @@ export const getActivities = async (courseId: string): Promise<(Assignment | Vid */ const getAssignmentAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) => { const sectionOne = $('#section-0 .modtype_assign .activityinstance').map((i, el) => { - const link = $(el).find('a').attr('href'); + const link = $(el).find('a').attr('href') - const id = getLinkId(link); - const title = $(el).find('.instancename').clone().children().remove().end().text().trim(); + const id = getLinkId(link) + const title = $(el).find('.instancename').clone().children().remove().end().text().trim() const assignment = { type: 'assignment', @@ -91,26 +91,26 @@ const getAssignmentAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) sectionTitle: '', startAt: '', endAt: '', - }; + } - return assignment; - }); + return assignment + }) const sectionTwo = $('.total_sections .content').map((i, el) => { - const sectionTitle = $(el).find('.sectionname').text().trim(); + const sectionTitle = $(el).find('.sectionname').text().trim() return $(el) .find('.modtype_assign .activityinstance') .map((i, el) => { - const link = $(el).find('a').attr('href'); // 링크 없는 과제도 존재 + const link = $(el).find('a').attr('href') // 링크 없는 과제도 존재 - const id = getLinkId(link); - const title = $(el).find('.instancename').clone().children().remove().end().text().trim(); + const id = getLinkId(link) + const title = $(el).find('.instancename').clone().children().remove().end().text().trim() const [startAt, endAt] = $(el) .find('.displayoptions') .text() .split(' ~ ') - .map(t => t.trim()); + .map(t => t.trim()) return { type: 'assignment', @@ -120,13 +120,13 @@ const getAssignmentAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) sectionTitle, startAt, endAt, - }; + } }) - .get(); - }); + .get() + }) - return [...sectionOne.get(), ...sectionTwo.get()]; -}; + return [...sectionOne.get(), ...sectionTwo.get()] +} /** * 강의 페이지의 document에서 동영상 과제를 가져온다. @@ -136,14 +136,14 @@ const getAssignmentAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) const getVideoAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) => { return $('.total_sections .content') .map((i, el) => { - const sectionTitle = $(el).find('.sectionname').text().trim(); + const sectionTitle = $(el).find('.sectionname').text().trim() return $(el) .find('.activity.vod .activityinstance') .map((i, el) => { - const link = $(el).find('a').attr('href'); // 링크 없는 과제도 존재 - const id = getLinkId(link); - const title = $(el).find('.instancename').clone().children().remove().end().text().trim(); + const link = $(el).find('a').attr('href') // 링크 없는 과제도 존재 + const id = getLinkId(link) + const title = $(el).find('.instancename').clone().children().remove().end().text().trim() const [startAt, endAt] = $(el) .find('.displayoptions .text-ubstrap') .clone() @@ -152,13 +152,13 @@ const getVideoAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) => { .end() .text() .split(' ~ ') - .map(t => t.trim()); - const timeInfo = $(el).find('.displayoptions .time-info').text(); + .map(t => t.trim()) + const timeInfo = $(el).find('.displayoptions .time-info').text() if (!id) { captureException( new Error(`getVideoAtCourseDocument에서 id 없음. ${title} / ${startAt} / ${endAt}`), - ); + ) } return { @@ -170,41 +170,41 @@ const getVideoAtCourseDocument = ($: cheerio.CheerioAPI, courseId: string) => { endAt, timeInfo, sectionTitle, - }; + } }) - .get(); + .get() }) - .get(); -}; + .get() +} /** * 강의의 과제 제출 여부를 가져온다. * @param courseId course id */ const getAssignmentSubmitted = async (courseId: string) => { - const $ = await fetchDocument(`https://cyber.gachon.ac.kr/mod/assign/index.php?id=${courseId}`); + const $ = await fetchDocument(`https://cyber.gachon.ac.kr/mod/assign/index.php?id=${courseId}`) - let currentSectionTitle = ''; + let currentSectionTitle = '' return $('tbody tr') .map((i, el) => { - if ($(el).find('.tabledivider').length) return; + if ($(el).find('.tabledivider').length) return - const sectionTitle = $(el).find('.c0').text().trim(); - if (sectionTitle !== '') currentSectionTitle = sectionTitle; + const sectionTitle = $(el).find('.c0').text().trim() + if (sectionTitle !== '') currentSectionTitle = sectionTitle - const title = $(el).find('.c1 a').text().trim(); - const endAt = $(el).find('.c2').text().trim() + ':00'; - const hasSubmitted = /(Submitted for grading)|(제출 완료)/.test($(el).find('.c3').text()); + const title = $(el).find('.c1 a').text().trim() + const endAt = $(el).find('.c2').text().trim() + ':00' + const hasSubmitted = /(Submitted for grading)|(제출 완료)/.test($(el).find('.c3').text()) return { title, sectionTitle: currentSectionTitle, endAt, hasSubmitted, - }; + } }) - .get(); -}; + .get() +} /** * 강의의 비디오 제출 여부를 가져온다. @@ -213,31 +213,31 @@ const getAssignmentSubmitted = async (courseId: string) => { const getVideoSubmitted = async (courseId: string) => { const $ = await fetchDocument( `https://cyber.gachon.ac.kr/report/ubcompletion/progress.php?id=${courseId}`, - ); + ) const className = $('.user_progress tbody tr').length === 0 ? '.user_progress_table tbody tr' - : '.user_progress tbody tr'; + : '.user_progress tbody tr' - let currentSectionTitle = ''; + let currentSectionTitle = '' return $(className) .map((i, el) => { if ($(el).find('.sectiontitle').length) - currentSectionTitle = $(el).find('.sectiontitle').attr('title'); - const std = $(el).find('.text-center.hidden-xs.hidden-sm'); - const title = std.prev().text().trim(); - const requiredTime = std.text().trim(); - const totalStudyTime = std.next().clone().children().remove().end().text().trim(); + currentSectionTitle = $(el).find('.sectiontitle').attr('title') + const std = $(el).find('.text-center.hidden-xs.hidden-sm') + const title = std.prev().text().trim() + const requiredTime = std.text().trim() + const totalStudyTime = std.next().clone().children().remove().end().text().trim() const hasSubmitted = - Number(requiredTime.replace(/:/g, '')) <= Number(totalStudyTime.replace(/:/g, '')); + Number(requiredTime.replace(/:/g, '')) <= Number(totalStudyTime.replace(/:/g, '')) return { title, hasSubmitted, sectionTitle: currentSectionTitle, - }; + } }) - .get(); -}; + .get() +} diff --git a/src/types/index.ts b/src/types/index.ts index 7decc4c..345004e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,24 +1,24 @@ interface Activity { - id: string; - courseId: string; - title: string; - sectionTitle: string; - startAt: string; // 시작 날짜 없는 과제도 존재 ex) No time limit - endAt: string; // 마감 날짜 없는 과제가 있나? 확실하지 않음 - hasSubmitted: boolean; + id: string + courseId: string + title: string + sectionTitle: string + startAt: string // 시작 날짜 없는 과제도 존재 ex) No time limit + endAt: string // 마감 날짜 없는 과제가 있나? 확실하지 않음 + hasSubmitted: boolean } export interface Course { - id: string; - title: string; + id: string + title: string } export interface Assignment extends Activity { - type: 'assignment'; + type: 'assignment' } export interface Video extends Activity { - type: 'video'; - timeInfo: string; + type: 'video' + timeInfo: string } -export type ActivityType = Assignment | Video; +export type ActivityType = Assignment | Video diff --git a/src/utils/allProgress.ts b/src/utils/allProgress.ts index ed346dd..c4f4fa7 100644 --- a/src/utils/allProgress.ts +++ b/src/utils/allProgress.ts @@ -2,13 +2,13 @@ export default function allProgress( promise: Promise[], callback: (progress: number) => void, ) { - let d = 0; - callback(0); + let d = 0 + callback(0) for (const p of promise) { p.then(() => { - d++; - callback((d * 100) / promise.length); - }); + d++ + callback((d * 100) / promise.length) + }) } - return Promise.all(promise); + return Promise.all(promise) } diff --git a/src/utils/convertDateTime.ts b/src/utils/convertDateTime.ts index 1fd9937..e19422d 100644 --- a/src/utils/convertDateTime.ts +++ b/src/utils/convertDateTime.ts @@ -1,8 +1,8 @@ export default function convertDateTime(dateTime: string) { - if (!dateTime) return ''; - const [, month, day, hour, minute] = dateTime.split(/\-|:| /); - const date = new Date(dateTime); - const dayName = ['일', '월', '화', '수', '목', '금', '토'][date.getDay()]; + if (!dateTime) return '' + const [, month, day, hour, minute] = dateTime.split(/\-|:| /) + const date = new Date(dateTime) + const dayName = ['일', '월', '화', '수', '목', '금', '토'][date.getDay()] - return `${month}월 ${day}일 (${dayName}) ${hour}시 ${minute}분`; + return `${month}월 ${day}일 (${dayName}) ${hour}시 ${minute}분` } diff --git a/src/utils/filteredActivityList.ts b/src/utils/filteredActivityList.ts index a4bec2c..98f0c75 100644 --- a/src/utils/filteredActivityList.ts +++ b/src/utils/filteredActivityList.ts @@ -1,35 +1,35 @@ -import type { ActivityType } from '@/types'; +import type { ActivityType } from '@/types' -import { pipe } from '@/utils'; +import { pipe } from '@/utils' const activityListByCourse = (activityList: ActivityType[], id: string) => { if (id === '-1') { - return activityList; + return activityList } - return activityList.filter(activity => activity.courseId === id); -}; + return activityList.filter(activity => activity.courseId === id) +} const sortAcitivityList = (activityList: ActivityType[]) => { - return activityList.sort((a, b) => new Date(a.endAt).getTime() - new Date(b.endAt).getTime()); -}; + return activityList.sort((a, b) => new Date(a.endAt).getTime() - new Date(b.endAt).getTime()) +} const activityListByStatus = (activityList: ActivityType[], status: string) => { if (status === '진행중인 과제') { return activityList.filter( activity => new Date(activity.endAt).getTime() > new Date().getTime(), - ); + ) } - return activityList; -}; + return activityList +} const activityListBySubmitted = (activityList: ActivityType[], isChecked: boolean) => { if (isChecked) { - return activityList.filter(activity => !activity.hasSubmitted); + return activityList.filter(activity => !activity.hasSubmitted) } - return activityList; -}; + return activityList +} const filteredActivities = ( activityList: ActivityType[], @@ -43,6 +43,6 @@ const filteredActivities = ( activityList => sortAcitivityList(activityList), activityList => activityListByStatus(activityList, status), activityList => activityListBySubmitted(activityList, isChecked), - ); + ) -export default filteredActivities; +export default filteredActivities diff --git a/src/utils/getLinkId.ts b/src/utils/getLinkId.ts index a3a8c8e..7c09442 100644 --- a/src/utils/getLinkId.ts +++ b/src/utils/getLinkId.ts @@ -1,4 +1,4 @@ export default function getLinkId(link: string) { - if (!link) return ''; - return new URL(link).searchParams.get('id'); + if (!link) return '' + return new URL(link).searchParams.get('id') } diff --git a/src/utils/index.ts b/src/utils/index.ts index 6299365..7424606 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,6 @@ -export { default as pipe } from './pipe'; -export { default as allProgress } from './allProgress'; -export { default as timeFormat } from './timeFormat'; -export { default as getLinkId } from './getLinkId'; -export { default as convertDateTime } from './convertDateTime'; -export { default as filteredActivityList } from './filteredActivityList'; +export { default as pipe } from './pipe' +export { default as allProgress } from './allProgress' +export { default as timeFormat } from './timeFormat' +export { default as getLinkId } from './getLinkId' +export { default as convertDateTime } from './convertDateTime' +export { default as filteredActivityList } from './filteredActivityList' diff --git a/src/utils/pipe.ts b/src/utils/pipe.ts index fa69dd9..b884930 100644 --- a/src/utils/pipe.ts +++ b/src/utils/pipe.ts @@ -1,7 +1,7 @@ export default function pipe(value: T, ...funcs: ((value: T) => T)[]) { - let acc = value; + let acc = value for (const f of funcs) { - acc = f(acc); + acc = f(acc) } - return acc; + return acc } diff --git a/src/utils/timeFormat.ts b/src/utils/timeFormat.ts index 958ce1e..32a88c0 100644 --- a/src/utils/timeFormat.ts +++ b/src/utils/timeFormat.ts @@ -1,10 +1,10 @@ export default function timeFormat(endAt: string) { - if (!endAt) return ''; + if (!endAt) return '' - const curDate = new Date(); - const dueDate = new Date(endAt); - const timeDiff = dueDate.getTime() - curDate.getTime(); - const day = Math.floor(Math.abs(timeDiff) / (1000 * 60 * 60 * 24)); - if ((timeDiff > 0 && day === 0) || timeDiff === 0) return 'D-day'; - return timeDiff < 0 ? '제출마감' : `D-${day}`; + const curDate = new Date() + const dueDate = new Date(endAt) + const timeDiff = dueDate.getTime() - curDate.getTime() + const day = Math.floor(Math.abs(timeDiff) / (1000 * 60 * 60 * 24)) + if ((timeDiff > 0 && day === 0) || timeDiff === 0) return 'D-day' + return timeDiff < 0 ? '제출마감' : `D-${day}` }