Fix up KeyValue component, fix save issue of Bio, simplify UserProfile
This commit is contained in:
@@ -1,27 +1,47 @@
|
|||||||
import Svg from 'src/components/Svg/Svg'
|
import Svg from 'src/components/Svg/Svg'
|
||||||
|
|
||||||
|
interface EditToggleType {
|
||||||
|
hasPermissionToEdit: boolean
|
||||||
|
onEdit?: React.MouseEventHandler
|
||||||
|
isEditing?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditToggle = ({
|
||||||
|
onEdit = () => { console.error('Field declared editable without edit action.') },
|
||||||
|
isEditing = false,
|
||||||
|
} : EditToggleType) => (
|
||||||
|
(isEditing ? (
|
||||||
|
<button
|
||||||
|
className="font-fira-sans text-ch-gray-300 items-center ml-4 grid grid-flow-col-dense p-px px-2 gap-2 bg-ch-blue-500 bg-opacity-50 hover:bg-opacity-70 rounded-sm"
|
||||||
|
id="rename-button"
|
||||||
|
onClick={onEdit}
|
||||||
|
>
|
||||||
|
<Svg name="check" className="w-6 h-6" strokeWidth={3} />
|
||||||
|
<span>Update</span>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button onClick={onEdit}>
|
||||||
|
<Svg name="pencil-solid" className="h-4 w-4 ml-4 mb-2" />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
interface KeyValueType {
|
interface KeyValueType {
|
||||||
keyName: string
|
keyName: string
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
hide?: boolean
|
|
||||||
canEdit?: boolean
|
|
||||||
onEdit?: () => void
|
|
||||||
isEditable?: boolean
|
|
||||||
bottom?: boolean
|
bottom?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
edit?: EditToggleType
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyValue = ({
|
const KeyValue = ({
|
||||||
keyName,
|
keyName,
|
||||||
children,
|
children,
|
||||||
hide = false,
|
|
||||||
canEdit = false,
|
|
||||||
onEdit,
|
|
||||||
isEditable = false,
|
|
||||||
bottom = false,
|
bottom = false,
|
||||||
className = '',
|
className = '',
|
||||||
|
edit = { hasPermissionToEdit: false },
|
||||||
}: KeyValueType) => {
|
}: KeyValueType) => {
|
||||||
if (!children || hide) return null
|
if (!children) return null
|
||||||
return (
|
return (
|
||||||
<div className={'flex flex-col ' + className}>
|
<div className={'flex flex-col ' + className}>
|
||||||
<div
|
<div
|
||||||
@@ -30,22 +50,8 @@ const KeyValue = ({
|
|||||||
(bottom ? 'order-2' : '')
|
(bottom ? 'order-2' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={isEditable ? 'text-ch-blue-300' : ''}>{keyName}</span>
|
<span className={edit ? 'text-ch-blue-300' : ''}>{keyName}</span>
|
||||||
{canEdit &&
|
{edit && edit.hasPermissionToEdit && <EditToggle {...edit} /> }
|
||||||
(isEditable ? (
|
|
||||||
<button
|
|
||||||
className="font-fira-sans text-ch-gray-300 items-center ml-4 grid grid-flow-col-dense p-px px-2 gap-2 bg-ch-blue-500 bg-opacity-50 hover:bg-opacity-70 rounded-sm"
|
|
||||||
id="rename-button"
|
|
||||||
onClick={onEdit}
|
|
||||||
>
|
|
||||||
<Svg name="check" className="w-6 h-6" strokeWidth={3} />
|
|
||||||
<span>Update</span>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button onClick={onEdit}>
|
|
||||||
<Svg name="pencil-solid" className="h-4 w-4 ml-4 mb-2" />
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'text-ch-gray-300 ' + (bottom ? 'mb-1' : 'mt-1')}>
|
<div className={'text-ch-gray-300 ' + (bottom ? 'mb-1' : 'mt-1')}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
|||||||
<div className="flex items-center mt-1">
|
<div className="flex items-center mt-1">
|
||||||
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
|
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
|
||||||
<ImageFallback
|
<ImageFallback
|
||||||
imageId={user?.image} // http://res.cloudinary.com/irevdev/image/upload/c_scale,w_50/v1/CadHub/bc7smqwo9qqmrloyf9xr
|
imageId={user?.image}
|
||||||
width={80} // http://res.cloudinary.com/irevdev/image/upload/c_scale,w_300/v1/CadHub/bc7smqwo9qqmrloyf9xr
|
width={80}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
|
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const ProjectProfile = ({
|
|||||||
onComment,
|
onComment,
|
||||||
}) => {
|
}) => {
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
const [isEditable, setIsEditable] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const onCommentClear = () => {
|
const onCommentClear = () => {
|
||||||
onComment(comment)
|
onComment(comment)
|
||||||
setComment('')
|
setComment('')
|
||||||
@@ -36,14 +36,14 @@ const ProjectProfile = ({
|
|||||||
const [isReactionsModalOpen, setIsReactionsModalOpen] = useState(false)
|
const [isReactionsModalOpen, setIsReactionsModalOpen] = useState(false)
|
||||||
const { currentUser } = useAuth()
|
const { currentUser } = useAuth()
|
||||||
const editorRef = useRef(null)
|
const editorRef = useRef(null)
|
||||||
const canEdit =
|
const hasPermissionToEdit =
|
||||||
currentUser?.sub === userProject.id || currentUser?.roles.includes('admin')
|
currentUser?.sub === userProject.id || currentUser?.roles.includes('admin')
|
||||||
const project = userProject?.Project
|
const project = userProject?.Project
|
||||||
const emotes = countEmotes(project?.Reaction)
|
const emotes = countEmotes(project?.Reaction)
|
||||||
const userEmotes = project?.userReactions.map(({ emote }) => emote)
|
const userEmotes = project?.userReactions.map(({ emote }) => emote)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isEditable &&
|
isEditing &&
|
||||||
!canEdit &&
|
!hasPermissionToEdit &&
|
||||||
navigate(
|
navigate(
|
||||||
routes.project({
|
routes.project({
|
||||||
userName: userProject.userName,
|
userName: userProject.userName,
|
||||||
@@ -55,7 +55,7 @@ const ProjectProfile = ({
|
|||||||
const [newDescription, setNewDescription] = useState(project?.description)
|
const [newDescription, setNewDescription] = useState(project?.description)
|
||||||
const onDescriptionChange = (description) => setNewDescription(description())
|
const onDescriptionChange = (description) => setNewDescription(description())
|
||||||
const onEditSaveClick = () => {
|
const onEditSaveClick = () => {
|
||||||
if (isEditable) {
|
if (isEditing) {
|
||||||
onSave(project?.id, { description: newDescription })
|
onSave(project?.id, { description: newDescription })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -108,26 +108,27 @@ const ProjectProfile = ({
|
|||||||
className="px-3 py-2 rounded"
|
className="px-3 py-2 rounded"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<KeyValue
|
{ (project?.description || hasPermissionToEdit) && <KeyValue
|
||||||
keyName="Description"
|
keyName="Description"
|
||||||
hide={!project?.description && !canEdit}
|
edit={{
|
||||||
canEdit={canEdit}
|
hasPermissionToEdit,
|
||||||
onEdit={() => {
|
isEditing,
|
||||||
if (!isEditable) {
|
onEdit: () => {
|
||||||
setIsEditable(true)
|
if (!isEditing) {
|
||||||
} else {
|
setIsEditing(true)
|
||||||
onEditSaveClick()
|
} else {
|
||||||
setIsEditable(false)
|
onEditSaveClick()
|
||||||
}
|
setIsEditing(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
isEditable={isEditable}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="description-wrap"
|
id="description-wrap"
|
||||||
name="description"
|
name="description"
|
||||||
className={
|
className={
|
||||||
'markdown-overrides rounded-sm pb-2 mt-2' +
|
'markdown-overrides rounded-sm pb-2 mt-2' +
|
||||||
(isEditable ? ' min-h-md' : '')
|
(isEditing ? ' min-h-md' : '')
|
||||||
}
|
}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
e?.target?.id === 'description-wrap' &&
|
e?.target?.id === 'description-wrap' &&
|
||||||
@@ -137,11 +138,11 @@ const ProjectProfile = ({
|
|||||||
<Editor
|
<Editor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
defaultValue={project?.description || ''}
|
defaultValue={project?.description || ''}
|
||||||
readOnly={!isEditable}
|
readOnly={!isEditing}
|
||||||
onChange={onDescriptionChange}
|
onChange={onDescriptionChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</KeyValue>
|
</KeyValue> }
|
||||||
<div className="grid grid-flow-col-dense gap-6">
|
<div className="grid grid-flow-col-dense gap-6">
|
||||||
<KeyValue keyName="Created on">
|
<KeyValue keyName="Created on">
|
||||||
{new Date(project?.createdAt).toDateString()}
|
{new Date(project?.createdAt).toDateString()}
|
||||||
@@ -156,10 +157,11 @@ const ProjectProfile = ({
|
|||||||
userEmotes={userEmotes}
|
userEmotes={userEmotes}
|
||||||
onEmote={onReaction}
|
onEmote={onReaction}
|
||||||
onShowProjectReactions={() => setIsReactionsModalOpen(true)}
|
onShowProjectReactions={() => setIsReactionsModalOpen(true)}
|
||||||
|
className=""
|
||||||
/>
|
/>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
<KeyValue keyName="Comments" hide={!currentUser}>
|
{ currentUser && <KeyValue keyName="Comments">
|
||||||
{!isEditable && (
|
{!isEditing && (
|
||||||
<>
|
<>
|
||||||
{currentUser && (
|
{currentUser && (
|
||||||
<>
|
<>
|
||||||
@@ -215,8 +217,8 @@ const ProjectProfile = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</KeyValue>
|
</KeyValue> }
|
||||||
{canEdit && (
|
{hasPermissionToEdit && (
|
||||||
<>
|
<>
|
||||||
<h4 className="mt-10 text-red-600">Danger Zone</h4>
|
<h4 className="mt-10 text-red-600">Danger Zone</h4>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,45 +1,53 @@
|
|||||||
import { useState, useEffect, useRef, useReducer, ReactNode } from 'react'
|
import { useEffect, useReducer } from 'react'
|
||||||
import { useAuth } from '@redwoodjs/auth'
|
import { useAuth } from '@redwoodjs/auth'
|
||||||
import { Link, navigate, routes } from '@redwoodjs/router'
|
import { Link, navigate, routes } from '@redwoodjs/router'
|
||||||
import ProjectsOfUser from 'src/components/ProjectsOfUserCell'
|
import ProjectsOfUser from 'src/components/ProjectsOfUserCell'
|
||||||
import IdeHeader from 'src/components/IdeHeader/IdeHeader'
|
import IdeHeader from 'src/components/IdeHeader/IdeHeader'
|
||||||
import Svg from 'src/components/Svg/Svg'
|
import Svg from 'src/components/Svg/Svg'
|
||||||
import {
|
import {
|
||||||
fieldsConfig,
|
fieldComponents,
|
||||||
fieldReducer,
|
fieldReducer,
|
||||||
UserProfileType,
|
UserProfileType,
|
||||||
FieldConfigType,
|
FieldType,
|
||||||
} from './userProfileConfig'
|
} from './userProfileConfig'
|
||||||
|
|
||||||
function buildFieldsConfig(fieldsConfig, user) {
|
// This function initializes the state management object for each of the fields
|
||||||
Object.entries(fieldsConfig).forEach(
|
function buildFieldsConfig(fieldsConfig, user, hasPermissionToEdit) {
|
||||||
([key, field]: [string, FieldConfigType]) => {
|
return Object.fromEntries(Object.keys(fieldsConfig).map(
|
||||||
field.currentValue = field.newValue = user[key]
|
(key: string): [string, FieldType] => ([key, {
|
||||||
field.name = key
|
name: key,
|
||||||
}
|
currentValue: user[key],
|
||||||
)
|
newValue: user[key],
|
||||||
|
isEditing: false,
|
||||||
return fieldsConfig
|
hasPermissionToEdit,
|
||||||
|
}])
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserProfile = ({
|
const UserProfile = ({
|
||||||
user,
|
user,
|
||||||
isEditable,
|
isEditing,
|
||||||
loading,
|
loading,
|
||||||
onSave,
|
onSave,
|
||||||
error,
|
error,
|
||||||
projects,
|
|
||||||
}: UserProfileType) => {
|
}: UserProfileType) => {
|
||||||
const { currentUser } = useAuth()
|
const { currentUser } = useAuth()
|
||||||
const hasEditPermission = currentUser?.sub === user.id
|
const hasPermissionToEdit = currentUser?.sub === user.id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isEditable &&
|
isEditing &&
|
||||||
!hasEditPermission &&
|
!hasPermissionToEdit &&
|
||||||
navigate(routes.user({ userName: user.userName }))
|
navigate(routes.user({ userName: user.userName }))
|
||||||
}, [currentUser])
|
}, [currentUser])
|
||||||
|
|
||||||
const initializedFields = buildFieldsConfig(fieldsConfig, user)
|
const initializedFields = buildFieldsConfig(fieldComponents, user, hasPermissionToEdit)
|
||||||
const [fields, fieldDispatch] = useReducer(fieldReducer, initializedFields)
|
const [fields, fieldDispatch] = useReducer(fieldReducer, initializedFields)
|
||||||
|
const {
|
||||||
|
name: NameField,
|
||||||
|
userName: UserNameField,
|
||||||
|
image: ImageField,
|
||||||
|
bio: BioField,
|
||||||
|
createdAt: MemberSinceField,
|
||||||
|
} = fieldComponents
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -65,44 +73,45 @@ const UserProfile = ({
|
|||||||
{/* Side panel */}
|
{/* Side panel */}
|
||||||
<section className="bg-ch-gray-760 font-fira-sans p-12 md:overflow-y-auto ch-scrollbar">
|
<section className="bg-ch-gray-760 font-fira-sans p-12 md:overflow-y-auto ch-scrollbar">
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
{!isEditable && (
|
{!isEditing && (
|
||||||
<div className="w-28 flex-shrink-0">
|
<div className="w-28 flex-shrink-0">
|
||||||
<fields.image.component
|
<ImageField
|
||||||
field={fields.image}
|
field={fields.image}
|
||||||
|
dispatch={fieldDispatch}
|
||||||
user={user}
|
user={user}
|
||||||
save={onSave}
|
save={onSave}
|
||||||
hasEditPermission={hasEditPermission}
|
hasPermissionToEdit={hasPermissionToEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<fields.name.component
|
<NameField
|
||||||
field={fields.name}
|
field={fields.name}
|
||||||
dispatch={fieldDispatch}
|
dispatch={fieldDispatch}
|
||||||
user={user}
|
user={user}
|
||||||
save={onSave}
|
save={onSave}
|
||||||
hasEditPermission={hasEditPermission}
|
hasPermissionToEdit={hasPermissionToEdit}
|
||||||
/>
|
/>
|
||||||
<fields.userName.component
|
<UserNameField
|
||||||
field={fields.userName}
|
field={fields.userName}
|
||||||
dispatch={fieldDispatch}
|
dispatch={fieldDispatch}
|
||||||
user={user}
|
user={user}
|
||||||
save={onSave}
|
save={onSave}
|
||||||
hasEditPermission={hasEditPermission}
|
hasPermissionToEdit={hasPermissionToEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<fields.bio.component
|
<BioField
|
||||||
field={fields.bio}
|
field={fields.bio}
|
||||||
dispatch={fieldDispatch}
|
dispatch={fieldDispatch}
|
||||||
user={user}
|
user={user}
|
||||||
save={onSave}
|
save={onSave}
|
||||||
hasEditPermission={hasEditPermission}
|
hasPermissionToEdit={hasPermissionToEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
<fields.createdAt.component field={fields.createdAt} />
|
<MemberSinceField field={fields.createdAt} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/* Viewer */}
|
{/* Viewer */}
|
||||||
|
|||||||
@@ -5,24 +5,42 @@ import Editor from 'rich-markdown-editor'
|
|||||||
import ImageUploader from 'src/components/ImageUploader'
|
import ImageUploader from 'src/components/ImageUploader'
|
||||||
import { User } from 'types/graphql'
|
import { User } from 'types/graphql'
|
||||||
|
|
||||||
|
export const fieldComponents = {
|
||||||
|
name: NameField,
|
||||||
|
userName: UserNameField,
|
||||||
|
image: ImageField,
|
||||||
|
bio: BioField,
|
||||||
|
createdAt: MemberSinceField,
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserProfileType {
|
export interface UserProfileType {
|
||||||
user: User
|
user: User
|
||||||
isEditable: boolean
|
isEditing: boolean
|
||||||
loading: boolean
|
loading: boolean
|
||||||
error: boolean
|
error: boolean
|
||||||
onSave: Function
|
onSave: Function
|
||||||
projects: {}[]
|
projects: {}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConfigType {
|
export interface FieldType {
|
||||||
name?: string // introspection ugh
|
name: string
|
||||||
editable: boolean
|
currentValue: any
|
||||||
component?: ReactNode
|
newValue: any
|
||||||
needsRef?: boolean
|
isEditing: boolean
|
||||||
isEditing?: boolean | undefined
|
hasPermissionToEdit: boolean
|
||||||
onSave?: Function
|
}
|
||||||
currentValue?: any
|
|
||||||
newValue?: any
|
export interface FieldComponentPropsType {
|
||||||
|
field: FieldType
|
||||||
|
dispatch?: React.Dispatch<Object>
|
||||||
|
user?: User
|
||||||
|
save?: Function
|
||||||
|
hasPermissionToEdit?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProfileKeyValueType extends FieldComponentPropsType {
|
||||||
|
children: ReactNode
|
||||||
|
bottom: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileKeyValue = ({
|
const ProfileKeyValue = ({
|
||||||
@@ -30,26 +48,27 @@ const ProfileKeyValue = ({
|
|||||||
dispatch,
|
dispatch,
|
||||||
user,
|
user,
|
||||||
save,
|
save,
|
||||||
hasEditPermission,
|
hasPermissionToEdit,
|
||||||
children,
|
children,
|
||||||
bottom = false,
|
bottom = false,
|
||||||
}) => {
|
} : ProfileKeyValueType) => {
|
||||||
return (
|
return (
|
||||||
<KeyValue
|
(user[field.name] && hasPermissionToEdit) && <KeyValue
|
||||||
keyName={field.name}
|
keyName={field.name}
|
||||||
hide={!user[field.name] && !hasEditPermission}
|
edit={{
|
||||||
canEdit={hasEditPermission}
|
hasPermissionToEdit,
|
||||||
onEdit={() => {
|
isEditing: field.isEditing,
|
||||||
if (field.isEditing) {
|
onEdit: () => {
|
||||||
save(user.userName, { [field.name]: field.newValue })
|
if (field.isEditing && field.currentValue !== field.newValue) {
|
||||||
}
|
save(user.userName, { [field.name]: field.newValue })
|
||||||
dispatch({
|
}
|
||||||
type: 'SET_CURRENT_VALUE',
|
dispatch({
|
||||||
payload: { field: field.name, value: field.newValue },
|
type: 'SET_CURRENT_VALUE',
|
||||||
})
|
payload: { field: field.name, value: field.newValue },
|
||||||
dispatch({ type: 'TOGGLE_EDITING', payload: field.name })
|
})
|
||||||
|
dispatch({ type: 'TOGGLE_EDITING', payload: field.name })
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
isEditable={hasEditPermission && field.isEditing}
|
|
||||||
bottom={bottom}
|
bottom={bottom}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
>
|
>
|
||||||
@@ -58,148 +77,115 @@ const ProfileKeyValue = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const bioField: FieldConfigType = {
|
function BioField(props) {
|
||||||
editable: true,
|
const ref = useRef(null)
|
||||||
needsRef: true,
|
const { field, dispatch } = props
|
||||||
component: (props) => {
|
|
||||||
const ref = useRef(null)
|
|
||||||
|
|
||||||
const { dispatch, field } = props
|
return (
|
||||||
|
<ProfileKeyValue {...props}>
|
||||||
return (
|
<div
|
||||||
<ProfileKeyValue {...props}>
|
id="bio-wrap"
|
||||||
<div
|
name="bio"
|
||||||
id="bio-wrap"
|
className={
|
||||||
name="bio"
|
'markdown-overrides rounded-sm pb-2 mt-2' +
|
||||||
className={
|
(field.isEditing ? ' min-h-md' : '')
|
||||||
'markdown-overrides rounded-sm pb-2 mt-2' +
|
}
|
||||||
(field.isEditable ? ' min-h-md' : '')
|
onClick={(e) =>
|
||||||
|
e?.target?.id === 'bio-wrap' && ref?.current?.focusAtEnd()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Editor
|
||||||
|
ref={ref}
|
||||||
|
defaultValue={field?.currentValue || ''}
|
||||||
|
readOnly={!field.isEditing}
|
||||||
|
onChange={(bio) =>
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_NEW_VALUE',
|
||||||
|
payload: { field: 'bio', value: bio() },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
onClick={(e) =>
|
/>
|
||||||
e?.target?.id === 'bio-wrap' && ref?.current?.focusAtEnd()
|
</div>
|
||||||
|
</ProfileKeyValue>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MemberSinceField(props : FieldComponentPropsType) {
|
||||||
|
return (
|
||||||
|
<KeyValue keyName="Member Since">
|
||||||
|
<p className="text-ch-gray-300">
|
||||||
|
{new Date(props.field.currentValue).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</KeyValue>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImageField(props : FieldComponentPropsType) {
|
||||||
|
const { field, user, save, hasPermissionToEdit } = props
|
||||||
|
return (
|
||||||
|
<ImageUploader
|
||||||
|
className="rounded-3xl rounded-tr-none shadow-md border-2 border-ch-gray-300"
|
||||||
|
onImageUpload={({ cloudinaryPublicId: image }) => {
|
||||||
|
save(user.userName, {
|
||||||
|
image,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
aspectRatio={1}
|
||||||
|
isEditable={hasPermissionToEdit}
|
||||||
|
imageUrl={user.image}
|
||||||
|
width={300}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NameField(props : FieldComponentPropsType) {
|
||||||
|
const { user, dispatch, field } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProfileKeyValue {...props} bottom={true}>
|
||||||
|
{!field.isEditing ? (
|
||||||
|
<h1 className="text-4xl">{user?.name}</h1>
|
||||||
|
) : (
|
||||||
|
<InputText
|
||||||
|
className="text-xl"
|
||||||
|
value={field.newValue}
|
||||||
|
onChange={({ target: { value } }) =>
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_NEW_VALUE',
|
||||||
|
payload: { field: 'name', value },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
>
|
isEditable={field.isEditing}
|
||||||
<Editor
|
/>
|
||||||
ref={ref}
|
)}
|
||||||
defaultValue={field?.currentValue || ''}
|
</ProfileKeyValue>
|
||||||
readOnly={!field.isEditing}
|
)
|
||||||
onChange={(bio) =>
|
|
||||||
dispatch({
|
|
||||||
type: 'SET_NEW_VALUE',
|
|
||||||
payload: { field: field.bio, value: bio() },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ProfileKeyValue>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdAtField: FieldConfigType = {
|
function UserNameField(props : FieldComponentPropsType) {
|
||||||
editable: false,
|
const { dispatch, field } = props
|
||||||
component: (props) => {
|
|
||||||
const { field } = props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyValue keyName="Member Since">
|
<ProfileKeyValue {...props} bottom={true}>
|
||||||
<p className="text-ch-gray-300">
|
{!field.isEditing ? (
|
||||||
{new Date(field.currentValue).toLocaleDateString()}
|
<h2 className="text-ch-gray-400">
|
||||||
</p>
|
@{field?.currentValue?.replace(/([^a-zA-Z\d_:])/g, '-')}
|
||||||
</KeyValue>
|
</h2>
|
||||||
)
|
) : (
|
||||||
},
|
<InputText
|
||||||
}
|
className="text-xl"
|
||||||
|
value={field.newValue}
|
||||||
const imageField: FieldConfigType = {
|
onChange={({ target: { value } }) =>
|
||||||
editable: true,
|
dispatch({
|
||||||
component: (props) => {
|
type: 'SET_NEW_VALUE',
|
||||||
const { field, user, save, hasEditPermission } = props
|
payload: { field: 'userName', value },
|
||||||
return (
|
})
|
||||||
<ImageUploader
|
}
|
||||||
className="rounded-3xl rounded-tr-none shadow-md border-2 border-ch-gray-300"
|
isEditable={field.isEditing}
|
||||||
onImageUpload={({ cloudinaryPublicId: image }) => {
|
/>
|
||||||
save(user.userName, {
|
)}
|
||||||
image,
|
</ProfileKeyValue>
|
||||||
})
|
)
|
||||||
}}
|
|
||||||
aspectRatio={1}
|
|
||||||
isEditable={hasEditPermission && !field.isEditing}
|
|
||||||
imageUrl={user.image}
|
|
||||||
width={300}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameField: FieldConfigType = {
|
|
||||||
editable: true,
|
|
||||||
component: (props) => {
|
|
||||||
const { user, dispatch, field } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProfileKeyValue {...props} bottom={true}>
|
|
||||||
{!field.isEditing ? (
|
|
||||||
<h1 className="text-4xl">{user?.name}</h1>
|
|
||||||
) : (
|
|
||||||
<InputText
|
|
||||||
className="text-xl"
|
|
||||||
value={field.newValue}
|
|
||||||
onChange={({ target: { value } }) =>
|
|
||||||
dispatch({
|
|
||||||
type: 'SET_NEW_VALUE',
|
|
||||||
payload: { field: field.name, value },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isEditable={!field.isEditable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ProfileKeyValue>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const userNameField: FieldConfigType = {
|
|
||||||
editable: true,
|
|
||||||
component: (props) => {
|
|
||||||
const { dispatch, field } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProfileKeyValue {...props} bottom={true}>
|
|
||||||
{!field.isEditing ? (
|
|
||||||
<h2 className="text-ch-gray-400">
|
|
||||||
@{field?.currentValue?.replace(/([^a-zA-Z\d_:])/g, '-')}
|
|
||||||
</h2>
|
|
||||||
) : (
|
|
||||||
<InputText
|
|
||||||
className="text-xl"
|
|
||||||
value={field.newValue}
|
|
||||||
onChange={({ target: { value } }) =>
|
|
||||||
dispatch({
|
|
||||||
type: 'SET_NEW_VALUE',
|
|
||||||
payload: { field: field.name, value },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isEditable={!field.isEditable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ProfileKeyValue>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fieldsConfig = {
|
|
||||||
bio: bioField,
|
|
||||||
createdAt: createdAtField,
|
|
||||||
id: {
|
|
||||||
editable: false,
|
|
||||||
},
|
|
||||||
image: imageField,
|
|
||||||
name: nameField,
|
|
||||||
updatedAt: {
|
|
||||||
editable: false,
|
|
||||||
},
|
|
||||||
userName: userNameField,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fieldReducer(state, action) {
|
export function fieldReducer(state, action) {
|
||||||
@@ -210,7 +196,7 @@ export function fieldReducer(state, action) {
|
|||||||
[action.payload]: {
|
[action.payload]: {
|
||||||
...state[action.payload],
|
...state[action.payload],
|
||||||
isEditing:
|
isEditing:
|
||||||
state[action.payload].editable && !state[action.payload].isEditing
|
state[action.payload].hasPermissionToEdit && !state[action.payload].isEditing
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
import MainLayout from 'src/layouts/MainLayout'
|
||||||
import EditUserCell from 'src/components/EditUserCell'
|
import EditUserCell from 'src/components/EditUserCell'
|
||||||
import Seo from 'src/components/Seo/Seo'
|
import Seo from 'src/components/Seo/Seo'
|
||||||
|
import { Toaster } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
const UserPage = ({ userName }) => {
|
const UserPage = ({ userName }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Seo title={userName} description="User page" lang="en-US" />
|
<Seo title={userName} description="User page" lang="en-US" />
|
||||||
|
<Toaster timeout={9000} />
|
||||||
|
|
||||||
<EditUserCell userName={userName} />
|
<EditUserCell userName={userName} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user