+
+
-
{title}
+ {title}
{authToken() && (
)}
diff --git a/ui/src/components/album/__generated__/albumPathQuery.ts b/ui/src/components/album/__generated__/albumPathQuery.ts
new file mode 100644
index 00000000..6d141f5d
--- /dev/null
+++ b/ui/src/components/album/__generated__/albumPathQuery.ts
@@ -0,0 +1,32 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: albumPathQuery
+// ====================================================
+
+export interface albumPathQuery_album_path {
+ __typename: 'Album'
+ id: string
+ title: string
+}
+
+export interface albumPathQuery_album {
+ __typename: 'Album'
+ id: string
+ path: albumPathQuery_album_path[]
+}
+
+export interface albumPathQuery {
+ /**
+ * Get album by id, user must own the album or be admin
+ * If valid tokenCredentials are provided, the album may be retrived without further authentication
+ */
+ album: albumPathQuery_album
+}
+
+export interface albumPathQueryVariables {
+ id: string
+}
diff --git a/ui/src/components/album/icons/direction-arrow.svg b/ui/src/components/album/icons/direction-arrow.svg
new file mode 100644
index 00000000..1860effe
--- /dev/null
+++ b/ui/src/components/album/icons/direction-arrow.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/ui/src/components/album/icons/gear.svg b/ui/src/components/album/icons/gear.svg
new file mode 100644
index 00000000..8e48f3f5
--- /dev/null
+++ b/ui/src/components/album/icons/gear.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/ui/src/components/album/icons/sorting.svg b/ui/src/components/album/icons/sorting.svg
new file mode 100644
index 00000000..e2ee5c80
--- /dev/null
+++ b/ui/src/components/album/icons/sorting.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/ui/src/components/layout/__generated__/adminQuery.ts b/ui/src/components/layout/__generated__/adminQuery.ts
new file mode 100644
index 00000000..2301f231
--- /dev/null
+++ b/ui/src/components/layout/__generated__/adminQuery.ts
@@ -0,0 +1,20 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: adminQuery
+// ====================================================
+
+export interface adminQuery_myUser {
+ __typename: 'User'
+ admin: boolean
+}
+
+export interface adminQuery {
+ /**
+ * Information about the currently logged in user
+ */
+ myUser: adminQuery_myUser
+}
diff --git a/ui/src/components/layout/__generated__/mapboxEnabledQuery.ts b/ui/src/components/layout/__generated__/mapboxEnabledQuery.ts
new file mode 100644
index 00000000..06007845
--- /dev/null
+++ b/ui/src/components/layout/__generated__/mapboxEnabledQuery.ts
@@ -0,0 +1,15 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: mapboxEnabledQuery
+// ====================================================
+
+export interface mapboxEnabledQuery {
+ /**
+ * Get the mapbox api token, returns null if mapbox is not enabled
+ */
+ mapboxToken: string | null
+}
diff --git a/ui/src/components/layout/menu-icons/albums.svg b/ui/src/components/layout/menu-icons/albums.svg
new file mode 100644
index 00000000..1e93f5e5
--- /dev/null
+++ b/ui/src/components/layout/menu-icons/albums.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/ui/src/components/layout/menu-icons/people.svg b/ui/src/components/layout/menu-icons/people.svg
new file mode 100644
index 00000000..2af9060c
--- /dev/null
+++ b/ui/src/components/layout/menu-icons/people.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/ui/src/components/layout/menu-icons/photos.svg b/ui/src/components/layout/menu-icons/photos.svg
new file mode 100644
index 00000000..5b801f13
--- /dev/null
+++ b/ui/src/components/layout/menu-icons/photos.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/ui/src/components/layout/menu-icons/places.svg b/ui/src/components/layout/menu-icons/places.svg
new file mode 100644
index 00000000..3032ea3e
--- /dev/null
+++ b/ui/src/components/layout/menu-icons/places.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/ui/src/components/layout/menu-icons/settings.svg b/ui/src/components/layout/menu-icons/settings.svg
new file mode 100644
index 00000000..709a765b
--- /dev/null
+++ b/ui/src/components/layout/menu-icons/settings.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/ui/src/hooks/useDelay.ts b/ui/src/hooks/useDelay.ts
new file mode 100644
index 00000000..5a45309b
--- /dev/null
+++ b/ui/src/hooks/useDelay.ts
@@ -0,0 +1,23 @@
+import { useLayoutEffect, useState, useRef } from 'react'
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function useDelay(wait: number, deps: any[] = []) {
+ const [_, triggerUpdate] = useState(false)
+ const done = useRef(false)
+
+ useLayoutEffect(() => {
+ const handle = setTimeout(() => {
+ done.current = true
+ triggerUpdate(x => !x)
+ }, wait)
+
+ return () => {
+ done.current = false
+ clearTimeout(handle)
+ }
+ }, deps)
+
+ return done.current
+}
+
+export default useDelay
diff --git a/ui/src/primitives/form/Checkbox.tsx b/ui/src/primitives/form/Checkbox.tsx
new file mode 100644
index 00000000..a2b12683
--- /dev/null
+++ b/ui/src/primitives/form/Checkbox.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import styled from 'styled-components'
+
+const CheckboxLabelWrapper = styled.label`
+ & input[type='checkbox'] {
+ appearance: none;
+ outline: none;
+ }
+
+ & input[type='checkbox'] + span {
+ cursor: pointer;
+ }
+
+ & input[type='checkbox'] + span::before {
+ content: '';
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 12px;
+ height: 12px;
+ background-color: #f9fafb;
+ border: 1px solid #aaa;
+ display: inline-block;
+ border-radius: 3px;
+ cursor: pointer;
+ margin-right: 8px;
+ margin-bottom: -1px;
+ }
+
+ & input[type='checkbox']:checked + span::before {
+ background-color: #0072ff;
+ border-color: #109dff;
+ background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='7px' height='4px' viewBox='0 0 7 4' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3EPath 5%3C/title%3E%3Cg id='Page-1' stroke='none' stroke-width='1.5' fill='none' fill-rule='evenodd'%3E%3Cpolyline id='Path-5' stroke='%23FFFFFF' points='1 1.93487039 2.77174339 3.70661378 6.47835718 2.27373675e-13'%3E%3C/polyline%3E%3C/g%3E%3C/svg%3E");
+ }
+
+ & input[type='checkbox']:focus + span::before {
+ outline: 2px solid #86c6ff;
+ border-color: #109dff;
+ }
+`
+
+type CheckboxProps = React.InputHTMLAttributes
& {
+ label: string
+}
+
+const Checkbox = ({ label, className, ...props }: CheckboxProps) => {
+ return (
+
+
+ {label}
+
+ )
+}
+
+export default Checkbox
diff --git a/ui/src/primitives/form/Dropdown.tsx b/ui/src/primitives/form/Dropdown.tsx
new file mode 100644
index 00000000..f69e22ae
--- /dev/null
+++ b/ui/src/primitives/form/Dropdown.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import styled from 'styled-components'
+
+const DropdownStyledSelect = styled.select`
+ appearance: none;
+
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9px' height='5px' viewBox='0 0 9 5'%3E%3Cpolygon fill='%23D8D8D8' points='0 0 8.36137659 0 4.1806883 4.1806883'%3E%3C/polygon%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: center right 10px;
+`
+
+type DropdownItem = {
+ value: string
+ label: string
+}
+
+type DropdownProps = React.SelectHTMLAttributes & {
+ items: DropdownItem[]
+ selected?: string
+ setSelected(label: string): void
+}
+
+const Dropdown = ({
+ items,
+ selected,
+ setSelected,
+ ...otherProps
+}: DropdownProps) => {
+ const onChange = (e: React.ChangeEvent) => {
+ setSelected(e.target.value)
+ otherProps.onChange && otherProps.onChange(e)
+ }
+
+ const options = items.map(({ value, label }) => (
+
+ ))
+
+ return (
+
+ {options}
+
+ )
+}
+
+export default Dropdown