feat: edit user

This commit is contained in:
Joshua Shoemaker 2023-02-16 21:41:46 -06:00
parent 59cc371d50
commit 5639b01998
21 changed files with 439 additions and 111 deletions

View File

@ -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
}

View File

@ -2,8 +2,10 @@ package session
type User struct {
Id string
LocalId string
FirstName string
LastName string
AvatarPath string
AuthToken string
Email string
}

View File

@ -4,5 +4,6 @@
"@next/next/no-img-element": "off",
"quotes": ["warn", "single"],
"semi": ["warn", "never"]
}
},
"ignorePatterns": ["wailsjs/*"]
}

View File

@ -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">

View 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

View 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

View 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

View File

@ -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

View File

@ -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()}

View File

@ -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">

View File

@ -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}>

View File

@ -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
}

View File

@ -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

View File

@ -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}>

View File

@ -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

View File

@ -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() }
</>
)
}

View File

@ -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>;

View File

@ -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);
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}
}