Linting fixes
This commit is contained in:
@@ -95,16 +95,17 @@ const IdeHeader = ({
|
|||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
<div className="text-gray-200 grid grid-flow-col-dense gap-4 mr-4 items-center">
|
<div className="text-gray-200 grid grid-flow-col-dense gap-4 mr-4 items-center">
|
||||||
{ (!children)
|
{!children ? (
|
||||||
? <DefaultTopButtons
|
<DefaultTopButtons
|
||||||
project={project}
|
project={project}
|
||||||
projectTitle={projectTitle}
|
projectTitle={projectTitle}
|
||||||
_projectOwner={_projectOwner}
|
_projectOwner={_projectOwner}
|
||||||
handleRender={handleRender}
|
handleRender={handleRender}
|
||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
/>
|
/>
|
||||||
: children
|
) : (
|
||||||
}
|
children
|
||||||
|
)}
|
||||||
{/* <TopButton>Fork</TopButton> */}
|
{/* <TopButton>Fork</TopButton> */}
|
||||||
<div className="h-8 w-8">
|
<div className="h-8 w-8">
|
||||||
<NavPlusButton />
|
<NavPlusButton />
|
||||||
@@ -117,88 +118,95 @@ const IdeHeader = ({
|
|||||||
|
|
||||||
export default IdeHeader
|
export default IdeHeader
|
||||||
|
|
||||||
|
function DefaultTopButtons({
|
||||||
function DefaultTopButtons({ project, projectTitle, _projectOwner, handleRender, canEdit }) {
|
project,
|
||||||
return (<>
|
projectTitle,
|
||||||
{canEdit && !projectTitle && (
|
_projectOwner,
|
||||||
<CaptureButton
|
handleRender,
|
||||||
canEdit={canEdit}
|
canEdit,
|
||||||
projectTitle={project?.title}
|
}) {
|
||||||
userName={project?.user?.userName}
|
return (
|
||||||
shouldUpdateImage={!project?.mainImage}
|
<>
|
||||||
TheButton={({ onClick }) => (
|
{canEdit && !projectTitle && (
|
||||||
<TopButton
|
<CaptureButton
|
||||||
onClick={onClick}
|
canEdit={canEdit}
|
||||||
name="Save Project Image"
|
projectTitle={project?.title}
|
||||||
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
userName={project?.user?.userName}
|
||||||
>
|
shouldUpdateImage={!project?.mainImage}
|
||||||
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
|
TheButton={({ onClick }) => (
|
||||||
</TopButton>
|
<TopButton
|
||||||
)}
|
onClick={onClick}
|
||||||
/>
|
name="Save Project Image"
|
||||||
)}
|
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||||
{!projectTitle && (
|
>
|
||||||
<TopButton
|
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
|
||||||
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
</TopButton>
|
||||||
onClick={handleRender}
|
)}
|
||||||
name={canEdit ? 'Save' : 'Preview'}
|
|
||||||
>
|
|
||||||
<Svg
|
|
||||||
name={canEdit ? 'floppy-disk' : 'photograph'}
|
|
||||||
className="w-6 h-6 text-ch-pink-500"
|
|
||||||
/>
|
/>
|
||||||
</TopButton>
|
)}
|
||||||
)}
|
{!projectTitle && (
|
||||||
{projectTitle && (
|
<TopButton
|
||||||
<TopButton
|
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||||
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
onClick={handleRender}
|
||||||
onClick={() =>
|
name={canEdit ? 'Save' : 'Preview'}
|
||||||
navigate(routes.ide({ userName: _projectOwner, projectTitle }))
|
>
|
||||||
}
|
<Svg
|
||||||
name="Editor"
|
name={canEdit ? 'floppy-disk' : 'photograph'}
|
||||||
>
|
className="w-6 h-6 text-ch-pink-500"
|
||||||
<Svg name="terminal" className="w-6 h-6 text-ch-pink-500" />
|
/>
|
||||||
</TopButton>
|
</TopButton>
|
||||||
)}
|
)}
|
||||||
<Popover className="relative outline-none w-full h-full">
|
{projectTitle && (
|
||||||
{({ open }) => {
|
<TopButton
|
||||||
return (
|
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||||
<>
|
onClick={() =>
|
||||||
<Popover.Button className="h-full w-full outline-none">
|
navigate(routes.ide({ userName: _projectOwner, projectTitle }))
|
||||||
<TopButton
|
}
|
||||||
Tag="div"
|
name="Editor"
|
||||||
name="Share"
|
>
|
||||||
className=" bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
<Svg name="terminal" className="w-6 h-6 text-ch-pink-500" />
|
||||||
>
|
</TopButton>
|
||||||
<Svg
|
)}
|
||||||
name="share"
|
<Popover className="relative outline-none w-full h-full">
|
||||||
className="w-6 h-6 text-ch-purple-500 mt-1"
|
{({ open }) => {
|
||||||
/>
|
return (
|
||||||
</TopButton>
|
<>
|
||||||
</Popover.Button>
|
<Popover.Button className="h-full w-full outline-none">
|
||||||
{open && (
|
<TopButton
|
||||||
<Popover.Panel className="absolute z-10 mt-4 right-0">
|
Tag="div"
|
||||||
<Tabs
|
name="Share"
|
||||||
className="bg-ch-purple-gray-200 rounded-md shadow-md overflow-hidden text-gray-700"
|
className=" bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
|
||||||
selectedTabClassName="bg-ch-gray-700 text-white"
|
|
||||||
>
|
>
|
||||||
<TabPanel>
|
<Svg
|
||||||
<FullScriptEncoding />
|
name="share"
|
||||||
</TabPanel>
|
className="w-6 h-6 text-ch-purple-500 mt-1"
|
||||||
<TabPanel>
|
/>
|
||||||
<ExternalScript />
|
</TopButton>
|
||||||
</TabPanel>
|
</Popover.Button>
|
||||||
|
{open && (
|
||||||
|
<Popover.Panel className="absolute z-10 mt-4 right-0">
|
||||||
|
<Tabs
|
||||||
|
className="bg-ch-purple-gray-200 rounded-md shadow-md overflow-hidden text-gray-700"
|
||||||
|
selectedTabClassName="bg-ch-gray-700 text-white"
|
||||||
|
>
|
||||||
|
<TabPanel>
|
||||||
|
<FullScriptEncoding />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<ExternalScript />
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">
|
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">
|
||||||
<Tab className="p-3 px-5">encoded script</Tab>
|
<Tab className="p-3 px-5">encoded script</Tab>
|
||||||
<Tab className="p-3 px-5">external script</Tab>
|
<Tab className="p-3 px-5">external script</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</Popover>
|
</Popover>
|
||||||
</>)
|
</>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,17 +11,24 @@ import Svg from 'src/components/Svg'
|
|||||||
const CLOUDINARY_UPLOAD_PRESET = 'CadHub_project_images'
|
const CLOUDINARY_UPLOAD_PRESET = 'CadHub_project_images'
|
||||||
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload'
|
const CLOUDINARY_UPLOAD_URL = 'https://api.cloudinary.com/v1_1/irevdev/upload'
|
||||||
|
|
||||||
export function ImageFallback({ width = 100, imageId = 'CadHub/eia1kwru54g2kf02s2xx', className = '' }) {
|
export function ImageFallback({
|
||||||
|
width = 100,
|
||||||
|
imageId = 'CadHub/eia1kwru54g2kf02s2xx',
|
||||||
|
className = '',
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-hidden w-full h-full">
|
<div className="relative overflow-hidden w-full h-full">
|
||||||
<CloudinaryImage
|
<CloudinaryImage
|
||||||
className={"object-cover w-full h-full shadow overflow-hidden " + className }
|
className={
|
||||||
cloudName="irevdev"
|
'object-cover w-full h-full shadow overflow-hidden ' + className
|
||||||
publicId={imageId}
|
}
|
||||||
width={width}
|
cloudName="irevdev"
|
||||||
crop="scale"
|
publicId={imageId}
|
||||||
/>
|
width={width}
|
||||||
</div>)
|
crop="scale"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ImageUploader({
|
export default function ImageUploader({
|
||||||
@@ -82,10 +89,7 @@ export default function ImageUploader({
|
|||||||
{cloudinaryId && isEditable && (
|
{cloudinaryId && isEditable && (
|
||||||
<button className="w-full py-1 absolute z-10 bg-ch-blue-650 bg-opacity-50 hover:bg-opacity-80 bottom-0 right-0 left-0 flex items-center justify-center text-ch-gray-300">
|
<button className="w-full py-1 absolute z-10 bg-ch-blue-650 bg-opacity-50 hover:bg-opacity-80 bottom-0 right-0 left-0 flex items-center justify-center text-ch-gray-300">
|
||||||
<span className="font-fira-code text-sm leading-4">Update</span>
|
<span className="font-fira-code text-sm leading-4">Update</span>
|
||||||
<Svg
|
<Svg name="pencil-solid" className=" h-4 w-4 ml-4 mb-2" />
|
||||||
name="pencil-solid"
|
|
||||||
className=" h-4 w-4 ml-4 mb-2"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEditable && <input {...getInputProps()} />}
|
{isEditable && <input {...getInputProps()} />}
|
||||||
|
|||||||
@@ -12,43 +12,46 @@ interface KeyValueType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const KeyValue = ({
|
const KeyValue = ({
|
||||||
keyName,
|
keyName,
|
||||||
children,
|
children,
|
||||||
hide = false,
|
hide = false,
|
||||||
canEdit = false,
|
canEdit = false,
|
||||||
onEdit,
|
onEdit,
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
bottom = false,
|
bottom = false,
|
||||||
className = "",
|
className = '',
|
||||||
} : KeyValueType) => {
|
}: KeyValueType) => {
|
||||||
if (!children || hide) return null
|
if (!children || hide) return null
|
||||||
return (
|
return (
|
||||||
<div className={"flex flex-col " + className}>
|
<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" : "")}>
|
<div
|
||||||
<span className={isEditable ? "text-ch-blue-300" : ""}>{keyName}</span>
|
className={
|
||||||
{canEdit &&
|
'text-ch-blue-400 font-fira-code flex items-center leading-4 text-sm whitespace-nowrap ' +
|
||||||
(isEditable ? (
|
(bottom ? 'order-2' : '')
|
||||||
<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"
|
<span className={isEditable ? 'text-ch-blue-300' : ''}>{keyName}</span>
|
||||||
onClick={onEdit}
|
{canEdit &&
|
||||||
>
|
(isEditable ? (
|
||||||
<Svg
|
<button
|
||||||
name="check"
|
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"
|
||||||
className="w-6 h-6"
|
id="rename-button"
|
||||||
strokeWidth={3}
|
onClick={onEdit}
|
||||||
/>
|
>
|
||||||
<span>Update</span>
|
<Svg name="check" className="w-6 h-6" strokeWidth={3} />
|
||||||
</button>
|
<span>Update</span>
|
||||||
) : (
|
</button>
|
||||||
<button onClick={onEdit}>
|
) : (
|
||||||
<Svg name="pencil-solid" className="h-4 w-4 ml-4 mb-2" />
|
<button onClick={onEdit}>
|
||||||
</button>
|
<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>
|
</div>
|
||||||
)
|
<div className={'text-ch-gray-300 ' + (bottom ? 'mb-1' : 'mt-1')}>
|
||||||
}
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default KeyValue
|
export default KeyValue
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ const NavPlusButton: React.FC = () => {
|
|||||||
' px-4 py-1 my-4 bg-opacity-30 hover:bg-opacity-70 grid grid-flow-col-dense items-center gap-2'
|
' px-4 py-1 my-4 bg-opacity-30 hover:bg-opacity-70 grid grid-flow-col-dense items-center gap-2'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={dotClasses + " justify-self-center w-5 h-5 rounded-full"}></div>
|
<div
|
||||||
|
className={
|
||||||
|
dotClasses + ' justify-self-center w-5 h-5 rounded-full'
|
||||||
|
}
|
||||||
|
></div>
|
||||||
<Link to={routes.draftProject({ cadPackage: ideType })}>
|
<Link to={routes.draftProject({ cadPackage: ideType })}>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
<div className="text-xs text-ch-gray-400 font-light">{sub}</div>
|
<div className="text-xs text-ch-gray-400 font-light">{sub}</div>
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ const ProfileSlashLogin = () => {
|
|||||||
<Popover className="relative outline-none h-8 w-8">
|
<Popover className="relative outline-none h-8 w-8">
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
disabled={!isAuthenticated || !currentUser}
|
disabled={!isAuthenticated || !currentUser}
|
||||||
className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full">
|
className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full"
|
||||||
|
>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<ImageFallback
|
<ImageFallback
|
||||||
width={80}
|
width={80}
|
||||||
@@ -55,8 +56,8 @@ const ProfileSlashLogin = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
{ currentUser && (
|
{currentUser && (
|
||||||
<Popover.Panel className="w-48 absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
<Popover.Panel className="w-48 absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
||||||
<Link to={routes.user({ userName: user?.userName })}>
|
<Link to={routes.user({ userName: user?.userName })}>
|
||||||
<h3 className="text-lg hover:text-ch-pink-300">
|
<h3 className="text-lg hover:text-ch-pink-300">
|
||||||
Hello {user?.name}
|
Hello {user?.name}
|
||||||
@@ -65,14 +66,18 @@ const ProfileSlashLogin = () => {
|
|||||||
<hr className="my-2" />
|
<hr className="my-2" />
|
||||||
<Link
|
<Link
|
||||||
className="my-2 mt-4 block hover:text-ch-pink-300"
|
className="my-2 mt-4 block hover:text-ch-pink-300"
|
||||||
to={routes.user({ userName: user?.userName })}>
|
to={routes.user({ userName: user?.userName })}
|
||||||
|
>
|
||||||
<div>View Your Profile</div>
|
<div>View Your Profile</div>
|
||||||
</Link>
|
</Link>
|
||||||
<a href="#" onClick={logOut}
|
<a
|
||||||
className="text-ch-gray-400 hover:text-ch-pink-300">
|
href="#"
|
||||||
|
onClick={logOut}
|
||||||
|
className="text-ch-gray-400 hover:text-ch-pink-300"
|
||||||
|
>
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</a>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
|||||||
<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} // http://res.cloudinary.com/irevdev/image/upload/c_scale,w_50/v1/CadHub/bc7smqwo9qqmrloyf9xr
|
||||||
width={80} // http://res.cloudinary.com/irevdev/image/upload/c_scale,w_300/v1/CadHub/bc7smqwo9qqmrloyf9xr
|
width={80} // http://res.cloudinary.com/irevdev/image/upload/c_scale,w_300/v1/CadHub/bc7smqwo9qqmrloyf9xr
|
||||||
/>
|
/>
|
||||||
</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">
|
||||||
|
|||||||
@@ -257,4 +257,3 @@ const ProjectProfile = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ProjectProfile
|
export default ProjectProfile
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,24 @@ 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 { fieldsConfig, fieldReducer, UserProfileType, FieldConfigType } from './userProfileConfig'
|
import {
|
||||||
|
fieldsConfig,
|
||||||
|
fieldReducer,
|
||||||
|
UserProfileType,
|
||||||
|
FieldConfigType,
|
||||||
|
} from './userProfileConfig'
|
||||||
|
|
||||||
function buildFieldsConfig(fieldsConfig, user) {
|
function buildFieldsConfig(fieldsConfig, user) {
|
||||||
Object.entries(fieldsConfig).forEach(([key, field] : [string, FieldConfigType]) => {
|
Object.entries(fieldsConfig).forEach(
|
||||||
field.currentValue = field.newValue = user[key]
|
([key, field]: [string, FieldConfigType]) => {
|
||||||
field.name = key
|
field.currentValue = field.newValue = user[key]
|
||||||
})
|
field.name = key
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return fieldsConfig
|
return fieldsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const UserProfile = ({
|
const UserProfile = ({
|
||||||
user,
|
user,
|
||||||
isEditable,
|
isEditable,
|
||||||
@@ -23,11 +29,13 @@ const UserProfile = ({
|
|||||||
onSave,
|
onSave,
|
||||||
error,
|
error,
|
||||||
projects,
|
projects,
|
||||||
} : UserProfileType) => {
|
}: UserProfileType) => {
|
||||||
const { currentUser } = useAuth()
|
const { currentUser } = useAuth()
|
||||||
const hasEditPermission = currentUser?.sub === user.id
|
const hasEditPermission = currentUser?.sub === user.id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isEditable && !hasEditPermission && navigate(routes.user({ userName: user.userName }))
|
isEditable &&
|
||||||
|
!hasEditPermission &&
|
||||||
|
navigate(routes.user({ userName: user.userName }))
|
||||||
}, [currentUser])
|
}, [currentUser])
|
||||||
|
|
||||||
const initializedFields = buildFieldsConfig(fieldsConfig, user)
|
const initializedFields = buildFieldsConfig(fieldsConfig, user)
|
||||||
@@ -49,8 +57,7 @@ const UserProfile = ({
|
|||||||
projectOwnerImage={user?.image}
|
projectOwnerImage={user?.image}
|
||||||
projectOwnerId={user?.id}
|
projectOwnerId={user?.id}
|
||||||
>
|
>
|
||||||
|
<span></span>
|
||||||
<span></span>
|
|
||||||
</IdeHeader>
|
</IdeHeader>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-grow h-full">
|
<div className="relative flex-grow h-full">
|
||||||
@@ -63,7 +70,7 @@ const UserProfile = ({
|
|||||||
<fields.image.component
|
<fields.image.component
|
||||||
field={fields.image}
|
field={fields.image}
|
||||||
user={user}
|
user={user}
|
||||||
save={onSave}
|
save={onSave}
|
||||||
hasEditPermission={hasEditPermission}
|
hasEditPermission={hasEditPermission}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,14 +102,14 @@ const UserProfile = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
<fields.createdAt.component
|
<fields.createdAt.component field={fields.createdAt} />
|
||||||
field={fields.createdAt}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/* Viewer */}
|
{/* Viewer */}
|
||||||
<div className="py-10 px-8 w-full h-full relative bg-ch-gray-800 md:overflow-y-auto ch-scrollbar">
|
<div className="py-10 px-8 w-full h-full relative bg-ch-gray-800 md:overflow-y-auto ch-scrollbar">
|
||||||
<h3 className="text-2xl text-ch-gray-500 mb-4 md:hidden">Projects</h3>
|
<h3 className="text-2xl text-ch-gray-500 mb-4 md:hidden">
|
||||||
|
Projects
|
||||||
|
</h3>
|
||||||
<ProjectsOfUser userName={user?.userName} />
|
<ProjectsOfUser userName={user?.userName} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,149 +5,187 @@ 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 interface UserProfileType {
|
export interface UserProfileType {
|
||||||
user: User,
|
user: User
|
||||||
isEditable: boolean,
|
isEditable: boolean
|
||||||
loading: boolean,
|
loading: boolean
|
||||||
error: boolean,
|
error: boolean
|
||||||
onSave: Function,
|
onSave: Function
|
||||||
projects: {}[],
|
projects: {}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConfigType {
|
export interface FieldConfigType {
|
||||||
name?: string, // introspection ugh
|
name?: string // introspection ugh
|
||||||
editable: boolean,
|
editable: boolean
|
||||||
component?: ReactNode,
|
component?: ReactNode
|
||||||
needsRef?: boolean,
|
needsRef?: boolean
|
||||||
isEditing?: boolean | undefined,
|
isEditing?: boolean | undefined
|
||||||
onSave?: Function,
|
onSave?: Function
|
||||||
currentValue?: any,
|
currentValue?: any
|
||||||
newValue?: any,
|
newValue?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileKeyValue = ({ field, dispatch, user, save, hasEditPermission, children, bottom = false }) => {
|
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 (
|
return (
|
||||||
<KeyValue
|
<ProfileKeyValue {...props}>
|
||||||
keyName={field.name}
|
<div
|
||||||
hide={!user[field.name] && !hasEditPermission}
|
id="bio-wrap"
|
||||||
canEdit={hasEditPermission}
|
name="bio"
|
||||||
onEdit={() => {
|
className={
|
||||||
if (field.isEditing) {
|
'markdown-overrides rounded-sm pb-2 mt-2' +
|
||||||
save(user.userName, { [field.name]: field.newValue })
|
(field.isEditable ? ' min-h-md' : '')
|
||||||
}
|
}
|
||||||
dispatch({ type: "SET_CURRENT_VALUE", payload: { field: field.name, value: field.newValue }})
|
onClick={(e) =>
|
||||||
dispatch({ type: "TOGGLE_EDITING", payload: field.name })
|
e?.target?.id === 'bio-wrap' && ref?.current?.focusAtEnd()
|
||||||
}}
|
}
|
||||||
isEditable={hasEditPermission && field.isEditing}
|
>
|
||||||
bottom={bottom}
|
<Editor
|
||||||
className="mb-4"
|
ref={ref}
|
||||||
>
|
defaultValue={field?.currentValue || ''}
|
||||||
{ children }
|
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>
|
</KeyValue>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const bioField : FieldConfigType = {
|
const imageField: FieldConfigType = {
|
||||||
editable: true,
|
editable: true,
|
||||||
needsRef: true,
|
component: (props) => {
|
||||||
component: (props) => {
|
const { field, user, save, hasEditPermission } = props
|
||||||
const ref = useRef(null)
|
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 { dispatch, field } = props
|
const nameField: FieldConfigType = {
|
||||||
|
editable: true,
|
||||||
|
component: (props) => {
|
||||||
|
const { user, dispatch, field } = props
|
||||||
|
|
||||||
return <ProfileKeyValue {...props}>
|
return (
|
||||||
<div
|
<ProfileKeyValue {...props} bottom={true}>
|
||||||
id="bio-wrap"
|
{!field.isEditing ? (
|
||||||
name="bio"
|
<h1 className="text-4xl">{user?.name}</h1>
|
||||||
className={
|
) : (
|
||||||
'markdown-overrides rounded-sm pb-2 mt-2' +
|
<InputText
|
||||||
(field.isEditable ? ' min-h-md' : '')
|
className="text-xl"
|
||||||
|
value={field.newValue}
|
||||||
|
onChange={({ target: { value } }) =>
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_NEW_VALUE',
|
||||||
|
payload: { field: field.name, value },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
onClick={(e) =>
|
isEditable={!field.isEditable}
|
||||||
e?.target?.id === 'bio-wrap' &&
|
/>
|
||||||
ref?.current?.focusAtEnd()
|
)}
|
||||||
|
</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}
|
||||||
<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 = {
|
|
||||||
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 = {
|
export const fieldsConfig = {
|
||||||
@@ -166,24 +204,27 @@ export const fieldsConfig = {
|
|||||||
|
|
||||||
export function fieldReducer(state, action) {
|
export function fieldReducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "TOGGLE_EDITING":
|
case 'TOGGLE_EDITING':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[action.payload]: {
|
[action.payload]: {
|
||||||
...state[action.payload],
|
...state[action.payload],
|
||||||
isEditing: (state[action.payload].editable && !state[action.payload].isEditing) ? true : false,
|
isEditing:
|
||||||
}
|
state[action.payload].editable && !state[action.payload].isEditing
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case "SET_NEW_VALUE":
|
case 'SET_NEW_VALUE':
|
||||||
const newState = {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
[action.payload.field]: {
|
[action.payload.field]: {
|
||||||
...state[action.payload.field],
|
...state[action.payload.field],
|
||||||
newValue: action.payload.value,
|
newValue: action.payload.value,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
return newState
|
return newState
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
<Popover className="relative outline-none w-full h-full">
|
<Popover className="relative outline-none w-full h-full">
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
disabled={!isAuthenticated || !currentUser}
|
disabled={!isAuthenticated || !currentUser}
|
||||||
className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full">
|
className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full"
|
||||||
|
>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<ImageFallback
|
<ImageFallback
|
||||||
width={80}
|
width={80}
|
||||||
@@ -144,8 +145,8 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
{ currentUser && (
|
{currentUser && (
|
||||||
<Popover.Panel className="w-48 absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
<Popover.Panel className="w-48 absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
||||||
<Link to={routes.user({ userName: user?.userName })}>
|
<Link to={routes.user({ userName: user?.userName })}>
|
||||||
<h3 className="text-lg hover:text-ch-pink-300">
|
<h3 className="text-lg hover:text-ch-pink-300">
|
||||||
Hello {user?.name}
|
Hello {user?.name}
|
||||||
@@ -154,14 +155,18 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
<hr className="my-2" />
|
<hr className="my-2" />
|
||||||
<Link
|
<Link
|
||||||
className="my-2 mt-4 block hover:text-ch-pink-300"
|
className="my-2 mt-4 block hover:text-ch-pink-300"
|
||||||
to={routes.user({ userName: user?.userName })}>
|
to={routes.user({ userName: user?.userName })}
|
||||||
|
>
|
||||||
<div>View Your Profile</div>
|
<div>View Your Profile</div>
|
||||||
</Link>
|
</Link>
|
||||||
<a href="#" onClick={logOut}
|
<a
|
||||||
className="text-ch-gray-400 hover:text-ch-pink-300">
|
href="#"
|
||||||
|
onClick={logOut}
|
||||||
|
className="text-ch-gray-400 hover:text-ch-pink-300"
|
||||||
|
>
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</a>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ const AccountRecoveryPage = () => {
|
|||||||
className="grid items-center gap-2"
|
className="grid items-center gap-2"
|
||||||
style={{ gridTemplateColumns: 'auto 1fr' }}
|
style={{ gridTemplateColumns: 'auto 1fr' }}
|
||||||
>
|
>
|
||||||
<span className="capitalize text-ch-gray-300 text-sm">
|
<span className="capitalize text-ch-gray-300 text-sm">email</span>
|
||||||
email
|
|
||||||
</span>
|
|
||||||
<InputTextForm
|
<InputTextForm
|
||||||
className="text-xl"
|
className="text-xl"
|
||||||
name="email"
|
name="email"
|
||||||
|
|||||||
@@ -49,9 +49,7 @@ const UpdatePasswordPage = () => {
|
|||||||
required: true,
|
required: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="capitalize text-ch-gray-300 text-sm">
|
<span className="capitalize text-ch-gray-300 text-sm">confirm</span>
|
||||||
confirm
|
|
||||||
</span>
|
|
||||||
<InputTextForm
|
<InputTextForm
|
||||||
className="text-xl"
|
className="text-xl"
|
||||||
name="confirm"
|
name="confirm"
|
||||||
|
|||||||
Reference in New Issue
Block a user