check for usernames on typing

This commit is contained in:
keyan 2021-05-21 17:32:21 -05:00
parent 8a462252af
commit 4f627e2a5c
7 changed files with 80 additions and 9 deletions

View File

@ -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: {

View File

@ -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 {

View File

@ -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>

View File

@ -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
}

View File

@ -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}
>

View File

@ -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
}
})

View File

@ -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;