Style part profile editable state
This commit is contained in:
@@ -16,15 +16,15 @@ const Routes = () => {
|
||||
{/* <Route path="/blah/*" page={PartsPage} name="home" /> */}
|
||||
<Route notfound page={NotFoundPage} />
|
||||
|
||||
{/* Ownership enforced routes */}
|
||||
<Route path="/u/{userName}/edit" page={EditUser2Page} name="editUser2" />
|
||||
<Route path="/u/{userName}/{partTitle}/edit" page={EditPart2Page} name="editPart2" />
|
||||
{/* End ownership enforced routes */}
|
||||
|
||||
<Route path="/u/{userName}" page={User2Page} name="user2" />
|
||||
<Route path="/u/{userName}/{partTitle}" page={Part2Page} name="part2" />
|
||||
{/* <Route path="/u/{userName}/{partTitle}/ide" page={Part2Page} name="part2" /> */}
|
||||
|
||||
{/* Ownership enforced routes */}
|
||||
<Route path="/u/{userName}/edit" page={EditUser2Page} name="editUser2" />
|
||||
{/* <Route path="/u/{userName}/{partTitle}/edit" page={Part2Page} name="part2" /> */}
|
||||
{/* End ownership enforced routes */}
|
||||
|
||||
{/* GENERATED ROUTES BELOW, probably going to clean these up and delete most of them, but the CRUD functionality is useful for now */}
|
||||
{/* All private by default for safety and because the routes that are left after clean up will probably be admin pages */}
|
||||
<Private unauthenticated="home" role="admin">
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { getActiveClasses } from "get-active-classes"
|
||||
|
||||
const Breadcrumb = ({ userName, partTitle, className }) => {
|
||||
import InputText from 'src/components/InputText'
|
||||
|
||||
const Breadcrumb = ({ userName, partTitle, onPartTitleChange, className }) => {
|
||||
return (
|
||||
<h3 className={getActiveClasses("text-2xl font-roboto", className)}>
|
||||
<div className="w-1 inline-block text-indigo-800 bg-indigo-800 mr-2">.</div>
|
||||
<span className="text-gray-500">
|
||||
<span className={getActiveClasses({"text-gray-500": !onPartTitleChange, 'text-gray-400': onPartTitleChange})}>
|
||||
{userName}
|
||||
</span>
|
||||
<div className="w-1 inline-block bg-gray-400 text-gray-400 mx-3 transform -skew-x-20" >.</div>
|
||||
<span className="text-indigo-800">
|
||||
{partTitle}
|
||||
</span>
|
||||
<InputText
|
||||
value={partTitle}
|
||||
onChange={onPartTitleChange}
|
||||
isEditable={onPartTitleChange}
|
||||
className={getActiveClasses("text-indigo-800 text-2xl", {'-ml-2': !onPartTitleChange})}
|
||||
/>
|
||||
</h3>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
||||
import { navigate, routes } from '@redwoodjs/router'
|
||||
|
||||
import UserProfile from 'src/components/UserProfile'
|
||||
|
||||
export const QUERY = gql`
|
||||
|
||||
21
web/src/components/InputText/InputText.js
Normal file
21
web/src/components/InputText/InputText.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getActiveClasses } from 'get-active-classes'
|
||||
|
||||
const InputText = ({value, isEditable, onChange ,className}) => {
|
||||
return (
|
||||
<>
|
||||
<div className={getActiveClasses('relative inline-block', {'hidden': !isEditable}, className)}>
|
||||
<div className="absolute inset-0 mb-2 rounded bg-gray-200 shadow-inner bg-gray-100" />
|
||||
<input
|
||||
className="pl-2 pt-1 text-indigo-800 font-medium mb-px pb-px bg-transparent relative"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
readOnly={!onChange}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<span className={getActiveClasses('pl-2 text-indigo-800 font-medium mb-px pb-px',{'hidden': isEditable}, className)}>{value}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputText
|
||||
7
web/src/components/InputText/InputText.stories.js
Normal file
7
web/src/components/InputText/InputText.stories.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import InputText from './InputText'
|
||||
|
||||
export const generated = () => {
|
||||
return <InputText />
|
||||
}
|
||||
|
||||
export default { title: 'Components/InputText' }
|
||||
11
web/src/components/InputText/InputText.test.js
Normal file
11
web/src/components/InputText/InputText.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import InputText from './InputText'
|
||||
|
||||
describe('InputText', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<InputText />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,7 @@
|
||||
import Editor from "rich-markdown-editor";
|
||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
||||
import { navigate, routes } from '@redwoodjs/router'
|
||||
|
||||
// import Part from 'src/components/Part'
|
||||
import ImageUploader from 'src/components/ImageUploader'
|
||||
import Breadcrumb from 'src/components/Breadcrumb'
|
||||
import EmojiReaction from 'src/components/EmojiReaction'
|
||||
import Button from 'src/components/Button'
|
||||
import PartProfile from 'src/components/PartProfile'
|
||||
|
||||
export const QUERY = gql`
|
||||
query FIND_PART_BY_USERNAME_TITLE($userName: String!, $partTitle: String!) {
|
||||
@@ -34,77 +31,6 @@ export const Empty = () => <div>Empty</div>
|
||||
|
||||
export const Failure = ({ error }) => <div>Error: {error.message}</div>
|
||||
|
||||
export const Success = ({ userPart, variables }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="grid mt-20 gap-8" style={{gridTemplateColumns: 'auto 12rem minmax(12rem, 42rem) 10rem auto'}}>
|
||||
|
||||
{/* Side column */}
|
||||
<aside className="col-start-2">
|
||||
<ImageUploader
|
||||
className="rounded-half rounded-br-lg shadow-md border-2 border-gray-200 border-solid"
|
||||
onImageUpload={() => {}}
|
||||
aspectRatio={1}
|
||||
imageUrl={userPart.image === 'abc' ? '': userPart.image}
|
||||
/>
|
||||
<h4 className="text-indigo-800 text-xl underline text-right py-4">{userPart?.name}</h4>
|
||||
<div className="h-px bg-indigo-200 mb-4" />
|
||||
{/* TODO hook up to emoji data properly */}
|
||||
<EmojiReaction
|
||||
// emotes={[{emoji: '❤️',count: 3},{emoji: '😁',count: 2},{emoji: '😜',count: 2},{emoji: '🤩',count: 2},{emoji: '🤣',count: 2},{emoji: '🙌',count: 2},{emoji: '🚀',count: 2}]}
|
||||
emotes={[{emoji: '❤️',count: 3},{emoji: '😁',count: 2}]}
|
||||
// emotes={[]}
|
||||
onEmote={() => {}}
|
||||
/>
|
||||
<Button
|
||||
className="mt-6 ml-auto hover:shadow-lg bg-gradient-to-r from-transparent to-indigo-100"
|
||||
shouldAnimateHover
|
||||
iconName="plus"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Comments 11
|
||||
</Button>
|
||||
<Button
|
||||
className="mt-4 ml-auto shadow-md hover:shadow-lg bg-indigo-200"
|
||||
shouldAnimateHover
|
||||
iconName="plus"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Open IDE
|
||||
</Button>
|
||||
<Button
|
||||
className="mt-4 ml-auto shadow-md hover:shadow-lg bg-indigo-200"
|
||||
shouldAnimateHover
|
||||
iconName="pencil"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Edit Part
|
||||
</Button>
|
||||
</aside>
|
||||
|
||||
{/* main project center column */}
|
||||
<section className="col-start-3">
|
||||
<Breadcrumb className="mb-8" userName={userPart.userName} partTitle={userPart?.Part?.title}/>
|
||||
{ userPart?.Part?.mainImage && <ImageUploader
|
||||
className="rounded-lg shadow-md border-2 border-gray-200 border-solid"
|
||||
onImageUpload={() => {}}
|
||||
aspectRatio={16/9}
|
||||
imageUrl={userPart?.Part?.mainImage}
|
||||
/>}
|
||||
<div name="description" className="markdown-overrides rounded-lg shadow-md bg-white p-12 my-8 min-h-md">
|
||||
<Editor
|
||||
defaultValue={userPart?.Part?.description || ''}
|
||||
readOnly
|
||||
// readOnly={!isEditable}
|
||||
// onChange={(bioFn) => setInput({
|
||||
// ...input,
|
||||
// bio: bioFn(),
|
||||
// })}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
export const Success = ({ userPart, variables: {isEditable} }) => {
|
||||
return <PartProfile userPart={userPart} isEditable={isEditable} />
|
||||
}
|
||||
|
||||
92
web/src/components/PartProfile/PartProfile.js
Normal file
92
web/src/components/PartProfile/PartProfile.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import {useState} from 'react'
|
||||
import Editor from "rich-markdown-editor";
|
||||
|
||||
import ImageUploader from 'src/components/ImageUploader'
|
||||
import Breadcrumb from 'src/components/Breadcrumb'
|
||||
import EmojiReaction from 'src/components/EmojiReaction'
|
||||
import Button from 'src/components/Button'
|
||||
|
||||
const PartProfile = ({userPart, isEditable}) => {
|
||||
const part = userPart?.Part
|
||||
const [input, setInput] = useState({
|
||||
title: part.title,
|
||||
mainImage: part.mainImage,
|
||||
description: part.description,
|
||||
})
|
||||
const hi = () => {}
|
||||
return (
|
||||
<>
|
||||
<div className="grid mt-20 gap-8" style={{gridTemplateColumns: 'auto 12rem minmax(12rem, 42rem) 10rem auto'}}>
|
||||
|
||||
{/* Side column */}
|
||||
<aside className="col-start-2 relative">
|
||||
<ImageUploader
|
||||
className="rounded-half rounded-br-lg shadow-md border-2 border-gray-200 border-solid"
|
||||
onImageUpload={() => {}}
|
||||
aspectRatio={1}
|
||||
imageUrl={userPart.image === 'abc' ? '': userPart.image}
|
||||
/>
|
||||
<h4 className="text-indigo-800 text-xl underline text-right py-4">{userPart?.name}</h4>
|
||||
<div className="h-px bg-indigo-200 mb-4" />
|
||||
{/* TODO hook up to emoji data properly */}
|
||||
<EmojiReaction
|
||||
// emotes={[{emoji: '❤️',count: 3},{emoji: '😁',count: 2},{emoji: '😜',count: 2},{emoji: '🤩',count: 2},{emoji: '🤣',count: 2},{emoji: '🙌',count: 2},{emoji: '🚀',count: 2}]}
|
||||
emotes={[{emoji: '❤️',count: 3},{emoji: '😁',count: 2}]}
|
||||
// emotes={[]}
|
||||
onEmote={() => {}}
|
||||
/>
|
||||
<Button
|
||||
className="mt-6 ml-auto hover:shadow-lg bg-gradient-to-r from-transparent to-indigo-100"
|
||||
shouldAnimateHover
|
||||
iconName="chevron-down"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Comments 11
|
||||
</Button>
|
||||
<Button
|
||||
className="mt-4 ml-auto shadow-md hover:shadow-lg bg-indigo-200"
|
||||
shouldAnimateHover
|
||||
iconName="terminal"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Open IDE
|
||||
</Button>
|
||||
{true && <Button
|
||||
className="mt-4 ml-auto shadow-md hover:shadow-lg bg-indigo-200 relative z-20"
|
||||
shouldAnimateHover
|
||||
iconName={isEditable ? 'save' : 'pencil'}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{isEditable ? 'Save Details' : 'Edit Details'}
|
||||
</Button>}
|
||||
{isEditable && <div className="absolute inset-0 bg-gray-300 opacity-75 z-10 transform scale-x-110 -ml-1 -mt-2" />}
|
||||
</aside>
|
||||
|
||||
{/* main project center column */}
|
||||
<section className="col-start-3">
|
||||
<Breadcrumb className="inline" onPartTitleChange={isEditable && hi} userName={userPart.userName} partTitle={input?.title}/>
|
||||
{ input?.mainImage && <ImageUploader
|
||||
className="rounded-lg shadow-md border-2 border-gray-200 border-solid mt-8"
|
||||
onImageUpload={() => {}}
|
||||
aspectRatio={16/9}
|
||||
isEditable={isEditable}
|
||||
imageUrl={input?.mainImage}
|
||||
/>}
|
||||
<div name="description" className="markdown-overrides rounded-lg shadow-md bg-white p-12 my-8 min-h-md">
|
||||
<Editor
|
||||
defaultValue={part?.description || ''}
|
||||
readOnly={!isEditable}
|
||||
// onChange={(bioFn) => setInput({
|
||||
// ...input,
|
||||
// bio: bioFn(),
|
||||
// })}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartProfile
|
||||
7
web/src/components/PartProfile/PartProfile.stories.js
Normal file
7
web/src/components/PartProfile/PartProfile.stories.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import PartProfile from './PartProfile'
|
||||
|
||||
export const generated = () => {
|
||||
return <PartProfile />
|
||||
}
|
||||
|
||||
export default { title: 'Components/PartProfile' }
|
||||
11
web/src/components/PartProfile/PartProfile.test.js
Normal file
11
web/src/components/PartProfile/PartProfile.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import PartProfile from './PartProfile'
|
||||
|
||||
describe('PartProfile', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<PartProfile />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Fragment, useState} from 'react'
|
||||
import { getActiveClasses } from "get-active-classes"
|
||||
import {Fragment} from 'react'
|
||||
|
||||
import InputText from 'src/components/InputText'
|
||||
|
||||
const ProfileTextInput = ({fields, isEditable, onChange= () => {}}) => {
|
||||
return (
|
||||
@@ -8,16 +9,12 @@ const ProfileTextInput = ({fields, isEditable, onChange= () => {}}) => {
|
||||
{Object.entries(fields).map(([property, value]) => (<Fragment key={property}>
|
||||
<span className="capitalize text-gray-500 text-sm align-middle my-3">{property}:</span>
|
||||
|
||||
<div className={getActiveClasses('relative ml-2', {'hidden': !isEditable})}>
|
||||
<div className="absolute inset-0 mb-2 rounded bg-gray-200 shadow-inner bg-gray-100" />
|
||||
<input
|
||||
className="pl-2 pt-1 text-indigo-800 font-medium text-xl mb-px pb-px bg-transparent relative"
|
||||
onChange={({target}) => onChange({...fields, [property]: target.value})}
|
||||
value={value}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<span className={getActiveClasses('pl-2 text-indigo-800 font-medium text-xl mb-px pb-px',{'hidden': isEditable})}>{value}</span>
|
||||
<InputText
|
||||
className="text-xl"
|
||||
value={value}
|
||||
onChange={({target}) => onChange({...fields, [property]: target.value})}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
|
||||
</Fragment>))}
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
const Svg = ({name, className: className2, strokeWidth = 2}) => {
|
||||
|
||||
const svgs = {
|
||||
"plus-circle": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
"chevron-down": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M19 9l-7 7-7-7" />
|
||||
</svg>,
|
||||
"plus":<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
"dots-vertical": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
||||
</svg>,
|
||||
"pencil": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>,
|
||||
"dots-vertical": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
||||
</svg>
|
||||
"plus":<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>,
|
||||
"plus-circle": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>,
|
||||
"save": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
|
||||
</svg>,
|
||||
"terminal": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>,
|
||||
}
|
||||
|
||||
return <div className={className2 || "h-10 w-10"}>
|
||||
|
||||
12
web/src/pages/EditPart2Page/EditPart2Page.js
Normal file
12
web/src/pages/EditPart2Page/EditPart2Page.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import MainLayout from 'src/layouts/MainLayout'
|
||||
import Part2Cell from 'src/components/Part2Cell'
|
||||
|
||||
const EditPart2Page = ({userName, partTitle}) => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<Part2Cell userName={userName} partTitle={partTitle} isEditable/>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditPart2Page
|
||||
7
web/src/pages/EditPart2Page/EditPart2Page.stories.js
Normal file
7
web/src/pages/EditPart2Page/EditPart2Page.stories.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import EditPart2Page from './EditPart2Page'
|
||||
|
||||
export const generated = () => {
|
||||
return <EditPart2Page />
|
||||
}
|
||||
|
||||
export default { title: 'Pages/EditPart2Page' }
|
||||
11
web/src/pages/EditPart2Page/EditPart2Page.test.js
Normal file
11
web/src/pages/EditPart2Page/EditPart2Page.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import EditPart2Page from './EditPart2Page'
|
||||
|
||||
describe('EditPart2Page', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<EditPart2Page />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user