Merge branch 'main' into sidebar-tray

This commit is contained in:
Kurt Hutten
2021-09-18 15:41:52 +10:00
23 changed files with 714 additions and 420 deletions

View File

@@ -22,6 +22,7 @@ module.exports = {
800: '#1A1A1D', 800: '#1A1A1D',
750: '#222222', 750: '#222222',
760: '#232532', 760: '#232532',
710: '#2B303C', // TODO: Use HSL so I stop adding grays to fix the warm/cool problem
700: '#2A3038', 700: '#2A3038',
600: '#3B3E4B', 600: '#3B3E4B',
550: '#63636A', 550: '#63636A',
@@ -66,6 +67,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%': {

View File

@@ -43,8 +43,7 @@ export default function AssetWithGooey({
<group ref={coffeeRef}> <group ref={coffeeRef}>
<mesh ref={mesh} scale={scaleArr} geometry={geo}> <mesh ref={mesh} scale={scaleArr} geometry={geo}>
<meshPhysicalMaterial <meshPhysicalMaterial
envMapIntensity={2} color="#FF6EBD"
color="#F472B6"
map={colorMap} map={colorMap}
clearcoat={0.5} clearcoat={0.5}
clearcoatRoughness={0.01} clearcoatRoughness={0.01}
@@ -59,6 +58,7 @@ export default function AssetWithGooey({
</group> </group>
<ambientLight intensity={2} /> <ambientLight intensity={2} />
<Gooey /> <Gooey />
<ambientLight intensity={1.8} />
</group> </group>
) )
} }
@@ -73,22 +73,22 @@ function Gooey() {
const dist = Math.random() * 3 + 2.5 const dist = Math.random() * 3 + 2.5
const x = randomSign(Math.random() * dist) const x = randomSign(Math.random() * dist)
const y = randomSign(Math.sqrt(dist * dist - x * x)) const y = randomSign(Math.sqrt(dist * dist - x * x))
const z = randomSign(Math.random() * 3) const z = randomSign(Math.random() * 2)
const position: [number, number, number] = [x, z, y] const position: [number, number, number] = [x, z, y]
const size = Math.random() * 0.8 + 0.1 const size = Math.random() * 0.8 + 0.1
const distort = Math.random() * 0.8 + 0.1 const distort = (size > .1) ? Math.random() * .6 * size + 0.2 : 0
const speed = (Math.random() * 0.8) / size / size + 0.1 const speed = (size > .1) ? (Math.random() * 0.8) / size / size + 0.1 : 0
return { position, size, distort, speed } return { position, size, distort, speed }
}) })
const secondSet = Array.from({ length: 5 }).map((_, index) => { const secondSet = Array.from({ length: 5 }).map((_, index) => {
const dist = Math.random() * 3 + 1.5 const dist = Math.random() * 3 + 1.5
const x = randomSign(Math.random() * dist) const x = randomSign(Math.random() * dist)
const y = randomSign(Math.sqrt(dist * dist - x * x)) const y = randomSign(Math.sqrt(dist * dist - x * x))
const z = randomSign(Math.random() * 3) const z = randomSign(Math.random() * 2)
const position: [number, number, number] = [x, z, y] const position: [number, number, number] = [x, z, y]
const size = Math.random() * 0.2 + 0.05 const size = Math.random() * 0.2 + 0.05
const distort = Math.random() * 0.8 + 0.1 const distort = (size > .1) ? Math.random() * .8 * size + 0.2 : 0
const speed = (Math.random() * 0.5) / size / size + 0.1 const speed = (size > .1) ? (Math.random() * 0.5) / size / size + 0.1 : 0
return { position, size, distort, speed } return { position, size, distort, speed }
}) })
return [...firstSet, ...secondSet] return [...firstSet, ...secondSet]

View File

@@ -11,6 +11,7 @@ import ProfileSlashLogin from 'src/components/ProfileSlashLogin'
import Gravatar from 'src/components/Gravatar/Gravatar' import Gravatar from 'src/components/Gravatar/Gravatar'
import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle' import EditableProjectTitle from 'src/components/EditableProjecTitle/EditableProjecTitle'
import CaptureButton from 'src/components/CaptureButton/CaptureButton' import CaptureButton from 'src/components/CaptureButton/CaptureButton'
import { ReactNode } from 'react'
const TopButton = ({ const TopButton = ({
onClick, onClick,
@@ -44,6 +45,7 @@ interface IdeHeaderProps {
projectOwnerId?: string projectOwnerId?: string
projectOwnerImage?: string projectOwnerImage?: string
projectId?: string projectId?: string
children?: ReactNode
} }
const IdeHeader = ({ const IdeHeader = ({
@@ -53,6 +55,7 @@ const IdeHeader = ({
projectOwnerImage, projectOwnerImage,
projectId, projectId,
projectOwnerId, projectOwnerId,
children,
}: IdeHeaderProps) => { }: IdeHeaderProps) => {
const { currentUser } = useAuth() const { currentUser } = useAuth()
const { project } = useIdeContext() const { project } = useIdeContext()
@@ -97,89 +100,19 @@ 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">
{canEdit && !projectTitle && ( {!children ? (
<CaptureButton <DefaultTopButtons
project={project}
projectTitle={projectTitle}
_projectOwner={_projectOwner}
handleRender={handleRender}
canEdit={canEdit} canEdit={canEdit}
projectTitle={project?.title}
userName={project?.user?.userName}
shouldUpdateImage={!project?.mainImage}
TheButton={({ onClick }) => (
<TopButton
onClick={onClick}
name="Save Project Image"
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
</TopButton>
)}
/> />
) : (
children
)} )}
{!projectTitle && (
<TopButton
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
onClick={handleRender}
name={canEdit ? 'Save' : 'Preview'}
>
<Svg
name={canEdit ? 'floppy-disk' : 'photograph'}
className="w-6 h-6 text-ch-pink-500"
/>
</TopButton>
)}
{projectTitle && (
<TopButton
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
onClick={() =>
navigate(routes.ide({ userName: _projectOwner, projectTitle }))
}
name="Editor"
>
<Svg name="terminal" className="w-6 h-6 text-ch-pink-500" />
</TopButton>
)}
<Popover className="relative outline-none w-full h-full">
{({ open }) => {
return (
<>
<Popover.Button className="h-full w-full outline-none">
<TopButton
Tag="div"
name="Share"
className=" bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg
name="share"
className="w-6 h-6 text-ch-purple-500 mt-1"
/>
</TopButton>
</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">
<Tab className="p-3 px-5">encoded script</Tab>
<Tab className="p-3 px-5">external script</Tab>
</TabList>
</Tabs>
</Popover.Panel>
)}
</>
)
}}
</Popover>
{/* <TopButton>Fork</TopButton> */} {/* <TopButton>Fork</TopButton> */}
<div className="h-8 w-8 flex-shrink-0 rounded-full border-2 border-gray-200 flex items-center justify-center"> <div className="h-8 w-8">
<NavPlusButton /> <NavPlusButton />
</div> </div>
<ProfileSlashLogin /> <ProfileSlashLogin />
@@ -189,3 +122,96 @@ const IdeHeader = ({
} }
export default IdeHeader export default IdeHeader
function DefaultTopButtons({
project,
projectTitle,
_projectOwner,
handleRender,
canEdit,
}) {
return (
<>
{canEdit && !projectTitle && (
<CaptureButton
canEdit={canEdit}
projectTitle={project?.title}
userName={project?.user?.userName}
shouldUpdateImage={!project?.mainImage}
TheButton={({ onClick }) => (
<TopButton
onClick={onClick}
name="Save Project Image"
className=" bg-ch-blue-650 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg name="camera" className="w-6 h-6 text-ch-blue-400" />
</TopButton>
)}
/>
)}
{!projectTitle && (
<TopButton
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
onClick={handleRender}
name={canEdit ? 'Save' : 'Preview'}
>
<Svg
name={canEdit ? 'floppy-disk' : 'photograph'}
className="w-6 h-6 text-ch-pink-500"
/>
</TopButton>
)}
{projectTitle && (
<TopButton
className="bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
onClick={() =>
navigate(routes.ide({ userName: _projectOwner, projectTitle }))
}
name="Editor"
>
<Svg name="terminal" className="w-6 h-6 text-ch-pink-500" />
</TopButton>
)}
<Popover className="relative outline-none w-full h-full">
{({ open }) => {
return (
<>
<Popover.Button className="h-full w-full outline-none">
<TopButton
Tag="div"
name="Share"
className=" bg-ch-purple-400 bg-opacity-30 hover:bg-opacity-80 text-ch-gray-300"
>
<Svg
name="share"
className="w-6 h-6 text-ch-purple-500 mt-1"
/>
</TopButton>
</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">
<Tab className="p-3 px-5">encoded script</Tab>
<Tab className="p-3 px-5">external script</Tab>
</TabList>
</Tabs>
</Popover.Panel>
)}
</>
)
}}
</Popover>
</>
)
}

View File

@@ -11,6 +11,26 @@ 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 = '',
}) {
return (
<div className="relative overflow-hidden w-full h-full">
<CloudinaryImage
className={
'object-cover w-full h-full shadow overflow-hidden ' + className
}
cloudName="irevdev"
publicId={imageId}
width={width}
crop="scale"
/>
</div>
)
}
export default function ImageUploader({ export default function ImageUploader({
onImageUpload = () => {}, onImageUpload = () => {},
imageUrl, imageUrl,
@@ -65,36 +85,24 @@ export default function ImageUploader({
} }
style={{ paddingBottom: `${(1 / aspectRatio) * 100}%` }} style={{ paddingBottom: `${(1 / aspectRatio) * 100}%` }}
> >
<div className="absolute w-full h-full" {...getRootProps()}> <div className="absolute w-full h-full inset-0" {...getRootProps()}>
{cloudinaryId && isEditable && ( {cloudinaryId && isEditable && (
<button className="absolute z-10 bg-indigo-900 opacity-75 bottom-0 right-0 flex items-center p-1 mb-6 mr-2 rounded-lg"> <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="text-gray-100 pr-2">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"
strokeWidth={2}
className=" text-gray-100 h-6 w-6"
/>
</button> </button>
)} )}
{isEditable && <input {...getInputProps()} />} {isEditable && <input {...getInputProps()} />}
{(cloudinaryId || !isEditable) && ( {(cloudinaryId || !isEditable) && (
<div className="relative overflow-hidden w-full h-full"> <ImageFallback imageId={cloudinaryId} width={width} />
<CloudinaryImage
className="object-cover w-full h-full shadow overflow-hidden"
cloudName="irevdev"
publicId={cloudinaryId || 'CadHub/eia1kwru54g2kf02s2xx'}
width={width}
crop="scale"
/>
</div>
)} )}
{!cloudinaryId && <button className="absolute inset-0"></button>} {!cloudinaryId && <button className="absolute inset-0"></button>}
{!cloudinaryId && isEditable && ( {!cloudinaryId && isEditable && (
<div className="text-indigo-500 flex items-center justify-center rounded-lg w-full h-full"> <div className="text-ch-blue-400 flex items-center justify-center rounded-lg w-full h-full">
<div className="px-6 text-center"> <div className="px-2 text-sm text-center">
Drop files here ... or{' '} Drop files here or{' '}
<span className="group flex w-full items-center justify-center py-2"> <span className="group flex w-full items-center justify-center pt-2">
<span className="bg-indigo-500 shadow rounded text-gray-200 cursor-pointer p-2 hover:shadow-lg transform hover:-translate-y-1 transition-all duration-150"> <span className="text-base bg-ch-blue-400 rounded-sm text-ch-gray-300 cursor-pointer px-3 py-2 leading-4 bg-ch-blue-700 bg-opacity-60 hover:bg-opacity-90">
upload upload
</span> </span>
</span> </span>

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
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 {
keyName: string
children: React.ReactNode
bottom?: boolean
className?: string
edit?: EditToggleType
}
const KeyValue = ({
keyName,
children,
bottom = false,
className = '',
edit = { hasPermissionToEdit: false },
}: KeyValueType) => {
if (!children) 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' : '')
}
>
<span className={edit ? 'text-ch-blue-300' : ''}>{keyName}</span>
{edit && edit.hasPermissionToEdit && <EditToggle {...edit} /> }
</div>
<div className={'text-ch-gray-300 ' + (bottom ? 'mb-1' : 'mt-1')}>
{children}
</div>
</div>
)
}
export default KeyValue

View File

@@ -40,8 +40,9 @@ const NavPlusButton: React.FC = () => {
<Svg name="plus" className="text-ch-gray-300" /> <Svg name="plus" className="text-ch-gray-300" />
</Popover.Button> </Popover.Button>
<Popover.Panel className="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="absolute w-48 z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
<p className="text-lg">New Project</p> <p className="text-lg">New Project</p>
<hr className="my-2" />
<ul className=""> <ul className="">
{menuOptions.map(({ name, sub, ideType, bgClasses, dotClasses }) => ( {menuOptions.map(({ name, sub, ideType, bgClasses, dotClasses }) => (
<li <li
@@ -51,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 + ' 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>

View File

@@ -2,7 +2,8 @@ import { useState } from 'react'
import { useAuth } from '@redwoodjs/auth' import { useAuth } from '@redwoodjs/auth'
import { Link, routes } from '@redwoodjs/router' import { Link, routes } from '@redwoodjs/router'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import Popover from '@material-ui/core/Popover' import { Popover } from '@headlessui/react'
import { ImageFallback } from 'src/components/ImageUploader'
import useUser from 'src/helpers/hooks/useUser' import useUser from 'src/helpers/hooks/useUser'
import LoginModal from 'src/components/LoginModal' import LoginModal from 'src/components/LoginModal'
@@ -42,60 +43,54 @@ const ProfileSlashLogin = () => {
return ( return (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{isAuthenticated ? ( {isAuthenticated ? (
<div <Popover className="relative outline-none h-8 w-8">
className="h-8 w-8 relative text-indigo-200" <Popover.Button
aria-describedby={popoverId} disabled={!isAuthenticated || !currentUser}
> className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full"
<button
className="absolute inset-0 w-full h-full"
onClick={togglePopover}
> >
{!loading && <Gravatar image={user?.image} />} {!loading && (
</button> <ImageFallback
</div> width={80}
className="rounded-full object-cover"
imageId={user?.image}
/>
)}
</Popover.Button>
{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">
<Link to={routes.user({ userName: user?.userName })}>
<h3 className="text-lg hover:text-ch-pink-300">
Hello {user?.name}
</h3>
</Link>
<hr className="my-2" />
<Link
className="my-2 mt-4 block hover:text-ch-pink-300"
to={routes.user({ userName: user?.userName })}
>
<div>View Your Profile</div>
</Link>
<a
href="#"
onClick={logOut}
className="text-ch-gray-400 hover:text-ch-pink-300"
>
Logout
</a>
</Popover.Panel>
)}
</Popover>
) : ( ) : (
<div> <div>
<a <a
href="#" href="#"
className="text-indigo-200 font-semibold underline mr-2" className="text-sm text-ch-gray-300 mr-2 py-2 px-3 border-2 border-ch-gray-400 rounded-full hover:bg-ch-gray-600"
onClick={recordedLogin} onClick={recordedLogin}
> >
Sign in/up Sign In/Up
</a> </a>
</div> </div>
)} )}
{isAuthenticated && currentUser && (
<Popover
id={popoverId}
open={isOpen}
anchorEl={anchorEl}
onClose={closePopover}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<div className="p-4 w-48">
<Link to={routes.user({ userName: user?.userName })}>
<h3 className="text-indigo-800" style={{ fontWeight: '500' }}>
Hello {user?.name}
</h3>
</Link>
<hr />
<br />
<Link to={routes.user({ userName: user?.userName })}>
<div className="text-indigo-800">Your Profile</div>
</Link>
<a href="#" className="text-indigo-800" onClick={logOut}>
Logout
</a>
</div>
</Popover>
)}
<LoginModal <LoginModal
open={isLoginModalOpen} open={isLoginModalOpen}
onClose={() => setIsLoginModalOpen(false)} onClose={() => setIsLoginModalOpen(false)}

View File

@@ -4,10 +4,11 @@ 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
className="rounded p-1.5 bg-ch-gray-760 shadow-ch" className="rounded p-1.5 bg-ch-gray-760 hover:bg-ch-gray-710 shadow-ch"
key={`${user?.userName}--${title}`} key={`${user?.userName}--${title}`}
> >
<Link <Link
@@ -31,11 +32,9 @@ 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="" imageId={user?.image}
aspectRatio={1} width={80}
imageUrl={user?.image}
width={50}
/> />
</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">

View File

@@ -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,
@@ -71,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('')
@@ -80,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,
@@ -99,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
} }
@@ -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, ' ')}
@@ -152,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' &&
@@ -181,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()}
@@ -200,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 && (
<> <>
@@ -259,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

View File

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

View File

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

View File

@@ -0,0 +1,131 @@
import { useEffect, useReducer } 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 {
fieldComponents,
fieldReducer,
UserProfileType,
FieldType,
} from './userProfileConfig'
// This function initializes the state management object for each of the fields
function buildFieldsConfig(fieldsConfig, user, hasPermissionToEdit) {
return Object.fromEntries(Object.keys(fieldsConfig).map(
(key: string): [string, FieldType] => ([key, {
name: key,
currentValue: user[key],
newValue: user[key],
isEditing: false,
hasPermissionToEdit,
}])
))
}
const UserProfile = ({
user,
isEditing,
loading,
onSave,
error,
}: UserProfileType) => {
const { currentUser } = useAuth()
const hasPermissionToEdit = currentUser?.sub === user.id
useEffect(() => {
isEditing &&
!hasPermissionToEdit &&
navigate(routes.user({ userName: user.userName }))
}, [currentUser])
const initializedFields = buildFieldsConfig(fieldComponents, user, hasPermissionToEdit)
const [fields, fieldDispatch] = useReducer(fieldReducer, initializedFields)
const {
name: NameField,
userName: UserNameField,
image: ImageField,
bio: BioField,
createdAt: MemberSinceField,
} = fieldComponents
return (
<>
<div className="md: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}
>
<span></span>
</IdeHeader>
</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 p-12 md:overflow-y-auto ch-scrollbar">
<div className="flex gap-6">
{!isEditing && (
<div className="w-28 flex-shrink-0">
<ImageField
field={fields.image}
dispatch={fieldDispatch}
user={user}
save={onSave}
hasPermissionToEdit={hasPermissionToEdit}
/>
</div>
)}
<div>
<NameField
field={fields.name}
dispatch={fieldDispatch}
user={user}
save={onSave}
hasPermissionToEdit={hasPermissionToEdit}
/>
<UserNameField
field={fields.userName}
dispatch={fieldDispatch}
user={user}
save={onSave}
hasPermissionToEdit={hasPermissionToEdit}
/>
</div>
</div>
<div className="mt-10">
<BioField
field={fields.bio}
dispatch={fieldDispatch}
user={user}
save={onSave}
hasPermissionToEdit={hasPermissionToEdit}
/>
</div>
<div className="my-5">
<MemberSinceField field={fields.createdAt} />
</div>
</section>
{/* Viewer */}
<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>
<ProjectsOfUser userName={user?.userName} />
</div>
</div>
</div>
</div>
</>
)
}
export default UserProfile

View File

@@ -0,0 +1,216 @@
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 const fieldComponents = {
name: NameField,
userName: UserNameField,
image: ImageField,
bio: BioField,
createdAt: MemberSinceField,
}
export interface UserProfileType {
user: User
isEditing: boolean
loading: boolean
error: boolean
onSave: Function
projects: {}[]
}
export interface FieldType {
name: string
currentValue: any
newValue: any
isEditing: boolean
hasPermissionToEdit: boolean
}
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 = ({
field,
dispatch,
user,
save,
hasPermissionToEdit,
children,
bottom = false,
} : ProfileKeyValueType) => {
return (
(user[field.name] && hasPermissionToEdit) && <KeyValue
keyName={field.name}
edit={{
hasPermissionToEdit,
isEditing: field.isEditing,
onEdit: () => {
if (field.isEditing && field.currentValue !== field.newValue) {
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 })
},
}}
bottom={bottom}
className="mb-4"
>
{children}
</KeyValue>
)
}
function BioField(props) {
const ref = useRef(null)
const { field, dispatch } = props
return (
<ProfileKeyValue {...props}>
<div
id="bio-wrap"
name="bio"
className={
'markdown-overrides rounded-sm pb-2 mt-2' +
(field.isEditing ? ' 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() },
})
}
/>
</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}
/>
)}
</ProfileKeyValue>
)
}
function UserNameField(props : FieldComponentPropsType) {
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: 'userName', value },
})
}
isEditable={field.isEditing}
/>
)}
</ProfileKeyValue>
)
}
export function fieldReducer(state, action) {
switch (action.type) {
case 'TOGGLE_EDITING':
return {
...state,
[action.payload]: {
...state[action.payload],
isEditing:
state[action.payload].hasPermissionToEdit && !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
}
}

View File

@@ -18,6 +18,18 @@
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";
} }
.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;
}
/* https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ */ /* https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ */
.visually-hidden { .visually-hidden {
clip: rect(0 0 0 0); clip: rect(0 0 0 0);
@@ -39,6 +51,7 @@
} }
.tabToggle.disabled { .tabToggle.disabled {
@apply text-ch-gray-550 cursor-not-allowed; @apply text-ch-gray-550 cursor-not-allowed;
} }
} }

View File

@@ -3,7 +3,7 @@ import { Link, routes, navigate } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth' import { useAuth } from '@redwoodjs/auth'
import { Toaster, toast } from '@redwoodjs/web/toast' import { Toaster, toast } from '@redwoodjs/web/toast'
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip'
import Popover from '@material-ui/core/Popover' import { Popover } from '@headlessui/react'
import { getActiveClasses } from 'get-active-classes' import { getActiveClasses } from 'get-active-classes'
import Footer from 'src/components/Footer' import Footer from 'src/components/Footer'
import { useLocation } from '@redwoodjs/router' import { useLocation } from '@redwoodjs/router'
@@ -13,7 +13,7 @@ import ReactGA from 'react-ga'
import { isBrowser } from '@redwoodjs/prerender/browserUtils' import { isBrowser } from '@redwoodjs/prerender/browserUtils'
import Svg from 'src/components/Svg' import Svg from 'src/components/Svg'
import ImageUploader from 'src/components/ImageUploader' import { ImageFallback } from 'src/components/ImageUploader'
import useUser from 'src/helpers/hooks/useUser' import useUser from 'src/helpers/hooks/useUser'
let previousSubmission = '' let previousSubmission = ''
@@ -91,7 +91,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
} }
}, [hash, client]) }, [hash, client])
return ( return (
<div> <div className="min-h-screen flex flex-col ch-scrollbar">
<header id="cadhub-main-header"> <header id="cadhub-main-header">
<nav className="flex justify-between h-16 sm:px-4 bg-ch-gray-900"> <nav className="flex justify-between h-16 sm:px-4 bg-ch-gray-900">
<ul className="flex items-center"> <ul className="flex items-center">
@@ -131,20 +131,44 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
<NavPlusButton /> <NavPlusButton />
</li> </li>
{isAuthenticated ? ( {isAuthenticated ? (
<li <li className="h-10 w-10">
className="h-10 w-10 border-2 rounded-full border-indigo-300 text-indigo-200" <Popover className="relative outline-none w-full h-full">
aria-describedby={popoverId} <Popover.Button
> disabled={!isAuthenticated || !currentUser}
<button className="w-full h-full" onClick={togglePopover}> className="h-full w-full outline-none border-ch-gray-400 border-2 rounded-full"
{!loading && ( >
<ImageUploader {!loading && (
className="rounded-full object-cover" <ImageFallback
aspectRatio={1} width={80}
imageUrl={user?.image} className="rounded-full object-cover"
width={80} imageId={user?.image}
/> />
)}
</Popover.Button>
{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">
<Link to={routes.user({ userName: user?.userName })}>
<h3 className="text-lg hover:text-ch-pink-300">
Hello {user?.name}
</h3>
</Link>
<hr className="my-2" />
<Link
className="my-2 mt-4 block hover:text-ch-pink-300"
to={routes.user({ userName: user?.userName })}
>
<div>View Your Profile</div>
</Link>
<a
href="#"
onClick={logOut}
className="text-ch-gray-400 hover:text-ch-pink-300"
>
Logout
</a>
</Popover.Panel>
)} )}
</button> </Popover>
</li> </li>
) : ( ) : (
<li> <li>
@@ -158,38 +182,6 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
</li> </li>
)} )}
</ul> </ul>
{isAuthenticated && currentUser && (
<Popover
id={popoverId}
open={isOpen}
anchorEl={anchorEl}
onClose={closePopover}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<div className="p-4 w-48 text-ch-gray-300">
<Link to={routes.user({ userName: user?.userName })}>
<h3 className="" style={{ fontWeight: '500' }}>
Hello {user?.name}
</h3>
</Link>
<hr />
<br />
<Link to={routes.user({ userName: user?.userName })}>
<div className="">Your Profile</div>
</Link>
<a href="#" className="" onClick={logOut}>
Logout
</a>
</div>
</Popover>
)}
</nav> </nav>
</header> </header>
<Toaster timeout={1500} /> <Toaster timeout={1500} />
@@ -197,7 +189,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
open={isLoginModalOpen} open={isLoginModalOpen}
onClose={() => setIsLoginModalOpen(false)} onClose={() => setIsLoginModalOpen(false)}
/> />
<main>{children}</main> <main className="flex-grow bg-ch-gray-800">{children}</main>
{!shouldRemoveFooterInIde && <Footer />} {!shouldRemoveFooterInIde && <Footer />}
</div> </div>
) )

View File

@@ -31,15 +31,13 @@ const AccountRecoveryPage = () => {
/> />
<section className="max-w-md mx-auto mt-20"> <section className="max-w-md mx-auto mt-20">
<h2 className="text-xl text-indigo-500 pb-4">Send recovery email</h2> <h2 className="text-xl text-ch-gray-300 pb-4">Send recovery email</h2>
<Form onSubmit={onSubmit}> <Form onSubmit={onSubmit}>
<div <div
className="grid items-center gap-2" className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }} style={{ gridTemplateColumns: 'auto 1fr' }}
> >
<span className="capitalize text-gray-500 text-sm align-middle my-3"> <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"
@@ -51,10 +49,10 @@ const AccountRecoveryPage = () => {
}, },
}} }}
/> />
<Submit className="col-start-2 mt-4 bg-ch-purple-400 bg-opacity-50 hover:bg-opacity-80 text-ch-gray-300 flex h-10 flex-shrink-0 justify-center items-center px-4 rounded">
Send email
</Submit>
</div> </div>
<Submit className="bg-indigo-200 text-indigo-800 p-2 px-4 shadow hover:shadow-lg mt-4 rounded">
Send email
</Submit>
</Form> </Form>
</section> </section>
</MainLayout> </MainLayout>

View File

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

View File

@@ -6,10 +6,10 @@ const ProjectsPage = () => {
return ( return (
<MainLayout shouldRemoveFooterInIde> <MainLayout shouldRemoveFooterInIde>
<Seo <Seo
title="Home page" title="CadHub Home page"
description="Learn about Code CAD and the CadHub community" description="Learn about Code CAD and the CadHub community"
lang="en-US" lang="en-US"
socialImageUrl="https://cadhub.xyz/default-social-image.jpg" socialImageUrl="http://cadhub.xyz/default-social-image.jpg"
/> />
<Hero /> <Hero />
</MainLayout> </MainLayout>

View File

@@ -8,7 +8,7 @@ const ProjectsPage = () => {
<Seo <Seo
title="Projects page" title="Projects page"
description="Cadhub Projects page" description="Cadhub Projects page"
socialImageUrl="https://cadhub.xyz/default-social-image.jpg" socialImageUrl="http://cadhub.xyz/default-social-image.jpg"
lang="en-US" lang="en-US"
/> />
<div className="bg-ch-gray-800 pb-64"> <div className="bg-ch-gray-800 pb-64">

View File

@@ -32,14 +32,14 @@ const UpdatePasswordPage = () => {
<Seo title="Update Password" description="Update Password" lang="en-US" /> <Seo title="Update Password" description="Update Password" lang="en-US" />
<section className="max-w-md mx-auto mt-20"> <section className="max-w-md mx-auto mt-20">
<h2 className="text-xl text-indigo-500 pb-4">Reset Password</h2> <h2 className="text-xl text-ch-gray-300 pb-4">Reset Password</h2>
<Form onSubmit={onSubmit}> <Form onSubmit={onSubmit}>
<div <div
className="grid items-center gap-2" className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }} style={{ gridTemplateColumns: 'auto 1fr' }}
> >
<span className="capitalize text-gray-500 text-sm align-middle my-3"> <span className="capitalize text-ch-gray-300 text-sm">
password: password
</span> </span>
<InputTextForm <InputTextForm
className="text-xl" className="text-xl"
@@ -49,9 +49,7 @@ const UpdatePasswordPage = () => {
required: true, required: true,
}} }}
/> />
<span className="capitalize text-gray-500 text-sm align-middle my-3"> <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"
@@ -60,10 +58,10 @@ const UpdatePasswordPage = () => {
required: true, required: true,
}} }}
/> />
<Submit className="col-start-2 mt-4 bg-ch-purple-400 bg-opacity-50 hover:bg-opacity-80 text-ch-gray-300 flex h-10 flex-shrink-0 justify-center items-center px-4 rounded">
Update
</Submit>
</div> </div>
<Submit className="bg-indigo-200 text-indigo-800 p-2 px-4 shadow hover:shadow-lg mt-4 rounded">
Update
</Submit>
</Form> </Form>
</section> </section>
</MainLayout> </MainLayout>

View File

@@ -1,14 +1,16 @@
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 (
<MainLayout> <>
<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} />
</MainLayout> </>
) )
} }