Skip to content

Commit fa1c24e

Browse files
committed
feat(edit-profile): adds image avatar upload
Adds ability to upload and change a users avatar
1 parent eadcdfd commit fa1c24e

File tree

20 files changed

+424
-94
lines changed

20 files changed

+424
-94
lines changed

next.config.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1+
const path = require('path');
2+
13
/** @type {import('next').NextConfig} */
2-
module.exports = {
3-
reactStrictMode: true,
4-
}
4+
const config = {};
5+
6+
config.reactStrictMode = true;
7+
8+
config.resolve = {
9+
alias: {
10+
'graphql-upload': path.join(__dirname, 'node_modules/graphql-upload/dist/graphql-upload.umd.js'),
11+
},
12+
};
13+
14+
console.log(config);
15+
16+
module.exports = config;

package-lock.json

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@types/bytes": "^3.1.1",
4040
"@types/graphql-upload": "^8.0.11",
4141
"apollo-server-micro": "^3.6.2",
42+
"apollo-upload-client": "^17.0.0",
4243
"babel-plugin-macros": "^3.1.0",
4344
"bytes": "^3.1.2",
4445
"consola": "^2.15.3",
@@ -76,6 +77,7 @@
7677
"@testing-library/cypress": "^8.0.2",
7778
"@testing-library/jest-dom": "^5.15.0",
7879
"@testing-library/react": "^12.1.2",
80+
"@types/apollo-upload-client": "^17.0.0",
7981
"@types/graphql-iso-date": "^3.4.0",
8082
"@types/micro-cors": "^0.1.2",
8183
"@types/node": "16.11.7",

pages/account/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { NextPage } from 'next';
22
import Head from 'next/head';
3+
import { Flex } from '@chakra-ui/react';
34
import PageHeader from '@components/layout/PageHeader';
45
import EditProfile from '@components/account/EditProfile';
56

@@ -11,7 +12,15 @@ const Account: NextPage = () => {
1112
<link rel="icon" href="/favicon.ico" />
1213
</Head>
1314
<PageHeader title="User Account" />
14-
<EditProfile />
15+
<Flex
16+
direction="column"
17+
alignItems={{
18+
base: 'center',
19+
lg: 'flex-start',
20+
}}
21+
>
22+
<EditProfile />
23+
</Flex>
1524
</>
1625
);
1726
};

pages/api/graphql.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import Cors from 'micro-cors';
33
import { RequestHandler } from 'micro';
44
import { withSentry } from '@sentry/nextjs';
55
import { createContext } from '@graphql/context';
6+
import { processRequest } from 'graphql-upload';
7+
import logger from '@utils/logger';
68
import schema from '../../schema';
79

810
const cors = Cors();
@@ -36,6 +38,19 @@ const graphql = cors(async (req, res) => {
3638
return null;
3739
}
3840

41+
try {
42+
const contentType = req.headers['content-type'];
43+
44+
if (contentType && contentType.startsWith('multipart/form-data')) {
45+
// TypeScript is stupid
46+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
47+
// @ts-ignore
48+
req.filePayload = await processRequest(req, res);
49+
}
50+
} catch (error) {
51+
logger.error('processRequest', error);
52+
}
53+
3954
return handler(req, res);
4055
});
4156

schema/types/Image.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from 'path';
66
import { UserInputError, AuthenticationError } from 'apollo-server-micro';
77
import { isImageMimeType, MAX_IMAGE_SIZE, getFileSize, uploadFile } from '../../src/utils/cdn';
88
import { FileTypeError, FileSizeError } from '../../src/errors/file';
9+
import logger from '../../src/utils/logger';
910

1011
interface ImageWhere {
1112
id?: string;
@@ -20,6 +21,8 @@ const uploadImage = async (
2021
{ filename, mimetype, encoding, createReadStream }: FileUpload,
2122
{ filename: rename, path: subpath }: StorageFileOptions,
2223
) => {
24+
logger.debug(`Uploading image:`, { filename, mimetype, encoding, createReadStream });
25+
2326
const isImage = isImageMimeType(mimetype);
2427

2528
if (!isImage) {
@@ -108,7 +111,9 @@ export const ImageMutation = extendType({
108111
args: {
109112
file: arg({ type: 'Upload' }),
110113
},
111-
resolve: async (_parent, { file }, { prisma, token }) => {
114+
resolve: async (_parent, args, { prisma, token }) => {
115+
const file = await args.file;
116+
logger.debug('uploadImage', { file });
112117
const guid = uuid();
113118
const userId = token?.sub || '';
114119

schema/types/Profile.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const ProfileInput = inputObjectType({
4242
t.string('lastName', { description: "The User Profile's Last Name" });
4343
t.string('nickname', { description: "The User Profile's Nickname" });
4444
t.string('bio', { description: 'A 250 character description of the User' });
45+
t.string('avatarId', { description: 'The ID of the User Profile Avatar' });
4546
},
4647
});
4748

@@ -84,7 +85,7 @@ export const ProfileMutation = extendType({
8485
args: { input: ProfileInput },
8586
async resolve(_parent, args, { prisma, token }) {
8687
const authId = token?.sub;
87-
const { id, firstName, lastName, nickname, bio } = args.input || {};
88+
const { id, firstName, lastName, nickname, bio, avatarId } = args.input || {};
8889

8990
const where: ProfileWhere = {};
9091

@@ -102,7 +103,7 @@ export const ProfileMutation = extendType({
102103
throw new UserInputError('profile not found');
103104
}
104105

105-
const data = { firstName, lastName, nickname, bio };
106+
const data = { firstName, lastName, nickname, bio, avatarId };
106107

107108
const result = await prisma.profile.update({
108109
where,

schema/types/Upload.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import { scalarType } from 'nexus';
12
import { GraphQLUpload } from 'graphql-upload';
23

3-
export const Upload = GraphQLUpload;
4+
export const Upload = scalarType({
5+
name: GraphQLUpload.name,
6+
asNexusMethod: 'upload', // We set this to be used as a method later as `t.upload()` if needed
7+
description: GraphQLUpload.description,
8+
serialize: GraphQLUpload.serialize,
9+
parseValue: GraphQLUpload.parseValue,
10+
parseLiteral: GraphQLUpload.parseLiteral,
11+
});

src/components/account/EditProfile/EditProfile.stories.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Story } from '@storybook/react';
22
import { GET_PROFILE, UPDATE_PROFILE } from '@graphql/queries';
3+
import { UPLOAD_IMAGE } from '@components/form/ImageUpload';
34
import { EditProfileProps, EditProfile } from './EditProfile';
45

56
export default {
@@ -27,6 +28,17 @@ Default.parameters = {
2728
id: '1',
2829
userId: '1',
2930
firstName: 'John',
31+
lastName: 'Doe',
32+
nickname: 'JD',
33+
bio: 'Somewhere over the rainbow...',
34+
avatar: {
35+
id: '1',
36+
filename: 'angus-perkerson.jpeg',
37+
filepath: '/images',
38+
mimeType: 'image/jpeg',
39+
title: 'Angus Perkerson',
40+
description: 'Angus Perkerson',
41+
},
3042
},
3143
},
3244
},
@@ -43,6 +55,23 @@ Default.parameters = {
4355
},
4456
},
4557
},
58+
{
59+
request: {
60+
query: UPLOAD_IMAGE,
61+
},
62+
result: {
63+
data: {
64+
uploadImage: {
65+
id: '1',
66+
filename: 'angus-perkerson.jpeg',
67+
filepath: '/images',
68+
mimeType: 'image/jpeg',
69+
title: 'Angus Perkerson Saved',
70+
description: 'Angus Perkerson Saved',
71+
},
72+
},
73+
},
74+
},
4675
],
4776
},
4877
};

0 commit comments

Comments
 (0)