-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unprofile: add avatar image upload #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
@tailwind base; | ||
|
||
@layer base { | ||
a { | ||
@apply text-kernel-eggplant-light | ||
} | ||
} | ||
@tailwind components; | ||
@tailwind utilities; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,11 +8,21 @@ | |
|
||
import { useEffect, useReducer } from 'react' | ||
import { useNavigate } from 'react-router-dom' | ||
import Resizer from 'react-image-file-resizer' | ||
import { Switch } from '@headlessui/react' | ||
import { useServices, Navbar, Footer, Alert } from '@kernel/common' | ||
import { useServices, Navbar, Footer, Alert, getUrl } from '@kernel/common' | ||
|
||
import AppConfig from 'App.config' | ||
|
||
const AVATAR_CONFIG = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does this ensure the size stays below a limit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you mean the file size, it doesn't, and neither would a straightforward canvas implementation. I figure there is an upper bound to how large of a file a 500x500 image can be, and resizing it by height/width would keep it reasonably small in the vast majority of cases (or maybe in all cases if there's not some way that a 500x500 image can be huge). Since you didn't seem to be attached to a specific hard limit, it didn't feel necessary to sink more effort into enforcing both a max height/width and a max file size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File size is my primary concern and it should definitely be checked. |
||
maxHeight: 500, | ||
maxWidth: 500, | ||
format: 'PNG', | ||
quality: 100, // maximum: no compression | ||
rotation: 0, | ||
outputType: 'base64' | ||
} | ||
|
||
const FORM_INPUT = ['email', 'name', 'pronouns', 'twitter', 'city', 'company', 'bio'] | ||
|
||
// initializes state in the form: | ||
|
@@ -21,6 +31,7 @@ const INITIAL_FORM_KEYS = ['wallet'].concat(FORM_INPUT) | |
const INITIAL_FORM_FIELDS_STATE = INITIAL_FORM_KEYS | ||
.reduce((acc, key) => Object.assign(acc, { [key]: '' }), {}) | ||
INITIAL_FORM_FIELDS_STATE.consent = true | ||
INITIAL_FORM_FIELDS_STATE.avatar = null | ||
|
||
const INITIAL_FORM_SUBMISSION_STATE = { | ||
formStatus: 'clean', | ||
|
@@ -56,13 +67,46 @@ const reducer = (state, action) => { | |
const change = (dispatch, type, e) => { | ||
try { | ||
const target = e.target | ||
|
||
if (type === 'avatar') { | ||
onAvatarChange(dispatch, target) | ||
target.value = null // reset so onChange will fire even for same image | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the dom cannot directly be manipulated in react. I recommend you read up on controlled vs uncontrolled components: |
||
return | ||
} | ||
|
||
const payload = target.type === 'checkbox' ? target.checked : target.value | ||
dispatch({ type, payload }) | ||
} catch (error) { | ||
console.log(error) | ||
} | ||
} | ||
|
||
const onAvatarChange = (dispatch, target) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you should define fns before they are called ex. this is called in change and then defined later. It should be switched. |
||
if (target.files.length === 0) { | ||
return | ||
} | ||
|
||
const { maxHeight, maxWidth, format, quality, rotation, outputType } = AVATAR_CONFIG | ||
|
||
Resizer.imageFileResizer( | ||
target.files[0], | ||
maxWidth, | ||
maxHeight, | ||
format, | ||
quality, | ||
rotation, | ||
(uri) => { | ||
console.log(uri) | ||
dispatch({ type: 'avatar', payload: uri }) | ||
}, | ||
outputType | ||
) | ||
} | ||
|
||
const removeAvatar = (dispatch) => { | ||
dispatch({ type: 'avatar', payload: null }) | ||
} | ||
|
||
const value = (state, type) => state[type] | ||
|
||
const save = async (state, dispatch, e) => { | ||
|
@@ -127,7 +171,12 @@ const ProfileAlert = ({ formStatus, errorMessage }) => { | |
case 'submitting': | ||
return <Alert type='transparent'>Saving your changes...</Alert> | ||
case 'success': | ||
return <Alert type='success'>Your changes have been saved!</Alert> | ||
return ( | ||
<Alert type='success'> | ||
Your changes have been saved! | ||
<a href={getUrl('explorer')}>Start exploring.</a> | ||
</Alert> | ||
) | ||
case 'error': | ||
return <Alert type='danger'>Something went wrong. {errorMessage}</Alert> | ||
default: | ||
|
@@ -207,6 +256,64 @@ const Profile = () => { | |
<Input key={fieldName} fieldName={fieldName} state={state} dispatch={dispatch} /> | ||
) | ||
})} | ||
<div className='mt-8 mb-2 w-min'> | ||
<label className='label block mb-1'> | ||
<span className='label-text text-gray-700 capitalize'>Avatar</span> | ||
</label> | ||
|
||
<div className='mb-2 text-sm text-gray-700'> | ||
Upload a JPEG or PNG. Recommended size is 500 x 500. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why jpeg and png? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are just the ones I know should render properly for everyone. |
||
</div> | ||
|
||
<div className={`my-8 grid place-items-center box-border rounded-full h-80 w-80 | ||
${state.avatar ? '' : 'border-dashed border-2 border-gray-400'}`} | ||
> | ||
{state.avatar | ||
? <img | ||
src={state.avatar} alt='avatar' | ||
className='w-80 h-80 object-cover rounded-full' | ||
/> | ||
: <span className='text-gray-400'>add an image</span>} | ||
</div> | ||
|
||
<div className='my-4'> | ||
{state.avatar && | ||
<div className='grid grid-cols-2 gap-x-2'> | ||
<label | ||
className='px-6 py-3 w-full bg-kernel-eggplant-mid text-white rounded text-center cursor-pointer' | ||
> | ||
<input | ||
type='file' accept='image/png, image/jpeg' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should use image/* so mobile phones allow you to take a picture with the camera see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers |
||
className='hidden' | ||
onChange={change.bind(null, dispatch, 'avatar')} | ||
/> | ||
<span className='inline-block mt-0.5'>Change</span> | ||
</label> | ||
<button | ||
className='px-6 py-3 w-full border-2 border-kernel-eggplant-mid text-kernel-eggplant-mid rounded' | ||
onClick={removeAvatar.bind(null, dispatch)} | ||
> | ||
Remove | ||
</button> | ||
</div>} | ||
{!state.avatar && | ||
<div className='grid grid-cols-1'> | ||
<label | ||
className='px-6 py-3 w-full bg-kernel-eggplant-mid text-white rounded text-center cursor-pointer' | ||
> | ||
<input | ||
type='file' accept='image/png, image/jpeg' | ||
className='hidden' | ||
onChange={change.bind(null, dispatch, 'avatar')} | ||
/> | ||
<span className='inline-block mt-0.5'>Choose image</span> | ||
</label> | ||
</div>} | ||
</div> | ||
</div> | ||
|
||
<hr className='my-12' /> | ||
|
||
<div className='mt-8 mb-2'> | ||
<Switch.Group> | ||
<Switch | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This library doesn't add enough value to warrant adding it IMHO.