commit ac03ca3250aa3ccb69a816db7b67c677699dbd8a Author: Michael Schramm Date: Fri May 22 21:18:48 2020 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..101b2ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# development environments +/.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..3aff5a1 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/zeit/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/assets/global.scss b/assets/global.scss new file mode 100644 index 0000000..5bbb37b --- /dev/null +++ b/assets/global.scss @@ -0,0 +1,24 @@ +@import "variables"; + +:root { + --backgroundColor: #{$background-color}; + --primaryColor: #{$primary-color}; + --textColorSecondary: #{$text-color-secondary}; + + --amplify-primary-color: #{$primary-color}; + --amplify-primary-tint: #{lighten($primary-color, 0.1)}; + --amplify-primary-shade: #{$primary-color}; +} + +.sidebar-toggle { + font-size: 18px; + line-height: 64px; + padding: 0 24px; + cursor: pointer; + transition: color 0.3s; + color: #FFF; + + &:hover { + color: #1890ff; + } +} diff --git a/assets/images/logo_white.png b/assets/images/logo_white.png new file mode 100644 index 0000000..cc9bb06 Binary files /dev/null and b/assets/images/logo_white.png differ diff --git a/assets/images/logo_white_small.png b/assets/images/logo_white_small.png new file mode 100644 index 0000000..99c79d8 Binary files /dev/null and b/assets/images/logo_white_small.png differ diff --git a/assets/variables.scss b/assets/variables.scss new file mode 100644 index 0000000..0535181 --- /dev/null +++ b/assets/variables.scss @@ -0,0 +1,3 @@ +$background-color: #f7f7f7; +$primary-color: #4182e4; +$text-color-secondary: rgba(0, 0, 0, 0.45); diff --git a/components/date.time.tsx b/components/date.time.tsx new file mode 100644 index 0000000..f2117df --- /dev/null +++ b/components/date.time.tsx @@ -0,0 +1,20 @@ +import dayjs from 'dayjs' +import React from 'react' + +interface Props { + date: string + + hideTime?: boolean +} + +export const DateTime: React.FC = props => { + const format = props.hideTime ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm' + + return ( +
+ {dayjs(props.date).format(format)} +
+ ) +} diff --git a/components/sidemenu.tsx b/components/sidemenu.tsx new file mode 100644 index 0000000..39588e0 --- /dev/null +++ b/components/sidemenu.tsx @@ -0,0 +1,40 @@ +import {HomeOutlined, MessageOutlined, TeamOutlined} from '@ant-design/icons' +import React from 'react' + +export interface SideMenuElement { + items?: SideMenuElement[] + + key: string + name: string + group?: boolean + href?: string + icon?: any +} + +export const sideMenu: SideMenuElement[] = [ + { + key: 'home', + name: 'Home', + href: '/', + icon: , + }, + { + key: 'communication', + name: 'Communication', + group: true, + items: [ + { + key: 'users', + name: 'Users', + href: '/users', + icon: , + }, + { + key: 'chats', + name: 'Chats', + href: '/chats', + icon: , + }, + ], + }, +] diff --git a/components/structure.tsx b/components/structure.tsx new file mode 100644 index 0000000..847144f --- /dev/null +++ b/components/structure.tsx @@ -0,0 +1,270 @@ +import {CaretDownOutlined, UserOutlined} from '@ant-design/icons' +import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons/lib' +import {Dropdown, Layout, Menu, PageHeader, Spin, Tag} from 'antd' +import getConfig from 'next/config' +import Link from 'next/link' +import {useRouter} from 'next/router' +import React, {FunctionComponent} from 'react' +import {sideMenu, SideMenuElement} from './sidemenu' +import {useWindowSize} from './use.window.size' + +const { publicRuntimeConfig } = getConfig() + +const { SubMenu, ItemGroup } = Menu +const { Header, Content, Sider } = Layout + +interface BreadcrumbEntry { + name: string + href?: string + as?: string +} + +interface Props { + loading?: boolean + padded?: boolean + style?: any + + selected?: string + + + breadcrumbs?: BreadcrumbEntry[] + title?: string + subTitle?: string + extra?: any[] +} + +const Structure: FunctionComponent = (props) => { + const size = useWindowSize() + const [userMenu, setUserMenu] = React.useState(false) + const [open, setOpen] = React.useState() + const [selected, setSelected] = React.useState() + const [sidebar, setSidebar] = React.useState(size.width < 700) + const router = useRouter() + + React.useEffect(() => { + if (sidebar !== size.width < 700) { + setSidebar(size.width < 700) + } + }, [size.width]) + + React.useEffect(() => { + if (props.selected) { + const parts = props.selected.split('.') + + const last = parts.pop() + + if (parts.length > 0) { + setOpen(parts) + } + + setSelected([last]) + } + }, [props.selected]) + + const buildMenu = (data: SideMenuElement[]): JSX.Element[] => { + return data.map((element): JSX.Element => { + if (element.items && element.items.length > 0) { + if (element.group) { + return ( + + {element.icon} + {element.name} + + )} + > + {buildMenu(element.items)} + + ) + } + + return ( + + {element.icon} + {element.name} + + } + > + {buildMenu(element.items)} + + ) + } + + return ( + { + if (element.href) { + router.push(element.href) + } + }} + key={element.key} + > + {element.icon} + {element.name} + + ) + }) + } + + const signOut = async (): Promise => { + // TODO sign out + } + + return ( + +
+
+ {React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, { + className: 'sidebar-toggle', + onClick: () => setSidebar(!sidebar), + })} + + {'RecTag'} + + {publicRuntimeConfig.area.toUpperCase()} +
+
+ + console.log('profile??')}>Profile + + Logout + + )} + onVisibleChange={setUserMenu} + visible={userMenu} + > + + + + + +
+
+ + + setSelected(s.keyPath)} + openKeys={open} + onOpenChange={(open): void => setOpen(open)} + > + {buildMenu(sideMenu)} + + + + Version: {publicRuntimeConfig.version} + + + + + {props.title && ( + ({ + breadcrumbName: b.name, + path: '' + })), + { + breadcrumbName: props.title, + path: '' + } + ], + params: props.breadcrumbs, + itemRender: (route, params: BreadcrumbEntry[], routes, paths) => { + if (routes.indexOf(route) === routes.length - 1) { + return {route.breadcrumbName} + } + + const entry = params[routes.indexOf(route)] + + return ( + + + {entry.name} + + + ) + }}} + /> + )} + + + + {props.children} + + + + +
+ ) +} + +Structure.defaultProps = { + padded: true, + style: {}, +} + +export default Structure diff --git a/components/time.ago.tsx b/components/time.ago.tsx new file mode 100644 index 0000000..ffbddfb --- /dev/null +++ b/components/time.ago.tsx @@ -0,0 +1,23 @@ +import {Tooltip} from 'antd' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import React from 'react' + +dayjs.extend(relativeTime) + +interface Props { + date: string +} + +export const TimeAgo: React.FC = props => { + const date = dayjs(props.date) + return ( + +
+ {date.fromNow()} +
+
+ ) +} diff --git a/components/use.window.size.ts b/components/use.window.size.ts new file mode 100644 index 0000000..258bd0b --- /dev/null +++ b/components/use.window.size.ts @@ -0,0 +1,29 @@ +import {useEffect, useState} from 'react' + +export const useWindowSize = () => { + const isClient = typeof window === 'object'; + + function getSize() { + return { + width: isClient ? window.innerWidth : undefined, + height: isClient ? window.innerHeight : undefined + }; + } + + const [windowSize, setWindowSize] = useState(getSize); + + useEffect(() => { + if (!isClient) { + return; + } + + function handleResize() { + setWindowSize(getSize()); + } + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); // Empty array ensures that effect is only run on mount and unmount + + return windowSize; +} diff --git a/components/with.auth.tsx b/components/with.auth.tsx new file mode 100644 index 0000000..b389f6e --- /dev/null +++ b/components/with.auth.tsx @@ -0,0 +1,59 @@ +import {Spin} from 'antd' +import {AxiosRequestConfig} from 'axios' +import getConfig from 'next/config' +import React from 'react' + +const { publicRuntimeConfig } = getConfig() + +export const authConfig = async (config: AxiosRequestConfig = {}): Promise => { + if (!config.headers) { + config.headers = {} + } + + try { + // TODO config.headers.Authorization = `Bearer ${session.getAccessToken().getJwtToken()}` + } catch (e) { + return config + } + + return config +} + +export const withAuth = (Component): React.FC => { + return props => { + const [signedIn, setSignedIn] = React.useState(false); + const [loading, setLoading] = React.useState(true); + + + React.useEffect(() => { + (async () => { + try { + } catch (err) { + console.error(err); + } + + setLoading(false) + })(); + }, []); + + if (loading) { + return ( +
+ +
+ ) + } + + if (!signedIn) { + // TODO + } + + return + }; +} diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..6240b81 --- /dev/null +++ b/next.config.js @@ -0,0 +1,11 @@ +const withImages = require('next-images') +const module = require('./package.json') + +const version = module.version; + +module.exports = withImages({ + publicRuntimeConfig: { + endpoint: process.env.API_HOST || '/graphql', + version, + } +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..7509a69 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "ohmyform-react", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "dev": "next dev", + "build": "next build", + "export": "next build && next export", + "start": "next start" + }, + "dependencies": { + "@ant-design/icons": "^4.1.0", + "antd": "^4.2.2", + "axios": "^0.19.2", + "dayjs": "^1.8.27", + "next": "9.4.0", + "next-images": "^1.4.0", + "next-redux-wrapper": "^6.0.0", + "react": "16.13.1", + "react-dom": "16.13.1", + "react-icons": "^3.10.0", + "react-redux": "^7.2.0", + "redux": "^4.0.5", + "redux-devtools-extension": "^2.13.8", + "redux-thunk": "^2.3.0", + "sass": "^1.26.5" + }, + "devDependencies": { + "@types/node": "^14.0.1", + "@types/react": "^16.9.35", + "typescript": "^3.9.2" + } +} diff --git a/pages/_app.tsx b/pages/_app.tsx new file mode 100644 index 0000000..40bf010 --- /dev/null +++ b/pages/_app.tsx @@ -0,0 +1,25 @@ +import 'antd/dist/antd.css' +import 'assets/global.scss' +import 'assets/variables.scss' +import {AppProps} from 'next/app' +import getConfig from 'next/config' +import Head from 'next/head' +import React from 'react' +import {wrapper} from 'store' + +const { publicRuntimeConfig } = getConfig() + +const App: React.FC = ({ Component, pageProps }) => { + return ( + <> + + OhMyForm + + + + + ) +} + +export default wrapper.withRedux(App) +// export default App diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/pages/index.tsx b/pages/index.tsx new file mode 100644 index 0000000..cf4dde6 --- /dev/null +++ b/pages/index.tsx @@ -0,0 +1,18 @@ +import {Alert} from 'antd' +import {withAuth} from 'components/with.auth' +import {NextPage} from 'next' +import React from 'react' +import Structure from '../components/structure' + +const Index: NextPage = () => { + return ( + + + + ) +} + +export default withAuth(Index) diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3660ce9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/store/index.ts b/store/index.ts new file mode 100644 index 0000000..5dea0d4 --- /dev/null +++ b/store/index.ts @@ -0,0 +1,37 @@ +import {createWrapper, HYDRATE, MakeStore} from 'next-redux-wrapper' +import {AnyAction, applyMiddleware, combineReducers, createStore} from 'redux' +import {composeWithDevTools} from 'redux-devtools-extension' +import thunkMiddleware from 'redux-thunk' + +export interface State { +} + +const state = {} + +const root = (state: State, action: AnyAction) => { + switch (action.type) { + case HYDRATE: + return {...state, ...action.payload}; + default: + return state; + } +}; + +const makeStore: MakeStore = (context) => { + return createStore( + (state, action): State => { + const simple = combineReducers({ + // TODO add child reducers + }) + + return root(simple, action) + }, + {}, + composeWithDevTools(applyMiddleware( + thunkMiddleware, + )) + ) +} + +export const wrapper = createWrapper(makeStore, {debug: true}); + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..712bf7a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "exclude": [ + "node_modules" + ], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ] +}