check for usernames on typing
This commit is contained in:
parent
8a462252af
commit
4f627e2a5c
|
@ -1,3 +1,5 @@
|
|||
import { AuthenticationError } from 'apollo-server-errors'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
me: async (parent, args, { models, me }) =>
|
||||
|
@ -6,7 +8,14 @@ export default {
|
|||
return await models.user.findUnique({ where: { name } })
|
||||
},
|
||||
users: async (parent, args, { models }) =>
|
||||
await models.user.findMany()
|
||||
await models.user.findMany(),
|
||||
nameAvailable: async (parent, { name }, { models, me }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
|
||||
return me.name === name || !(await models.user.findUnique({ where: { name } }))
|
||||
}
|
||||
},
|
||||
|
||||
User: {
|
||||
|
|
|
@ -3,8 +3,9 @@ import { gql } from 'apollo-server-micro'
|
|||
export default gql`
|
||||
extend type Query {
|
||||
me: User
|
||||
user(name: String): User
|
||||
user(name: String!): User
|
||||
users: [User!]
|
||||
nameAvailable(name: String!): Boolean!
|
||||
}
|
||||
|
||||
type User {
|
||||
|
|
|
@ -48,11 +48,11 @@ export function InputSkeleton ({ label }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function Input ({ label, prepend, append, hint, ...props }) {
|
||||
export function Input ({ label, prepend, append, hint, showValid, noBottomMargin, ...props }) {
|
||||
const [field, meta] = props.readOnly ? [{}, {}] : useField(props)
|
||||
|
||||
return (
|
||||
<BootstrapForm.Group>
|
||||
<BootstrapForm.Group className={noBottomMargin ? 'mb-0' : ''}>
|
||||
{label && <BootstrapForm.Label>{label}</BootstrapForm.Label>}
|
||||
<InputGroup hasValidation>
|
||||
{prepend && (
|
||||
|
@ -63,6 +63,7 @@ export function Input ({ label, prepend, append, hint, ...props }) {
|
|||
<BootstrapForm.Control
|
||||
{...field} {...props}
|
||||
isInvalid={meta.touched && meta.error}
|
||||
isValid={showValid && meta.touched && !meta.error}
|
||||
/>
|
||||
{append && (
|
||||
<InputGroup.Append>
|
||||
|
|
|
@ -50,9 +50,7 @@ export default function UpVote ({ itemId, meSats, className }) {
|
|||
try {
|
||||
await vote({ variables: { id: itemId, sats: 1 } })
|
||||
} catch (error) {
|
||||
console.log(error.toString())
|
||||
if (error.toString().includes('insufficient funds')) {
|
||||
console.log('hjo')
|
||||
setError(true)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,12 +1,75 @@
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { useSession } from 'next-auth/client'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import Nav from 'react-bootstrap/Nav'
|
||||
import { useState } from 'react'
|
||||
import { Form, Input, SubmitButton } from './form'
|
||||
import InputGroup from 'react-bootstrap/InputGroup'
|
||||
import * as Yup from 'yup'
|
||||
import { gql, useApolloClient, useQuery } from '@apollo/client'
|
||||
|
||||
const NAME_QUERY =
|
||||
gql`
|
||||
query nameAvailable($name: String!) {
|
||||
nameAvailable(name: $name)
|
||||
}
|
||||
`
|
||||
|
||||
export default function UserHeader ({ user }) {
|
||||
const [editting, setEditting] = useState(false)
|
||||
const [session] = useSession()
|
||||
const router = useRouter()
|
||||
const client = useApolloClient()
|
||||
|
||||
const Satistics = () => <h1 className='ml-2'><small className='text-success'>[{user.stacked} stacked, {user.sats} sats]</small></h1>
|
||||
|
||||
const UserSchema = Yup.object({
|
||||
name: Yup.string()
|
||||
.required('required')
|
||||
.matches(/^[\w_]+$/, 'only letters, numbers, and _')
|
||||
.max(32, 'too long')
|
||||
.test({
|
||||
name: 'name',
|
||||
test: async name => {
|
||||
if (!name || !name.length) return false
|
||||
const { data } = await client.query({ query: NAME_QUERY, variables: { name } })
|
||||
return data.nameAvailable
|
||||
},
|
||||
message: 'taken'
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>@{user.name} <small className='text-success'>[{user.stacked} stacked, {user.sats} sats]</small></h1>
|
||||
{editting
|
||||
? (
|
||||
<Form
|
||||
className='d-flex align-items-center flex-wrap'
|
||||
schema={UserSchema}
|
||||
initial={{
|
||||
name: user.name
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
prepend=<InputGroup.Text>@</InputGroup.Text>
|
||||
name='name'
|
||||
autoFocus
|
||||
noBottomMargin
|
||||
showValid
|
||||
/>
|
||||
<Satistics user={user} />
|
||||
<SubmitButton className='ml-2' variant='info' size='sm' onClick={() => setEditting(true)}>save</SubmitButton>
|
||||
</Form>
|
||||
)
|
||||
: (
|
||||
<div className='d-flex align-items-center flex-wrap'>
|
||||
<h1>@{user.name}</h1>
|
||||
<Satistics user={user} />
|
||||
{session && session.user && session.user.name === user.name &&
|
||||
<Button className='ml-2' variant='boost' size='sm' onClick={() => setEditting(true)}>edit</Button>}
|
||||
</div>
|
||||
)}
|
||||
<Nav
|
||||
activeKey={router.asPath}
|
||||
>
|
||||
|
|
|
@ -21,7 +21,6 @@ const options = {
|
|||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
profile: profile => {
|
||||
console.log(profile)
|
||||
return {
|
||||
...profile,
|
||||
name: profile.screen_name
|
||||
|
@ -32,7 +31,6 @@ const options = {
|
|||
server: process.env.EMAIL_SERVER,
|
||||
from: process.env.EMAIL_FROM,
|
||||
profile: profile => {
|
||||
console.log(profile)
|
||||
return profile
|
||||
}
|
||||
})
|
||||
|
|
|
@ -14,6 +14,7 @@ $enable-transitions: false;
|
|||
$enable-gradients: false;
|
||||
$enable-shadows: false;
|
||||
$btn-transition: none;
|
||||
$form-feedback-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M2 9h3v12H2a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1zm5.293-1.293l6.4-6.4a.5.5 0 0 1 .654-.047l.853.64a1.5 1.5 0 0 1 .553 1.57L14.6 8H21a2 2 0 0 1 2 2v2.104a2 2 0 0 1-.15.762l-3.095 7.515a1 1 0 0 1-.925.619H8a1 1 0 0 1-1-1V8.414a1 1 0 0 1 .293-.707z' fill='rgba(92, 128, 1, 1)'/%3E%3C/svg%3E");
|
||||
$form-feedback-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z' fill='rgba(192,50,33,1)'/%3E%3C/svg%3E");
|
||||
$line-height-base: 1.75;
|
||||
$input-btn-padding-y: .42rem;
|
||||
|
|
Loading…
Reference in New Issue