diff --git a/api/resolvers/upload.js b/api/resolvers/upload.js index a3b9acca..03cf8d01 100644 --- a/api/resolvers/upload.js +++ b/api/resolvers/upload.js @@ -42,7 +42,7 @@ export default { const s3 = new AWS.S3({ apiVersion: '2006-03-01' }) const res = await new Promise((resolve, reject) => { s3.createPresignedPost({ - Bucket: process.env.AWS_UPLOAD_BUCKET, + Bucket: process.env.NEXT_PUBLIC_AWS_UPLOAD_BUCKET, Fields: { key: String(upload.id) }, diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 0ea16b7d..f55b8395 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -116,6 +116,18 @@ export default { return true }, + setPhoto: async (parent, { photoId }, { me, models }) => { + if (!me) { + throw new AuthenticationError('you must be logged in') + } + + await models.user.update({ + where: { id: me.id }, + data: { photoId: Number(photoId) } + }) + + return Number(photoId) + }, upsertBio: async (parent, { bio }, { me, models }) => { if (!me) { throw new AuthenticationError('you must be logged in') diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index 701e3605..32fc324f 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -30,6 +30,7 @@ export default gql` setSettings(tipDefault: Int!, noteItemSats: Boolean!, noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!, noteInvites: Boolean!, noteJobIndicator: Boolean!): Boolean + setPhoto(photoId: ID!): Int! upsertBio(bio: String!): User! setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean } @@ -48,6 +49,7 @@ export default gql` tipDefault: Int! bio: Item bioId: Int + photoId: Int sats: Int! upvotePopover: Boolean! tipPopover: Boolean! diff --git a/components/upload.js b/components/upload.js index 8c4ea025..f0158c5a 100644 --- a/components/upload.js +++ b/components/upload.js @@ -1,16 +1,8 @@ -import { useRef, useState } from 'react' +import { useRef } from 'react' import { gql, useMutation } from '@apollo/client' import { UPLOAD_TYPES_ALLOW } from '../lib/constants' -import AddImage from '../svgs/image-add-fill.svg' -import Moon from '../svgs/moon-fill.svg' -import { Image as BImage } from 'react-bootstrap' -// what we want is that they supply a component that we turn into an upload button -// we need to return an error if the upload fails -// we need to report that the upload started -// we return the image id on success - -export default function Upload ({ as: Component, onStarted, onError, onSuccess }) { +export default function Upload ({ as: Component, onSelect, onStarted, onError, onSuccess }) { const [getSignedPOST] = useMutation( gql` mutation getSignedPOST($type: String!, $size: Int!, $width: Int!, $height: Int!) { @@ -21,98 +13,77 @@ export default function Upload ({ as: Component, onStarted, onError, onSuccess } }`) const ref = useRef() + const upload = file => { + onStarted && onStarted() + + const img = new Image() + img.src = window.URL.createObjectURL(file) + img.onload = async () => { + let data + try { + ({ data } = await getSignedPOST({ + variables: { + type: file.type, + size: file.size, + width: img.width, + height: img.height + } + })) + } catch (e) { + onError && onError(e.toString()) + return + } + + const form = new FormData() + Object.keys(data.getSignedPOST.fields).forEach(key => + form.append(key, data.getSignedPOST.fields[key])) + form.append('Content-Type', file.type) + form.append('acl', 'public-read') + form.append('file', file) + + const res = await fetch(data.getSignedPOST.url, { + method: 'POST', + body: form + }) + + if (!res.ok) { + onError && onError(res.statusText) + return + } + + onSuccess && onSuccess(data.getSignedPOST.fields.key) + } + } + return ( <> { if (e.target.files.length === 0) { return } - onStarted && onStarted() - const file = e.target.files[0] + if (UPLOAD_TYPES_ALLOW.indexOf(file.type) === -1) { onError && onError(`image must be ${UPLOAD_TYPES_ALLOW.map(t => t.replace('image/', '')).join(', ')}`) return } - const img = new Image() - img.src = window.URL.createObjectURL(file) - img.onload = async () => { - let data - try { - ({ data } = await getSignedPOST({ - variables: { - type: file.type, - size: file.size, - width: img.width, - height: img.height - } - })) - } catch (e) { - onError && onError(e.toString()) - return - } - const form = new FormData() - Object.keys(data.getSignedPOST.fields).forEach(key => - form.append(key, data.getSignedPOST.fields[key])) - form.append('Content-Type', file.type) - form.append('acl', 'public-read') - form.append('file', file) - - const res = await fetch(data.getSignedPOST.url, { - method: 'POST', - body: form - }) - - if (!res.ok) { - onError && onError(res.statusText) - return - } - - onSuccess && onSuccess(data.getSignedPOST.fields.key) + if (onSelect) { + onSelect(file, upload) + } else { + upload(file) } + + e.target.value = null }} /> ref.current?.click()} /> ) } - -export function UploadExample () { - const [error, setError] = useState() - const [key, setKey] = useState() - const [uploading, setUploading] = useState() - - const Component = uploading - ? ({ onClick }) => - : ({ onClick }) => - - return ( - <> - { - setUploading(false) - setError(e) - }} - onSuccess={key => { - setUploading(false) - setKey(key) - }} - onStarted={() => { - setError(false) - setUploading(true) - }} - as={Component} - /> -
- {key && } - {error &&
{error}
} -
- - ) -} diff --git a/components/user-header.js b/components/user-header.js index d0926d8b..9aec579b 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -1,20 +1,22 @@ -import { Button } from 'react-bootstrap' +import { Button, InputGroup, Image, Modal, Form as BootstrapForm } from 'react-bootstrap' import Link from 'next/link' import { useRouter } from 'next/router' import Nav from 'react-bootstrap/Nav' -import { useState } from 'react' +import { useRef, useState } from 'react' import { Form, Input, SubmitButton } from './form' -import InputGroup from 'react-bootstrap/InputGroup' import * as Yup from 'yup' import { gql, useApolloClient, useMutation } from '@apollo/client' import styles from './user-header.module.css' import { useMe } from './me' import { NAME_MUTATION, NAME_QUERY } from '../fragments/users' -// import Image from 'next/image' import QRCode from 'qrcode.react' import LightningIcon from '../svgs/bolt.svg' import ModalButton from './modal-button' import { encodeLNUrl } from '../lib/lnurl' +import Upload from './upload' +import EditImage from '../svgs/image-edit-fill.svg' +import Moon from '../svgs/moon-fill.svg' +import AvatarEditor from 'react-avatar-editor' export default function UserHeader ({ user }) { const [editting, setEditting] = useState(false) @@ -24,7 +26,7 @@ export default function UserHeader ({ user }) { const [setName] = useMutation(NAME_MUTATION) const isMe = me?.name === user.name - const Satistics = () =>
{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked
+ const Satistics = () =>
{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked
const UserSchema = Yup.object({ name: Yup.string() @@ -46,12 +48,15 @@ export default function UserHeader ({ user }) { return ( <> -
- {/* */} -
+
+
+ + {isMe && } +
+
{editting ? (
@{user.name}
{isMe && - } + }
)} + + + + +
+ {uploading + ? + : } +
} + onError={e => { + console.log(e) + setUploading(false) + }} + onSelect={(file, upload) => { + setEditProps({ file, upload }) + }} + onSuccess={async key => { + const { error } = await setPhoto({ variables: { photoId: key } }) + if (error) { + console.log(error) + } + setUploading(false) + }} + onStarted={() => { + setUploading(true) + }} + /> + + ) +} diff --git a/fragments/users.js b/fragments/users.js index 51343d51..8c9d26d3 100644 --- a/fragments/users.js +++ b/fragments/users.js @@ -14,6 +14,7 @@ export const ME = gql` hasNewNotes tipDefault bioId + photoId hasInvites upvotePopover tipPopover @@ -39,6 +40,7 @@ export const ME_SSR = gql` freeComments tipDefault bioId + photoId upvotePopover tipPopover noteItemSats @@ -76,6 +78,7 @@ export const USER_FIELDS = gql` ncomments stacked sats + photoId bio { ...ItemFields text diff --git a/package-lock.json b/package-lock.json index eec1d617..e0f1df6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,27 +81,84 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, "requires": { "@babel/highlight": "^7.14.5" } }, + "@babel/compat-data": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==" + }, "@babel/generator": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", - "dev": true, "requires": { "@babel/types": "^7.15.4", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, + "@babel/helper-compilation-targets": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "requires": { + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", + "escalade": "^3.1.1", + "node-releases": "^2.0.3", + "picocolors": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001341", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz", + "integrity": "sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==" + }, + "electron-to-chromium": { + "version": "1.4.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", + "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==" + }, + "node-releases": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==" + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, "@babel/helper-function-name": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.15.4", "@babel/template": "^7.15.4", @@ -112,7 +169,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, "requires": { "@babel/types": "^7.15.4" } @@ -121,11 +177,34 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, "requires": { "@babel/types": "^7.15.4" } }, + "@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "requires": { + "@babel/types": "^7.16.7" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + }, + "@babel/types": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + } + } + }, "@babel/helper-plugin-utils": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", @@ -135,7 +214,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, "requires": { "@babel/types": "^7.15.4" } @@ -145,6 +223,11 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + }, "@babel/highlight": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", @@ -168,6 +251,26 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, + "@babel/plugin-transform-runtime": { + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.10.tgz", + "integrity": "sha512-6jrMilUAJhktTr56kACL8LnWC5hx3Lf27BS0R0DSyW/OoJfb/iTHeE96V3b1dgKG3FSFdd/0culnYWMkjcKCig==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + } + } + }, "@babel/runtime": { "version": "7.15.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", @@ -180,7 +283,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, "requires": { "@babel/code-frame": "^7.14.5", "@babel/parser": "^7.15.4", @@ -191,7 +293,6 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, "requires": { "@babel/code-frame": "^7.14.5", "@babel/generator": "^7.15.4", @@ -208,7 +309,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" @@ -1536,6 +1636,33 @@ "svgo": "^2.0.3" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -2345,6 +2472,49 @@ "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==", "dev": true }, + "core-js-compat": { + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.5.tgz", + "integrity": "sha512-rEF75n3QtInrYICvJjrAgV03HwKiYvtKHdPtaba1KucG+cNZ4NJnH9isqt979e67KZlhpbCOTwnsvnIr+CVeOg==", + "requires": { + "browserslist": "^4.20.3", + "semver": "7.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "requires": { + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", + "escalade": "^3.1.1", + "node-releases": "^2.0.3", + "picocolors": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001341", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz", + "integrity": "sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==" + }, + "electron-to-chromium": { + "version": "1.4.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", + "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==" + }, + "node-releases": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==" + }, + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, "core-js-pure": { "version": "3.18.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.1.tgz", @@ -3689,8 +3859,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "got": { "version": "6.7.1", @@ -4425,8 +4594,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -6635,6 +6803,11 @@ "split2": "^4.1.0" } }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -7236,6 +7409,16 @@ "object-assign": "^4.1.1" } }, + "react-avatar-editor": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-13.0.0.tgz", + "integrity": "sha512-0xw63MbRRQdDy7YI1IXU9+7tTFxYEFLV8CABvryYOGjZmXRTH2/UA0mafe57ns62uaEFX181kA4XlGlxCaeXKA==", + "requires": { + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/runtime": "^7.12.5", + "prop-types": "^15.7.2" + } + }, "react-bootstrap": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.4.tgz", @@ -7902,8 +8085,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "space-separated-tokens": { "version": "1.1.5", diff --git a/package.json b/package.json index 9dce1527..de7c6083 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "prisma": "^2.25.0", "qrcode.react": "^1.0.1", "react": "^17.0.1", + "react-avatar-editor": "^13.0.0", "react-bootstrap": "^1.5.2", "react-countdown": "^2.3.2", "react-dom": "^17.0.2", diff --git a/prisma/migrations/20220516144530_profile_photo/migration.sql b/prisma/migrations/20220516144530_profile_photo/migration.sql new file mode 100644 index 00000000..bf6d93cd --- /dev/null +++ b/prisma/migrations/20220516144530_profile_photo/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "photoId" INTEGER; + +-- AddForeignKey +ALTER TABLE "users" ADD FOREIGN KEY ("photoId") REFERENCES "Upload"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d613da91..3100dffd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -40,6 +40,8 @@ model User { trust Float @default(0) lastSeenAt DateTime? lastCheckedJobs DateTime? + photoId Int? + photo Upload? @relation(fields: [photoId], references: [id]) upvotePopover Boolean @default(false) tipPopover Boolean @default(false) @@ -54,7 +56,7 @@ model User { noteJobIndicator Boolean @default(true) Earn Earn[] - Upload Upload[] + Upload Upload[] @relation(name: "Uploads") @@index([createdAt]) @@index([inviteId]) @@map(name: "users") @@ -70,9 +72,10 @@ model Upload { height Int? item Item? @relation(fields: [itemId], references: [id]) itemId Int? - user User @relation(fields: [userId], references: [id]) + user User @relation(name: "Uploads", fields: [userId], references: [id]) userId Int + User User[] @@index([createdAt]) @@index([itemId]) @@index([userId]) diff --git a/svgs/image-edit-fill.svg b/svgs/image-edit-fill.svg new file mode 100644 index 00000000..617e3b65 --- /dev/null +++ b/svgs/image-edit-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file