feat: edit user
This commit is contained in:
parent
59cc371d50
commit
5639b01998
@ -21,3 +21,8 @@ func InitializeModule(newSession Session) *Session {
|
|||||||
}
|
}
|
||||||
return sessionInstance
|
return sessionInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Session) UpdateCurrentUser(updatedUser User) User {
|
||||||
|
s.User = User(updatedUser)
|
||||||
|
return s.User
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package session
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id string
|
Id string
|
||||||
|
LocalId string
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
AvatarPath string
|
AvatarPath string
|
||||||
AuthToken string
|
AuthToken string
|
||||||
|
Email string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,6 @@
|
|||||||
"@next/next/no-img-element": "off",
|
"@next/next/no-img-element": "off",
|
||||||
"quotes": ["warn", "single"],
|
"quotes": ["warn", "single"],
|
||||||
"semi": ["warn", "never"]
|
"semi": ["warn", "never"]
|
||||||
}
|
},
|
||||||
|
"ignorePatterns": ["wailsjs/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import { ChevronDownIcon, FolderArrowDownIcon, FolderOpenIcon, FolderPlusIcon } from '@heroicons/react/20/solid'
|
import { ChevronDownIcon, FolderArrowDownIcon, FolderOpenIcon, FolderPlusIcon } from '@heroicons/react/20/solid'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
|
import { useNavigation } from '../../context/Navigation/provider'
|
||||||
|
import { mainPages } from '../../context/Navigation/types'
|
||||||
import { useProject } from '../../context/Project/provider'
|
import { useProject } from '../../context/Project/provider'
|
||||||
import NewProjectModal from './NewProjectModal'
|
import NewProjectModal from './NewProjectModal'
|
||||||
|
|
||||||
@ -11,6 +13,7 @@ const MainProject = () => {
|
|||||||
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false)
|
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false)
|
||||||
const [canPopoverBeOpen, setCanPopoverBeOpen] = useState(true)
|
const [canPopoverBeOpen, setCanPopoverBeOpen] = useState(true)
|
||||||
const { createNewProject } = useProject()
|
const { createNewProject } = useProject()
|
||||||
|
const { setSelectedMainPage } = useNavigation()
|
||||||
|
|
||||||
const buttonOptions = [
|
const buttonOptions = [
|
||||||
{
|
{
|
||||||
@ -44,6 +47,7 @@ const MainProject = () => {
|
|||||||
setIsNewProjectModalOpen(false)
|
setIsNewProjectModalOpen(false)
|
||||||
setCanPopoverBeOpen(true)
|
setCanPopoverBeOpen(true)
|
||||||
createNewProject(projectName)
|
createNewProject(projectName)
|
||||||
|
setSelectedMainPage(mainPages.WORKSPACE)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <main className=" text-gray-100 h-screen overflow-y-scroll">
|
return <main className=" text-gray-100 h-screen overflow-y-scroll">
|
||||||
|
|||||||
145
frontend/components/settings/User.tsx
Normal file
145
frontend/components/settings/User.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import Search from '../utils/Search'
|
||||||
|
import { useProject } from '../../context/Project/provider'
|
||||||
|
import { EnvelopeIcon } from '@heroicons/react/24/outline'
|
||||||
|
import UserAvatar from '../utils/UserAvatar'
|
||||||
|
import { useRef, useState } from 'react'
|
||||||
|
|
||||||
|
const User = () => {
|
||||||
|
const { currentSession, requestUpdateCurrentUser, requestChooseUserAvatar } = useProject()
|
||||||
|
|
||||||
|
const firstNameRef = useRef<HTMLInputElement>(null)
|
||||||
|
const lastNameRef = useRef<HTMLInputElement>(null)
|
||||||
|
const emailRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [avatarPath, setAvatarPath] = useState(currentSession?.user?.avatarPath || '')
|
||||||
|
|
||||||
|
const onSaveButtonClickHandler = () => {
|
||||||
|
requestUpdateCurrentUser({
|
||||||
|
localId: currentSession?.user.localId,
|
||||||
|
firstName: firstNameRef?.current?.value,
|
||||||
|
lastName: lastNameRef?.current?.value,
|
||||||
|
email: emailRef?.current?.value,
|
||||||
|
avatarPath: avatarPath || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAvatarSelectButtonClickHandler = async () => {
|
||||||
|
const chosenAvatarPath = await requestChooseUserAvatar()
|
||||||
|
setAvatarPath(chosenAvatarPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAvatarRemoveButtonClickHandler = () => {
|
||||||
|
setAvatarPath('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return <main className="flex-1 bg-gray-50 min-h-screen">
|
||||||
|
<Search />
|
||||||
|
<div className="relative mx-auto max-w-4xl md:px-8 xl:px-0">
|
||||||
|
<div className="pt-10 pb-16">
|
||||||
|
<div className="px-4 sm:px-6 md:px-0">
|
||||||
|
<div className="py-6">
|
||||||
|
|
||||||
|
<div className="mt-10 divide-y divide-gray-200">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="text-lg font-medium leading-6 text-gray-900">Profile</h3>
|
||||||
|
<p className="max-w-2xl text-sm text-gray-500">
|
||||||
|
This information will be stored in a database if connected to a hosted account, so be careful what you share.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<dl className="divide-y divide-gray-200">
|
||||||
|
|
||||||
|
{/* ----- Name ----- */}
|
||||||
|
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||||
|
<dt className="text-sm font-medium text-gray-500">Name</dt>
|
||||||
|
<dd className="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0 items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="first-name"
|
||||||
|
id="first-name"
|
||||||
|
autoComplete="given-name"
|
||||||
|
placeholder='First Name'
|
||||||
|
defaultValue={currentSession?.user?.firstName}
|
||||||
|
ref={firstNameRef}
|
||||||
|
className="mr-2 mt-1 block w-full rounded-md border border-gray-300 py-2 px-3 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-sky-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="last-name"
|
||||||
|
id="last-name"
|
||||||
|
autoComplete="given-name"
|
||||||
|
placeholder='Last Name'
|
||||||
|
defaultValue={currentSession?.user?.lastName}
|
||||||
|
ref={lastNameRef}
|
||||||
|
className="mt-1 block w-full rounded-md border border-gray-300 py-2 px-3 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-sky-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{/* ----- Avatar ----- */}
|
||||||
|
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:pt-5">
|
||||||
|
<dt className="text-sm font-medium text-gray-500">Avatar</dt>
|
||||||
|
<dd className="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||||
|
<span className="flex-grow">
|
||||||
|
<UserAvatar overrideImagePath={avatarPath} />
|
||||||
|
</span>
|
||||||
|
<span className="ml-4 flex flex-shrink-0 items-start space-x-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||||
|
onClick={() => onAvatarSelectButtonClickHandler()}
|
||||||
|
>
|
||||||
|
Select Image
|
||||||
|
</button>
|
||||||
|
<span className="text-gray-300" aria-hidden="true">
|
||||||
|
|
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||||
|
onClick={() => onAvatarRemoveButtonClickHandler()}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{/* ----- Email ----- */}
|
||||||
|
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:pt-5">
|
||||||
|
<dt className="text-sm font-medium text-gray-500">Email</dt>
|
||||||
|
<dd className="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0 items-center relative">
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
|
<EnvelopeIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
className="block w-full rounded-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
defaultValue={currentSession?.user?.email}
|
||||||
|
ref={emailRef}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-5">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
|
onClick={() => onSaveButtonClickHandler()}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default User
|
||||||
102
frontend/components/utils/Search.tsx
Normal file
102
frontend/components/utils/Search.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { Menu, Transition } from '@headlessui/react'
|
||||||
|
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
||||||
|
import { BellIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
import { useNavigation } from '../../context/Navigation/provider'
|
||||||
|
import { mainPages } from '../../context/Navigation/types'
|
||||||
|
import UserAvatar from './UserAvatar'
|
||||||
|
|
||||||
|
function classNames(...classes: any[]) {
|
||||||
|
return classes.filter(Boolean).join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
const Search = () => {
|
||||||
|
const { setSelectedMainPage } = useNavigation()
|
||||||
|
|
||||||
|
const userNavigation = [
|
||||||
|
{
|
||||||
|
name: 'Your Profile',
|
||||||
|
onClick: () => { setSelectedMainPage(mainPages.EDITUSER) }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Document Workspace',
|
||||||
|
onClick: () => { setSelectedMainPage(mainPages.WORKSPACE) }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sign Out',
|
||||||
|
onClick: () => { setSelectedMainPage(mainPages.SELECTPROJECT) }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return <div className="top-0 z-10 flex h-16 flex-shrink-0 bg-white">
|
||||||
|
<div className="flex flex-1 justify-between px-4">
|
||||||
|
<div className="flex flex-1">
|
||||||
|
<form className="flex w-full md:ml-0" action="#" method="GET">
|
||||||
|
<label htmlFor="search-field" className="sr-only">
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div className="relative w-full text-gray-400 focus-within:text-gray-600">
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center">
|
||||||
|
<MagnifyingGlassIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="search-field"
|
||||||
|
className="block h-full w-full border-transparent py-2 pl-8 pr-3 text-gray-900 placeholder-gray-500 focus:border-transparent focus:placeholder-gray-400 focus:outline-none focus:ring-0 sm:text-sm"
|
||||||
|
placeholder="Search"
|
||||||
|
type="search"
|
||||||
|
name="search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex items-center md:ml-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<span className="sr-only">View notifications</span>
|
||||||
|
<BellIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Profile dropdown */}
|
||||||
|
<Menu as="div" className="relative ml-3">
|
||||||
|
<div>
|
||||||
|
<Menu.Button className="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||||
|
<span className="sr-only">Open user menu</span>
|
||||||
|
<UserAvatar />
|
||||||
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
|
{userNavigation.map((item) => (
|
||||||
|
<Menu.Item key={item.name}>
|
||||||
|
{({ active }) => (
|
||||||
|
<a
|
||||||
|
className={classNames(
|
||||||
|
active ? 'bg-gray-100' : '',
|
||||||
|
'block px-4 py-2 text-sm text-gray-700'
|
||||||
|
)}
|
||||||
|
onClick={item.onClick}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Search
|
||||||
29
frontend/components/utils/UserAvatar.tsx
Normal file
29
frontend/components/utils/UserAvatar.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useProject } from '../../context/Project/provider'
|
||||||
|
|
||||||
|
type Props = { overrideImagePath?: string }
|
||||||
|
|
||||||
|
const UserAvatar = (props?: Props) => {
|
||||||
|
const { currentSession } = useProject()
|
||||||
|
|
||||||
|
const avatarPath = props?.overrideImagePath ?? currentSession?.user?.avatarPath
|
||||||
|
|
||||||
|
if (avatarPath) return <img
|
||||||
|
className="h-8 w-8 rounded-full"
|
||||||
|
src={avatarPath}
|
||||||
|
alt="user avatar"
|
||||||
|
/>
|
||||||
|
else if (currentSession?.user?.firstName || currentSession?.user?.lastName) return (
|
||||||
|
<span className="inline-flex h-8 w-8 items-center justify-center rounded-full bg-gray-500">
|
||||||
|
<span className="text-sm font-medium leading-none text-white">
|
||||||
|
{`${currentSession?.user?.firstName[0]}${currentSession?.user?.lastName[0]}`}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
else return <span className="inline-block h-8 w-8 overflow-hidden rounded-full bg-gray-100">
|
||||||
|
<svg className="h-full w-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserAvatar
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import { Menu, Transition } from '@headlessui/react'
|
|
||||||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { BellIcon } from '@heroicons/react/24/outline'
|
|
||||||
import { Fragment } from 'react'
|
|
||||||
|
|
||||||
const userNavigation = [
|
|
||||||
{ name: 'Your Profile' },
|
|
||||||
{ name: 'Settings' },
|
|
||||||
{ name: 'Sign out' },
|
|
||||||
]
|
|
||||||
|
|
||||||
function classNames(...classes: any[]) {
|
|
||||||
return classes.filter(Boolean).join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
const Search = () => <div className="top-0 z-10 flex h-16 flex-shrink-0 bg-white">
|
|
||||||
<div className="flex flex-1 justify-between px-4">
|
|
||||||
<div className="flex flex-1">
|
|
||||||
<form className="flex w-full md:ml-0" action="#" method="GET">
|
|
||||||
<label htmlFor="search-field" className="sr-only">
|
|
||||||
Search
|
|
||||||
</label>
|
|
||||||
<div className="relative w-full text-gray-400 focus-within:text-gray-600">
|
|
||||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center">
|
|
||||||
<MagnifyingGlassIcon className="h-5 w-5" aria-hidden="true" />
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
id="search-field"
|
|
||||||
className="block h-full w-full border-transparent py-2 pl-8 pr-3 text-gray-900 placeholder-gray-500 focus:border-transparent focus:placeholder-gray-400 focus:outline-none focus:ring-0 sm:text-sm"
|
|
||||||
placeholder="Search"
|
|
||||||
type="search"
|
|
||||||
name="search"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div className="ml-4 flex items-center md:ml-6">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="rounded-full bg-white p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
||||||
>
|
|
||||||
<span className="sr-only">View notifications</span>
|
|
||||||
<BellIcon className="h-6 w-6" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Profile dropdown */}
|
|
||||||
<Menu as="div" className="relative ml-3">
|
|
||||||
<div>
|
|
||||||
<Menu.Button className="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
|
||||||
<span className="sr-only">Open user menu</span>
|
|
||||||
<img
|
|
||||||
className="h-8 w-8 rounded-full"
|
|
||||||
src="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"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Menu.Button>
|
|
||||||
</div>
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
||||||
{userNavigation.map((item) => (
|
|
||||||
<Menu.Item key={item.name}>
|
|
||||||
{({ active }) => (
|
|
||||||
<a
|
|
||||||
className={classNames(
|
|
||||||
active ? 'bg-gray-100' : '',
|
|
||||||
'block px-4 py-2 text-sm text-gray-700'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
export default Search
|
|
||||||
@ -390,7 +390,7 @@ function Sidebar() {
|
|||||||
<div className="flex min-h-0 flex-1 flex-col bg-gray-800 bg-opacity-25">
|
<div className="flex min-h-0 flex-1 flex-col bg-gray-800 bg-opacity-25">
|
||||||
<div className="flex h-16 flex-shrink-0 items-center bg-gray-900 px-4 bg-opacity-25">
|
<div className="flex h-16 flex-shrink-0 items-center bg-gray-900 px-4 bg-opacity-25">
|
||||||
<img className="h-8 w-auto" src='/images/logo.svg' alt="Textualize" />
|
<img className="h-8 w-auto" src='/images/logo.svg' alt="Textualize" />
|
||||||
<h1 className='text-gray-100 text-xl ml-2'>{currentSession.project.name}</h1>
|
<h1 className='text-gray-100 text-xl ml-2'>{currentSession?.project?.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||||
{renderNavigationItems()}
|
{renderNavigationItems()}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import Search from './Search'
|
import Search from '../utils/Search'
|
||||||
import ToolTabs from './ToolTabs'
|
import ToolTabs from './ToolTabs'
|
||||||
|
|
||||||
const TopBar = () => <div className="flex flex-col md:pl-64 sticky">
|
const TopBar = () => <div className="flex flex-col md:pl-64 sticky">
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { createContext, ReactNode, useContext, useState } from 'react'
|
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||||
import makeDefaultNavigation from './makeDefaultNavigation'
|
import makeDefaultNavigation from './makeDefaultNavigation'
|
||||||
import { NavigationContextType, NavigationProps, workspaces } from './types'
|
import { mainPages, NavigationContextType, NavigationProps, workspaces } from './types'
|
||||||
|
|
||||||
const NavigationContext = createContext<NavigationContextType>(makeDefaultNavigation())
|
const NavigationContext = createContext<NavigationContextType>(makeDefaultNavigation())
|
||||||
|
|
||||||
@ -13,10 +13,13 @@ export function useNavigation() {
|
|||||||
type Props = { children: ReactNode, navigationProps: NavigationProps }
|
type Props = { children: ReactNode, navigationProps: NavigationProps }
|
||||||
export function NavigationProvidor({ children, navigationProps }: Props) {
|
export function NavigationProvidor({ children, navigationProps }: Props) {
|
||||||
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
|
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
|
||||||
|
const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(mainPages.SELECTPROJECT)
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
selectedWorkspace,
|
selectedWorkspace,
|
||||||
setSelectedWorkspace
|
setSelectedWorkspace,
|
||||||
|
selectedMainPage,
|
||||||
|
setSelectedMainPage
|
||||||
}
|
}
|
||||||
|
|
||||||
return <NavigationContext.Provider value={value}>
|
return <NavigationContext.Provider value={value}>
|
||||||
|
|||||||
@ -5,13 +5,22 @@ enum workspaces {
|
|||||||
DETAILS = 'DETAILS',
|
DETAILS = 'DETAILS',
|
||||||
}
|
}
|
||||||
|
|
||||||
export { workspaces }
|
enum mainPages {
|
||||||
|
WORKSPACE = 'WORKSPACE',
|
||||||
|
EDITUSER = 'EDITUSER',
|
||||||
|
SELECTPROJECT = 'SELECTPROJECT'
|
||||||
|
}
|
||||||
|
|
||||||
|
export { workspaces, mainPages }
|
||||||
|
|
||||||
export type NavigationContextType = {
|
export type NavigationContextType = {
|
||||||
selectedWorkspace: workspaces,
|
selectedWorkspace: workspaces,
|
||||||
setSelectedWorkspace: (workspace: workspaces) => void
|
setSelectedWorkspace: (workspace: workspaces) => void
|
||||||
|
selectedMainPage: mainPages
|
||||||
|
setSelectedMainPage: (mainPage: mainPages) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavigationProps = {
|
export type NavigationProps = {
|
||||||
selectedWorkspace: workspaces
|
selectedWorkspace: workspaces
|
||||||
|
selectedMainPage: mainPages
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
|
import { ipc } from '../../wailsjs/wailsjs/go/models'
|
||||||
import { ProjectContextType } from './types'
|
import { ProjectContextType, UserProps } from './types'
|
||||||
|
|
||||||
const makeDefaultProject = (): ProjectContextType => ({
|
const makeDefaultProject = (): ProjectContextType => ({
|
||||||
id: '',
|
id: '',
|
||||||
@ -21,6 +21,8 @@ const makeDefaultProject = (): ProjectContextType => ({
|
|||||||
setSelectedDocumentId: (id) => {},
|
setSelectedDocumentId: (id) => {},
|
||||||
currentSession: new ipc.Session(),
|
currentSession: new ipc.Session(),
|
||||||
createNewProject: (name: string) => Promise.resolve(new ipc.Session()),
|
createNewProject: (name: string) => Promise.resolve(new ipc.Session()),
|
||||||
|
requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise.resolve(new ipc.User()),
|
||||||
|
requestChooseUserAvatar: () => Promise.resolve(''),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default makeDefaultProject
|
export default makeDefaultProject
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||||
import { CreateNewProject, GetCurrentSession, GetDocuments, GetProcessedAreasByDocumentId, GetUserMarkdownByDocumentId, RequestAddArea, RequestAddDocument, RequestAddDocumentGroup, RequestAddProcessedArea, RequestUpdateArea, RequestUpdateDocumentUserMarkdown } from '../../wailsjs/wailsjs/go/ipc/Channel'
|
import {
|
||||||
|
CreateNewProject, GetCurrentSession, GetDocuments,
|
||||||
|
GetProcessedAreasByDocumentId, GetUserMarkdownByDocumentId, RequestAddArea,
|
||||||
|
RequestAddDocument, RequestAddDocumentGroup, RequestAddProcessedArea,
|
||||||
|
RequestUpdateArea, RequestUpdateCurrentUser, RequestUpdateDocumentUserMarkdown,
|
||||||
|
RequestChooseUserAvatar,
|
||||||
|
} from '../../wailsjs/wailsjs/go/ipc/Channel'
|
||||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
|
import { ipc } from '../../wailsjs/wailsjs/go/models'
|
||||||
import { AddAreaProps, AreaProps, ProjectContextType, ProjectProps } from './types'
|
import { AddAreaProps, AreaProps, ProjectContextType, ProjectProps, UserProps } from './types'
|
||||||
import makeDefaultProject from './makeDefaultProject'
|
import makeDefaultProject from './makeDefaultProject'
|
||||||
|
|
||||||
const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
|
const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
|
||||||
@ -106,6 +112,17 @@ export function ProjectProvider({ children, projectProps }: Props) {
|
|||||||
return sessionResponse
|
return sessionResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestUpdateCurrentUser = async (userProps: UserProps) => {
|
||||||
|
const response = await RequestUpdateCurrentUser(new ipc.User(userProps))
|
||||||
|
await updateSession()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestChooseUserAvatar = async () => {
|
||||||
|
const filePathResponse = await RequestChooseUserAvatar()
|
||||||
|
return filePathResponse
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!documents.length && !groups.length) updateDocuments()
|
if (!documents.length && !groups.length) updateDocuments()
|
||||||
}, [documents.length, groups.length])
|
}, [documents.length, groups.length])
|
||||||
@ -130,6 +147,8 @@ export function ProjectProvider({ children, projectProps }: Props) {
|
|||||||
getUserMarkdownByDocumentId,
|
getUserMarkdownByDocumentId,
|
||||||
currentSession,
|
currentSession,
|
||||||
createNewProject,
|
createNewProject,
|
||||||
|
requestUpdateCurrentUser,
|
||||||
|
requestChooseUserAvatar,
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ProjectContext.Provider value={value}>
|
return <ProjectContext.Provider value={value}>
|
||||||
|
|||||||
@ -16,6 +16,16 @@ export type AddAreaProps = {
|
|||||||
|
|
||||||
export type AreaProps = { id: string } & AddAreaProps
|
export type AreaProps = { id: string } & AddAreaProps
|
||||||
|
|
||||||
|
export type UserProps = {
|
||||||
|
id?: string,
|
||||||
|
localId?: string,
|
||||||
|
firstName?: string,
|
||||||
|
lastName?: string,
|
||||||
|
avatarPath?: string,
|
||||||
|
authToken?: string,
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ProjectContextType = {
|
export type ProjectContextType = {
|
||||||
getSelectedDocument: () => ipc.Document | undefined
|
getSelectedDocument: () => ipc.Document | undefined
|
||||||
getAreaById: (areaId: string) => ipc.Area | undefined
|
getAreaById: (areaId: string) => ipc.Area | undefined
|
||||||
@ -33,4 +43,6 @@ export type ProjectContextType = {
|
|||||||
setSelectedDocumentId: (id: string) => void
|
setSelectedDocumentId: (id: string) => void
|
||||||
currentSession: ipc.Session
|
currentSession: ipc.Session
|
||||||
createNewProject: (name: string) => Promise<ipc.Session>
|
createNewProject: (name: string) => Promise<ipc.Session>
|
||||||
|
requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise<ipc.User>
|
||||||
|
requestChooseUserAvatar: () => Promise<string>
|
||||||
} & ProjectProps
|
} & ProjectProps
|
||||||
@ -1,24 +1,34 @@
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import MainHead from '../components/head'
|
import MainHead from '../components/head'
|
||||||
import MainProject from '../components/project/Main'
|
import MainProject from '../components/project/Main'
|
||||||
|
import User from '../components/settings/User'
|
||||||
import MainWorkspace from '../components/workspace/Main'
|
import MainWorkspace from '../components/workspace/Main'
|
||||||
import Navigation from '../components/workspace/Navigation'
|
import Navigation from '../components/workspace/Navigation'
|
||||||
|
import { useNavigation } from '../context/Navigation/provider'
|
||||||
|
import { mainPages } from '../context/Navigation/types'
|
||||||
import { useProject } from '../context/Project/provider'
|
import { useProject } from '../context/Project/provider'
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
|
|
||||||
const { currentSession } = useProject()
|
const { currentSession } = useProject()
|
||||||
|
const { selectedMainPage } = useNavigation()
|
||||||
|
|
||||||
return (
|
const renderSelectedMainPage = () => {
|
||||||
<>
|
if (selectedMainPage === mainPages.SELECTPROJECT) return <MainProject />
|
||||||
<MainHead />
|
else if (selectedMainPage === mainPages.EDITUSER) return <User />
|
||||||
{!currentSession?.project?.id
|
else if ((selectedMainPage === mainPages.WORKSPACE) && currentSession?.project?.id) {
|
||||||
? <MainProject />
|
return <>
|
||||||
: <>
|
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<MainWorkspace />
|
<MainWorkspace />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
else return <MainProject />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MainHead />
|
||||||
|
{ renderSelectedMainPage() }
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
6
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
6
frontend/wailsjs/wailsjs/go/ipc/Channel.d.ts
vendored
@ -6,6 +6,8 @@ export function CreateNewProject(arg1:string):Promise<ipc.Session>;
|
|||||||
|
|
||||||
export function GetCurrentSession():Promise<ipc.Session>;
|
export function GetCurrentSession():Promise<ipc.Session>;
|
||||||
|
|
||||||
|
export function GetCurrentUser():Promise<ipc.User>;
|
||||||
|
|
||||||
export function GetDocumentById(arg1:string):Promise<ipc.Document>;
|
export function GetDocumentById(arg1:string):Promise<ipc.Document>;
|
||||||
|
|
||||||
export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
|
export function GetDocuments():Promise<ipc.GetDocumentsResponse>;
|
||||||
@ -22,6 +24,10 @@ export function RequestAddDocumentGroup(arg1:string):Promise<ipc.Group>;
|
|||||||
|
|
||||||
export function RequestAddProcessedArea(arg1:ipc.ProcessedArea):Promise<ipc.ProcessedArea>;
|
export function RequestAddProcessedArea(arg1:ipc.ProcessedArea):Promise<ipc.ProcessedArea>;
|
||||||
|
|
||||||
|
export function RequestChooseUserAvatar():Promise<string>;
|
||||||
|
|
||||||
export function RequestUpdateArea(arg1:ipc.Area):Promise<ipc.Area>;
|
export function RequestUpdateArea(arg1:ipc.Area):Promise<ipc.Area>;
|
||||||
|
|
||||||
|
export function RequestUpdateCurrentUser(arg1:ipc.User):Promise<ipc.User>;
|
||||||
|
|
||||||
export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<ipc.UserMarkdown>;
|
export function RequestUpdateDocumentUserMarkdown(arg1:string,arg2:string):Promise<ipc.UserMarkdown>;
|
||||||
|
|||||||
@ -10,6 +10,10 @@ export function GetCurrentSession() {
|
|||||||
return window['go']['ipc']['Channel']['GetCurrentSession']();
|
return window['go']['ipc']['Channel']['GetCurrentSession']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetCurrentUser() {
|
||||||
|
return window['go']['ipc']['Channel']['GetCurrentUser']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetDocumentById(arg1) {
|
export function GetDocumentById(arg1) {
|
||||||
return window['go']['ipc']['Channel']['GetDocumentById'](arg1);
|
return window['go']['ipc']['Channel']['GetDocumentById'](arg1);
|
||||||
}
|
}
|
||||||
@ -42,10 +46,18 @@ export function RequestAddProcessedArea(arg1) {
|
|||||||
return window['go']['ipc']['Channel']['RequestAddProcessedArea'](arg1);
|
return window['go']['ipc']['Channel']['RequestAddProcessedArea'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RequestChooseUserAvatar() {
|
||||||
|
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
||||||
|
}
|
||||||
|
|
||||||
export function RequestUpdateArea(arg1) {
|
export function RequestUpdateArea(arg1) {
|
||||||
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
|
return window['go']['ipc']['Channel']['RequestUpdateArea'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RequestUpdateCurrentUser(arg1) {
|
||||||
|
return window['go']['ipc']['Channel']['RequestUpdateCurrentUser'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function RequestUpdateDocumentUserMarkdown(arg1, arg2) {
|
export function RequestUpdateDocumentUserMarkdown(arg1, arg2) {
|
||||||
return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
|
return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,10 +115,12 @@ export namespace ipc {
|
|||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
id: string;
|
id: string;
|
||||||
|
localId: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
avatarPath: string;
|
avatarPath: string;
|
||||||
authToken: string;
|
authToken: string;
|
||||||
|
email: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new User(source);
|
return new User(source);
|
||||||
@ -127,10 +129,12 @@ export namespace ipc {
|
|||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
this.localId = source["localId"];
|
||||||
this.firstName = source["firstName"];
|
this.firstName = source["firstName"];
|
||||||
this.lastName = source["lastName"];
|
this.lastName = source["lastName"];
|
||||||
this.avatarPath = source["avatarPath"];
|
this.avatarPath = source["avatarPath"];
|
||||||
this.authToken = source["authToken"];
|
this.authToken = source["authToken"];
|
||||||
|
this.email = source["email"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class Organization {
|
export class Organization {
|
||||||
|
|||||||
@ -81,10 +81,12 @@ type UserMarkdownCollection struct {
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
LocalId string `json:"localId"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
AvatarPath string `json:"avatarPath"`
|
AvatarPath string `json:"avatarPath"`
|
||||||
AuthToken string `json:"authToken"`
|
AuthToken string `json:"authToken"`
|
||||||
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
app "textualize/core/App"
|
||||||
session "textualize/core/Session"
|
session "textualize/core/Session"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,3 +39,50 @@ func (c *Channel) CreateNewProject(name string) Session {
|
|||||||
|
|
||||||
return c.GetCurrentSession()
|
return c.GetCurrentSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Channel) GetCurrentUser() User {
|
||||||
|
return User(session.GetInstance().User)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) RequestUpdateCurrentUser(updatedUserRequest User) User {
|
||||||
|
sessionInstance := session.GetInstance()
|
||||||
|
|
||||||
|
user := session.User(sessionInstance.User)
|
||||||
|
|
||||||
|
if user.LocalId == "" {
|
||||||
|
user.LocalId = uuid.NewString()
|
||||||
|
}
|
||||||
|
if updatedUserRequest.FirstName != "" {
|
||||||
|
user.FirstName = updatedUserRequest.FirstName
|
||||||
|
}
|
||||||
|
if updatedUserRequest.LastName != "" {
|
||||||
|
user.LastName = updatedUserRequest.LastName
|
||||||
|
}
|
||||||
|
if updatedUserRequest.Email != "" {
|
||||||
|
user.Email = updatedUserRequest.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
user.AvatarPath = updatedUserRequest.AvatarPath
|
||||||
|
|
||||||
|
sessionInstance.UpdateCurrentUser(user)
|
||||||
|
return User(sessionInstance.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) RequestChooseUserAvatar() string {
|
||||||
|
filePath, err := runtime.OpenFileDialog(app.GetInstance().Context, runtime.OpenDialogOptions{
|
||||||
|
Title: "Select an Image",
|
||||||
|
Filters: []runtime.FileFilter{
|
||||||
|
{
|
||||||
|
DisplayName: "Image Files (*.jpg, *.png)",
|
||||||
|
Pattern: "*.jpg;*.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
runtime.LogError(app.GetInstance().Context, err.Error())
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user