feat: edit user
This commit is contained in:
parent
59cc371d50
commit
5639b01998
@ -21,3 +21,8 @@ func InitializeModule(newSession Session) *Session {
|
||||
}
|
||||
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 {
|
||||
Id string
|
||||
LocalId string
|
||||
FirstName string
|
||||
LastName string
|
||||
AvatarPath string
|
||||
AuthToken string
|
||||
Email string
|
||||
}
|
||||
|
||||
@ -4,5 +4,6 @@
|
||||
"@next/next/no-img-element": "off",
|
||||
"quotes": ["warn", "single"],
|
||||
"semi": ["warn", "never"]
|
||||
}
|
||||
},
|
||||
"ignorePatterns": ["wailsjs/*"]
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon, FolderArrowDownIcon, FolderOpenIcon, FolderPlusIcon } from '@heroicons/react/20/solid'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { useNavigation } from '../../context/Navigation/provider'
|
||||
import { mainPages } from '../../context/Navigation/types'
|
||||
import { useProject } from '../../context/Project/provider'
|
||||
import NewProjectModal from './NewProjectModal'
|
||||
|
||||
@ -11,6 +13,7 @@ const MainProject = () => {
|
||||
const [isNewProjectModalOpen, setIsNewProjectModalOpen] = useState(false)
|
||||
const [canPopoverBeOpen, setCanPopoverBeOpen] = useState(true)
|
||||
const { createNewProject } = useProject()
|
||||
const { setSelectedMainPage } = useNavigation()
|
||||
|
||||
const buttonOptions = [
|
||||
{
|
||||
@ -44,6 +47,7 @@ const MainProject = () => {
|
||||
setIsNewProjectModalOpen(false)
|
||||
setCanPopoverBeOpen(true)
|
||||
createNewProject(projectName)
|
||||
setSelectedMainPage(mainPages.WORKSPACE)
|
||||
}
|
||||
|
||||
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 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" />
|
||||
<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 className="flex flex-1 flex-col overflow-y-auto">
|
||||
{renderNavigationItems()}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import Search from './Search'
|
||||
import Search from '../utils/Search'
|
||||
import ToolTabs from './ToolTabs'
|
||||
|
||||
const TopBar = () => <div className="flex flex-col md:pl-64 sticky">
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, ReactNode, useContext, useState } from 'react'
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||
import makeDefaultNavigation from './makeDefaultNavigation'
|
||||
import { NavigationContextType, NavigationProps, workspaces } from './types'
|
||||
import { mainPages, NavigationContextType, NavigationProps, workspaces } from './types'
|
||||
|
||||
const NavigationContext = createContext<NavigationContextType>(makeDefaultNavigation())
|
||||
|
||||
@ -13,10 +13,13 @@ export function useNavigation() {
|
||||
type Props = { children: ReactNode, navigationProps: NavigationProps }
|
||||
export function NavigationProvidor({ children, navigationProps }: Props) {
|
||||
const [selectedWorkspace, setSelectedWorkspace] = useState<workspaces>(navigationProps.selectedWorkspace)
|
||||
const [selectedMainPage, setSelectedMainPage] = useState<mainPages>(mainPages.SELECTPROJECT)
|
||||
|
||||
const value = {
|
||||
selectedWorkspace,
|
||||
setSelectedWorkspace
|
||||
setSelectedWorkspace,
|
||||
selectedMainPage,
|
||||
setSelectedMainPage
|
||||
}
|
||||
|
||||
return <NavigationContext.Provider value={value}>
|
||||
|
||||
@ -5,13 +5,22 @@ enum workspaces {
|
||||
DETAILS = 'DETAILS',
|
||||
}
|
||||
|
||||
export { workspaces }
|
||||
enum mainPages {
|
||||
WORKSPACE = 'WORKSPACE',
|
||||
EDITUSER = 'EDITUSER',
|
||||
SELECTPROJECT = 'SELECTPROJECT'
|
||||
}
|
||||
|
||||
export { workspaces, mainPages }
|
||||
|
||||
export type NavigationContextType = {
|
||||
selectedWorkspace: workspaces,
|
||||
setSelectedWorkspace: (workspace: workspaces) => void
|
||||
selectedMainPage: mainPages
|
||||
setSelectedMainPage: (mainPage: mainPages) => void
|
||||
}
|
||||
|
||||
export type NavigationProps = {
|
||||
selectedWorkspace: workspaces
|
||||
selectedMainPage: mainPages
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ipc } from '../../wailsjs/wailsjs/go/models'
|
||||
import { ProjectContextType } from './types'
|
||||
import { ProjectContextType, UserProps } from './types'
|
||||
|
||||
const makeDefaultProject = (): ProjectContextType => ({
|
||||
id: '',
|
||||
@ -21,6 +21,8 @@ const makeDefaultProject = (): ProjectContextType => ({
|
||||
setSelectedDocumentId: (id) => {},
|
||||
currentSession: 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
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
'use client'
|
||||
|
||||
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 { AddAreaProps, AreaProps, ProjectContextType, ProjectProps } from './types'
|
||||
import { AddAreaProps, AreaProps, ProjectContextType, ProjectProps, UserProps } from './types'
|
||||
import makeDefaultProject from './makeDefaultProject'
|
||||
|
||||
const ProjectContext = createContext<ProjectContextType>(makeDefaultProject())
|
||||
@ -106,6 +112,17 @@ export function ProjectProvider({ children, projectProps }: Props) {
|
||||
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(() => {
|
||||
if (!documents.length && !groups.length) updateDocuments()
|
||||
}, [documents.length, groups.length])
|
||||
@ -130,6 +147,8 @@ export function ProjectProvider({ children, projectProps }: Props) {
|
||||
getUserMarkdownByDocumentId,
|
||||
currentSession,
|
||||
createNewProject,
|
||||
requestUpdateCurrentUser,
|
||||
requestChooseUserAvatar,
|
||||
}
|
||||
|
||||
return <ProjectContext.Provider value={value}>
|
||||
|
||||
@ -16,6 +16,16 @@ export type 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 = {
|
||||
getSelectedDocument: () => ipc.Document | undefined
|
||||
getAreaById: (areaId: string) => ipc.Area | undefined
|
||||
@ -33,4 +43,6 @@ export type ProjectContextType = {
|
||||
setSelectedDocumentId: (id: string) => void
|
||||
currentSession: ipc.Session
|
||||
createNewProject: (name: string) => Promise<ipc.Session>
|
||||
requestUpdateCurrentUser: (updatedUserProps: UserProps) => Promise<ipc.User>
|
||||
requestChooseUserAvatar: () => Promise<string>
|
||||
} & ProjectProps
|
||||
@ -1,24 +1,34 @@
|
||||
import { NextPage } from 'next'
|
||||
import MainHead from '../components/head'
|
||||
import MainProject from '../components/project/Main'
|
||||
import User from '../components/settings/User'
|
||||
import MainWorkspace from '../components/workspace/Main'
|
||||
import Navigation from '../components/workspace/Navigation'
|
||||
import { useNavigation } from '../context/Navigation/provider'
|
||||
import { mainPages } from '../context/Navigation/types'
|
||||
import { useProject } from '../context/Project/provider'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
|
||||
const { currentSession } = useProject()
|
||||
const { selectedMainPage } = useNavigation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainHead />
|
||||
{!currentSession?.project?.id
|
||||
? <MainProject />
|
||||
: <>
|
||||
const renderSelectedMainPage = () => {
|
||||
if (selectedMainPage === mainPages.SELECTPROJECT) return <MainProject />
|
||||
else if (selectedMainPage === mainPages.EDITUSER) return <User />
|
||||
else if ((selectedMainPage === mainPages.WORKSPACE) && currentSession?.project?.id) {
|
||||
return <>
|
||||
<Navigation />
|
||||
<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 GetCurrentUser():Promise<ipc.User>;
|
||||
|
||||
export function GetDocumentById(arg1:string):Promise<ipc.Document>;
|
||||
|
||||
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 RequestChooseUserAvatar():Promise<string>;
|
||||
|
||||
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>;
|
||||
|
||||
@ -10,6 +10,10 @@ export function GetCurrentSession() {
|
||||
return window['go']['ipc']['Channel']['GetCurrentSession']();
|
||||
}
|
||||
|
||||
export function GetCurrentUser() {
|
||||
return window['go']['ipc']['Channel']['GetCurrentUser']();
|
||||
}
|
||||
|
||||
export function GetDocumentById(arg1) {
|
||||
return window['go']['ipc']['Channel']['GetDocumentById'](arg1);
|
||||
}
|
||||
@ -42,10 +46,18 @@ export function RequestAddProcessedArea(arg1) {
|
||||
return window['go']['ipc']['Channel']['RequestAddProcessedArea'](arg1);
|
||||
}
|
||||
|
||||
export function RequestChooseUserAvatar() {
|
||||
return window['go']['ipc']['Channel']['RequestChooseUserAvatar']();
|
||||
}
|
||||
|
||||
export function 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) {
|
||||
return window['go']['ipc']['Channel']['RequestUpdateDocumentUserMarkdown'](arg1, arg2);
|
||||
}
|
||||
|
||||
@ -115,10 +115,12 @@ export namespace ipc {
|
||||
|
||||
export class User {
|
||||
id: string;
|
||||
localId: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
avatarPath: string;
|
||||
authToken: string;
|
||||
email: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new User(source);
|
||||
@ -127,10 +129,12 @@ export namespace ipc {
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.localId = source["localId"];
|
||||
this.firstName = source["firstName"];
|
||||
this.lastName = source["lastName"];
|
||||
this.avatarPath = source["avatarPath"];
|
||||
this.authToken = source["authToken"];
|
||||
this.email = source["email"];
|
||||
}
|
||||
}
|
||||
export class Organization {
|
||||
|
||||
@ -81,10 +81,12 @@ type UserMarkdownCollection struct {
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id"`
|
||||
LocalId string `json:"localId"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
AvatarPath string `json:"avatarPath"`
|
||||
AuthToken string `json:"authToken"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
app "textualize/core/App"
|
||||
session "textualize/core/Session"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@ -36,3 +39,50 @@ func (c *Channel) CreateNewProject(name string) Session {
|
||||
|
||||
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