profile photos
This commit is contained in:
parent
9abc41b7b2
commit
29fb37b763
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<input
|
||||
ref={ref}
|
||||
type='file'
|
||||
className='d-none'
|
||||
accept={UPLOAD_TYPES_ALLOW.join(', ')}
|
||||
onChange={(e) => {
|
||||
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
|
||||
}}
|
||||
/>
|
||||
<Component onClick={() => ref.current?.click()} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function UploadExample () {
|
||||
const [error, setError] = useState()
|
||||
const [key, setKey] = useState()
|
||||
const [uploading, setUploading] = useState()
|
||||
|
||||
const Component = uploading
|
||||
? ({ onClick }) => <Moon className='fill-grey spin' onClick={onClick} />
|
||||
: ({ onClick }) => <AddImage className='fill-grey' onClick={onClick} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<Upload
|
||||
onError={e => {
|
||||
setUploading(false)
|
||||
setError(e)
|
||||
}}
|
||||
onSuccess={key => {
|
||||
setUploading(false)
|
||||
setKey(key)
|
||||
}}
|
||||
onStarted={() => {
|
||||
setError(false)
|
||||
setUploading(true)
|
||||
}}
|
||||
as={Component}
|
||||
/>
|
||||
<div>
|
||||
{key && <BImage src={`https://sn-mtest.s3.amazonaws.com/${key}`} width='100%' />}
|
||||
{error && <div>{error}</div>}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 = () => <div className={`mb-2 ${styles.username} text-success`}>{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked</div>
|
||||
const Satistics = () => <div className={`mb-2 ml-0 ml-sm-1 ${styles.username} text-success`}>{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked</div>
|
||||
|
||||
const UserSchema = Yup.object({
|
||||
name: Yup.string()
|
||||
|
@ -46,12 +48,15 @@ export default function UserHeader ({ user }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex align-items-center mt-2 flex-wrap'>
|
||||
{/* <Image
|
||||
src='/dorian400.jpg' width='135' height='135' layout='fixed'
|
||||
className={styles.userimg}
|
||||
/> */}
|
||||
<div>
|
||||
<div className='d-flex mt-2 flex-wrap flex-column flex-sm-row'>
|
||||
<div className='position-relative' style={{ width: 'fit-content' }}>
|
||||
<Image
|
||||
src={user.photoId ? `https://${process.env.NEXT_PUBLIC_AWS_UPLOAD_BUCKET}.s3.amazonaws.com/${user.photoId}` : '/dorian400.jpg'} width='135' height='135'
|
||||
className={styles.userimg}
|
||||
/>
|
||||
{isMe && <PhotoEditor userId={me.id} />}
|
||||
</div>
|
||||
<div className='ml-0 ml-sm-1 mt-3 mt-sm-0 justify-content-center align-self-sm-center'>
|
||||
{editting
|
||||
? (
|
||||
<Form
|
||||
|
@ -105,13 +110,13 @@ export default function UserHeader ({ user }) {
|
|||
<div className='d-flex align-items-center mb-2'>
|
||||
<div className={styles.username}>@{user.name}</div>
|
||||
{isMe &&
|
||||
<Button className='py-0' variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
|
||||
<Button className='py-0' style={{ lineHeight: '1.25' }} variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
|
||||
</div>
|
||||
)}
|
||||
<Satistics user={user} />
|
||||
<ModalButton
|
||||
clicker={
|
||||
<Button className='font-weight-bold'>
|
||||
<Button className='font-weight-bold ml-0 ml-sm-2'>
|
||||
<LightningIcon
|
||||
width={20}
|
||||
height={20}
|
||||
|
@ -156,3 +161,92 @@ export default function UserHeader ({ user }) {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PhotoEditor ({ userId }) {
|
||||
const [uploading, setUploading] = useState()
|
||||
const [editProps, setEditProps] = useState()
|
||||
const ref = useRef()
|
||||
const [scale, setScale] = useState(1)
|
||||
|
||||
const [setPhoto] = useMutation(
|
||||
gql`
|
||||
mutation setPhoto($photoId: ID!) {
|
||||
setPhoto(photoId: $photoId)
|
||||
}`, {
|
||||
update (cache, { data: { setPhoto } }) {
|
||||
cache.modify({
|
||||
id: `User:${userId}`,
|
||||
fields: {
|
||||
photoId () {
|
||||
return setPhoto
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
show={!!editProps}
|
||||
onHide={() => setEditProps(null)}
|
||||
>
|
||||
<div className='modal-close' onClick={() => setEditProps(null)}>X</div>
|
||||
<Modal.Body className='text-right mt-1 p-4'>
|
||||
<AvatarEditor
|
||||
ref={ref} width={200} height={200}
|
||||
image={editProps?.file}
|
||||
scale={scale}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
}}
|
||||
/>
|
||||
<BootstrapForm.Group controlId='formBasicRange'>
|
||||
<BootstrapForm.Control
|
||||
type='range' onChange={e => setScale(parseFloat(e.target.value))}
|
||||
min={1} max={2} step='0.05'
|
||||
defaultValue={scale} custom
|
||||
/>
|
||||
</BootstrapForm.Group>
|
||||
<Button onClick={() => {
|
||||
ref.current.getImageScaledToCanvas().toBlob(blob => {
|
||||
if (blob) {
|
||||
editProps.upload(blob)
|
||||
setEditProps(null)
|
||||
}
|
||||
}, 'image/jpeg')
|
||||
}}
|
||||
>save
|
||||
</Button>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
<Upload
|
||||
as={({ onClick }) =>
|
||||
<div className='position-absolute p-1 bg-dark pointer' onClick={onClick} style={{ bottom: '0', right: '0' }}>
|
||||
{uploading
|
||||
? <Moon className='fill-white spin' />
|
||||
: <EditImage className='fill-white' />}
|
||||
</div>}
|
||||
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)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
|
@ -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])
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0H24V24H0z"/><path d="M20 3c.552 0 1 .448 1 1v1.757l-2 2V5H5v8.1l4-4 4.328 4.329-1.327 1.327-.006 4.239 4.246.006 1.33-1.33L18.899 19H19v-2.758l2-2V20c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h16zm1.778 4.808l1.414 1.414L15.414 17l-1.416-.002.002-1.412 7.778-7.778zM15.5 7c.828 0 1.5.672 1.5 1.5s-.672 1.5-1.5 1.5S14 9.328 14 8.5 14.672 7 15.5 7z"/></svg>
|
After Width: | Height: | Size: 478 B |
Loading…
Reference in New Issue