diff --git a/app/package-lock.json b/app/package-lock.json
index af9d037..e5aac93 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -8,6 +8,8 @@
"name": "app",
"version": "0.0.0",
"dependencies": {
+ "@headlessui/react": "^1.7.18",
+ "@heroicons/react": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -826,6 +828,30 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@headlessui/react": {
+ "version": "1.7.18",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.18.tgz",
+ "integrity": "sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==",
+ "dependencies": {
+ "@tanstack/react-virtual": "^3.0.0-beta.60",
+ "client-only": "^0.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^16 || ^17 || ^18",
+ "react-dom": "^16 || ^17 || ^18"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz",
+ "integrity": "sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA==",
+ "peerDependencies": {
+ "react": ">= 16"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -1177,6 +1203,31 @@
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
}
},
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.1.tgz",
+ "integrity": "sha512-IFOFuRUTaiM/yibty9qQ9BfycQnYXIDHGP2+cU+0LrFFGNhVxCXSQnaY6wkX8uJVteFEBjUondX0Hmpp7TNcag==",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz",
+ "integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1704,6 +1755,11 @@
"node": ">= 6"
}
},
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
diff --git a/app/package.json b/app/package.json
index 29072f6..1c72cdc 100644
--- a/app/package.json
+++ b/app/package.json
@@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "@headlessui/react": "^1.7.18",
+ "@heroicons/react": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
diff --git a/app/src/App.css b/app/src/App.css
index b9d355d..450bf4d 100644
--- a/app/src/App.css
+++ b/app/src/App.css
@@ -1,4 +1,4 @@
-#root {
+/*#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
@@ -40,3 +40,4 @@
.read-the-docs {
color: #888;
}
+*/
\ No newline at end of file
diff --git a/app/src/App.jsx b/app/src/App.jsx
index 2be4772..fb4f232 100644
--- a/app/src/App.jsx
+++ b/app/src/App.jsx
@@ -1,10 +1,117 @@
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
+import Home from './pages/Home'
-function Signin() {
- const [state, setState] = useState({email: '', password: ''})
+function API(url, user) {
+ const call = (verb, route, callbacks, body) => {
+ const headers = {
+ method: verb
+ }
+ if(body) {
+ headers['body'] = JSON.stringify(body)
+ }
+ let response = fetch(url+route, headers)
+ if(callbacks && callbacks.success !== undefined) {
+ response = response.then(r => r.ok && callbacks.success())
+ }
+ else if(callbacks && callbacks.json !== undefined) {
+ response = response
+ .then(r => (r.ok && r.json()) || (Promise.reject(r.text())))
+ .then(callbacks.json)
+ }
+ if(callbacks && callbacks.error !== undefined) {
+ response = response.catch(err => {
+ console.log(err)
+ callbacks.error({error: err})
+ })
+ }
+ return response
+ }
+ //const callv2 = (verb, route, return_type, body) => {}
+ const get = (route, callbacks) => call('GET', route, callbacks)
+ const post = (route, body, callbacks) => call('POST', route, callbacks, body)
+ return {
+ get: get,
+ post: post,
+ auth: {
+ sendOTP: (user_email, onSuccess) => {
+ console.log('/login/otp?user_email='+user_email)
+ get('/login/otp?user_email='+user_email, {success: onSuccess})
+ },
+ getToken: (user_email, otp, onSuccess, onError) => {
+ post('/login', {otp: otp, user_email: user_email}, {
+ json: (token) => onSuccess(token),
+ error: (err) => onError(err)
+ })
+ },
+ authenticated: user !== undefined
+ },
+ userdata: {
+ datasources: {
+ read: (callback) => {
+ console.log(user)
+ get("/datasources?owner="+user.user_email, {
+ json: callback
+ })
+ },
+ write: (datasource) => post("/datasources", Object.assign({}, datasource, {owner: user.user_email}))
+ },
+ notebooks: {
+ read: (callback) => {
+ get("/notebooks?owner="+user.user_email, {
+ json: callback
+ })
+ },
+ write: (nb) => post("/notebooks", Object.assign({}, nb, {owner: user.user_email}))
+ }
+ },
+ sql: {}
+ }
+}
+
+/*function Login({show, onLogin, api}) {
+ const [state, setState] = useState({user_email: '', otp: '', stage: 'init'})
+ console.log(state)
+ if(!show) return
+ if(state.stage === 'init')
+ return (
+
+ setState(prev => Object.assign({}, prev, {user_email: email}))}
+ />
+
+
+ )
+ return (
+
+ setState(prev => Object.assign({}, prev, {otp: code}))}
+ />
+
+
+ )
+}*/
+
+
+
+function Signin({api, show, onLogin}) {
+ const [state, setState] = useState({user_email: '', otp: '', stage: 'init'})
+ console.log(state)
+ if(!show) return
+ if(state.stage === 'init')
return (
@@ -19,7 +126,6 @@ function Signin() {
+
+
+
+
+
+
+ )
+ return (
+
+
+
+
+ Sign in to your account
+
+
+
+
-
+
setState(prev => Object.assign({}, prev, {otp: e.target.value}))}
+
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
-
-
-
-
- Not a member?{' '}
-
- Start a 14 day free trial
-
-
+
+
)
}
+/*function Home({show}) {
+ if(!show) return
+ return (
+
+
Home
+
+ )
+}*/
+
function App() {
const [count, setCount] = useState(0)
-
+ const [state, setState] = useState({pageid: 'login'})
+ const api = API('https://sequel.gessfred.xyz/api', state.user)
+ const setStateProperty = property => setState(prev => Object.assign({}, prev, property))
+ useEffect(() => {
+ const cachedUser = localStorage.getItem("user")
+ if(cachedUser) {
+ setStateProperty({user: JSON.parse(cachedUser), pageid: 'main'})
+ }
+ }, [])
return (
<>
-
+ {
+ localStorage.setItem('user', JSON.stringify(user))
+ setState({user: user, pageid: 'main'})
+ }}
+ />
+
>
)
}
diff --git a/app/src/components/Dropdown.jsx b/app/src/components/Dropdown.jsx
new file mode 100644
index 0000000..1bb477c
--- /dev/null
+++ b/app/src/components/Dropdown.jsx
@@ -0,0 +1,79 @@
+import { Fragment, useState } from 'react'
+import { Listbox, Transition } from '@headlessui/react'
+import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
+
+
+function classNames(...classes) {
+ return classes.filter(Boolean).join(' ')
+}
+
+export default function Dropdown({name, items, selected, setSelected}) {
+
+ return (
+
+ {({ open }) => (
+ <>
+ {name}
+
+
+
+
+ {selected.name}
+
+
+
+
+
+
+
+
+ {items.map((person) => (
+
+ classNames(
+ active ? 'bg-indigo-600 text-white' : 'text-gray-900',
+ 'relative cursor-default select-none py-2 pl-3 pr-9'
+ )
+ }
+ value={person}
+ >
+ {({ selected, active }) => (
+ <>
+
+
+
+ {person.name}
+
+
+
+ {selected ? (
+
+
+
+ ) : null}
+ >
+ )}
+
+ ))}
+
+
+
+ >
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/app/src/index.css b/app/src/index.css
index e7d4bb2..24be1a2 100644
--- a/app/src/index.css
+++ b/app/src/index.css
@@ -2,7 +2,7 @@
@tailwind components;
@tailwind utilities;
-:root {
+/*:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@@ -70,3 +70,4 @@ button:focus-visible {
background-color: #f9f9f9;
}
}
+*/
\ No newline at end of file
diff --git a/app/src/pages/ConnectionEditor.jsx b/app/src/pages/ConnectionEditor.jsx
new file mode 100644
index 0000000..9cd2281
--- /dev/null
+++ b/app/src/pages/ConnectionEditor.jsx
@@ -0,0 +1,139 @@
+import { Fragment, useRef, useState } from 'react'
+import { Dialog, Transition } from '@headlessui/react'
+import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
+import Dropdown from '../components/Dropdown'
+//import uniqid from 'uniqid'
+
+const engines = [
+ {
+ id: 1,
+ name: 'PostgresSQL',
+ avatar:
+ 'https://wiki.postgresql.org/images/3/30/PostgreSQL_logo.3colors.120x120.png',
+ },
+ {
+ id: 2,
+ name: 'Arlene Mccoy',
+ avatar:
+ 'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
+ },
+ {
+ id: 3,
+ name: 'Devon Webb',
+ avatar:
+ 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80',
+ },
+ {
+ id: 4,
+ name: 'Tom Cook',
+ avatar:
+ 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
+ },
+ {
+ id: 5,
+ name: 'Tanya Fox',
+ avatar:
+ 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
+ }
+]
+
+
+export function Input({name, value, setValue}) {
+ const hint = false
+ return (
+
+
+
+ {hint &&
+ $
+
}
+
setValue(e.target.value)}
+ />
+
+
+ )
+}
+
+export default function ConnectionEditor({api, show, onHide}) {
+ const [open, setOpen] = useState(true)
+ const [connection, setConnection] = useState({engine: engines[0]})
+ const cancelButtonRef = useRef(null)
+ const setConnectionProperty = property => value => setConnection(prev => Object.assign({}, prev, {[property]: value}))
+
+ return (
+
+
+
+ )
+}
diff --git a/app/src/pages/Home.jsx b/app/src/pages/Home.jsx
new file mode 100644
index 0000000..67af2ab
--- /dev/null
+++ b/app/src/pages/Home.jsx
@@ -0,0 +1,204 @@
+import { Fragment, useState } from 'react'
+import { Disclosure, Menu, Transition } from '@headlessui/react'
+import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline'
+import ConnectionEditor from './ConnectionEditor'
+
+const user = {
+ name: 'Tom Cook',
+ email: 'tom@example.com',
+ imageUrl:
+ 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
+ }
+ const navigation = [
+ { name: 'Dashboard', href: '#', current: true },
+ { name: 'Team', href: '#', current: false },
+ { name: 'Projects', href: '#', current: false },
+ { name: 'Calendar', href: '#', current: false },
+ { name: 'Reports', href: '#', current: false },
+ ]
+ const userNavigation = [
+ { name: 'Your Profile', href: '#' },
+ { name: 'Settings', href: '#' },
+ { name: 'Sign out', href: '#' },
+ ]
+
+ function classNames(...classes) {
+ return classes.filter(Boolean).join(' ')
+ }
+
+ export default function Home({show}) {
+ const [state, setState] = useState({showConnectionEditor: false})
+ if(!show) return
+ return (
+ <>
+ {/*
+ This example requires updating your template:
+
+ ```
+
+
+ ```
+ */}
+
+
+ {({ open }) => (
+ <>
+
+
+
+
+
+
+
+ {/* Profile dropdown */}
+
+
+
+
+ {/* Mobile menu button */}
+
+
+ Open main menu
+ {open ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {navigation.map((item) => (
+
+ {item.name}
+
+ ))}
+
+
+
+
+
+
+
+
{user.name}
+
{user.email}
+
+
+
+
+ {userNavigation.map((item) => (
+
+ {item.name}
+
+ ))}
+
+
+
+ >
+ )}
+
+
+
+
+
+
+ {}} />
+
+
+
+ >
+ )
+ }
\ No newline at end of file
diff --git a/web/src/App.js b/web/src/App.js
index d78f3b4..00d1e67 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -233,7 +233,7 @@ function Login({show, onLogin, api}) {
)
}
-function App2() {
+function App() {
const [state, setState] = useState({pageid: 'login'})
const api = API(process.env.REACT_APP_API_URL, state.user)
const setStateProperty = property => setState(prev => Object.assign({}, prev, property))
@@ -282,47 +282,5 @@ function App2() {
)
}
-function App() {
- return (
-
-
-
-
Sign in to your account
-
-
-
-
- )
-}
export default App