Skip to content

Instantly share code, notes, and snippets.

@rystraum
Created June 30, 2021 22:12
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save rystraum/e8c2906336464e62e372f2d2582e2ded to your computer and use it in GitHub Desktop.
File Upload using Rails ActiveStorage, GraphQL
class Mutations::UploadAvatar < Mutations::BaseMutation
null true
field :success, Boolean, null: false
field :errors, [String], null: true
field :thing, Types::ThingType, null: true
# query:
# mutation requestResetPassword($input: UploadAvatarInput!) {
# uploadAvatar(input:$input) {
# success
# errors
# user { avatar_url }
# }
# }
#
# variables:
# {
# "input": {
# "id": "1",
# "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAANlBMVEVmZmb///9mZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZW6fJrAAAAEXRSTlMAAAYHG21ub8fLz9DR8/T4+RrZ9owAAAB3SURBVHja7dNLDoAgDATQWv4gKve/rEajJOJiWLgg6WzpSyB0aHqHiNj6nL1lovb4C+hYzkSNAT7mryQFAVOeGAj4CjwEtgrWXpD/uZKtwEJApXt+Vn0flzRhgNiFZQkOXY0aADQZCOCPlsZJ46Rx0jhp3IiN2wGDHhxtldrlwQAAAABJRU5ErkJggg=="
# }
# }
def resolve(attributes:)
user = User.find_by(id: attributes[:id])
# attributes[:avatar] is passed in as a base64 string
Tempfile.open('avatar') do |file|
file.write Base64.decode64(attributes[:file].split(",")[1..-1].join)
user.avatar.attach(
io: file,
filename: 'avatar',
)
end
return {
success: true,
errors: [],
user: user
}
end
end
module Types
class UserType < Types::BaseObject
field :avatar_url, String, null: true
def avatar_url
Rails.application.routes.url_helpers.rails_blob_url(object.avatar)
end
end
end
class User < ApplicationRecord
has_attached_file :avatar
end
import toBase64 from 'toBase64';
const ImageUpload = ({ id, field, form, disabled, user }) => {
const [preview, setPreview] = useState(user.avatarUrl);
return (
<div className="w-100 d-flex justify-content-center">
<label
htmlFor="avatar"
style={{ position: 'relative' }}
className="avatar avatar-xxl mb-4 mx-auto"
style={{ position: 'relative', cursor: 'pointer' }}>
{preview && preview !== '/images/user-icon.png' ? (
<img
src={preview}
className="avatar-img rounded-circle"
style={{
backgroundColor: getDefaultAvatarColors(
`${user.firstName} ${user.lastName}`,
),
}}
/>
) : (
<div
className="avatar-img rounded-circle d-flex align-items-center text-center"
style={{
backgroundColor: getDefaultAvatarColors(
`${user.firstName} ${user.lastName}`,
),
}}
/>
)}
<i
className="fe fe-camera"
style={{
fontSize: '32px',
width: '32px',
height: '32px',
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
margin: 'auto',
color: '#BFBFC1',
lineHeight: 1,
}}
/>
</label>
<input
style={{ display: 'none' }}
type="file"
name={field.name}
disabled={disabled}
id="avatar"
onChange={e => {
const file = e.currentTarget.files[0];
form.setFieldValue(field.name, file);
toBase64(file).then(base64 => {
setPreview(base64);
});
}}
/>
</div>
);
};
// then use `preview` in state to upload via apollo
const toBase64 = file =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
export default toBase64;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment