├── packages ├── database │ ├── src │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── api.ts │ │ └── fish.ts │ ├── tsconfig.json │ ├── package.json │ └── rollup.config.js ├── app │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── components │ │ │ └── fish │ │ │ │ ├── interface.d.ts │ │ │ │ ├── lib │ │ │ │ ├── convertHoursToDate.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── convertHoursToDate.test.ts │ │ │ │ │ ├── getApplyHoursText.test.ts │ │ │ │ │ ├── getShadowSizeText.test.ts │ │ │ │ │ ├── getPlaceText.test.ts │ │ │ │ │ └── groupByFishesByNow.test.ts │ │ │ │ ├── getPlaceText.ts │ │ │ │ ├── getShadowSizeText.ts │ │ │ │ ├── getApplyHoursText.ts │ │ │ │ └── groupFishesByNow.ts │ │ │ │ ├── FishList.tsx │ │ │ │ └── FishListContainer.tsx │ │ ├── constants │ │ │ ├── storageKey.ts │ │ │ └── text.ts │ │ ├── fontObserver.ts │ │ ├── setupTests.ts │ │ ├── detectBrowser.ts │ │ ├── styles │ │ │ └── containerStyle.ts │ │ ├── index.tsx │ │ ├── App.tsx │ │ ├── index.css │ │ ├── hooks │ │ │ └── usePromise.ts │ │ └── serviceWorker.ts │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── images │ │ │ └── fishes │ │ │ │ ├── carp.png │ │ │ │ ├── char.png │ │ │ │ ├── dab.png │ │ │ │ ├── dace.png │ │ │ │ ├── frog.png │ │ │ │ ├── gar.png │ │ │ │ ├── koi.png │ │ │ │ ├── pike.png │ │ │ │ ├── ray.png │ │ │ │ ├── tuna.png │ │ │ │ ├── betta.png │ │ │ │ ├── dorado.png │ │ │ │ ├── guppy.png │ │ │ │ ├── loach.png │ │ │ │ ├── salmon.png │ │ │ │ ├── squid.png │ │ │ │ ├── anchovy.png │ │ │ │ ├── angelfish.png │ │ │ │ ├── arapaima.png │ │ │ │ ├── arowana.png │ │ │ │ ├── barreleye.png │ │ │ │ ├── blowfish.png │ │ │ │ ├── bluegill.png │ │ │ │ ├── catfish.png │ │ │ │ ├── crawfish.png │ │ │ │ ├── goldfish.png │ │ │ │ ├── killifish.png │ │ │ │ ├── mahi-mahi.png │ │ │ │ ├── moray_eel.png │ │ │ │ ├── oarfish.png │ │ │ │ ├── pale_chub.png │ │ │ │ ├── piranha.png │ │ │ │ ├── saw_shark.png │ │ │ │ ├── sea_bass.png │ │ │ │ ├── sea_horse.png │ │ │ │ ├── sturgeon.png │ │ │ │ ├── sweetfish.png │ │ │ │ ├── tadpole.png │ │ │ │ ├── tilapia.png │ │ │ │ ├── bitterling.png │ │ │ │ ├── black_bass.png │ │ │ │ ├── blue_marlin.png │ │ │ │ ├── clown_fish.png │ │ │ │ ├── coelacanth.png │ │ │ │ ├── king_salmon.png │ │ │ │ ├── mitten_crab.png │ │ │ │ ├── neon_tetra.png │ │ │ │ ├── nibble_fish.png │ │ │ │ ├── pond_smelt.png │ │ │ │ ├── puffer_fish.png │ │ │ │ ├── rainbowfish.png │ │ │ │ ├── red_snapper.png │ │ │ │ ├── ribbon_eel.png │ │ │ │ ├── stringfish.png │ │ │ │ ├── suckerfish.png │ │ │ │ ├── surgeonfish.png │ │ │ │ ├── whale_shark.png │ │ │ │ ├── butterfly_fish.png │ │ │ │ ├── cherry_salmon.png │ │ │ │ ├── crucian_carp.png │ │ │ │ ├── football_fish.png │ │ │ │ ├── giant_trevally.png │ │ │ │ ├── golden_trout.png │ │ │ │ ├── horse_mackerel.png │ │ │ │ ├── napoleonfish.png │ │ │ │ ├── ocean_sunfish.png │ │ │ │ ├── olive_flounder.png │ │ │ │ ├── saddled_bichir.png │ │ │ │ ├── sea_butterfly.png │ │ │ │ ├── yellow_perch.png │ │ │ │ ├── barred_knifejaw.png │ │ │ │ ├── freshwater_goby.png │ │ │ │ ├── giant_snakehead.png │ │ │ │ ├── hammerhead_shark.png │ │ │ │ ├── ranchu_goldfish.png │ │ │ │ ├── snapping_turtle.png │ │ │ │ ├── zebra_turkeyfish.png │ │ │ │ ├── great_white_shark.png │ │ │ │ ├── pop-eyed_goldfish.png │ │ │ │ └── soft-shelled_turtle.png │ │ ├── manifest.json │ │ └── index.html │ ├── config-overrides.js │ ├── tsconfig.json │ ├── workbox-config.js │ ├── package.json │ └── README.md ├── api │ ├── index.d.ts │ ├── package.json │ ├── src │ │ ├── get-fishes.ts │ │ └── fishData.json │ └── tsconfig.json └── ui │ ├── .babelrc.json │ ├── src │ ├── index.ts │ ├── ApplyMonths.stories.tsx │ ├── Text.stories.tsx │ ├── colors.ts │ ├── Select.stories.tsx │ ├── FishCard.stories.tsx │ ├── ApplyMonthsCell.stories.tsx │ ├── Text.tsx │ ├── Button.stories.tsx │ ├── ApplyMonthsCell.tsx │ ├── ApplyMonths.tsx │ ├── Select.tsx │ ├── Button.tsx │ └── FishCard.tsx │ ├── tsconfig.json │ ├── rollup.config.js │ ├── .storybook │ └── main.js │ └── package.json ├── pnpm-workspace.yaml ├── .gitignore ├── README.md ├── now.json ├── .vscode └── launch.json └── package.json /packages/database/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fish'; 2 | -------------------------------------------------------------------------------- /packages/app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "!**/__tests__/**" 4 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/interface.d.ts: -------------------------------------------------------------------------------- 1 | export type Hemisphere = 'northern' | 'southern'; 2 | -------------------------------------------------------------------------------- /packages/api/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface ApiResponse { 2 | version: string; 3 | data: T; 4 | } 5 | -------------------------------------------------------------------------------- /packages/app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/database/src/interface.ts: -------------------------------------------------------------------------------- 1 | export interface Database { 2 | get: () => Promise; 3 | } 4 | -------------------------------------------------------------------------------- /packages/ui/.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["react-app", { "flow": false, "typescript": true }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/favicon.ico -------------------------------------------------------------------------------- /packages/app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/logo192.png -------------------------------------------------------------------------------- /packages/app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/logo512.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/carp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/carp.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/char.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/char.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/dab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/dab.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/dace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/dace.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/frog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/frog.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/gar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/gar.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/koi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/koi.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/pike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/pike.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/ray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/ray.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/tuna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/tuna.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/betta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/betta.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/dorado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/dorado.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/guppy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/guppy.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/loach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/loach.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/salmon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/salmon.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/squid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/squid.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/anchovy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/anchovy.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/angelfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/angelfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/arapaima.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/arapaima.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/arowana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/arowana.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/barreleye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/barreleye.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/blowfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/blowfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/bluegill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/bluegill.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/catfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/catfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/crawfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/crawfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/goldfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/goldfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/killifish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/killifish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/mahi-mahi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/mahi-mahi.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/moray_eel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/moray_eel.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/oarfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/oarfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/pale_chub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/pale_chub.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/piranha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/piranha.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/saw_shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/saw_shark.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/sea_bass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/sea_bass.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/sea_horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/sea_horse.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/sturgeon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/sturgeon.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/sweetfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/sweetfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/tadpole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/tadpole.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/tilapia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/tilapia.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/bitterling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/bitterling.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/black_bass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/black_bass.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/blue_marlin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/blue_marlin.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/clown_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/clown_fish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/coelacanth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/coelacanth.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/king_salmon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/king_salmon.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/mitten_crab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/mitten_crab.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/neon_tetra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/neon_tetra.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/nibble_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/nibble_fish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/pond_smelt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/pond_smelt.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/puffer_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/puffer_fish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/rainbowfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/rainbowfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/red_snapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/red_snapper.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/ribbon_eel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/ribbon_eel.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/stringfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/stringfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/suckerfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/suckerfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/surgeonfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/surgeonfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/whale_shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/whale_shark.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/butterfly_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/butterfly_fish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/cherry_salmon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/cherry_salmon.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/crucian_carp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/crucian_carp.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/football_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/football_fish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/giant_trevally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/giant_trevally.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/golden_trout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/golden_trout.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/horse_mackerel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/horse_mackerel.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/napoleonfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/napoleonfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/ocean_sunfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/ocean_sunfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/olive_flounder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/olive_flounder.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/saddled_bichir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/saddled_bichir.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/sea_butterfly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/sea_butterfly.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/yellow_perch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/yellow_perch.png -------------------------------------------------------------------------------- /packages/app/src/constants/storageKey.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 로컬 스토리지에 사용할 키 3 | */ 4 | export default { 5 | HEMISPHERE: '@canifish/hemisphere', 6 | } as const; 7 | -------------------------------------------------------------------------------- /packages/app/public/images/fishes/barred_knifejaw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/barred_knifejaw.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/freshwater_goby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/freshwater_goby.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/giant_snakehead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/giant_snakehead.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/hammerhead_shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/hammerhead_shark.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/ranchu_goldfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/ranchu_goldfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/snapping_turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/snapping_turtle.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/zebra_turkeyfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/zebra_turkeyfish.png -------------------------------------------------------------------------------- /packages/database/src/api.ts: -------------------------------------------------------------------------------- 1 | const API_BASE_URL = 'https://canifish.now.sh/api'; 2 | 3 | export default { 4 | GET_FISHES: API_BASE_URL + '/fishes', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/app/public/images/fishes/great_white_shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/great_white_shark.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/pop-eyed_goldfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/pop-eyed_goldfish.png -------------------------------------------------------------------------------- /packages/app/public/images/fishes/soft-shelled_turtle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchanii/canifish/HEAD/packages/app/public/images/fishes/soft-shelled_turtle.png -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Button'; 2 | export { default as colors } from './colors'; 3 | export * from './FishCard'; 4 | export * from './Text'; 5 | export * from './Select'; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | build 5 | 6 | .DS_Store 7 | .env.local 8 | .env.development.local 9 | .env.test.local 10 | .env.production.local 11 | .now 12 | 13 | pnpm-debug.log 14 | .ultra.cache.json -------------------------------------------------------------------------------- /packages/app/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { override, addBundleVisualizer } = require('customize-cra'); 2 | 3 | const isProd = process.env.NODE_ENV === 'production'; 4 | 5 | module.exports = override(addBundleVisualizer({}, true)); 6 | -------------------------------------------------------------------------------- /packages/app/src/fontObserver.ts: -------------------------------------------------------------------------------- 1 | import FontFaceObserver from 'fontfaceobserver'; 2 | 3 | const font = new FontFaceObserver('NanumSquareRound', { 4 | weight: 800, 5 | }); 6 | 7 | font.load().then(() => { 8 | document.body.classList.add('font-loaded'); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@canifish/api", 3 | "version": "1.1.0", 4 | "description": "", 5 | "author": "iamchanii", 6 | "license": "MIT", 7 | "types": "index.d.ts", 8 | "devDependencies": { 9 | "@now/node": "^1.5.0", 10 | "typescript": "^3.8.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/api/src/get-fishes.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@now/node'; 2 | import fishData from './fishData.json'; 3 | import pkg from '../package.json'; 4 | 5 | export default (req: NowRequest, res: NowResponse) => { 6 | res.json({ 7 | version: pkg.version, 8 | data: fishData, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/convertHoursToDate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 시간을 Date 객체로 변환하는 함수. 현재 날짜를 바탕으로 해당 시간으로 변경한 Date 객체를 반환. 3 | * @param hour 시간 4 | */ 5 | const convertHoursToDate = (hour: number): Date => { 6 | const date = new Date(); 7 | date.setHours(hour, 0, 0, 0); 8 | return date; 9 | }; 10 | 11 | export default convertHoursToDate; 12 | -------------------------------------------------------------------------------- /packages/ui/src/ApplyMonths.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApplyMonths } from './ApplyMonths'; 3 | 4 | export default { 5 | title: 'components|ApplyMonths', 6 | component: ApplyMonths, 7 | }; 8 | 9 | const applyMonthsList = [2, 3, 4, 8, 9, 10]; 10 | 11 | export const applyMonths = () => ; 12 | -------------------------------------------------------------------------------- /packages/ui/src/Text.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withKnobs } from '@storybook/addon-knobs'; 3 | import { Text } from './Text'; 4 | 5 | export default { 6 | title: 'components|Text', 7 | component: Text, 8 | decorators: [withKnobs], 9 | }; 10 | 11 | export const text = () => { 12 | return Text; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/ui/src/colors.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | lightGreen: '#65D2C9', 3 | green: '#1AC8B9', 4 | darkGreen: '#2EA096', 5 | brown: '#835A2A', 6 | lightBrown: '#9D9384', 7 | yellow: '#F5BC3F', 8 | imageBgColor: '#F4ECBD', 9 | beige: '#F5F2E7', 10 | white: '#FFF', 11 | appBgColor: '#FBFAF8', 12 | gray: '#ADB5BD', 13 | lightGray: '#E9ECEF', 14 | } as const; 15 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "./dist", 5 | "module": "es6", 6 | "noImplicitAny": true, 7 | "outDir": "./dist", 8 | "target": "es5", 9 | "esModuleInterop": true, 10 | "moduleResolution": "node", 11 | "jsx": "preserve", 12 | "lib": ["ES2015", "ES2016.Array.Include", "DOM"], 13 | "downlevelIteration": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Can I Fish?](https://canifish.now.sh) 2 | 3 | [모여봐요 동물의 숲](https://store.nintendo.co.kr/70010000027621) 게임에서 나타나는 물고기 도감입니다. 4 | 5 | ## Resources 6 | 7 | - [모여봐요 동물의 숲 생물 도감](https://docs.google.com/spreadsheets/d/1oJGO78ou4hJFj2gBYSo-WFaKmaUmItTSlh3AuTxBewc/htmlview?usp=sharing) 8 | by RottenBlotch 9 | - [동물의 숲 시리즈/물고기](https://namu.wiki/w/%EB%8F%99%EB%AC%BC%EC%9D%98%20%EC%88%B2%20%EC%8B%9C%EB%A6%AC%EC%A6%88/%EB%AC%BC%EA%B3%A0%EA%B8%B0) 10 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/__tests__/convertHoursToDate.test.ts: -------------------------------------------------------------------------------- 1 | import Chance from 'chance'; 2 | import convertHoursToDate from '../convertHoursToDate'; 3 | 4 | const chance = new Chance(); 5 | 6 | test('특정 시간을 전달하면 해당 시간을 기준으로 Date 객체를 반환한다.', () => { 7 | const hour = chance.hour({ twentyfour: true }); 8 | const date = convertHoursToDate(hour); 9 | 10 | expect(date).toBeInstanceOf(Date); 11 | expect(date.getHours()).toBe(hour); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/ui/src/Select.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select } from './Select'; 3 | 4 | export default { 5 | title: 'components|Select', 6 | component: Select, 7 | }; 8 | 9 | const buttonVariantOptions: any = { 10 | Primary: 'primary', 11 | }; 12 | 13 | export const select = () => { 14 | return ( 15 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/app/src/detectBrowser.ts: -------------------------------------------------------------------------------- 1 | const isChrome = 2 | typeof window !== 'undefined' && 3 | window.navigator.userAgent.toLowerCase().includes('chrome'); 4 | 5 | const isSafari = 6 | !isChrome && 7 | typeof window !== 'undefined' && 8 | window.navigator.userAgent.toLowerCase().includes('safari'); 9 | 10 | const detectBrowser = () => { 11 | if (isSafari) { 12 | document.documentElement.classList.add('safari'); 13 | } 14 | }; 15 | 16 | export default detectBrowser; 17 | -------------------------------------------------------------------------------- /packages/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationDir": "./dist", 5 | "module": "ESNEXT", 6 | "noImplicitAny": true, 7 | "outDir": "./dist", 8 | "target": "es5", 9 | "esModuleInterop": true, 10 | "lib": ["DOM", "ES5", "ES2015", "ES2016", "ES2017", "ES2018.Regexp"], 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["**/__tests__/**"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui/src/FishCard.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FishCard } from './FishCard'; 3 | 4 | export default { 5 | title: 'components|FishCard', 6 | component: FishCard, 7 | }; 8 | 9 | const applyMonths = [2, 3, 4]; 10 | 11 | export const fishCard = () => { 12 | return ( 13 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/app/src/styles/containerStyle.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/core'; 2 | import media, { getBreakPoints } from 'css-in-js-media'; 3 | 4 | export default css` 5 | margin-left: auto; 6 | margin-right: auto; 7 | 8 | ${media('>=largeDesktop')} { 9 | max-width: ${getBreakPoints().largeDesktop}px; 10 | } 11 | 12 | ${media('<=largeDesktop', '>desktop')} { 13 | max-width: ${getBreakPoints().desktop}px; 14 | } 15 | 16 | ${media('<=desktop', '>tablet')} { 17 | max-width: ${getBreakPoints().tablet}px; 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "packages/app/build/**", 6 | "use": "@now/static" 7 | }, 8 | { 9 | "src": "packages/api/src/*.ts", 10 | "use": "@now/node" 11 | } 12 | ], 13 | "routes": [ 14 | { 15 | "src": "/api/fishes", 16 | "methods": ["GET"], 17 | "dest": "/packages/api/src/get-fishes.ts", 18 | "headers": { 19 | "Access-Control-Allow-Origin": "*" 20 | } 21 | }, 22 | { 23 | "src": "/(.*)", 24 | "dest": "/packages/app/build/$1" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "preserve", 17 | "downlevelIteration": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/app/src/constants/text.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 애플리케이션 내에서 사용하는 텍스트 3 | */ 4 | export default { 5 | PLACE_RIVER: '강', 6 | PLACE_MOUTH: '강(하구)', 7 | PLACE_CLIFFTOP: '강(절벽위)', 8 | PLACE_POND: '연못', 9 | PLACE_OCEAN: '바다', 10 | PLACE_PIER: '바다(부둣가)', 11 | SIZE_NARROW: '길다', 12 | SIZE_XSMALL: '가장 작음', 13 | SIZE_SMALL: '작음', 14 | SIZE_MEDIUM: '보통', 15 | SIZE_LARGE: '큼', 16 | SIZE_XLARGE: '약간 큼', 17 | SIZE_XXLARGE: '가장 큼', 18 | HAS_FIN: '지느러미', 19 | HAS_SOUND: '울음소리', 20 | ALL_DAY: '하루종일 나타나요.', 21 | ONLY_RAINING: '비 또는 눈', 22 | NORTHERN: '북반구', 23 | SOUTHERN: '남반구', 24 | }; 25 | -------------------------------------------------------------------------------- /packages/app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Can I Fish?", 3 | "name": "모여봐요 동물의 숲 물고기 도감", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // IntelliSense를 사용하여 가능한 특성에 대해 알아보세요. 3 | // 기존 특성에 대한 설명을 보려면 가리킵니다. 4 | // 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요. 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | "args": ["--runInBand"], 12 | "cwd": "${workspaceFolder}", 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen", 15 | "disableOptimisticBPs": true, 16 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/__tests__/getApplyHoursText.test.ts: -------------------------------------------------------------------------------- 1 | import { Chance } from 'chance'; 2 | import getApplyHoursText from '../getApplyHoursText'; 3 | import text from '../../../../constants/text'; 4 | 5 | test('하루 종일에 해당하는 경우 하루 종일을 반환한다.', () => { 6 | expect(getApplyHoursText([[0, 23]])).toContain(text.ALL_DAY); 7 | }); 8 | 9 | test('시간 배열을 텍스트로 반환한다.', () => { 10 | const chance = new Chance(); 11 | const applyHours = [[chance.hour(), chance.hour()]]; 12 | const text = getApplyHoursText(applyHours); 13 | 14 | expect(text).toContain(applyHours[0][0].toString()); 15 | expect(text).toContain(applyHours[0][1].toString()); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/ui/src/ApplyMonthsCell.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApplyMonthsCell } from './ApplyMonthsCell'; 3 | 4 | export default { 5 | title: 'components|ApplyMonthsCell', 6 | component: ApplyMonthsCell, 7 | }; 8 | 9 | export const applyMonthsCell = () => ( 10 | 11 | 1 12 | 2 13 | 3 14 | 4 15 | 5 16 | 6 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /packages/database/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@canifish/database", 3 | "version": "1.1.0", 4 | "author": "iamchanii", 5 | "license": "MIT", 6 | "main": "dist/index.js", 7 | "module": "dist/index.es.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "types": "dist/index.d.ts", 12 | "scripts": { 13 | "build": "rollup -c", 14 | "start": "rollup -cw", 15 | "test": "echo No tests" 16 | }, 17 | "devDependencies": { 18 | "@canifish/api": "workspace:^1.0.0", 19 | "@rollup/plugin-json": "^4.0.2", 20 | "rollup": "^2.2.0", 21 | "rollup-plugin-terser": "^5.3.0", 22 | "rollup-plugin-typescript2": "^0.26.0", 23 | "tslib": "^1.11.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/database/rollup.config.js: -------------------------------------------------------------------------------- 1 | import json from '@rollup/plugin-json'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | import typescript from 'rollup-plugin-typescript2'; 4 | import pkg from './package.json'; 5 | 6 | export default { 7 | input: 'src/index.ts', 8 | 9 | output: [ 10 | { 11 | file: pkg.main, 12 | format: 'cjs', 13 | }, 14 | { 15 | file: pkg.module, 16 | format: 'es', 17 | }, 18 | ], 19 | external: [ 20 | ...Object.keys(pkg.dependencies || {}), 21 | ...Object.keys(pkg.devDependencies || {}), 22 | ], 23 | plugins: [ 24 | json(), 25 | typescript({ 26 | typescript: require('typescript'), 27 | }), 28 | terser(), 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /packages/app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import './fontObserver'; 7 | import detectBrowser from './detectBrowser'; 8 | 9 | detectBrowser(); 10 | 11 | const rootNode = document.getElementById('root'); 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | rootNode, 18 | ); 19 | 20 | // If you want your app to work offline and load faster, you can change 21 | // unregister() to register() below. Note this comes with some pitfalls. 22 | // Learn more about service workers: https://bit.ly/CRA-PWA 23 | serviceWorker.register(); 24 | -------------------------------------------------------------------------------- /packages/ui/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import babel from 'rollup-plugin-babel'; 3 | import peerDepsExternal from 'rollup-plugin-peer-deps-external'; 4 | import { terser } from 'rollup-plugin-terser'; 5 | import typescript from 'rollup-plugin-typescript2'; 6 | import pkg from './package.json'; 7 | 8 | const extensions = ['.js', '.jsx', '.ts', '.tsx']; 9 | 10 | export default { 11 | input: './src/index.ts', 12 | plugins: [ 13 | typescript({ typescript: require('typescript') }), 14 | peerDepsExternal(), 15 | resolve({ extensions }), 16 | babel({ extensions, include: ['src/**/*'], runtimeHelpers: true }), 17 | terser(), 18 | ], 19 | output: [{ file: pkg.module, format: 'es' }], 20 | }; 21 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/getPlaceText.ts: -------------------------------------------------------------------------------- 1 | import { FishPlace } from '@canifish/database'; 2 | import text from '../../../constants/text'; 3 | 4 | const placeTextMap: { [key in FishPlace]: string } = { 5 | river: text.PLACE_RIVER, 6 | mouth: text.PLACE_MOUTH, 7 | clifftop: text.PLACE_CLIFFTOP, 8 | pond: text.PLACE_POND, 9 | ocean: text.PLACE_OCEAN, 10 | pier: text.PLACE_PIER, 11 | }; 12 | 13 | const getPlaceText = ( 14 | fishPlaces: FishPlace[], 15 | options: { onlyRaining?: boolean } = {}, 16 | ): string => { 17 | return [ 18 | ...fishPlaces.map((place) => placeTextMap[place]), 19 | options.onlyRaining && text.ONLY_RAINING, 20 | ] 21 | .filter(Boolean) 22 | .join(', '); 23 | }; 24 | 25 | export default getPlaceText; 26 | -------------------------------------------------------------------------------- /packages/app/src/App.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | 3 | import { colors } from '@canifish/ui'; 4 | import { css, jsx } from '@emotion/core'; 5 | import { Suspense, lazy } from 'react'; 6 | 7 | const FishListContainer = lazy(() => 8 | import('./components/fish/FishListContainer'), 9 | ); 10 | 11 | const style = css` 12 | background: ${colors.appBgColor}; 13 | padding: 2rem 0; 14 | min-height: 100%; 15 | 16 | h1 { 17 | margin: 0 0 1em; 18 | text-align: center; 19 | color: ${colors.brown}; 20 | } 21 | `; 22 | 23 | function App() { 24 | return ( 25 |
26 |

Can I Fish?

27 | 28 | 29 | 30 | 31 |
32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /packages/ui/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.(tsx)'], 3 | presets: ['@storybook/addon-docs/preset'], 4 | addons: [ 5 | '@storybook/addon-actions', 6 | '@storybook/addon-links', 7 | '@storybook/addon-knobs', 8 | ], 9 | 10 | webpackFinal: async (config) => { 11 | config.module.rules.push({ 12 | test: /\.(ts|tsx)$/, 13 | use: [ 14 | { 15 | loader: require.resolve('babel-loader'), 16 | options: { 17 | presets: [['react-app', { flow: false, typescript: true }]], 18 | }, 19 | }, 20 | require.resolve('react-docgen-typescript-loader'), 21 | ], 22 | }); 23 | 24 | config.resolve.extensions.push('.ts', '.tsx'); 25 | 26 | return config; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/ui/src/Text.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { css, jsx } from '@emotion/core'; 3 | import React from 'react'; 4 | import colors from './colors'; 5 | 6 | const style = css` 7 | letter-spacing: -0.02em; 8 | `; 9 | 10 | export type TextVariants = 'listTitle'; 11 | 12 | const variants: { [key in TextVariants]: any } = { 13 | listTitle: css` 14 | color: ${colors.brown}; 15 | font-weight: 800; 16 | font-size: 0.875rem; 17 | line-height: 1rem; 18 | text-align: center; 19 | `, 20 | }; 21 | 22 | export interface TextProps { 23 | variant: TextVariants; 24 | } 25 | 26 | export const Text: React.FC = React.memo(({ children, variant }) => { 27 | return ( 28 |

29 | {children} 30 |

31 | ); 32 | }); 33 | 34 | Text.displayName = 'Text'; 35 | -------------------------------------------------------------------------------- /packages/ui/src/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, ButtonVariants } from './Button'; 3 | import { withKnobs, select, boolean } from '@storybook/addon-knobs'; 4 | import { action } from '@storybook/addon-actions'; 5 | 6 | export default { 7 | title: 'components|Buttons', 8 | component: Button, 9 | decorators: [withKnobs], 10 | }; 11 | 12 | const buttonVariantOptions: any = { 13 | Primary: 'primary', 14 | }; 15 | 16 | export const primary = () => { 17 | const variant = select( 18 | 'variant', 19 | buttonVariantOptions, 20 | 'primary', 21 | ); 22 | const disabled = boolean('disabled', false); 23 | const onClick = action('onClick'); 24 | 25 | return ( 26 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/getShadowSizeText.ts: -------------------------------------------------------------------------------- 1 | import { FishShadowSize } from '@canifish/database'; 2 | import text from '../../../constants/text'; 3 | 4 | const shadowSizeTextMap: { [key in FishShadowSize]: string } = { 5 | narrow: text.SIZE_NARROW, 6 | 1: text.SIZE_XSMALL, 7 | 2: text.SIZE_SMALL, 8 | 3: text.SIZE_MEDIUM, 9 | 4: text.SIZE_LARGE, 10 | 5: text.SIZE_XLARGE, 11 | 6: text.SIZE_XXLARGE, 12 | }; 13 | 14 | const getShadowSizeText = ( 15 | shadowSize: FishShadowSize, 16 | options: { hasFin?: boolean; hasSound?: boolean } = {}, 17 | ): string => { 18 | return [ 19 | shadowSizeTextMap[shadowSize] + 20 | (typeof shadowSize === 'number' ? `(${shadowSize})` : ''), 21 | options.hasFin && text.HAS_FIN, 22 | options.hasSound && text.HAS_SOUND, 23 | ] 24 | .filter(Boolean) 25 | .join(', '); 26 | }; 27 | 28 | export default getShadowSizeText; 29 | -------------------------------------------------------------------------------- /packages/database/src/fish.ts: -------------------------------------------------------------------------------- 1 | import type { ApiResponse } from '@canifish/api'; 2 | import api from './api'; 3 | import type { Database } from './interface'; 4 | 5 | export type FishPlace = 6 | | 'river' 7 | | 'clifftop' 8 | | 'mouth' 9 | | 'pond' 10 | | 'ocean' 11 | | 'pier'; 12 | 13 | export type FishShadowSize = number | 'narrow'; 14 | 15 | export interface Fish { 16 | id: number; 17 | name: string; 18 | price: number; 19 | place: FishPlace[]; 20 | shadowSize: FishShadowSize; 21 | hasFin: boolean; 22 | hasSound: boolean; 23 | onlyRaining: boolean; 24 | applyHours: number[][]; 25 | applyMonths: number[]; 26 | imageUrl: string; 27 | } 28 | 29 | export const fishDatabase: Database = { 30 | async get() { 31 | const response = await fetch(api.GET_FISHES); 32 | const { data } = (await response.json()) as ApiResponse; 33 | 34 | return data; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/app/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://cdn.rawgit.com/innks/NanumSquareRound/master/nanumsquareround.min.css'); 2 | 3 | html { 4 | box-sizing: border-box; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | html.safari { 10 | -webkit-font-smoothing: subpixel-antialiased; 11 | } 12 | 13 | html, 14 | body, 15 | #root { 16 | height: 100%; 17 | } 18 | 19 | body { 20 | margin: 0; 21 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 22 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 23 | sans-serif; 24 | } 25 | 26 | body.font-loaded { 27 | font-family: 'NanumSquareRound', -apple-system, BlinkMacSystemFont, 'Segoe UI', 28 | 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 29 | 'Helvetica Neue', sans-serif; 30 | } 31 | 32 | *, 33 | *:before, 34 | *:after { 35 | box-sizing: inherit; 36 | } 37 | -------------------------------------------------------------------------------- /packages/ui/src/ApplyMonthsCell.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { css, jsx } from '@emotion/core'; 3 | import type { FC } from 'react'; 4 | import colors from './colors'; 5 | 6 | export type ApplyMonthsCellVariant = 'default' | 'active'; 7 | 8 | export interface ApplyMonthsCellProps { 9 | variant?: ApplyMonthsCellVariant; 10 | } 11 | 12 | export const ApplyMonthsCell: FC = ({ 13 | children, 14 | variant = 'default', 15 | }) => {children}; 16 | 17 | ApplyMonthsCell.displayName = 'ApplyMonthsCell'; 18 | 19 | const style = css` 20 | width: 1rem; 21 | height: 1rem; 22 | border-radius: 0.25rem; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | font-weight: 800; 27 | font-size: 0.625rem; 28 | `; 29 | 30 | const variants = { 31 | default: css` 32 | background-color: ${colors.beige}; 33 | color: ${colors.brown}; 34 | `, 35 | active: css` 36 | background-color: ${colors.yellow}; 37 | color: ${colors.white}; 38 | `, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/ui/src/ApplyMonths.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { css, jsx } from '@emotion/core'; 3 | import type { FC } from 'react'; 4 | import { ApplyMonthsCell } from './ApplyMonthsCell'; 5 | 6 | const range = (length: number) => [...Array(length).keys()]; 7 | 8 | export interface ApplyMonthsProps { 9 | applyMonths: number[]; 10 | } 11 | 12 | export const ApplyMonths: FC = ({ applyMonths }) => { 13 | return ( 14 |
    15 | {range(12).map((index) => { 16 | const variant = applyMonths.includes(index) ? 'active' : 'default'; 17 | 18 | return ( 19 |
  • 20 | {index + 1} 21 |
  • 22 | ); 23 | })} 24 |
25 | ); 26 | }; 27 | 28 | const style = css` 29 | margin: 0; 30 | padding: 0; 31 | display: flex; 32 | flex-wrap: wrap; 33 | list-style: none; 34 | width: 5rem; 35 | 36 | > li { 37 | padding: 0.125rem; 38 | } 39 | `; 40 | 41 | ApplyMonths.displayName = 'ApplyMonths'; 42 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/__tests__/getShadowSizeText.test.ts: -------------------------------------------------------------------------------- 1 | import { FishShadowSize } from '@canifish/database'; 2 | import { Chance } from 'chance'; 3 | import text from '../../../../constants/text'; 4 | import getShadowSizeText from '../getShadowSizeText'; 5 | 6 | test.each<[FishShadowSize, string]>([ 7 | ['narrow', text.SIZE_NARROW], 8 | [1, text.SIZE_XSMALL + '(1)'], 9 | [2, text.SIZE_SMALL + '(2)'], 10 | [3, text.SIZE_MEDIUM + '(3)'], 11 | [4, text.SIZE_LARGE + '(4)'], 12 | [5, text.SIZE_XLARGE + '(5)'], 13 | [6, text.SIZE_XXLARGE + '(6)'], 14 | ])('%s를 넣으면 %s를 반환한다.', (size, text) => { 15 | expect(getShadowSizeText(size)).toBe(text); 16 | }); 17 | 18 | const chance = new Chance(); 19 | const size = chance.pickone(['narrow', 1, 2, 3, 4, 5, 6]); 20 | 21 | test('options.hasFin 을 사용하면 text.HAS_FIN을 포함한다.', () => { 22 | expect(getShadowSizeText(size, { hasFin: true })).toContain(text.HAS_FIN); 23 | }); 24 | 25 | test('options.hasSound 을 사용하면 text.HAS_SOUND 포함한다.', () => { 26 | expect(getShadowSizeText(size, { hasSound: true })).toContain(text.HAS_SOUND); 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canifish", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/jest": "^25.1.4", 13 | "husky": "^4.2.3", 14 | "jest": "24.9.0", 15 | "prettier": "^2.0.2", 16 | "pretty-quick": "^2.0.1", 17 | "ts-jest": "^25.2.1", 18 | "typescript": "^3.8.3" 19 | }, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "pretty-quick --staged" 23 | } 24 | }, 25 | "prettier": { 26 | "singleQuote": true, 27 | "trailingComma": "all" 28 | }, 29 | "jest": { 30 | "transform": { 31 | "^.+\\.tsx?$": "ts-jest" 32 | }, 33 | "testRegex": "\\.test\\.tsx?$", 34 | "testMatch": null, 35 | "moduleFileExtensions": [ 36 | "js", 37 | "ts" 38 | ], 39 | "globals": { 40 | "ts-jest": { 41 | "packageJson": "package.json", 42 | "diagnostics": { 43 | "warnOnly": true 44 | } 45 | } 46 | }, 47 | "preset": "ts-jest" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/app/src/hooks/usePromise.ts: -------------------------------------------------------------------------------- 1 | import isEqual from 'fast-deep-equal'; 2 | 3 | type PromiseFn = (...args: any[]) => Promise; 4 | 5 | interface PromiseCache { 6 | promise: Promise; 7 | inputs?: any[]; 8 | data?: any; 9 | error?: Error; 10 | } 11 | 12 | const promiseCacheMap = new Map(); 13 | 14 | const usePromise = (promiseFn: PromiseFn, inputs: any[] = []): T => { 15 | let promiseCacheList = promiseCacheMap.get(promiseFn); 16 | 17 | if (!promiseCacheList) { 18 | promiseCacheMap.set(promiseFn, []); 19 | promiseCacheList = promiseCacheMap.get(promiseFn); 20 | } else if (promiseCacheList.length > 0) { 21 | for (const cache of promiseCacheList) { 22 | if (isEqual(cache.inputs, inputs)) { 23 | if (cache.data) { 24 | return cache.data; 25 | } 26 | 27 | throw cache.promise; 28 | } 29 | } 30 | } 31 | 32 | const promiseCache: PromiseCache = { 33 | promise: promiseFn(...inputs) 34 | .then((resolved) => (promiseCache.data = resolved)) 35 | .catch((error) => (promiseCache.error = error)), 36 | inputs, 37 | }; 38 | 39 | promiseCacheList!.push(promiseCache); 40 | 41 | throw promiseCache.promise; 42 | }; 43 | 44 | export default usePromise; 45 | -------------------------------------------------------------------------------- /packages/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | Can I Fish? | 모여봐요 동물의 숲 물고기 도감 15 | 16 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/app/src/components/fish/lib/__tests__/getPlaceText.test.ts: -------------------------------------------------------------------------------- 1 | import { FishPlace } from './../../../../../../database/src/fish'; 2 | import { Chance } from 'chance'; 3 | import text from '../../../../constants/text'; 4 | import getPlaceText from '../getPlaceText'; 5 | 6 | test('river인 경우 text.PLACE_RIVER을 반환한다.', () => { 7 | expect(getPlaceText(['river'])).toBe(text.PLACE_RIVER); 8 | }); 9 | 10 | test('mouth인 경우 text.PLACE_MOUTH을 반환한다.', () => { 11 | expect(getPlaceText(['mouth'])).toBe(text.PLACE_MOUTH); 12 | }); 13 | 14 | test('clifftop인 경우 text.PLACE_CLIFFTOP을 반환한다.', () => { 15 | expect(getPlaceText(['clifftop'])).toBe(text.PLACE_CLIFFTOP); 16 | }); 17 | 18 | test('pond인 경우 text.PLACE_POND을 반환한다.', () => { 19 | expect(getPlaceText(['pond'])).toBe(text.PLACE_POND); 20 | }); 21 | 22 | test('ocean인 경우 text.PLACE_OCEAN을 반환한다.', () => { 23 | expect(getPlaceText(['ocean'])).toBe(text.PLACE_OCEAN); 24 | }); 25 | test('pier인 경우 text.PLACE_PIER을 반환한다.', () => { 26 | expect(getPlaceText(['pier'])).toBe(text.PLACE_PIER); 27 | }); 28 | 29 | it('onlyRaining 옵션을 전달하면 비 또는 눈이 포함되어 반환된다.', () => { 30 | const chance = new Chance(); 31 | const place = chance.pickone([ 32 | 'clifftop', 33 | 'mouth', 34 | 'ocean', 35 | 'pier', 36 | 'pond', 37 | 'river', 38 | ]); 39 | 40 | expect(getPlaceText([place], { onlyRaining: true })).toContain( 41 | text.ONLY_RAINING, 42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/ui/src/Select.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { css, jsx } from '@emotion/core'; 3 | import React, { FC, memo } from 'react'; 4 | import { FaAngleDown } from 'react-icons/fa'; 5 | import { colors } from '.'; 6 | 7 | export interface SelectProps 8 | extends React.DetailedHTMLProps< 9 | React.SelectHTMLAttributes, 10 | HTMLSelectElement 11 | > {} 12 | 13 | export const Select: FC = memo((props) => { 14 | return ( 15 |
16 | 76 | 77 | 78 | 79 |
80 | 81 | 82 | 83 | 84 | ); 85 | }; 86 | 87 | FishListContainer.displayName = 'FishListContainer'; 88 | 89 | export default FishListContainer; 90 | 91 | const filterStyle = css` 92 | padding: 0.5rem; 93 | `; 94 | -------------------------------------------------------------------------------- /packages/ui/src/FishCard.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { css, jsx } from '@emotion/core'; 3 | import type { FC } from 'react'; 4 | import { useRef, useEffect } from 'react'; 5 | import { FaClock, FaFish, FaMapPin } from 'react-icons/fa'; 6 | import { ApplyMonths } from './ApplyMonths'; 7 | import colors from './colors'; 8 | 9 | export interface FishCardProps { 10 | /** 생선 이름 */ 11 | name: string; 12 | /** 가격 */ 13 | price: number; 14 | /** 출현 시간 */ 15 | applyHours: string; 16 | /** 출현 장소 */ 17 | place: string; 18 | /** 그림자 크기 */ 19 | shadowSize: string; 20 | /** 출현 기간 */ 21 | applyMonths: number[]; 22 | /** 이미지 URL */ 23 | imageUrl: string; 24 | } 25 | 26 | const { format } = new Intl.NumberFormat(); 27 | 28 | export const FishCard: FC = ({ 29 | name, 30 | price, 31 | applyHours, 32 | place, 33 | shadowSize, 34 | applyMonths, 35 | imageUrl, 36 | }) => { 37 | const imageRef = useRef(null); 38 | 39 | useEffect(() => { 40 | const loadImage = (imageElement: HTMLImageElement) => { 41 | imageElement.src = imageElement.dataset.src; 42 | imageElement.classList.add('loaded'); 43 | }; 44 | 45 | if ('IntersectionObserver' in window) { 46 | const intersectionCallback: IntersectionObserverCallback = ( 47 | [entry], 48 | observer, 49 | ) => { 50 | if (entry.isIntersecting) { 51 | loadImage(entry.target as HTMLImageElement); 52 | observer.unobserve(entry.target); 53 | } 54 | }; 55 | 56 | const observer = new IntersectionObserver(intersectionCallback); 57 | observer.observe(imageRef.current); 58 | 59 | return () => observer?.disconnect(); 60 | } 61 | 62 | loadImage(imageRef.current); 63 | }, []); 64 | 65 | return ( 66 |
67 |
68 | {name} 69 |
70 |
71 |
72 |

{name}

73 |

{format(price)}벨

74 |
75 |
    76 |
  • 77 | 78 | {applyHours} 79 |
  • 80 |
  • 81 | 82 | {place} 83 |
  • 84 |
  • 85 | 86 | {shadowSize} 87 |
  • 88 |
89 |
90 | 91 |
92 | ); 93 | }; 94 | 95 | FishCard.displayName = 'FishCard'; 96 | 97 | const fishCardStyle = css` 98 | padding: 1rem; 99 | display: flex; 100 | align-items: center; 101 | background-color: ${colors.white}; 102 | box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.08); 103 | border-radius: 0.5rem; 104 | 105 | .apply-months { 106 | margin-left: auto; 107 | flex-shrink: 0; 108 | } 109 | `; 110 | 111 | const imageStyle = css` 112 | width: 3rem; 113 | min-width: 3rem; 114 | height: 3rem; 115 | background-color: ${colors.imageBgColor}; 116 | border-radius: 9999px; 117 | margin: 0 0.5rem 0 0; 118 | 119 | > img { 120 | width: 100%; 121 | height: 100%; 122 | opacity: 0; 123 | transition: opacity 200ms ease-out; 124 | 125 | &.loaded { 126 | opacity: 1; 127 | } 128 | } 129 | `; 130 | 131 | const nameAndPriceAndInformationStyle = css` 132 | margin-right: auto; 133 | `; 134 | 135 | const nameAndPriceStyle = css` 136 | margin-bottom: 0.5rem; 137 | display: flex; 138 | align-items: baseline; 139 | 140 | h2, 141 | h3 { 142 | letter-spacing: -0.04em; 143 | font-size: 0.875rem; 144 | line-height: 0.875rem; 145 | font-weight: 800; 146 | } 147 | 148 | h2 { 149 | margin: 0 0.25rem 0 0; 150 | color: ${colors.brown}; 151 | } 152 | 153 | h3 { 154 | margin: 0; 155 | color: ${colors.green}; 156 | } 157 | `; 158 | 159 | const informationStyle = css` 160 | list-style: none; 161 | margin: 0; 162 | padding: 0; 163 | 164 | display: flex; 165 | flex-wrap: wrap; 166 | 167 | li { 168 | color: ${colors.lightBrown}; 169 | font-weight: 800; 170 | font-size: 0.75rem; 171 | letter-spacing: -0.04em; 172 | display: flex; 173 | align-items: center; 174 | margin: 0.125rem 0; 175 | 176 | svg { 177 | width: 0.75rem; 178 | margin-right: 0.25rem; 179 | } 180 | 181 | &.time { 182 | flex: 0 0 100%; 183 | } 184 | } 185 | 186 | li + li:not(:last-of-type) { 187 | margin-right: 0.5rem; 188 | } 189 | `; 190 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 65 | "resolveJsonModule": true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/app/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, 20 | ), 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 32 | if (publicUrl.origin !== window.location.origin) { 33 | // Our service worker won't work if PUBLIC_URL is on a different origin 34 | // from what our page is served on. This might happen if a CDN is used to 35 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 36 | return; 37 | } 38 | 39 | window.addEventListener('load', () => { 40 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 41 | 42 | if (isLocalhost) { 43 | // This is running on localhost. Let's check if a service worker still exists or not. 44 | checkValidServiceWorker(swUrl, config); 45 | 46 | // Add some additional logging to localhost, pointing developers to the 47 | // service worker/PWA documentation. 48 | navigator.serviceWorker.ready.then(() => { 49 | console.log( 50 | 'This web app is being served cache-first by a service ' + 51 | 'worker. To learn more, visit https://bit.ly/CRA-PWA', 52 | ); 53 | }); 54 | } else { 55 | // Is not localhost. Just register service worker 56 | registerValidSW(swUrl, config); 57 | } 58 | }); 59 | } 60 | } 61 | 62 | function registerValidSW(swUrl: string, config?: Config) { 63 | navigator.serviceWorker 64 | .register(swUrl) 65 | .then((registration) => { 66 | registration.onupdatefound = () => { 67 | const installingWorker = registration.installing; 68 | if (installingWorker == null) { 69 | return; 70 | } 71 | installingWorker.onstatechange = () => { 72 | if (installingWorker.state === 'installed') { 73 | if (navigator.serviceWorker.controller) { 74 | // At this point, the updated precached content has been fetched, 75 | // but the previous service worker will still serve the older 76 | // content until all client tabs are closed. 77 | console.log( 78 | 'New content is available and will be used when all ' + 79 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', 80 | ); 81 | 82 | // Execute callback 83 | if (config && config.onUpdate) { 84 | config.onUpdate(registration); 85 | } 86 | } else { 87 | // At this point, everything has been precached. 88 | // It's the perfect time to display a 89 | // "Content is cached for offline use." message. 90 | console.log('Content is cached for offline use.'); 91 | 92 | // Execute callback 93 | if (config && config.onSuccess) { 94 | config.onSuccess(registration); 95 | } 96 | } 97 | } 98 | }; 99 | }; 100 | }) 101 | .catch((error) => { 102 | console.error('Error during service worker registration:', error); 103 | }); 104 | } 105 | 106 | function checkValidServiceWorker(swUrl: string, config?: Config) { 107 | // Check if the service worker can be found. If it can't reload the page. 108 | fetch(swUrl, { 109 | headers: { 'Service-Worker': 'script' }, 110 | }) 111 | .then((response) => { 112 | // Ensure service worker exists, and that we really are getting a JS file. 113 | const contentType = response.headers.get('content-type'); 114 | if ( 115 | response.status === 404 || 116 | (contentType != null && contentType.indexOf('javascript') === -1) 117 | ) { 118 | // No service worker found. Probably a different app. Reload the page. 119 | navigator.serviceWorker.ready.then((registration) => { 120 | registration.unregister().then(() => { 121 | window.location.reload(); 122 | }); 123 | }); 124 | } else { 125 | // Service worker found. Proceed as normal. 126 | registerValidSW(swUrl, config); 127 | } 128 | }) 129 | .catch(() => { 130 | console.log( 131 | 'No internet connection found. App is running in offline mode.', 132 | ); 133 | }); 134 | } 135 | 136 | export function unregister() { 137 | if ('serviceWorker' in navigator) { 138 | navigator.serviceWorker.ready 139 | .then((registration) => { 140 | registration.unregister(); 141 | }) 142 | .catch((error) => { 143 | console.error(error.message); 144 | }); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /packages/api/src/fishData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "납줄개", 5 | "price": 900, 6 | "place": ["river"], 7 | "shadowSize": 1, 8 | "hasFin": false, 9 | "hasSound": false, 10 | "onlyRaining": false, 11 | "applyHours": [[0, 23]], 12 | "applyMonths": [0, 1, 2, 10, 11], 13 | "imageUrl": "bitterling.png" 14 | }, 15 | { 16 | "id": 2, 17 | "name": "피라미", 18 | "price": 200, 19 | "place": ["river"], 20 | "shadowSize": 1, 21 | "hasFin": false, 22 | "hasSound": false, 23 | "onlyRaining": false, 24 | "applyHours": [[9, 16]], 25 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 26 | "imageUrl": "pale_chub.png" 27 | }, 28 | { 29 | "id": 3, 30 | "name": "붕어", 31 | "price": 160, 32 | "place": ["river"], 33 | "shadowSize": 2, 34 | "hasFin": false, 35 | "hasSound": false, 36 | "onlyRaining": false, 37 | "applyHours": [[0, 23]], 38 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 39 | "imageUrl": "crucian_carp.png" 40 | }, 41 | { 42 | "id": 4, 43 | "name": "황어", 44 | "price": 240, 45 | "place": ["river"], 46 | "shadowSize": 3, 47 | "hasFin": false, 48 | "hasSound": false, 49 | "onlyRaining": false, 50 | "applyHours": [[16, 9]], 51 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 52 | "imageUrl": "dace.png" 53 | }, 54 | { 55 | "id": 5, 56 | "name": "잉어", 57 | "price": 300, 58 | "place": ["pond"], 59 | "shadowSize": 4, 60 | "hasFin": false, 61 | "hasSound": false, 62 | "onlyRaining": false, 63 | "applyHours": [[0, 23]], 64 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 65 | "imageUrl": "carp.png" 66 | }, 67 | { 68 | "id": 6, 69 | "name": "비단잉어", 70 | "price": 4000, 71 | "place": ["pond"], 72 | "shadowSize": 4, 73 | "hasFin": false, 74 | "hasSound": false, 75 | "onlyRaining": false, 76 | "applyHours": [[16, 9]], 77 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 78 | "imageUrl": "koi.png" 79 | }, 80 | { 81 | "id": 7, 82 | "name": "금붕어", 83 | "price": 1300, 84 | "place": ["pond"], 85 | "shadowSize": 1, 86 | "hasFin": false, 87 | "hasSound": false, 88 | "onlyRaining": false, 89 | "applyHours": [[0, 23]], 90 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 91 | "imageUrl": "goldfish.png" 92 | }, 93 | { 94 | "id": 8, 95 | "name": "툭눈금붕어", 96 | "price": 1300, 97 | "place": ["pond"], 98 | "shadowSize": 1, 99 | "hasFin": false, 100 | "hasSound": false, 101 | "onlyRaining": false, 102 | "applyHours": [[9, 16]], 103 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 104 | "imageUrl": "pop-eyed_goldfish.png" 105 | }, 106 | { 107 | "id": 9, 108 | "name": "난주", 109 | "price": 4500, 110 | "place": ["pond"], 111 | "shadowSize": 2, 112 | "hasFin": false, 113 | "hasSound": false, 114 | "onlyRaining": false, 115 | "applyHours": [[9, 16]], 116 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 117 | "imageUrl": "ranchu_goldfish.png" 118 | }, 119 | { 120 | "id": 10, 121 | "name": "송사리", 122 | "price": 300, 123 | "place": ["pond"], 124 | "shadowSize": 1, 125 | "hasFin": false, 126 | "hasSound": false, 127 | "onlyRaining": false, 128 | "applyHours": [[0, 23]], 129 | "applyMonths": [3, 4, 5, 6, 7], 130 | "imageUrl": "killifish.png" 131 | }, 132 | { 133 | "id": 11, 134 | "name": "가재", 135 | "price": 200, 136 | "place": ["pond"], 137 | "shadowSize": 2, 138 | "hasFin": false, 139 | "hasSound": false, 140 | "onlyRaining": false, 141 | "applyHours": [[0, 23]], 142 | "applyMonths": [3, 4, 5, 6, 7, 8], 143 | "imageUrl": "crawfish.png" 144 | }, 145 | { 146 | "id": 12, 147 | "name": "자라", 148 | "price": 3750, 149 | "place": ["river"], 150 | "shadowSize": 4, 151 | "hasFin": false, 152 | "hasSound": false, 153 | "onlyRaining": false, 154 | "applyHours": [[16, 9]], 155 | "applyMonths": [7, 8], 156 | "imageUrl": "soft-shelled_turtle.png" 157 | }, 158 | { 159 | "id": 13, 160 | "name": "늑대거북", 161 | "price": 5000, 162 | "place": ["river"], 163 | "shadowSize": 5, 164 | "hasFin": false, 165 | "hasSound": false, 166 | "onlyRaining": false, 167 | "applyHours": [[21, 4]], 168 | "applyMonths": [3, 4, 5, 6, 7, 8, 9], 169 | "imageUrl": "snapping_turtle.png" 170 | }, 171 | { 172 | "id": 14, 173 | "name": "올챙이", 174 | "price": 100, 175 | "place": ["pond"], 176 | "shadowSize": 1, 177 | "hasFin": false, 178 | "hasSound": false, 179 | "onlyRaining": false, 180 | "applyHours": [[0, 23]], 181 | "applyMonths": [2, 3, 4, 5, 6], 182 | "imageUrl": "tadpole.png" 183 | }, 184 | { 185 | "id": 15, 186 | "name": "개구리", 187 | "price": 120, 188 | "place": ["pond"], 189 | "shadowSize": 2, 190 | "hasFin": false, 191 | "hasSound": true, 192 | "onlyRaining": false, 193 | "applyHours": [[0, 23]], 194 | "applyMonths": [4, 5, 6, 7], 195 | "imageUrl": "frog.png" 196 | }, 197 | { 198 | "id": 16, 199 | "name": "동사리", 200 | "price": 400, 201 | "place": ["river"], 202 | "shadowSize": 2, 203 | "hasFin": false, 204 | "hasSound": false, 205 | "onlyRaining": false, 206 | "applyHours": [[16, 9]], 207 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 208 | "imageUrl": "freshwater_goby.png" 209 | }, 210 | { 211 | "id": 17, 212 | "name": "미꾸라지", 213 | "price": 400, 214 | "place": ["river"], 215 | "shadowSize": 2, 216 | "hasFin": false, 217 | "hasSound": false, 218 | "onlyRaining": false, 219 | "applyHours": [[0, 23]], 220 | "applyMonths": [2, 3, 4], 221 | "imageUrl": "loach.png" 222 | }, 223 | { 224 | "id": 18, 225 | "name": "메기", 226 | "price": 800, 227 | "place": ["pond"], 228 | "shadowSize": 4, 229 | "hasFin": false, 230 | "hasSound": false, 231 | "onlyRaining": false, 232 | "applyHours": [[16, 9]], 233 | "applyMonths": [4, 5, 6, 7, 8, 9], 234 | "imageUrl": "catfish.png" 235 | }, 236 | { 237 | "id": 19, 238 | "name": "가물치", 239 | "price": 5500, 240 | "place": ["pond"], 241 | "shadowSize": 5, 242 | "hasFin": false, 243 | "hasSound": false, 244 | "onlyRaining": false, 245 | "applyHours": [[9, 16]], 246 | "applyMonths": [5, 6, 7], 247 | "imageUrl": "giant_snakehead.png" 248 | }, 249 | { 250 | "id": 20, 251 | "name": "블루길", 252 | "price": 180, 253 | "place": ["river"], 254 | "shadowSize": 2, 255 | "hasFin": false, 256 | "hasSound": false, 257 | "onlyRaining": false, 258 | "applyHours": [[9, 16]], 259 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 260 | "imageUrl": "bluegill.png" 261 | }, 262 | { 263 | "id": 21, 264 | "name": "옐로우퍼치", 265 | "price": 300, 266 | "place": ["river"], 267 | "shadowSize": 3, 268 | "hasFin": false, 269 | "hasSound": false, 270 | "onlyRaining": false, 271 | "applyHours": [[0, 23]], 272 | "applyMonths": [0, 1, 2, 9, 10, 11], 273 | "imageUrl": "yellow_perch.png" 274 | }, 275 | { 276 | "id": 22, 277 | "name": "큰입배스", 278 | "price": 400, 279 | "place": ["river"], 280 | "shadowSize": 4, 281 | "hasFin": false, 282 | "hasSound": false, 283 | "onlyRaining": false, 284 | "applyHours": [[0, 23]], 285 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 286 | "imageUrl": "black_bass.png" 287 | }, 288 | { 289 | "id": 23, 290 | "name": "틸라피아", 291 | "price": 800, 292 | "place": ["river"], 293 | "shadowSize": 3, 294 | "hasFin": false, 295 | "hasSound": false, 296 | "onlyRaining": false, 297 | "applyHours": [[0, 23]], 298 | "applyMonths": [5, 6, 7, 8, 9], 299 | "imageUrl": "tilapia.png" 300 | }, 301 | { 302 | "id": 24, 303 | "name": "강꼬치고기", 304 | "price": 1800, 305 | "place": ["river"], 306 | "shadowSize": 5, 307 | "hasFin": false, 308 | "hasSound": false, 309 | "onlyRaining": false, 310 | "applyHours": [[0, 23]], 311 | "applyMonths": [8, 9, 10, 11], 312 | "imageUrl": "pike.png" 313 | }, 314 | { 315 | "id": 25, 316 | "name": "빙어", 317 | "price": 400, 318 | "place": ["river"], 319 | "shadowSize": 2, 320 | "hasFin": false, 321 | "hasSound": false, 322 | "onlyRaining": false, 323 | "applyHours": [[0, 23]], 324 | "applyMonths": [0, 1, 11], 325 | "imageUrl": "pond_smelt.png" 326 | }, 327 | { 328 | "id": 26, 329 | "name": "은어", 330 | "price": 900, 331 | "place": ["river"], 332 | "shadowSize": 3, 333 | "hasFin": false, 334 | "hasSound": false, 335 | "onlyRaining": false, 336 | "applyHours": [[0, 23]], 337 | "applyMonths": [6, 7, 8], 338 | "imageUrl": "sweetfish.png" 339 | }, 340 | { 341 | "id": 27, 342 | "name": "산천어", 343 | "price": 1000, 344 | "place": ["pond", "clifftop"], 345 | "shadowSize": 3, 346 | "hasFin": false, 347 | "hasSound": false, 348 | "onlyRaining": false, 349 | "applyHours": [[16, 9]], 350 | "applyMonths": [2, 3, 4, 5, 8, 9, 10], 351 | "imageUrl": "cherry_salmon.png" 352 | }, 353 | { 354 | "id": 28, 355 | "name": "열목어", 356 | "price": 3800, 357 | "place": ["pond", "clifftop"], 358 | "shadowSize": 3, 359 | "hasFin": false, 360 | "hasSound": false, 361 | "onlyRaining": false, 362 | "applyHours": [[16, 9]], 363 | "applyMonths": [2, 3, 4, 5, 8, 9, 10], 364 | "imageUrl": "char.png" 365 | }, 366 | { 367 | "id": 29, 368 | "name": "금송어", 369 | "price": 15000, 370 | "place": ["pond", "clifftop"], 371 | "shadowSize": 3, 372 | "hasFin": false, 373 | "hasSound": false, 374 | "onlyRaining": false, 375 | "applyHours": [[16, 9]], 376 | "applyMonths": [2, 3, 4, 8, 9, 10], 377 | "imageUrl": "golden_trout.png" 378 | }, 379 | { 380 | "id": 30, 381 | "name": "일본연어", 382 | "price": 15000, 383 | "place": ["pond", "clifftop"], 384 | "shadowSize": 5, 385 | "hasFin": false, 386 | "hasSound": false, 387 | "onlyRaining": false, 388 | "applyHours": [[16, 9]], 389 | "applyMonths": [0, 1, 2, 11], 390 | "imageUrl": "stringfish.png" 391 | }, 392 | { 393 | "id": 31, 394 | "name": "연어", 395 | "price": 700, 396 | "place": ["mouth"], 397 | "shadowSize": 4, 398 | "hasFin": false, 399 | "hasSound": false, 400 | "onlyRaining": false, 401 | "applyHours": [[0, 23]], 402 | "applyMonths": [8], 403 | "imageUrl": "salmon.png" 404 | }, 405 | { 406 | "id": 32, 407 | "name": "왕연어", 408 | "price": 1800, 409 | "place": ["mouth"], 410 | "shadowSize": 6, 411 | "hasFin": false, 412 | "hasSound": false, 413 | "onlyRaining": false, 414 | "applyHours": [[0, 23]], 415 | "applyMonths": [8], 416 | "imageUrl": "king_salmon.png" 417 | }, 418 | { 419 | "id": 33, 420 | "name": "참게", 421 | "price": 2000, 422 | "place": ["river"], 423 | "shadowSize": 2, 424 | "hasFin": false, 425 | "hasSound": false, 426 | "onlyRaining": false, 427 | "applyHours": [[16, 9]], 428 | "applyMonths": [8, 9, 10], 429 | "imageUrl": "mitten_crab.png" 430 | }, 431 | { 432 | "id": 34, 433 | "name": "구피", 434 | "price": 1300, 435 | "place": ["river"], 436 | "shadowSize": 1, 437 | "hasFin": false, 438 | "hasSound": false, 439 | "onlyRaining": false, 440 | "applyHours": [[9, 16]], 441 | "applyMonths": [3, 4, 5, 6, 7, 8, 9, 10], 442 | "imageUrl": "guppy.png" 443 | }, 444 | { 445 | "id": 35, 446 | "name": "닥터피시", 447 | "price": 1500, 448 | "place": ["river"], 449 | "shadowSize": 2, 450 | "hasFin": false, 451 | "hasSound": false, 452 | "onlyRaining": false, 453 | "applyHours": [[9, 16]], 454 | "applyMonths": [4, 5, 6, 7, 8], 455 | "imageUrl": "nibble_fish.png" 456 | }, 457 | { 458 | "id": 36, 459 | "name": "천사어", 460 | "price": 3000, 461 | "place": ["river"], 462 | "shadowSize": 2, 463 | "hasFin": false, 464 | "hasSound": false, 465 | "onlyRaining": false, 466 | "applyHours": [[16, 9]], 467 | "applyMonths": [4, 5, 6, 7, 8, 9], 468 | "imageUrl": "angelfish.png" 469 | }, 470 | { 471 | "id": 37, 472 | "name": "베타", 473 | "price": 2500, 474 | "place": ["river"], 475 | "shadowSize": 2, 476 | "hasFin": false, 477 | "hasSound": false, 478 | "onlyRaining": false, 479 | "applyHours": [[9, 16]], 480 | "applyMonths": [4, 5, 6, 7, 8, 9], 481 | "imageUrl": "betta.png" 482 | }, 483 | { 484 | "id": 38, 485 | "name": "네온테트라", 486 | "price": 500, 487 | "place": ["river"], 488 | "shadowSize": 1, 489 | "hasFin": false, 490 | "hasSound": false, 491 | "onlyRaining": false, 492 | "applyHours": [[9, 16]], 493 | "applyMonths": [3, 4, 5, 6, 7, 8, 9, 10], 494 | "imageUrl": "neon_tetra.png" 495 | }, 496 | { 497 | "id": 39, 498 | "name": "레인보우피시", 499 | "price": 800, 500 | "place": ["river"], 501 | "shadowSize": 1, 502 | "hasFin": false, 503 | "hasSound": false, 504 | "onlyRaining": false, 505 | "applyHours": [[9, 16]], 506 | "applyMonths": [4, 5, 6, 7, 8, 9], 507 | "imageUrl": "rainbowfish.png" 508 | }, 509 | { 510 | "id": 40, 511 | "name": "피라니아", 512 | "price": 2500, 513 | "place": ["river"], 514 | "shadowSize": 2, 515 | "hasFin": false, 516 | "hasSound": false, 517 | "onlyRaining": false, 518 | "applyHours": [ 519 | [9, 16], 520 | [21, 4] 521 | ], 522 | "applyMonths": [5, 6, 7, 8], 523 | "imageUrl": "piranha.png" 524 | }, 525 | { 526 | "id": 41, 527 | "name": "아로와나", 528 | "price": 10000, 529 | "place": ["river"], 530 | "shadowSize": 4, 531 | "hasFin": false, 532 | "hasSound": false, 533 | "onlyRaining": false, 534 | "applyHours": [[16, 9]], 535 | "applyMonths": [5, 6, 7, 8], 536 | "imageUrl": "arowana.png" 537 | }, 538 | { 539 | "id": 42, 540 | "name": "황금연어", 541 | "price": 15000, 542 | "place": ["river"], 543 | "shadowSize": 5, 544 | "hasFin": false, 545 | "hasSound": false, 546 | "onlyRaining": false, 547 | "applyHours": [[4, 21]], 548 | "applyMonths": [5, 6, 7, 8], 549 | "imageUrl": "dorado.png" 550 | }, 551 | { 552 | "id": 43, 553 | "name": "가아", 554 | "price": 6000, 555 | "place": ["pond"], 556 | "shadowSize": 6, 557 | "hasFin": false, 558 | "hasSound": false, 559 | "onlyRaining": false, 560 | "applyHours": [[16, 9]], 561 | "applyMonths": [5, 6, 7, 8], 562 | "imageUrl": "gar.png" 563 | }, 564 | { 565 | "id": 44, 566 | "name": "피라루쿠", 567 | "price": 10000, 568 | "place": ["river"], 569 | "shadowSize": 6, 570 | "hasFin": false, 571 | "hasSound": false, 572 | "onlyRaining": false, 573 | "applyHours": [[16, 9]], 574 | "applyMonths": [5, 6, 7, 8], 575 | "imageUrl": "arapaima.png" 576 | }, 577 | { 578 | "id": 45, 579 | "name": "엔드리케리", 580 | "price": 4000, 581 | "place": ["river"], 582 | "shadowSize": 4, 583 | "hasFin": false, 584 | "hasSound": false, 585 | "onlyRaining": false, 586 | "applyHours": [[21, 4]], 587 | "applyMonths": [5, 6, 7, 8], 588 | "imageUrl": "saddled_bichir.png" 589 | }, 590 | { 591 | "id": 46, 592 | "name": "철갑상어", 593 | "price": 10000, 594 | "place": ["mouth"], 595 | "shadowSize": 6, 596 | "hasFin": false, 597 | "hasSound": false, 598 | "onlyRaining": false, 599 | "applyHours": [[0, 23]], 600 | "applyMonths": [0, 1, 2, 8, 9, 10, 11], 601 | "imageUrl": "sturgeon.png" 602 | }, 603 | { 604 | "id": 47, 605 | "name": "클리오네", 606 | "price": 1000, 607 | "place": ["ocean"], 608 | "shadowSize": 1, 609 | "hasFin": false, 610 | "hasSound": false, 611 | "onlyRaining": false, 612 | "applyHours": [[0, 23]], 613 | "applyMonths": [0, 1, 2, 11], 614 | "imageUrl": "sea_butterfly.png" 615 | }, 616 | { 617 | "id": 48, 618 | "name": "해마", 619 | "price": 1100, 620 | "place": ["ocean"], 621 | "shadowSize": 1, 622 | "hasFin": false, 623 | "hasSound": false, 624 | "onlyRaining": false, 625 | "applyHours": [[0, 23]], 626 | "applyMonths": [3, 4, 5, 6, 7, 8, 9, 10], 627 | "imageUrl": "sea_horse.png" 628 | }, 629 | { 630 | "id": 49, 631 | "name": "흰동가리", 632 | "price": 650, 633 | "place": ["ocean"], 634 | "shadowSize": 1, 635 | "hasFin": false, 636 | "hasSound": false, 637 | "onlyRaining": false, 638 | "applyHours": [[0, 23]], 639 | "applyMonths": [3, 4, 5, 6, 7, 8], 640 | "imageUrl": "clown_fish.png" 641 | }, 642 | { 643 | "id": 50, 644 | "name": "블루탱", 645 | "price": 1000, 646 | "place": ["ocean"], 647 | "shadowSize": 2, 648 | "hasFin": false, 649 | "hasSound": false, 650 | "onlyRaining": false, 651 | "applyHours": [[0, 23]], 652 | "applyMonths": [3, 4, 5, 6, 7, 8], 653 | "imageUrl": "surgeonfish.png" 654 | }, 655 | { 656 | "id": 51, 657 | "name": "나비고기", 658 | "price": 1000, 659 | "place": ["ocean"], 660 | "shadowSize": 2, 661 | "hasFin": false, 662 | "hasSound": false, 663 | "onlyRaining": false, 664 | "applyHours": [[0, 23]], 665 | "applyMonths": [3, 4, 5, 6, 7, 8], 666 | "imageUrl": "butterfly_fish.png" 667 | }, 668 | { 669 | "id": 52, 670 | "name": "나폴레옹고기", 671 | "price": 10000, 672 | "place": ["ocean"], 673 | "shadowSize": 6, 674 | "hasFin": false, 675 | "hasSound": false, 676 | "onlyRaining": false, 677 | "applyHours": [[4, 21]], 678 | "applyMonths": [6, 7], 679 | "imageUrl": "napoleonfish.png" 680 | }, 681 | { 682 | "id": 53, 683 | "name": "쏨뱅이", 684 | "price": 500, 685 | "place": ["ocean"], 686 | "shadowSize": 3, 687 | "hasFin": false, 688 | "hasSound": false, 689 | "onlyRaining": false, 690 | "applyHours": [[0, 23]], 691 | "applyMonths": [3, 4, 5, 6, 7, 8, 9, 10], 692 | "imageUrl": "zebra_turkeyfish.png" 693 | }, 694 | { 695 | "id": 54, 696 | "name": "복어", 697 | "price": 5000, 698 | "place": ["ocean"], 699 | "shadowSize": 3, 700 | "hasFin": false, 701 | "hasSound": false, 702 | "onlyRaining": false, 703 | "applyHours": [[21, 4]], 704 | "applyMonths": [0, 1, 10, 11], 705 | "imageUrl": "blowfish.png" 706 | }, 707 | { 708 | "id": 55, 709 | "name": "가시복", 710 | "price": 250, 711 | "place": ["ocean"], 712 | "shadowSize": 3, 713 | "hasFin": false, 714 | "hasSound": false, 715 | "onlyRaining": false, 716 | "applyHours": [[0, 23]], 717 | "applyMonths": [6, 7, 8], 718 | "imageUrl": "puffer_fish.png" 719 | }, 720 | { 721 | "id": 56, 722 | "name": "멸치", 723 | "price": 200, 724 | "place": ["ocean"], 725 | "shadowSize": 2, 726 | "hasFin": false, 727 | "hasSound": false, 728 | "onlyRaining": false, 729 | "applyHours": [[4, 21]], 730 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 731 | "imageUrl": "anchovy.png" 732 | }, 733 | { 734 | "id": 57, 735 | "name": "전갱이", 736 | "price": 150, 737 | "place": ["ocean"], 738 | "shadowSize": 2, 739 | "hasFin": false, 740 | "hasSound": false, 741 | "onlyRaining": false, 742 | "applyHours": [[0, 23]], 743 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 744 | "imageUrl": "horse_mackerel.png" 745 | }, 746 | { 747 | "id": 58, 748 | "name": "돌돔", 749 | "price": 5000, 750 | "place": ["ocean"], 751 | "shadowSize": 3, 752 | "hasFin": false, 753 | "hasSound": false, 754 | "onlyRaining": false, 755 | "applyHours": [[0, 23]], 756 | "applyMonths": [2, 3, 4, 5, 6, 7, 8, 9, 10], 757 | "imageUrl": "barred_knifejaw.png" 758 | }, 759 | { 760 | "id": 59, 761 | "name": "농어", 762 | "price": 400, 763 | "place": ["ocean"], 764 | "shadowSize": 5, 765 | "hasFin": false, 766 | "hasSound": false, 767 | "onlyRaining": false, 768 | "applyHours": [[0, 23]], 769 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 770 | "imageUrl": "sea_bass.png" 771 | }, 772 | { 773 | "id": 60, 774 | "name": "도미", 775 | "price": 3000, 776 | "place": ["ocean"], 777 | "shadowSize": 4, 778 | "hasFin": false, 779 | "hasSound": false, 780 | "onlyRaining": false, 781 | "applyHours": [[0, 23]], 782 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 783 | "imageUrl": "red_snapper.png" 784 | }, 785 | { 786 | "id": 61, 787 | "name": "가자미", 788 | "price": 300, 789 | "place": ["ocean"], 790 | "shadowSize": 3, 791 | "hasFin": false, 792 | "hasSound": false, 793 | "onlyRaining": false, 794 | "applyHours": [[0, 23]], 795 | "applyMonths": [0, 1, 2, 3, 9, 10, 11], 796 | "imageUrl": "dab.png" 797 | }, 798 | { 799 | "id": 62, 800 | "name": "넙치", 801 | "price": 800, 802 | "place": ["ocean"], 803 | "shadowSize": 5, 804 | "hasFin": false, 805 | "hasSound": false, 806 | "onlyRaining": false, 807 | "applyHours": [[0, 23]], 808 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 809 | "imageUrl": "olive_flounder.png" 810 | }, 811 | { 812 | "id": 63, 813 | "name": "오징어", 814 | "price": 500, 815 | "place": ["ocean"], 816 | "shadowSize": 3, 817 | "hasFin": false, 818 | "hasSound": false, 819 | "onlyRaining": false, 820 | "applyHours": [[0, 23]], 821 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 11], 822 | "imageUrl": "squid.png" 823 | }, 824 | { 825 | "id": 64, 826 | "name": "곰치", 827 | "price": 2000, 828 | "place": ["ocean"], 829 | "shadowSize": "narrow", 830 | "hasFin": false, 831 | "hasSound": false, 832 | "onlyRaining": false, 833 | "applyHours": [[0, 23]], 834 | "applyMonths": [7, 8, 9], 835 | "imageUrl": "moray_eel.png" 836 | }, 837 | { 838 | "id": 65, 839 | "name": "리본장어", 840 | "price": 600, 841 | "place": ["ocean"], 842 | "shadowSize": "narrow", 843 | "hasFin": false, 844 | "hasSound": false, 845 | "onlyRaining": false, 846 | "applyHours": [[0, 23]], 847 | "applyMonths": [5, 6, 7, 8, 9], 848 | "imageUrl": "ribbon_eel.png" 849 | }, 850 | { 851 | "id": 66, 852 | "name": "다랑어", 853 | "price": 7000, 854 | "place": ["pier"], 855 | "shadowSize": 6, 856 | "hasFin": false, 857 | "hasSound": false, 858 | "onlyRaining": false, 859 | "applyHours": [[0, 23]], 860 | "applyMonths": [0, 1, 2, 3, 10, 11], 861 | "imageUrl": "tuna.png" 862 | }, 863 | { 864 | "id": 67, 865 | "name": "청새치", 866 | "price": 10000, 867 | "place": ["pier"], 868 | "shadowSize": 6, 869 | "hasFin": false, 870 | "hasSound": false, 871 | "onlyRaining": false, 872 | "applyHours": [[0, 23]], 873 | "applyMonths": [0, 1, 2, 3, 6, 7, 8, 10, 11], 874 | "imageUrl": "blue_marlin.png" 875 | }, 876 | { 877 | "id": 68, 878 | "name": "무명갈전갱이", 879 | "price": 4500, 880 | "place": ["pier"], 881 | "shadowSize": 5, 882 | "hasFin": false, 883 | "hasSound": false, 884 | "onlyRaining": false, 885 | "applyHours": [[0, 23]], 886 | "applyMonths": [4, 5, 6, 7, 8, 9], 887 | "imageUrl": "giant_trevally.png" 888 | }, 889 | { 890 | "id": 69, 891 | "name": "만새기", 892 | "price": 6000, 893 | "place": ["pier"], 894 | "shadowSize": 5, 895 | "hasFin": false, 896 | "hasSound": false, 897 | "onlyRaining": false, 898 | "applyHours": [[0, 23]], 899 | "applyMonths": [4, 5, 6, 7, 8, 9], 900 | "imageUrl": "mahi-mahi.png" 901 | }, 902 | { 903 | "id": 70, 904 | "name": "개복치", 905 | "price": 4000, 906 | "place": ["ocean"], 907 | "shadowSize": 6, 908 | "hasFin": true, 909 | "hasSound": false, 910 | "onlyRaining": false, 911 | "applyHours": [[4, 21]], 912 | "applyMonths": [6, 7, 8], 913 | "imageUrl": "ocean_sunfish.png" 914 | }, 915 | { 916 | "id": 71, 917 | "name": "가오리", 918 | "price": 3000, 919 | "place": ["ocean"], 920 | "shadowSize": 5, 921 | "hasFin": false, 922 | "hasSound": false, 923 | "onlyRaining": false, 924 | "applyHours": [[4, 21]], 925 | "applyMonths": [7, 8, 9, 10], 926 | "imageUrl": "ray.png" 927 | }, 928 | { 929 | "id": 72, 930 | "name": "톱상어", 931 | "price": 12000, 932 | "place": ["ocean"], 933 | "shadowSize": 6, 934 | "hasFin": true, 935 | "hasSound": false, 936 | "onlyRaining": false, 937 | "applyHours": [[16, 9]], 938 | "applyMonths": [5, 6, 7, 8], 939 | "imageUrl": "saw_shark.png" 940 | }, 941 | { 942 | "id": 73, 943 | "name": "귀상어", 944 | "price": 8000, 945 | "place": ["ocean"], 946 | "shadowSize": 6, 947 | "hasFin": true, 948 | "hasSound": false, 949 | "onlyRaining": false, 950 | "applyHours": [[16, 9]], 951 | "applyMonths": [5, 6, 7, 8], 952 | "imageUrl": "hammerhead_shark.png" 953 | }, 954 | { 955 | "id": 74, 956 | "name": "상어", 957 | "price": 15000, 958 | "place": ["ocean"], 959 | "shadowSize": 6, 960 | "hasFin": true, 961 | "hasSound": false, 962 | "onlyRaining": false, 963 | "applyHours": [[16, 9]], 964 | "applyMonths": [5, 6, 7, 8], 965 | "imageUrl": "great_white_shark.png" 966 | }, 967 | { 968 | "id": 75, 969 | "name": "고래상어", 970 | "price": 13000, 971 | "place": ["ocean"], 972 | "shadowSize": 6, 973 | "hasFin": true, 974 | "hasSound": false, 975 | "onlyRaining": false, 976 | "applyHours": [[0, 23]], 977 | "applyMonths": [5, 6, 7, 8], 978 | "imageUrl": "whale_shark.png" 979 | }, 980 | { 981 | "id": 76, 982 | "name": "빨판상어", 983 | "price": 1500, 984 | "place": ["ocean"], 985 | "shadowSize": 4, 986 | "hasFin": true, 987 | "hasSound": false, 988 | "onlyRaining": false, 989 | "applyHours": [[0, 23]], 990 | "applyMonths": [5, 6, 7, 8], 991 | "imageUrl": "suckerfish.png" 992 | }, 993 | { 994 | "id": 77, 995 | "name": "초롱아귀", 996 | "price": 2500, 997 | "place": ["ocean"], 998 | "shadowSize": 4, 999 | "hasFin": false, 1000 | "hasSound": false, 1001 | "onlyRaining": false, 1002 | "applyHours": [[16, 9]], 1003 | "applyMonths": [0, 1, 2, 10, 11], 1004 | "imageUrl": "football_fish.png" 1005 | }, 1006 | { 1007 | "id": 78, 1008 | "name": "산갈치", 1009 | "price": 9000, 1010 | "place": ["ocean"], 1011 | "shadowSize": 6, 1012 | "hasFin": false, 1013 | "hasSound": false, 1014 | "onlyRaining": false, 1015 | "applyHours": [[0, 23]], 1016 | "applyMonths": [0, 1, 2, 3, 4, 11], 1017 | "imageUrl": "oarfish.png" 1018 | }, 1019 | { 1020 | "id": 79, 1021 | "name": "데메니기스", 1022 | "price": 15000, 1023 | "place": ["ocean"], 1024 | "shadowSize": 2, 1025 | "hasFin": false, 1026 | "hasSound": false, 1027 | "onlyRaining": false, 1028 | "applyHours": [[21, 4]], 1029 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1030 | "imageUrl": "barreleye.png" 1031 | }, 1032 | { 1033 | "id": 80, 1034 | "name": "실러캔스", 1035 | "price": 15000, 1036 | "place": ["ocean"], 1037 | "shadowSize": 6, 1038 | "hasFin": false, 1039 | "hasSound": false, 1040 | "onlyRaining": true, 1041 | "applyHours": [[0, 23]], 1042 | "applyMonths": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 1043 | "imageUrl": "coelacanth.png" 1044 | } 1045 | ] 1046 | --------------------------------------------------------------------------------