diff --git a/crabfit-frontend/.gcloudignore b/crabfit-frontend/.gcloudignore
deleted file mode 100644
index d3a7b16..0000000
--- a/crabfit-frontend/.gcloudignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.gcloudignore
-
-.git
-.gitignore
-
-node_modules/
diff --git a/crabfit-frontend/app.yaml b/crabfit-frontend/example-app.yaml
similarity index 100%
rename from crabfit-frontend/app.yaml
rename to crabfit-frontend/example-app.yaml
diff --git a/crabfit-frontend/jsconfig.json b/crabfit-frontend/jsconfig.json
deleted file mode 100644
index 5875dc5..0000000
--- a/crabfit-frontend/jsconfig.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": "src"
- },
- "include": ["src"]
-}
diff --git a/crabfit-frontend/package.json b/crabfit-frontend/package.json
index 072dd0d..90739d6 100644
--- a/crabfit-frontend/package.json
+++ b/crabfit-frontend/package.json
@@ -8,10 +8,17 @@
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
+ "@types/jest": "^26.0.20",
+ "@types/node": "^14.14.31",
+ "@types/react": "^17.0.2",
+ "@types/react-dom": "^17.0.1",
+ "dayjs": "^1.10.4",
"react": "^17.0.1",
"react-dom": "^17.0.1",
+ "react-hook-form": "^6.15.4",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
+ "typescript": "^4.2.2",
"web-vitals": "^1.0.1",
"zustand": "^3.3.2"
},
diff --git a/crabfit-frontend/public/fonts/karla-italic-variable.ttf b/crabfit-frontend/public/fonts/karla-italic-variable.ttf
new file mode 100644
index 0000000..aecc468
Binary files /dev/null and b/crabfit-frontend/public/fonts/karla-italic-variable.ttf differ
diff --git a/crabfit-frontend/public/fonts/karla-variable.ttf b/crabfit-frontend/public/fonts/karla-variable.ttf
new file mode 100644
index 0000000..172b500
Binary files /dev/null and b/crabfit-frontend/public/fonts/karla-variable.ttf differ
diff --git a/crabfit-frontend/public/index.css b/crabfit-frontend/public/index.css
new file mode 100644
index 0000000..749c6cd
--- /dev/null
+++ b/crabfit-frontend/public/index.css
@@ -0,0 +1,5 @@
+@font-face {
+ font-family: Karla;
+ src: url('fonts/karla-variable.ttf') format('truetype');
+ font-weight: 1 999;
+}
diff --git a/crabfit-frontend/public/index.html b/crabfit-frontend/public/index.html
index 523055d..11686e0 100644
--- a/crabfit-frontend/public/index.html
+++ b/crabfit-frontend/public/index.html
@@ -12,6 +12,8 @@
+
+
Crab Fit
diff --git a/crabfit-frontend/src/App.js b/crabfit-frontend/src/App.js
deleted file mode 100644
index 06a90b3..0000000
--- a/crabfit-frontend/src/App.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import {
- BrowserRouter,
- Switch,
- Route,
- Redirect,
- useLocation,
-} from 'react-router-dom';
-
-import {
- Home,
- Event,
-} from 'pages';
-
-const App = () => {
- return (
-
-
-
-
-
-
- );
-}
-
-export default App;
diff --git a/crabfit-frontend/src/App.test.js b/crabfit-frontend/src/App.test.ts
similarity index 100%
rename from crabfit-frontend/src/App.test.js
rename to crabfit-frontend/src/App.test.ts
diff --git a/crabfit-frontend/src/App.tsx b/crabfit-frontend/src/App.tsx
new file mode 100644
index 0000000..c7c7be2
--- /dev/null
+++ b/crabfit-frontend/src/App.tsx
@@ -0,0 +1,49 @@
+import { useState } from 'react';
+import {
+ BrowserRouter,
+ Switch,
+ Route,
+} from 'react-router-dom';
+import { ThemeProvider, Global } from '@emotion/react';
+
+import {
+ Home,
+ Event,
+} from 'pages';
+
+import theme from 'theme';
+
+const App = () => {
+ const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ const [isDark, setIsDark] = useState(darkQuery.matches);
+
+ darkQuery.addListener(e => setIsDark(e.matches));
+
+ return (
+
+
+
+ ({
+ body: {
+ backgroundColor: theme.background,
+ color: theme.text,
+ fontFamily: `'Karla', sans-serif`,
+ fontWeight: 600,
+ margin: 0,
+ },
+ a: {
+ color: theme.primary,
+ },
+ })}
+ />
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/crabfit-frontend/src/components/Button/Button.tsx b/crabfit-frontend/src/components/Button/Button.tsx
new file mode 100644
index 0000000..5e90449
--- /dev/null
+++ b/crabfit-frontend/src/components/Button/Button.tsx
@@ -0,0 +1,14 @@
+import { Wrapper, Top, Bottom } from './buttonStyle';
+
+const Button = ({
+ buttonHeight,
+ buttonWidth,
+ ...props
+}) => (
+
+
+
+
+);
+
+export default Button;
diff --git a/crabfit-frontend/src/components/Button/buttonStyle.ts b/crabfit-frontend/src/components/Button/buttonStyle.ts
new file mode 100644
index 0000000..b46842e
--- /dev/null
+++ b/crabfit-frontend/src/components/Button/buttonStyle.ts
@@ -0,0 +1,47 @@
+import styled from '@emotion/styled';
+
+export const Wrapper = styled.div`
+ display: inline-block;
+ position: relative;
+
+ --btn-height: ${props => props.buttonHeight || '40px'};
+ --btn-width: ${props => props.buttonWidth || '100px'};
+
+ height: var(--btn-height);
+ width: var(--btn-width);
+`;
+
+export const Top = styled.button`
+ border: 0;
+ cursor: pointer;
+ font: inherit;
+ box-sizing: border-box;
+ background: ${props => props.theme.primary};
+ color: #FFF;
+ font-weight: 600;
+ text-shadow: 0 -1.5px .5px ${props => props.theme.primaryDark};
+ padding: ${props => props.padding || '10px 14px'};
+ border-radius: 3px;
+ height: var(--btn-height);
+ width: var(--btn-width);
+ position: absolute;
+ top: -4px;
+ user-select: none;
+ transition: top .15s;
+ outline: none;
+
+ &:active {
+ top: 0;
+ }
+ &:focus-visible {
+ filter: brightness(1.2);
+ }
+`;
+
+export const Bottom = styled.div`
+ box-sizing: border-box;
+ background: ${props => props.theme.primaryDark};
+ border-radius: 3px;
+ height: var(--btn-height);
+ width: var(--btn-width);
+`;
diff --git a/crabfit-frontend/src/components/CalendarField/CalendarField.tsx b/crabfit-frontend/src/components/CalendarField/CalendarField.tsx
new file mode 100644
index 0000000..8636276
--- /dev/null
+++ b/crabfit-frontend/src/components/CalendarField/CalendarField.tsx
@@ -0,0 +1,192 @@
+import { useState, useEffect, useRef } from 'react';
+import dayjs from 'dayjs';
+import isToday from 'dayjs/plugin/isToday';
+
+import { Button } from 'components';
+import {
+ Wrapper,
+ StyledLabel,
+ StyledSubLabel,
+ CalendarHeader,
+ CalendarBody,
+ Date,
+ Day,
+} from './calendarFieldStyle';
+
+dayjs.extend(isToday);
+
+const days = [
+ 'Sun',
+ 'Mon',
+ 'Tue',
+ 'Wed',
+ 'Thu',
+ 'Fri',
+ 'Sat',
+];
+
+const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+];
+
+const calculateMonth = (month, year) => {
+ const date = dayjs().month(month).year(year);
+ const daysInMonth = date.daysInMonth();
+ const daysBefore = date.date(1).day();
+ const daysAfter = 6 - date.date(daysInMonth).day();
+
+ let dates = [];
+ let curDate = date.date(1).subtract(daysBefore, 'day');
+ let y = 0;
+ let x = 0;
+ for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) {
+ if (x === 0) dates[y] = [];
+ dates[y][x] = curDate.clone();
+ curDate = curDate.add(1, 'day');
+ x++;
+ if (x > 6) {
+ x = 0;
+ y++;
+ }
+ }
+
+ return dates;
+};
+
+const CalendarField = ({
+ label,
+ subLabel,
+ id,
+ register,
+ ...props
+}) => {
+ const [dates, setDates] = useState(calculateMonth(dayjs().month(), dayjs().year()));
+ const [month, setMonth] = useState(dayjs().month());
+ const [year, setYear] = useState(dayjs().year());
+
+ const [selectedDates, setSelectedDates] = useState([]);
+ const [selectingDates, _setSelectingDates] = useState([]);
+ const staticSelectingDates = useRef([]);
+ const setSelectingDates = newDates => {
+ staticSelectingDates.current = newDates;
+ _setSelectingDates(newDates);
+ };
+
+ const startPos = useRef({});
+ const staticMode = useRef(null);
+ const [mode, _setMode] = useState(staticMode.current);
+ const setMode = newMode => {
+ staticMode.current = newMode;
+ _setMode(newMode);
+ };
+
+ useEffect(() => {
+ setDates(calculateMonth(month, year));
+ }, [month, year]);
+
+ return (
+
+ {label && {label}}
+ {subLabel && {subLabel}}
+
+
+
+
+ {months[month]} {year}
+
+
+
+
+ {days.map((name, i) =>
+ {name}
+ )}
+ {dates.length > 0 && dates.map((dateRow, y) =>
+ dateRow.map((date, x) =>
+ {
+ startPos.current = {x, y};
+ setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add');
+ setSelectingDates([date]);
+
+ document.addEventListener('mouseup', () => {
+ if (staticMode.current === 'add') {
+ setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]);
+ } else if (staticMode.current === 'remove') {
+ const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY'));
+ setSelectedDates(selectedDates.filter(d => !toRemove.includes(d)));
+ }
+ setMode(null);
+ }, { once: true });
+ }}
+ onMouseEnter={() => {
+ if (staticMode.current) {
+ let found = [];
+ for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) {
+ for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) {
+ found.push({y: cy, x: cx});
+ }
+ }
+ setSelectingDates(found.map(d => dates[d.y][d.x]));
+ }
+ }}
+ >{date.date()}
+ )
+ )}
+
+
+ );
+};
+
+export default CalendarField;
diff --git a/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts b/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts
new file mode 100644
index 0000000..32d3cbc
--- /dev/null
+++ b/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts
@@ -0,0 +1,73 @@
+import styled from '@emotion/styled';
+
+export const Wrapper = styled.div`
+ margin: 30px 0;
+`;
+
+export const StyledLabel = styled.label`
+ display: block;
+ padding-bottom: 4px;
+ font-size: 18px;
+`;
+
+export const StyledSubLabel = styled.label`
+ display: block;
+ padding-bottom: 6px;
+ font-size: 13px;
+ opacity: .6;
+`;
+
+export const CalendarHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ user-select: none;
+ padding: 6px 0;
+ font-size: 1.2em;
+ font-weight: bold;
+`;
+
+export const CalendarBody = styled.div`
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ grid-gap: 2px;
+`;
+
+export const Date = styled.div`
+ background-color: ${props => props.theme.primary}22;
+ border: 1px solid ${props => props.theme.primaryLight};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 0;
+ border-radius: 3px;
+ user-select: none;
+
+ ${props => props.otherMonth && `
+ color: ${props.theme.primaryLight};
+ `}
+ ${props => props.isToday && `
+ font-weight: 900;
+ color: ${props.theme.primaryDark};
+ `}
+ ${props => (props.selected || (props.mode === 'add' && props.selecting)) && `
+ color: ${props.otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'};
+ background-color: ${props.theme.primary};
+ border-color: ${props.theme.primary};
+ `}
+ ${props => props.mode === 'remove' && props.selecting && `
+ background-color: ${props.theme.primary}22;
+ border: 1px solid ${props.theme.primaryLight};
+ color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')};
+ `}
+`;
+
+export const Day = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 3px 10px;
+ font-weight: bold;
+ user-select: none;
+ opacity: .7;
+`;
diff --git a/crabfit-frontend/src/components/TextField/TextField.tsx b/crabfit-frontend/src/components/TextField/TextField.tsx
new file mode 100644
index 0000000..0a91061
--- /dev/null
+++ b/crabfit-frontend/src/components/TextField/TextField.tsx
@@ -0,0 +1,22 @@
+import {
+ Wrapper,
+ StyledLabel,
+ StyledSubLabel,
+ StyledInput,
+} from './textFieldStyle';
+
+const TextField = ({
+ label,
+ subLabel,
+ id,
+ register,
+ ...props
+}) => (
+
+ {label && {label}}
+ {subLabel && {subLabel}}
+
+
+);
+
+export default TextField;
diff --git a/crabfit-frontend/src/components/TextField/textFieldStyle.ts b/crabfit-frontend/src/components/TextField/textFieldStyle.ts
new file mode 100644
index 0000000..ac37df0
--- /dev/null
+++ b/crabfit-frontend/src/components/TextField/textFieldStyle.ts
@@ -0,0 +1,38 @@
+import styled from '@emotion/styled';
+
+export const Wrapper = styled.div`
+ margin: 30px 0;
+`;
+
+export const StyledLabel = styled.label`
+ display: block;
+ padding-bottom: 4px;
+ font-size: 18px;
+`;
+
+export const StyledSubLabel = styled.label`
+ display: block;
+ padding-bottom: 6px;
+ font-size: 13px;
+ opacity: .6;
+`;
+
+export const StyledInput = styled.input`
+ width: 100%;
+ box-sizing: border-box;
+ font: inherit;
+ background: ${props => props.theme.primary}22;
+ color: inherit;
+ padding: 10px 14px;
+ border: 1px solid ${props => props.theme.primaryLight};
+ box-shadow: inset 0 0 0 0 ${props => props.theme.primaryLight};
+ border-radius: 3px;
+ font-size: 18px;
+ outline: none;
+ transition: border-color .15s, box-shadow .15s;
+
+ &:focus {
+ border: 1px solid ${props => props.theme.primary};
+ box-shadow: inset 0 -3px 0 0 ${props => props.theme.primary};
+ }
+`;
diff --git a/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx
new file mode 100644
index 0000000..fb5fce3
--- /dev/null
+++ b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx
@@ -0,0 +1,139 @@
+import { useState, useEffect, useRef } from 'react';
+
+import {
+ Wrapper,
+ StyledLabel,
+ StyledSubLabel,
+ Range,
+ Handle,
+ Selected,
+} from './timeRangeFieldStyle';
+
+const times = [
+ '12am',
+ '1am',
+ '2am',
+ '3am',
+ '4am',
+ '5am',
+ '6am',
+ '7am',
+ '8am',
+ '9am',
+ '10am',
+ '11am',
+ '12pm',
+ '1pm',
+ '2pm',
+ '3pm',
+ '4pm',
+ '5pm',
+ '6pm',
+ '7pm',
+ '8pm',
+ '9pm',
+ '10pm',
+ '11pm',
+ '12am',
+];
+
+const TimeRangeField = ({
+ label,
+ subLabel,
+ id,
+ register,
+ ...props
+}) => {
+ const [start, setStart] = useState(9);
+ const [end, setEnd] = useState(17);
+
+ const isStartMoving = useRef(false);
+ const isEndMoving = useRef(false);
+ const rangeRef = useRef();
+ const rangeRect = useRef();
+
+ useEffect(() => {
+ if (rangeRef.current) {
+ rangeRect.current = rangeRef.current.getBoundingClientRect();
+ }
+ }, [rangeRef]);
+
+ const handleMouseMove = e => {
+ if (isStartMoving.current || isEndMoving.current) {
+ let step = Math.round(((e.pageX - rangeRect.current.left) / rangeRect.current.width) * 24);
+ if (step < 0) step = 0;
+ if (step > 24) step = 24;
+ step = Math.abs(step);
+
+ if (isStartMoving.current) {
+ setStart(step);
+ } else if (isEndMoving.current) {
+ setEnd(step);
+ }
+ }
+ };
+
+ return (
+
+ {label && {label}}
+ {subLabel && {subLabel}}
+ end ? {start: end, end: start} : {start, end})}
+ {...props}
+ />
+
+
+ end ? end : start} end={start > end ? start : end} />
+ {
+ document.addEventListener('mousemove', handleMouseMove);
+ isStartMoving.current = true;
+
+ document.addEventListener('mouseup', () => {
+ isStartMoving.current = false;
+ document.removeEventListener('mousemove', handleMouseMove);
+ }, { once: true });
+ }}
+ onTouchMove={(e) => {
+ const touch = e.targetTouches[0];
+
+ let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24);
+ if (step < 0) step = 0;
+ if (step > 24) step = 24;
+ step = Math.abs(step);
+ setStart(step);
+ }}
+ />
+ {
+ document.addEventListener('mousemove', handleMouseMove);
+ isEndMoving.current = true;
+
+ document.addEventListener('mouseup', () => {
+ isEndMoving.current = false;
+ document.removeEventListener('mousemove', handleMouseMove);
+ }, { once: true });
+ }}
+ onTouchMove={(e) => {
+ const touch = e.targetTouches[0];
+
+ let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24);
+ if (step < 0) step = 0;
+ if (step > 24) step = 24;
+ step = Math.abs(step);
+ setEnd(step);
+ }}
+ />
+
+
+ );
+};
+
+export default TimeRangeField;
diff --git a/crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts b/crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts
new file mode 100644
index 0000000..ddaefb3
--- /dev/null
+++ b/crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts
@@ -0,0 +1,72 @@
+import styled from '@emotion/styled';
+
+export const Wrapper = styled.div`
+ margin: 30px 0;
+`;
+
+export const StyledLabel = styled.label`
+ display: block;
+ padding-bottom: 4px;
+ font-size: 18px;
+`;
+
+export const StyledSubLabel = styled.label`
+ display: block;
+ padding-bottom: 6px;
+ font-size: 13px;
+ opacity: .6;
+`;
+
+export const Range = styled.div`
+ user-select: none;
+ background-color: ${props => props.theme.primary}22;
+ border: 1px solid ${props => props.theme.primaryLight};
+ border-radius: 3px;
+ height: 50px;
+ position: relative;
+ margin: 38px 6px 18px;
+`;
+
+export const Handle = styled.div`
+ height: calc(100% + 20px);
+ width: 20px;
+ border: 1px solid ${props => props.theme.primary};
+ background-color: ${props => props.theme.primaryLight};
+ border-radius: 3px;
+ position: absolute;
+ top: -10px;
+ left: calc(${props => props.value * 4.1666666666666666}% - 11px);
+ cursor: ew-resize;
+
+ &:after {
+ content: '|||';
+ font-size: 8px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: ${props => props.theme.primaryDark};
+ }
+
+ &:before {
+ content: '${props => props.label}';
+ position: absolute;
+ bottom: calc(100% + 8px);
+ text-align: center;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+`;
+
+export const Selected = styled.div`
+ position: absolute;
+ height: 100%;
+ left: ${props => props.start * 4.1666666666666666}%;
+ right: calc(100% - ${props => props.end * 4.1666666666666666}%);
+ top: 0;
+ background-color: ${props => props.theme.primary};
+`;
diff --git a/crabfit-frontend/src/components/index.ts b/crabfit-frontend/src/components/index.ts
new file mode 100644
index 0000000..0146924
--- /dev/null
+++ b/crabfit-frontend/src/components/index.ts
@@ -0,0 +1,4 @@
+export { default as TextField } from './TextField/TextField';
+export { default as CalendarField } from './CalendarField/CalendarField';
+export { default as TimeRangeField } from './TimeRangeField/TimeRangeField';
+export { default as Button } from './Button/Button';
diff --git a/crabfit-frontend/src/index.css b/crabfit-frontend/src/index.css
deleted file mode 100644
index ec2585e..0000000
--- a/crabfit-frontend/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/crabfit-frontend/src/index.js b/crabfit-frontend/src/index.tsx
similarity index 95%
rename from crabfit-frontend/src/index.js
rename to crabfit-frontend/src/index.tsx
index ef2edf8..7998135 100644
--- a/crabfit-frontend/src/index.js
+++ b/crabfit-frontend/src/index.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
diff --git a/crabfit-frontend/src/pages/Event/Event.js b/crabfit-frontend/src/pages/Event/Event.tsx
similarity index 100%
rename from crabfit-frontend/src/pages/Event/Event.js
rename to crabfit-frontend/src/pages/Event/Event.tsx
diff --git a/crabfit-frontend/src/pages/Home/Home.js b/crabfit-frontend/src/pages/Home/Home.js
deleted file mode 100644
index c60398a..0000000
--- a/crabfit-frontend/src/pages/Home/Home.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Link } from 'react-router-dom';
-
-const Home = () => {
- return (
-
- );
-};
-
-export default Home;
diff --git a/crabfit-frontend/src/pages/Home/Home.tsx b/crabfit-frontend/src/pages/Home/Home.tsx
new file mode 100644
index 0000000..b57eda4
--- /dev/null
+++ b/crabfit-frontend/src/pages/Home/Home.tsx
@@ -0,0 +1,59 @@
+import { useForm } from 'react-hook-form';
+
+import {
+ TextField,
+ CalendarField,
+ TimeRangeField,
+ Button,
+} from 'components';
+
+import {
+ StyledMain,
+ CreateForm,
+ TitleSmall,
+ TitleLarge,
+} from './homeStyle';
+
+const Home = () => {
+ const { register, handleSubmit } = useForm();
+
+ const onSubmit = data => console.log('submit', data);
+
+ return (
+
+ Create a
+ CRAB FIT
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/crabfit-frontend/src/pages/Home/homeStyle.ts b/crabfit-frontend/src/pages/Home/homeStyle.ts
new file mode 100644
index 0000000..c9a75be
--- /dev/null
+++ b/crabfit-frontend/src/pages/Home/homeStyle.ts
@@ -0,0 +1,32 @@
+import styled from '@emotion/styled';
+
+export const StyledMain = styled.main`
+ width: 600px;
+ margin: 30px auto;
+ max-width: calc(100% - 30px);
+`;
+
+export const CreateForm = styled.form`
+`;
+
+export const TitleSmall = styled.span`
+ display: block;
+ margin: 20px 0 0;
+ font-size: 3rem;
+ text-align: center;
+ font-family: 'CF Samurai Bob';
+ font-weight: 400;
+ color: ${props => props.theme.primaryDark};
+ line-height: 1em;
+`;
+
+export const TitleLarge = styled.h1`
+ margin: 0 0 40px;
+ font-size: 4rem;
+ text-align: center;
+ color: ${props => props.theme.primary};
+ font-family: 'Molot';
+ font-weight: 400;
+ text-shadow: 0 4px 0 ${props => props.theme.primaryDark};
+ line-height: 1em;
+`;
diff --git a/crabfit-frontend/src/pages/index.js b/crabfit-frontend/src/pages/index.ts
similarity index 100%
rename from crabfit-frontend/src/pages/index.js
rename to crabfit-frontend/src/pages/index.ts
diff --git a/crabfit-frontend/src/react-app-env.d.ts b/crabfit-frontend/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/crabfit-frontend/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/crabfit-frontend/src/res/create_banner.svg b/crabfit-frontend/src/res/create_banner.svg
new file mode 100644
index 0000000..c4c1aff
--- /dev/null
+++ b/crabfit-frontend/src/res/create_banner.svg
@@ -0,0 +1,61 @@
+
diff --git a/crabfit-frontend/src/theme/index.ts b/crabfit-frontend/src/theme/index.ts
new file mode 100644
index 0000000..c6063e2
--- /dev/null
+++ b/crabfit-frontend/src/theme/index.ts
@@ -0,0 +1,20 @@
+const theme = {
+ light: {
+ mode: 'light',
+ background: '#FFFFFF',
+ text: '#000000',
+ primary: '#F79E00',
+ primaryDark: '#F48600',
+ primaryLight: '#F4BB60',
+ },
+ dark: {
+ mode: 'dark',
+ background: '#111',
+ text: '#DDDDDD',
+ primary: '#F79E00',
+ primaryDark: '#F4BB60',
+ primaryLight: '#F48600',
+ },
+};
+
+export default theme;
diff --git a/crabfit-frontend/tsconfig.json b/crabfit-frontend/tsconfig.json
new file mode 100644
index 0000000..425d574
--- /dev/null
+++ b/crabfit-frontend/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "baseUrl": "src"
+ },
+ "include": [
+ "src"
+ ]
+}
diff --git a/crabfit-frontend/yarn.lock b/crabfit-frontend/yarn.lock
index 4ab31ff..d1ce37f 100644
--- a/crabfit-frontend/yarn.lock
+++ b/crabfit-frontend/yarn.lock
@@ -1834,7 +1834,7 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@*":
+"@types/jest@*", "@types/jest@^26.0.20":
version "26.0.20"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.20.tgz#cd2f2702ecf69e86b586e1f5223a60e454056307"
integrity sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==
@@ -1857,7 +1857,7 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
-"@types/node@*":
+"@types/node@*", "@types/node@^14.14.31":
version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
@@ -1877,11 +1877,31 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd"
integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
+"@types/react-dom@^17.0.1":
+ version "17.0.1"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.1.tgz#d92d77d020bfb083e07cc8e0ac9f933599a4d56a"
+ integrity sha512-yIVyopxQb8IDZ7SOHeTovurFq+fXiPICa+GV3gp0Xedsl+MwQlMLKmvrnEjFbQxjliH5YVAEWFh975eVNmKj7Q==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^17.0.2":
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8"
+ integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^3.0.2"
+
"@types/resolve@0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
@@ -3951,6 +3971,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
+dayjs@^1.10.4:
+ version "1.10.4"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
+ integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
+
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -9106,6 +9131,11 @@ react-error-overlay@^6.0.9:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
+react-hook-form@^6.15.4:
+ version "6.15.4"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.15.4.tgz#328003e1ccc096cd158899ffe7e3b33735a9b024"
+ integrity sha512-K+Sw33DtTMengs8OdqFJI3glzNl1wBzSefD/ksQw/hJf9CnOHQAU6qy82eOrh0IRNt2G53sjr7qnnw1JDjvx1w==
+
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -10828,6 +10858,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c"
+ integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"