+
{t('login_page.welcome', 'Welcome to Photoview')}
@@ -71,7 +71,7 @@ const LoginForm = () => {
return (
)
}
diff --git a/ui/src/Layout.test.js b/ui/src/components/layout/Layout.test.js
similarity index 100%
rename from ui/src/Layout.test.js
rename to ui/src/components/layout/Layout.test.js
diff --git a/ui/src/components/layout/Layout.tsx b/ui/src/components/layout/Layout.tsx
new file mode 100644
index 00000000..13c1d8a1
--- /dev/null
+++ b/ui/src/components/layout/Layout.tsx
@@ -0,0 +1,230 @@
+import React, { ReactChild } from 'react'
+import PropTypes from 'prop-types'
+import styled from 'styled-components'
+import { NavLink } from 'react-router-dom'
+import { Icon } from 'semantic-ui-react'
+import { Sidebar, SidebarProvider } from '../sidebar/Sidebar'
+import { useQuery, gql } from '@apollo/client'
+import { Authorized } from '../routes/AuthorizedRoute'
+import { Helmet } from 'react-helmet'
+import Header from '../header/Header'
+import { authToken } from '../../helpers/authentication'
+import { useTranslation } from 'react-i18next'
+
+export const ADMIN_QUERY = gql`
+ query adminQuery {
+ myUser {
+ admin
+ }
+ }
+`
+
+export const MAPBOX_QUERY = gql`
+ query mapboxEnabledQuery {
+ mapboxToken
+ }
+`
+
+const SideMenuContainer = styled.div`
+ height: 100%;
+ width: 80px;
+ left: 0;
+ padding-top: 70px;
+
+ @media (max-width: 1000px) {
+ width: 100%;
+ height: 80px;
+ position: fixed;
+ background: white;
+ z-index: 10;
+ padding-top: 0;
+ display: flex;
+ bottom: 0;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
+ }
+`
+
+const Content = styled.div`
+ margin-top: 70px;
+ padding: 10px 12px 0;
+ width: 100%;
+ overflow-y: scroll;
+`
+
+const SideButtonLink = styled(NavLink)`
+ text-align: center;
+ padding-top: 8px;
+ padding-left: 2px;
+ display: block;
+ width: 60px;
+ height: 60px;
+ margin: 10px;
+ margin-bottom: 24px;
+
+ font-size: 28px;
+
+ color: #888;
+
+ transition: transform 200ms, box-shadow 200ms;
+
+ :hover {
+ transform: scale(1.02);
+ }
+`
+
+type SideButtonProps = {
+ to: string
+ exact: boolean
+ label: string
+ gradient: string
+ icon?: React.ReactChild
+}
+
+const SideButton = ({ to, exact, label, gradient, icon }: SideButtonProps) => {
+ return (
+
+
+ {label}
+ {icon}
+
+
+ )
+}
+
+export const SideMenu = () => {
+ const { t } = useTranslation()
+
+ const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null
+
+ const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken
+
+ return (
+
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+ {mapboxEnabled ? (
+
+
+
+
+ }
+ />
+ ) : null}
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+ )
+}
+
+type LayoutProps = {
+ children: React.ReactNode
+ title: string
+}
+
+const Layout = ({ children, title, ...otherProps }: LayoutProps) => {
+ return (
+
+
+ {title ? `${title} - Photoview` : `Photoview`}
+
+
+
+ )
+}
+
+Layout.propTypes = {
+ children: PropTypes.any.isRequired,
+ title: PropTypes.string,
+}
+
+export default Layout
diff --git a/ui/src/components/layout/icons/mountain.svg b/ui/src/components/layout/icons/mountain.svg
new file mode 100644
index 00000000..239dbe14
--- /dev/null
+++ b/ui/src/components/layout/icons/mountain.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/ui/src/components/routes/AuthorizedRoute.tsx b/ui/src/components/routes/AuthorizedRoute.tsx
index 6c0c6a41..854517d9 100644
--- a/ui/src/components/routes/AuthorizedRoute.tsx
+++ b/ui/src/components/routes/AuthorizedRoute.tsx
@@ -3,7 +3,7 @@ import PropTypes, { ReactComponentLike } from 'prop-types'
import { Route, Redirect, RouteProps } from 'react-router-dom'
import { useLazyQuery } from '@apollo/client'
import { authToken } from '../../helpers/authentication'
-import { ADMIN_QUERY } from '../../Layout'
+import { ADMIN_QUERY } from '../layout/Layout'
export const useIsAdmin = (enabled = true) => {
const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY)
diff --git a/ui/src/components/routes/Routes.tsx b/ui/src/components/routes/Routes.tsx
index 6bf93eb8..f5d1fe8f 100644
--- a/ui/src/components/routes/Routes.tsx
+++ b/ui/src/components/routes/Routes.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import { Loader } from 'semantic-ui-react'
-import Layout from '../../Layout'
+import Layout from '../layout/Layout'
import { clearTokenCookie } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next'
diff --git a/ui/src/components/sidebar/Sidebar.tsx b/ui/src/components/sidebar/Sidebar.tsx
index 10b1114b..34b33739 100644
--- a/ui/src/components/sidebar/Sidebar.tsx
+++ b/ui/src/components/sidebar/Sidebar.tsx
@@ -1,5 +1,4 @@
-import React, { createContext, useState } from 'react'
-import PropTypes from 'prop-types'
+import React, { createContext, useContext, useState } from 'react'
import styled from 'styled-components'
import { Icon } from 'semantic-ui-react'
@@ -56,11 +55,11 @@ export const SidebarContext = createContext
({
})
SidebarContext.displayName = 'SidebarContext'
-type SidebarProps = {
- children: React.ReactElement
+type SidebarProviderProps = {
+ children: React.ReactChild | React.ReactChild[]
}
-const Sidebar = ({ children }: SidebarProps) => {
+export const SidebarProvider = ({ children }: SidebarProviderProps) => {
const [state, setState] = useState<{ content: React.ReactNode | null }>({
content: null,
})
@@ -74,26 +73,23 @@ const Sidebar = ({ children }: SidebarProps) => {
value={{ updateSidebar: update, content: state.content }}
>
{children}
-
- {value => (
-
- {value.content}
- setState({ content: null })}
- />
-
-
- )}
-
)
}
-Sidebar.propTypes = {
- children: PropTypes.element,
-}
+export const Sidebar = () => {
+ const { updateSidebar, content } = useContext(SidebarContext)
-export default Sidebar
+ return (
+
+ {content}
+ updateSidebar(null)}
+ />
+
+
+ )
+}
diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js
index 36a447de..b39dd5cd 100644
--- a/ui/tailwind.config.js
+++ b/ui/tailwind.config.js
@@ -3,7 +3,11 @@ module.exports = {
purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
- extend: {},
+ extend: {
+ boxShadow: {
+ separator: '0 0 4px 0 rgba(0, 0, 0, 0.1)',
+ },
+ },
},
variants: {
extend: {},