Initial profile refactor of layout and config
This commit is contained in:
@@ -66,6 +66,9 @@ module.exports = {
|
|||||||
gridAutoColumns: {
|
gridAutoColumns: {
|
||||||
'preview-layout': 'minmax(30rem, 1fr) minmax(auto, 2fr)',
|
'preview-layout': 'minmax(30rem, 1fr) minmax(auto, 2fr)',
|
||||||
},
|
},
|
||||||
|
gridTemplateColumns: {
|
||||||
|
'profile-layout': 'minmax(32rem, 1fr) 2fr',
|
||||||
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'bounce-sm': {
|
'bounce-sm': {
|
||||||
'0%, 100%': {
|
'0%, 100%': {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const InputText = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="pl-2 pt-1 text-indigo-800 font-medium mb-px pb-px bg-transparent relative"
|
className="text-ch-gray-300 rounded-none bg-ch-gray-600 border border-transparent focus:border-ch-gray-300 px-2 py-1 relative w-full"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={value}
|
value={value}
|
||||||
readOnly={!onChange}
|
readOnly={!onChange}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const InputText = ({ type = 'text', className, name, validation }) => {
|
|||||||
<>
|
<>
|
||||||
<div className={getActiveClasses('relative mt-5', className)}>
|
<div className={getActiveClasses('relative mt-5', className)}>
|
||||||
<FieldError
|
<FieldError
|
||||||
className="absolute -my-5 text-sm text-red-500 font-ropa-sans"
|
className="absolute -my-5 text-sm text-red-500"
|
||||||
name={name}
|
name={name}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
54
app/web/src/components/KeyValue/KeyValue.tsx
Normal file
54
app/web/src/components/KeyValue/KeyValue.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import Svg from 'src/components/Svg/Svg'
|
||||||
|
|
||||||
|
interface KeyValueType {
|
||||||
|
keyName: string
|
||||||
|
children: React.ReactNode
|
||||||
|
hide?: boolean
|
||||||
|
canEdit?: boolean
|
||||||
|
onEdit?: () => void
|
||||||
|
isEditable?: boolean
|
||||||
|
bottom?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyValue = ({
|
||||||
|
keyName,
|
||||||
|
children,
|
||||||
|
hide = false,
|
||||||
|
canEdit = false,
|
||||||
|
onEdit,
|
||||||
|
isEditable = false,
|
||||||
|
bottom = false,
|
||||||
|
className = "",
|
||||||
|
} : KeyValueType) => {
|
||||||
|
if (!children || hide) return null
|
||||||
|
return (
|
||||||
|
<div className={"flex flex-col " + className}>
|
||||||
|
<div className={"text-ch-blue-400 font-fira-code flex items-center leading-4 text-sm whitespace-nowrap " + (bottom ? "order-2" : "")}>
|
||||||
|
{keyName}
|
||||||
|
{canEdit &&
|
||||||
|
(isEditable ? (
|
||||||
|
<button
|
||||||
|
className="font-fira-sans items-center ml-4 grid grid-flow-col-dense p-px px-2 gap-2 bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 rounded-sm border border-ch-purple-400"
|
||||||
|
id="rename-button"
|
||||||
|
onClick={onEdit}
|
||||||
|
>
|
||||||
|
<Svg
|
||||||
|
name="check"
|
||||||
|
className="w-6 h-6 text-ch-purple-500"
|
||||||
|
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 className={"text-ch-gray-300 " + (bottom ? "mb-1" : "mt-1")}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeyValue
|
||||||
@@ -4,6 +4,7 @@ import CadPackage from 'src/components/CadPackage/CadPackage'
|
|||||||
|
|
||||||
import { countEmotes } from 'src/helpers/emote'
|
import { countEmotes } from 'src/helpers/emote'
|
||||||
import ImageUploader from 'src/components/ImageUploader'
|
import ImageUploader from 'src/components/ImageUploader'
|
||||||
|
import { ImageFallback } from '../ImageUploader/ImageUploader'
|
||||||
|
|
||||||
const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
||||||
<li
|
<li
|
||||||
@@ -31,9 +32,7 @@ const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
|||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
<ImageUploader
|
<ImageFallback
|
||||||
className=""
|
|
||||||
aspectRatio={1}
|
|
||||||
imageUrl={user?.image}
|
imageUrl={user?.image}
|
||||||
width={50}
|
width={50}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -17,51 +17,7 @@ import { useIdeInit } from 'src/components/EncodedUrl/helpers'
|
|||||||
import ProfileViewer from '../ProfileViewer/ProfileViewer'
|
import ProfileViewer from '../ProfileViewer/ProfileViewer'
|
||||||
import Svg from 'src/components/Svg/Svg'
|
import Svg from 'src/components/Svg/Svg'
|
||||||
import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessage/OpenscadStaticImageMessage'
|
import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessage/OpenscadStaticImageMessage'
|
||||||
|
import KeyValue from 'src/components/KeyValue/KeyValue'
|
||||||
const KeyValue = ({
|
|
||||||
keyName,
|
|
||||||
children,
|
|
||||||
hide = false,
|
|
||||||
canEdit = false,
|
|
||||||
onEdit,
|
|
||||||
isEditable = false,
|
|
||||||
}: {
|
|
||||||
keyName: string
|
|
||||||
children: React.ReactNode
|
|
||||||
hide?: boolean
|
|
||||||
canEdit?: boolean
|
|
||||||
onEdit?: () => void
|
|
||||||
isEditable?: boolean
|
|
||||||
}) => {
|
|
||||||
if (!children || hide) return null
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="text-ch-blue-400 font-fira-code flex text-sm whitespace-nowrap">
|
|
||||||
{keyName}
|
|
||||||
{canEdit &&
|
|
||||||
(isEditable ? (
|
|
||||||
<button
|
|
||||||
className="font-fira-sans items-center ml-4 grid grid-flow-col-dense p-px px-2 gap-2 bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 rounded-sm border border-ch-purple-400"
|
|
||||||
id="rename-button"
|
|
||||||
onClick={onEdit}
|
|
||||||
>
|
|
||||||
<Svg
|
|
||||||
name="check"
|
|
||||||
className="w-6 h-6 text-ch-purple-500"
|
|
||||||
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 className="text-ch-gray-300">{children}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectProfile = ({
|
const ProjectProfile = ({
|
||||||
userProject,
|
userProject,
|
||||||
@@ -140,7 +96,7 @@ const ProjectProfile = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Side panel */}
|
{/* Side panel */}
|
||||||
<div className="bg-ch-gray-760 font-fira-sans px-20 pt-12 overflow-y-auto">
|
<div className="bg-ch-gray-760 font-fira-sans px-20 pt-12 overflow-y-auto ch-scrollbar">
|
||||||
<div className="grid grid-flow-row-dense gap-6">
|
<div className="grid grid-flow-row-dense gap-6">
|
||||||
<h3 className="text-5xl capitalize text-ch-gray-300">
|
<h3 className="text-5xl capitalize text-ch-gray-300">
|
||||||
{project?.title.replace(/-/g, ' ')}
|
{project?.title.replace(/-/g, ' ')}
|
||||||
@@ -301,3 +257,4 @@ const ProjectProfile = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectProfile
|
export default ProjectProfile
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const ProjectsList = ({
|
|||||||
return (
|
return (
|
||||||
<section className="max-w-7xl mx-auto">
|
<section className="max-w-7xl mx-auto">
|
||||||
<ul
|
<ul
|
||||||
className="grid gap-x-8 gap-y-8 items-center mx-4 relative"
|
className="grid gap-x-8 gap-y-8 items-center relative"
|
||||||
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
|
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
|
||||||
>
|
>
|
||||||
{filteredProjects.map(
|
{filteredProjects.map(
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { useAuth } from '@redwoodjs/auth'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import Editor from 'rich-markdown-editor'
|
|
||||||
import ImageUploader from 'src/components/ImageUploader'
|
|
||||||
import Button from 'src/components/Button'
|
|
||||||
import ProfileTextInput from 'src/components/ProfileTextInput'
|
|
||||||
import ProjectsOfUser from 'src/components/ProjectsOfUserCell'
|
|
||||||
|
|
||||||
const UserProfile = ({
|
|
||||||
user,
|
|
||||||
isEditable,
|
|
||||||
loading,
|
|
||||||
onSave,
|
|
||||||
error,
|
|
||||||
projects,
|
|
||||||
}) => {
|
|
||||||
const { currentUser } = useAuth()
|
|
||||||
const canEdit = currentUser?.sub === user.id
|
|
||||||
const isImageEditable = !isEditable && canEdit // image is editable when not in profile edit mode in order to separate them as it's too hard too to upload an image to cloudinary temporarily until the use saves (and maybe have to clean up) for the time being
|
|
||||||
useEffect(() => {
|
|
||||||
isEditable && !canEdit && navigate(routes.user({ userName: user.userName }))
|
|
||||||
}, [currentUser])
|
|
||||||
const [input, setInput] = useState({
|
|
||||||
userName: user.userName,
|
|
||||||
name: user.name,
|
|
||||||
bio: user.bio,
|
|
||||||
image: user.image,
|
|
||||||
})
|
|
||||||
const { userName, name } = input
|
|
||||||
const editableTextFields = { userName, name }
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section className="max-w-2xl mx-auto mt-20 ">
|
|
||||||
<div className="flex">
|
|
||||||
{!isEditable && (
|
|
||||||
<div className="w-40 flex-shrink-0">
|
|
||||||
<ImageUploader
|
|
||||||
className="rounded-half rounded-br-lg shadow-md border-2 border-gray-200 border-solid"
|
|
||||||
onImageUpload={({ cloudinaryPublicId: image }) => {
|
|
||||||
onSave(user.userName, {
|
|
||||||
...input,
|
|
||||||
image,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
aspectRatio={1}
|
|
||||||
isEditable={isImageEditable}
|
|
||||||
imageUrl={user.image}
|
|
||||||
width={300}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="ml-6 flex flex-col justify-between">
|
|
||||||
<ProfileTextInput
|
|
||||||
fields={editableTextFields}
|
|
||||||
onChange={({ userName, name }) =>
|
|
||||||
setInput({
|
|
||||||
...input,
|
|
||||||
name,
|
|
||||||
userName: userName.replace(/([^a-zA-Z\d_:])/g, '-'),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isEditable={isEditable}
|
|
||||||
/>
|
|
||||||
{isEditable ? (
|
|
||||||
<Button
|
|
||||||
className="bg-indigo-200"
|
|
||||||
iconName="plus"
|
|
||||||
onClick={() => onSave(user.userName, input)}
|
|
||||||
>
|
|
||||||
Save Profile
|
|
||||||
</Button> // TODO replace pencil with a save icon
|
|
||||||
) : canEdit ? (
|
|
||||||
<Button
|
|
||||||
className="bg-indigo-200"
|
|
||||||
iconName="pencil"
|
|
||||||
onClick={() =>
|
|
||||||
navigate(routes.editUser({ userName: user.userName }))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Edit Profile
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-10">
|
|
||||||
<h3 className="text-3xl text-gray-500 font-ropa-sans">Bio:</h3>
|
|
||||||
<div
|
|
||||||
name="description"
|
|
||||||
className="markdown-overrides rounded-lg shadow-md bg-white p-12 my-6 min-h-md"
|
|
||||||
>
|
|
||||||
<Editor
|
|
||||||
defaultValue={user.bio || ''}
|
|
||||||
readOnly={!isEditable}
|
|
||||||
onChange={(bioFn) =>
|
|
||||||
setInput({
|
|
||||||
...input,
|
|
||||||
bio: bioFn(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-10">
|
|
||||||
<h3 className="text-3xl text-gray-500 font-ropa-sans">Projects:</h3>
|
|
||||||
<ProjectsOfUser userName={user?.userName} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserProfile
|
|
||||||
112
app/web/src/components/UserProfile/UserProfile.tsx
Normal file
112
app/web/src/components/UserProfile/UserProfile.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { useState, useEffect, useRef, useReducer, ReactNode } from 'react'
|
||||||
|
import { useAuth } from '@redwoodjs/auth'
|
||||||
|
import { Link, navigate, routes } from '@redwoodjs/router'
|
||||||
|
import ProjectsOfUser from 'src/components/ProjectsOfUserCell'
|
||||||
|
import IdeHeader from 'src/components/IdeHeader/IdeHeader'
|
||||||
|
import Svg from 'src/components/Svg/Svg'
|
||||||
|
import { fieldsConfig, fieldReducer, UserProfileType, FieldConfigType } from './userProfileConfig'
|
||||||
|
|
||||||
|
function buildFieldsConfig(fieldsConfig, user) {
|
||||||
|
Object.entries(fieldsConfig).forEach(([key, field] : [string, FieldConfigType]) => {
|
||||||
|
field.currentValue = field.newValue = user[key]
|
||||||
|
field.name = key
|
||||||
|
})
|
||||||
|
|
||||||
|
return fieldsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const UserProfile = ({
|
||||||
|
user,
|
||||||
|
isEditable,
|
||||||
|
loading,
|
||||||
|
onSave,
|
||||||
|
error,
|
||||||
|
projects,
|
||||||
|
} : UserProfileType) => {
|
||||||
|
const { currentUser } = useAuth()
|
||||||
|
const hasEditPermission = currentUser?.sub === user.id
|
||||||
|
useEffect(() => {
|
||||||
|
isEditable && !hasEditPermission && navigate(routes.user({ userName: user.userName }))
|
||||||
|
}, [currentUser])
|
||||||
|
|
||||||
|
const initializedFields = buildFieldsConfig(fieldsConfig, user)
|
||||||
|
const [fields, fieldDispatch] = useReducer(fieldReducer, initializedFields)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="h-screen flex flex-col text-lg font-fira-sans">
|
||||||
|
<div className="flex">
|
||||||
|
<Link
|
||||||
|
to={routes.home()}
|
||||||
|
className="w-16 h-16 flex items-center justify-center bg-ch-gray-900"
|
||||||
|
>
|
||||||
|
<Svg className="w-12" name="favicon" />
|
||||||
|
</Link>
|
||||||
|
<IdeHeader
|
||||||
|
handleRender={() => {}}
|
||||||
|
projectOwner={user?.userName}
|
||||||
|
projectOwnerImage={user?.image}
|
||||||
|
projectOwnerId={user?.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex-grow h-full">
|
||||||
|
<div className="grid md:grid-cols-profile-layout grid-flow-row-dense absolute inset-0">
|
||||||
|
{/* Side panel */}
|
||||||
|
<section className="bg-ch-gray-760 font-fira-sans px-12 pt-12 overflow-y-auto ch-scrollbar">
|
||||||
|
<div className="flex gap-6">
|
||||||
|
{!isEditable && (
|
||||||
|
<div className="w-28 flex-shrink-0">
|
||||||
|
<fields.image.component
|
||||||
|
field={fields.image}
|
||||||
|
user={user}
|
||||||
|
save={onSave}
|
||||||
|
hasEditPermission={hasEditPermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<fields.name.component
|
||||||
|
field={fields.name}
|
||||||
|
dispatch={fieldDispatch}
|
||||||
|
user={user}
|
||||||
|
save={onSave}
|
||||||
|
hasEditPermission={hasEditPermission}
|
||||||
|
/>
|
||||||
|
<fields.userName.component
|
||||||
|
field={fields.userName}
|
||||||
|
dispatch={fieldDispatch}
|
||||||
|
user={user}
|
||||||
|
save={onSave}
|
||||||
|
hasEditPermission={hasEditPermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10">
|
||||||
|
<fields.bio.component
|
||||||
|
field={fields.bio}
|
||||||
|
dispatch={fieldDispatch}
|
||||||
|
user={user}
|
||||||
|
save={onSave}
|
||||||
|
hasEditPermission={hasEditPermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="my-5">
|
||||||
|
<fields.createdAt.component
|
||||||
|
field={fields.createdAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/* Viewer */}
|
||||||
|
<div className="py-10 px-8 w-full min-h-md relative bg-ch-gray-800 overflow-auto ch-scrollbar">
|
||||||
|
<h3 className="text-2xl text-ch-gray-500 mb-4">Projects</h3>
|
||||||
|
<ProjectsOfUser userName={user?.userName} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserProfile
|
||||||
189
app/web/src/components/UserProfile/userProfileConfig.tsx
Normal file
189
app/web/src/components/UserProfile/userProfileConfig.tsx
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import React, { ReactNode, useRef } from 'react'
|
||||||
|
import KeyValue from 'src/components/KeyValue/KeyValue'
|
||||||
|
import InputText from '../InputText/InputText'
|
||||||
|
import Editor from 'rich-markdown-editor'
|
||||||
|
import ImageUploader from 'src/components/ImageUploader'
|
||||||
|
import { User } from 'types/graphql'
|
||||||
|
|
||||||
|
|
||||||
|
export interface UserProfileType {
|
||||||
|
user: User,
|
||||||
|
isEditable: boolean,
|
||||||
|
loading: boolean,
|
||||||
|
error: boolean,
|
||||||
|
onSave: Function,
|
||||||
|
projects: {}[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldConfigType {
|
||||||
|
name?: string, // introspection ugh
|
||||||
|
editable: boolean,
|
||||||
|
component?: ReactNode,
|
||||||
|
needsRef?: boolean,
|
||||||
|
isEditing?: boolean | undefined,
|
||||||
|
onSave?: Function,
|
||||||
|
currentValue?: any,
|
||||||
|
newValue?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileKeyValue = ({ field, dispatch, user, save, hasEditPermission, children, bottom = false }) => {
|
||||||
|
return (
|
||||||
|
<KeyValue
|
||||||
|
keyName={field.name}
|
||||||
|
hide={!user[field.name] && !hasEditPermission}
|
||||||
|
canEdit={hasEditPermission}
|
||||||
|
onEdit={() => {
|
||||||
|
if (field.isEditing) {
|
||||||
|
save(user.userName, { [field.name]: field.newValue })
|
||||||
|
}
|
||||||
|
dispatch({ type: "SET_CURRENT_VALUE", payload: { field: field.name, value: field.newValue }})
|
||||||
|
dispatch({ type: "TOGGLE_EDITING", payload: field.name })
|
||||||
|
}}
|
||||||
|
isEditable={hasEditPermission && field.isEditing}
|
||||||
|
bottom={bottom}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</KeyValue>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bioField : FieldConfigType = {
|
||||||
|
editable: true,
|
||||||
|
needsRef: true,
|
||||||
|
component: (props) => {
|
||||||
|
const ref = useRef(null)
|
||||||
|
|
||||||
|
const { dispatch, field } = props
|
||||||
|
|
||||||
|
return <ProfileKeyValue {...props}>
|
||||||
|
<div
|
||||||
|
id="bio-wrap"
|
||||||
|
name="bio"
|
||||||
|
className={
|
||||||
|
'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: field.bio, value: bio() }})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ProfileKeyValue>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAtField : FieldConfigType = {
|
||||||
|
editable: false,
|
||||||
|
component: (props) => {
|
||||||
|
const { field } = props
|
||||||
|
|
||||||
|
return <KeyValue keyName="Member Since">
|
||||||
|
<p className="text-ch-gray-300">{ new Date(field.currentValue).toLocaleDateString() }</p>
|
||||||
|
</KeyValue>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageField : FieldConfigType = {
|
||||||
|
editable: true,
|
||||||
|
component: (props) => {
|
||||||
|
const { field, user, save, hasEditPermission } = 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={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) {
|
||||||
|
switch (action.type) {
|
||||||
|
case "TOGGLE_EDITING":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[action.payload]: {
|
||||||
|
...state[action.payload],
|
||||||
|
isEditing: (state[action.payload].editable && !state[action.payload].isEditing) ? true : false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "SET_NEW_VALUE":
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
[action.payload.field]: {
|
||||||
|
...state[action.payload.field],
|
||||||
|
newValue: action.payload.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newState
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,19 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'Fira Sans', ui-sans-serif, system-ui, -apple-system, system-ui, "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
font-family: 'Fira Sans', ui-sans-serif, system-ui, -apple-system, system-ui, "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* custom scrollbar */
|
||||||
|
.ch-scrollbar::-webkit-scrollbar {
|
||||||
|
@apply w-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-scrollbar::-webkit-scrollbar-track {
|
||||||
|
@apply bg-ch-gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-60;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
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'
|
||||||
|
|
||||||
const UserPage = ({ userName }) => {
|
const UserPage = ({ userName }) => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<>
|
||||||
<Seo title={userName} description="Add new project page" lang="en-US" />
|
<Seo title={userName} description="Add new project page" lang="en-US" />
|
||||||
|
|
||||||
<EditUserCell userName={userName} isEditable />
|
<EditUserCell userName={userName} isEditable />
|
||||||
</MainLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import Seo from 'src/components/Seo/Seo'
|
|||||||
|
|
||||||
const UserPage = ({ userName }) => {
|
const UserPage = ({ userName }) => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<>
|
||||||
<Seo title={userName} description="User page" lang="en-US" />
|
<Seo title={userName} description="User page" lang="en-US" />
|
||||||
|
|
||||||
<EditUserCell userName={userName} />
|
<EditUserCell userName={userName} />
|
||||||
</MainLayout>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user