slashtags auth
This commit is contained in:
parent
48448ea1ef
commit
9644a9f867
|
@ -39,6 +39,9 @@ export default {
|
|||
LnAuth: {
|
||||
encodedUrl: async (lnAuth, args, { models }) => {
|
||||
return encodedUrl(process.env.LNAUTH_URL, 'login', lnAuth.k1)
|
||||
},
|
||||
slashtagUrl: async (lnAuth, args, { models, slashtags }) => {
|
||||
return slashtags.formatURL(lnAuth.k1)
|
||||
}
|
||||
},
|
||||
LnWith: {
|
||||
|
|
|
@ -54,7 +54,8 @@ async function authMethods (user, args, { models, me }) {
|
|||
lightning: !!user.pubkey,
|
||||
email: user.emailVerified && user.email,
|
||||
twitter: oauth.indexOf('twitter') >= 0,
|
||||
github: oauth.indexOf('github') >= 0
|
||||
github: oauth.indexOf('github') >= 0,
|
||||
slashtags: !!user.slashtagId
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,27 +403,25 @@ export default {
|
|||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
|
||||
let user
|
||||
if (authType === 'twitter' || authType === 'github') {
|
||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||
user = await models.user.findUnique({ where: { id: me.id } })
|
||||
const account = await models.account.findFirst({ where: { userId: me.id, providerId: authType } })
|
||||
if (!account) {
|
||||
throw new UserInputError('no such account')
|
||||
}
|
||||
await models.account.delete({ where: { id: account.id } })
|
||||
return await authMethods(user, undefined, { models, me })
|
||||
} else if (authType === 'lightning') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { pubkey: null } })
|
||||
} else if (authType === 'slashtags') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { slashtagId: null } })
|
||||
} else if (authType === 'email') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
||||
} else {
|
||||
throw new UserInputError('no such account')
|
||||
}
|
||||
|
||||
if (authType === 'lightning') {
|
||||
const user = await models.user.update({ where: { id: me.id }, data: { pubkey: null } })
|
||||
return await authMethods(user, undefined, { models, me })
|
||||
}
|
||||
|
||||
if (authType === 'email') {
|
||||
const user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
||||
return await authMethods(user, undefined, { models, me })
|
||||
}
|
||||
|
||||
throw new UserInputError('no such account')
|
||||
return await authMethods(user, undefined, { models, me })
|
||||
},
|
||||
linkUnverifiedEmail: async (parent, { email }, { models, me }) => {
|
||||
if (!me) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,7 @@ import { getSession } from 'next-auth/client'
|
|||
import resolvers from './resolvers'
|
||||
import typeDefs from './typeDefs'
|
||||
import models from './models'
|
||||
import slashtags from './slashtags'
|
||||
import { print } from 'graphql'
|
||||
import lnd from './lnd'
|
||||
import search from './search'
|
||||
|
@ -26,7 +27,8 @@ export default async function getSSRApolloClient (req, me = null) {
|
|||
? session.user
|
||||
: me,
|
||||
lnd,
|
||||
search
|
||||
search,
|
||||
slashtags
|
||||
}
|
||||
}),
|
||||
cache: new InMemoryCache()
|
||||
|
|
|
@ -17,6 +17,7 @@ export default gql`
|
|||
k1: String!
|
||||
pubkey: String
|
||||
encodedUrl: String!
|
||||
slashtagUrl: String!
|
||||
}
|
||||
|
||||
type LnWith {
|
||||
|
|
|
@ -32,9 +32,10 @@ export default gql`
|
|||
|
||||
type AuthMethods {
|
||||
lightning: Boolean!
|
||||
email: String
|
||||
twitter: Boolean!
|
||||
slashtags: Boolean!
|
||||
github: Boolean!
|
||||
twitter: Boolean!
|
||||
email: String
|
||||
}
|
||||
|
||||
type User {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import LnQR from './lnqr'
|
||||
import Qr from './qr'
|
||||
|
||||
export function Invoice ({ invoice }) {
|
||||
let variant = 'default'
|
||||
|
@ -14,5 +14,5 @@ export function Invoice ({ invoice }) {
|
|||
status = 'expired'
|
||||
}
|
||||
|
||||
return <LnQR webLn value={invoice.bolt11} statusVariant={variant} status={status} />
|
||||
return <Qr webLn value={invoice.bolt11} statusVariant={variant} status={status} />
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ function ItemEmbed ({ item }) {
|
|||
|
||||
const youtube = item.url?.match(/(https?:\/\/)?((www\.)?(youtube(-nocookie)?|youtube.googleapis)\.com.*(v\/|v=|vi=|vi\/|e\/|embed\/|user\/.*\/u\/\d+\/)|youtu\.be\/)(?<id>[_0-9a-z-]+)((?:\?|&)(?:t|start)=(?<start>\d+))?/i)
|
||||
if (youtube?.groups?.id) {
|
||||
console.log(youtube?.groups?.start)
|
||||
return (
|
||||
<div style={{ maxWidth: '640px', paddingRight: '15px' }}>
|
||||
<YouTube
|
||||
|
|
|
@ -3,12 +3,12 @@ import { signIn } from 'next-auth/client'
|
|||
import { useEffect } from 'react'
|
||||
import { Col, Container, Row } from 'react-bootstrap'
|
||||
import AccordianItem from './accordian-item'
|
||||
import LnQR, { LnQRSkeleton } from './lnqr'
|
||||
import Qr, { QrSkeleton } from './qr'
|
||||
import styles from './lightning-auth.module.css'
|
||||
import BackIcon from '../svgs/arrow-left-line.svg'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
function LnQRAuth ({ k1, encodedUrl, callbackUrl }) {
|
||||
function QrAuth ({ k1, encodedUrl, slashtagUrl, callbackUrl }) {
|
||||
const query = gql`
|
||||
{
|
||||
lnAuth(k1: "${k1}") {
|
||||
|
@ -19,12 +19,12 @@ function LnQRAuth ({ k1, encodedUrl, callbackUrl }) {
|
|||
const { data } = useQuery(query, { pollInterval: 1000 })
|
||||
|
||||
if (data && data.lnAuth.pubkey) {
|
||||
signIn('credentials', { ...data.lnAuth, callbackUrl })
|
||||
signIn(encodedUrl ? 'lightning' : 'slashtags', { ...data.lnAuth, callbackUrl })
|
||||
}
|
||||
|
||||
// output pubkey and k1
|
||||
return (
|
||||
<LnQR value={encodedUrl} status='waiting for you' />
|
||||
<Qr value={encodedUrl || slashtagUrl} asIs={!!slashtagUrl} status='waiting for you' />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,15 @@ function LightningExplainer ({ text, children }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function LightningAuth ({ text, callbackUrl }) {
|
||||
export function LightningAuthWithExplainer ({ text, callbackUrl }) {
|
||||
return (
|
||||
<LightningExplainer text={text}>
|
||||
<LightningAuth callbackUrl={callbackUrl} />
|
||||
</LightningExplainer>
|
||||
)
|
||||
}
|
||||
|
||||
export function LightningAuth ({ callbackUrl }) {
|
||||
// query for challenge
|
||||
const [createAuth, { data, error }] = useMutation(gql`
|
||||
mutation createAuth {
|
||||
|
@ -100,9 +108,24 @@ export function LightningAuth ({ text, callbackUrl }) {
|
|||
|
||||
if (error) return <div>error</div>
|
||||
|
||||
return (
|
||||
<LightningExplainer text={text}>
|
||||
{data ? <LnQRAuth {...data.createAuth} callbackUrl={callbackUrl} /> : <LnQRSkeleton status='generating' />}
|
||||
</LightningExplainer>
|
||||
)
|
||||
return data ? <QrAuth {...data.createAuth} callbackUrl={callbackUrl} /> : <QrSkeleton status='generating' />
|
||||
}
|
||||
|
||||
export function SlashtagsAuth ({ callbackUrl }) {
|
||||
// query for challenge
|
||||
const [createAuth, { data, error }] = useMutation(gql`
|
||||
mutation createAuth {
|
||||
createAuth {
|
||||
k1
|
||||
slashtagUrl
|
||||
}
|
||||
}`)
|
||||
|
||||
useEffect(() => {
|
||||
createAuth()
|
||||
}, [])
|
||||
|
||||
if (error) return <div>error</div>
|
||||
|
||||
return data ? <QrAuth {...data.createAuth} callbackUrl={callbackUrl} /> : <QrSkeleton status='generating' />
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import GithubIcon from '../svgs/github-fill.svg'
|
||||
import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||
import LightningIcon from '../svgs/bolt.svg'
|
||||
import SlashtagsIcon from '../svgs/slashtags.svg'
|
||||
import { Button } from 'react-bootstrap'
|
||||
|
||||
export default function LoginButton ({ text, type, className, onClick }) {
|
||||
let Icon, variant
|
||||
switch (type) {
|
||||
|
@ -17,6 +19,10 @@ export default function LoginButton ({ text, type, className, onClick }) {
|
|||
Icon = LightningIcon
|
||||
variant = 'primary'
|
||||
break
|
||||
case 'slashtags':
|
||||
Icon = SlashtagsIcon
|
||||
variant = 'grey-medium'
|
||||
break
|
||||
}
|
||||
|
||||
const name = type.charAt(0).toUpperCase() + type.substr(1).toLowerCase()
|
||||
|
@ -25,7 +31,7 @@ export default function LoginButton ({ text, type, className, onClick }) {
|
|||
<Button className={className} variant={variant} onClick={onClick}>
|
||||
<Icon
|
||||
width={20}
|
||||
height={20} className='mr-2'
|
||||
height={20} className='mr-3'
|
||||
/>
|
||||
{text} {name}
|
||||
</Button>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import { signIn } from 'next-auth/client'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import styles from './login.module.css'
|
||||
import GithubIcon from '../svgs/github-fill.svg'
|
||||
import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||
import LightningIcon from '../svgs/bolt.svg'
|
||||
import { Form, Input, SubmitButton } from '../components/form'
|
||||
import * as Yup from 'yup'
|
||||
import { useState } from 'react'
|
||||
import Alert from 'react-bootstrap/Alert'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LightningAuth } from './lightning-auth'
|
||||
import { LightningAuthWithExplainer, SlashtagsAuth } from './lightning-auth'
|
||||
import LoginButton from './login-button'
|
||||
|
||||
export const EmailSchema = Yup.object({
|
||||
email: Yup.string().email('email is no good').required('required')
|
||||
|
@ -48,7 +45,7 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
Callback: 'Try signing with a different account.',
|
||||
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
|
||||
EmailSignin: 'Check your email address.',
|
||||
CredentialsSignin: 'Lightning auth failed.',
|
||||
CredentialsSignin: 'Auth failed',
|
||||
default: 'Unable to sign in.'
|
||||
}
|
||||
|
||||
|
@ -56,7 +53,11 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
const router = useRouter()
|
||||
|
||||
if (router.query.type === 'lightning') {
|
||||
return <LightningAuth callbackUrl={callbackUrl} text={text} />
|
||||
return <LightningAuthWithExplainer callbackUrl={callbackUrl} text={text} />
|
||||
}
|
||||
|
||||
if (router.query.type === 'slashtags') {
|
||||
return <SlashtagsAuth callbackUrl={callbackUrl} text={text} />
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -69,44 +70,41 @@ export default function Login ({ providers, callbackUrl, error, text, Header, Fo
|
|||
dismissible
|
||||
>{errorMessage}
|
||||
</Alert>}
|
||||
<Button
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
variant='primary'
|
||||
onClick={() => router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, type: 'lightning' }
|
||||
})}
|
||||
>
|
||||
<LightningIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className='mr-3'
|
||||
/>{text || 'Login'} with Lightning
|
||||
</Button>
|
||||
{providers && Object.values(providers).map(provider => {
|
||||
if (provider.name === 'Email' || provider.name === 'Lightning') {
|
||||
return null
|
||||
switch (provider.name) {
|
||||
case 'Email':
|
||||
return (
|
||||
<div className='w-100' key={provider.name}>
|
||||
<div className='mt-2 text-center text-muted font-weight-bold'>or</div>
|
||||
<EmailLoginForm text={text} callbackUrl={callbackUrl} />
|
||||
</div>
|
||||
)
|
||||
case 'Lightning':
|
||||
case 'Slashtags':
|
||||
return (
|
||||
<LoginButton
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
type={provider.name.toLowerCase()}
|
||||
onClick={() => router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, type: provider.name.toLowerCase() }
|
||||
})}
|
||||
text={`${text || 'Login'} with`}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<LoginButton
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
type={provider.name.toLowerCase()}
|
||||
onClick={() => signIn(provider.id, { callbackUrl })}
|
||||
text={`${text || 'Login'} with`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const [variant, Icon] =
|
||||
provider.name === 'Twitter'
|
||||
? ['twitter', TwitterIcon]
|
||||
: ['dark', GithubIcon]
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
variant={variant}
|
||||
onClick={() => signIn(provider.id, { callbackUrl })}
|
||||
>
|
||||
<Icon
|
||||
className='mr-3'
|
||||
/>{text || 'Login'} with {provider.name}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
<div className='mt-2 text-center text-muted font-weight-bold'>or</div>
|
||||
<EmailLoginForm text={text} callbackUrl={callbackUrl} />
|
||||
{Footer && <Footer />}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -4,8 +4,8 @@ import InvoiceStatus from './invoice-status'
|
|||
import { requestProvider } from 'webln'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function LnQR ({ value, webLn, statusVariant, status }) {
|
||||
const qrValue = 'lightning:' + value.toUpperCase()
|
||||
export default function Qr ({ asIs, value, webLn, statusVariant, status }) {
|
||||
const qrValue = asIs ? value : 'lightning:' + value.toUpperCase()
|
||||
|
||||
useEffect(() => {
|
||||
async function effect () {
|
||||
|
@ -36,7 +36,7 @@ export default function LnQR ({ value, webLn, statusVariant, status }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function LnQRSkeleton ({ status }) {
|
||||
export function QrSkeleton ({ status }) {
|
||||
return (
|
||||
<>
|
||||
<div className='h-auto w-100 clouds' style={{ paddingTop: 'min(300px + 2rem, 100%)', maxWidth: 'calc(300px + 2rem)' }} />
|
|
@ -52,9 +52,10 @@ export const SETTINGS_FIELDS = gql`
|
|||
greeterMode
|
||||
authMethods {
|
||||
lightning
|
||||
email
|
||||
twitter
|
||||
slashtags
|
||||
github
|
||||
twitter
|
||||
email
|
||||
}
|
||||
}`
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,8 @@
|
|||
"@lexical/react": "^0.7.5",
|
||||
"@opensearch-project/opensearch": "^1.1.0",
|
||||
"@prisma/client": "^2.30.3",
|
||||
"@synonymdev/slashtags-auth": "^1.0.0-alpha.5",
|
||||
"@synonymdev/slashtags-sdk": "^1.0.0-alpha.36",
|
||||
"apollo-server-micro": "^3.11.1",
|
||||
"async-retry": "^1.3.1",
|
||||
"aws-sdk": "^2.1248.0",
|
||||
|
|
|
@ -62,7 +62,8 @@ export default (req, res) => NextAuth(req, res, {
|
|||
},
|
||||
providers: [
|
||||
Providers.Credentials({
|
||||
// The name to display on the sign in form (e.g. 'Sign in with...')
|
||||
id: 'lightning',
|
||||
// The name to display on the sign in form (e.g. 'Sign in with...')
|
||||
name: 'Lightning',
|
||||
// The credentials is used to generate a suitable form on the sign in page.
|
||||
// You can specify whatever fields you are expecting to be submitted.
|
||||
|
@ -75,6 +76,7 @@ export default (req, res) => NextAuth(req, res, {
|
|||
const { k1, pubkey } = credentials
|
||||
try {
|
||||
const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } })
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
if (lnauth.pubkey === pubkey) {
|
||||
let user = await prisma.user.findUnique({ where: { pubkey } })
|
||||
const session = await getSession({ req })
|
||||
|
@ -89,7 +91,45 @@ export default (req, res) => NextAuth(req, res, {
|
|||
throw new Error('account not linked')
|
||||
}
|
||||
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
return user
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}),
|
||||
Providers.Credentials({
|
||||
id: 'slashtags',
|
||||
// The name to display on the sign in form (e.g. 'Sign in with...')
|
||||
name: 'Slashtags',
|
||||
// The credentials is used to generate a suitable form on the sign in page.
|
||||
// You can specify whatever fields you are expecting to be submitted.
|
||||
// e.g. domain, username, password, 2FA token, etc.
|
||||
credentials: {
|
||||
pubkey: { label: 'publickey', type: 'text' },
|
||||
k1: { label: 'k1', type: 'text' }
|
||||
},
|
||||
async authorize (credentials, req) {
|
||||
const { k1, pubkey } = credentials
|
||||
try {
|
||||
const lnauth = await prisma.lnAuth.findUnique({ where: { k1 } })
|
||||
await prisma.lnAuth.delete({ where: { k1 } })
|
||||
if (lnauth.pubkey === pubkey) {
|
||||
let user = await prisma.user.findUnique({ where: { slashtagId: pubkey } })
|
||||
const session = await getSession({ req })
|
||||
if (!user) {
|
||||
// if we are logged in, update rather than create
|
||||
if (session?.user) {
|
||||
user = await prisma.user.update({ where: { id: session.user.id }, data: { slashtagId: pubkey } })
|
||||
} else {
|
||||
user = await prisma.user.create({ data: { name: pubkey.slice(0, 10), slashtagId: pubkey } })
|
||||
}
|
||||
} else if (session && session.user?.id !== user.id) {
|
||||
throw new Error('account not linked')
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import lnd from '../../api/lnd'
|
|||
import typeDefs from '../../api/typeDefs'
|
||||
import { getSession } from 'next-auth/client'
|
||||
import search from '../../api/search'
|
||||
import slashtags from '../../api/slashtags'
|
||||
|
||||
const apolloServer = new ApolloServer({
|
||||
typeDefs,
|
||||
|
@ -40,7 +41,8 @@ const apolloServer = new ApolloServer({
|
|||
me: session
|
||||
? session.user
|
||||
: null,
|
||||
search
|
||||
search,
|
||||
slashtags
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import secp256k1 from 'secp256k1'
|
||||
import models from '../../api/models'
|
||||
|
||||
const HOUR = 1000 * 60 * 60
|
||||
|
||||
export default async ({ query }, res) => {
|
||||
try {
|
||||
const sig = Buffer.from(query.sig, 'hex')
|
||||
|
@ -11,6 +13,11 @@ export default async ({ query }, res) => {
|
|||
const key = Buffer.from(query.key, 'hex')
|
||||
const signature = secp256k1.signatureImport(sig)
|
||||
if (secp256k1.ecdsaVerify(signature, k1, key)) {
|
||||
const auth = await models.lnAuth.findUnique({ where: { k1: query.k1 } })
|
||||
if (!auth || auth.pubkey || auth.createdAt < Date.now() - HOUR) {
|
||||
return res.status(400).json({ status: 'ERROR', reason: 'token expired' })
|
||||
}
|
||||
|
||||
await models.lnAuth.update({ where: { k1: query.k1 }, data: { pubkey: query.key } })
|
||||
return res.status(200).json({ status: 'OK' })
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useQuery } from '@apollo/client'
|
||||
import { Invoice } from '../../components/invoice'
|
||||
import { LnQRSkeleton } from '../../components/lnqr'
|
||||
import { QrSkeleton } from '../../components/qr'
|
||||
import LayoutCenter from '../../components/layout-center'
|
||||
import { useRouter } from 'next/router'
|
||||
import { INVOICE } from '../../fragments/wallet'
|
||||
|
@ -24,7 +24,7 @@ function LoadInvoice () {
|
|||
return <div>error</div>
|
||||
}
|
||||
if (!data || loading) {
|
||||
return <LnQRSkeleton status='loading' />
|
||||
return <QrSkeleton status='loading' />
|
||||
}
|
||||
|
||||
return <Invoice invoice={data.invoice} />
|
||||
|
|
|
@ -8,7 +8,7 @@ import { getGetServerSideProps } from '../api/ssrApollo'
|
|||
import LoginButton from '../components/login-button'
|
||||
import { signIn } from 'next-auth/client'
|
||||
import ModalButton from '../components/modal-button'
|
||||
import { LightningAuth } from '../components/lightning-auth'
|
||||
import { LightningAuth, SlashtagsAuth } from '../components/lightning-auth'
|
||||
import { SETTINGS, SET_SETTINGS } from '../fragments/users'
|
||||
import { useRouter } from 'next/router'
|
||||
import Info from '../components/info'
|
||||
|
@ -300,14 +300,11 @@ function AuthMethods ({ methods }) {
|
|||
)
|
||||
const [obstacle, setObstacle] = useState()
|
||||
|
||||
const providers = Object.keys(methods).filter(k => k !== '__typename')
|
||||
|
||||
const unlink = async type => {
|
||||
// if there's only one auth method left
|
||||
let links = 0
|
||||
links += methods.lightning ? 1 : 0
|
||||
links += methods.email ? 1 : 0
|
||||
links += methods.twitter ? 1 : 0
|
||||
links += methods.github ? 1 : 0
|
||||
|
||||
const links = providers.reduce((t, p) => t + (methods[p] ? 1 : 0), 0)
|
||||
if (links === 1) {
|
||||
setObstacle(type)
|
||||
} else {
|
||||
|
@ -349,63 +346,78 @@ function AuthMethods ({ methods }) {
|
|||
</Modal.Body>
|
||||
</Modal>
|
||||
<div className='form-label mt-3'>auth methods</div>
|
||||
{methods.lightning
|
||||
? <LoginButton
|
||||
className='d-block' type='lightning' text='Unlink' onClick={
|
||||
async () => {
|
||||
await unlink('lightning')
|
||||
}
|
||||
{providers && providers.map(provider => {
|
||||
switch (provider) {
|
||||
case 'email':
|
||||
return methods.email
|
||||
? (
|
||||
<div className='mt-2 d-flex align-items-center'>
|
||||
<Input
|
||||
name='email'
|
||||
placeholder={methods.email}
|
||||
groupClassName='mb-0'
|
||||
readOnly
|
||||
noForm
|
||||
/>
|
||||
<Button
|
||||
className='ml-2' variant='secondary' onClick={
|
||||
async () => {
|
||||
await unlink('email')
|
||||
}
|
||||
}
|
||||
>Unlink Email
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
: <div className='mt-2'><EmailLinkForm /></div>
|
||||
case 'lightning':
|
||||
return methods.lightning
|
||||
? <LoginButton
|
||||
className='d-block' type='lightning' text='Unlink' onClick={
|
||||
async () => {
|
||||
await unlink('lightning')
|
||||
}
|
||||
}
|
||||
/>
|
||||
: (
|
||||
<ModalButton clicker={<LoginButton className='d-block' type='lightning' text='Link' />}>
|
||||
<div className='d-flex flex-column align-items-center'>
|
||||
<LightningAuth />
|
||||
</div>
|
||||
</ModalButton>)
|
||||
case 'slashtags':
|
||||
return methods.slashtags
|
||||
? <LoginButton
|
||||
className='d-block mt-2' type='slashtags' text='Unlink' onClick={
|
||||
async () => {
|
||||
await unlink('slashtags')
|
||||
}
|
||||
}
|
||||
/>
|
||||
: (
|
||||
<ModalButton clicker={<LoginButton className='d-block mt-2' type='slashtags' text='Link' />}>
|
||||
<div className='d-flex flex-column align-items-center'>
|
||||
<SlashtagsAuth />
|
||||
</div>
|
||||
</ModalButton>)
|
||||
default:
|
||||
return (
|
||||
<LoginButton
|
||||
className='mt-2 d-block'
|
||||
key={provider}
|
||||
type={provider.toLowerCase()}
|
||||
onClick={async () => {
|
||||
if (methods[provider]) {
|
||||
await unlink(provider)
|
||||
} else {
|
||||
signIn(provider)
|
||||
}
|
||||
}}
|
||||
text={methods[provider] ? 'Unlink' : 'Link'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
: (
|
||||
<ModalButton clicker={<LoginButton className='d-block' type='lightning' text='Link' />}>
|
||||
<div className='d-flex flex-column align-items-center'>
|
||||
<LightningAuth />
|
||||
</div>
|
||||
</ModalButton>)}
|
||||
<LoginButton
|
||||
className='d-block mt-2' type='twitter' text={methods.twitter ? 'Unlink' : 'Link'} onClick={
|
||||
async () => {
|
||||
if (methods.twitter) {
|
||||
await unlink('twitter')
|
||||
} else {
|
||||
signIn('twitter')
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<LoginButton
|
||||
className='d-block mt-2' type='github' text={methods.github ? 'Unlink' : 'Link'} onClick={
|
||||
async () => {
|
||||
if (methods.github) {
|
||||
await unlink('github')
|
||||
} else {
|
||||
signIn('github')
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
{methods.email
|
||||
? (
|
||||
<div className='mt-2 d-flex align-items-center'>
|
||||
<Input
|
||||
name='email'
|
||||
placeholder={methods.email}
|
||||
groupClassName='mb-0'
|
||||
readOnly
|
||||
noForm
|
||||
/>
|
||||
<Button
|
||||
className='ml-2' variant='secondary' onClick={
|
||||
async () => {
|
||||
await unlink('email')
|
||||
}
|
||||
}
|
||||
>Unlink Email
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
: <div className='mt-2'><EmailLinkForm /></div>}
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import Link from 'next/link'
|
|||
import Button from 'react-bootstrap/Button'
|
||||
import * as Yup from 'yup'
|
||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import LnQR, { LnQRSkeleton } from '../components/lnqr'
|
||||
import Qr, { QrSkeleton } from '../components/qr'
|
||||
import LayoutCenter from '../components/layout-center'
|
||||
import InputGroup from 'react-bootstrap/InputGroup'
|
||||
import { WithdrawlSkeleton } from './withdrawals/[id]'
|
||||
|
@ -95,7 +95,7 @@ export function FundForm () {
|
|||
}, [])
|
||||
|
||||
if (called && !error) {
|
||||
return <LnQRSkeleton status='generating' />
|
||||
return <QrSkeleton status='generating' />
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -226,7 +226,7 @@ function LnQRWith ({ k1, encodedUrl }) {
|
|||
router.push(`/withdrawals/${data.lnWith.withdrawalId}`)
|
||||
}
|
||||
|
||||
return <LnQR value={encodedUrl} status='waiting for you' />
|
||||
return <Qr value={encodedUrl} status='waiting for you' />
|
||||
}
|
||||
|
||||
export function LnWithdrawal () {
|
||||
|
@ -246,7 +246,7 @@ export function LnWithdrawal () {
|
|||
if (error) return <div>error</div>
|
||||
|
||||
if (!data) {
|
||||
return <LnQRSkeleton status='generating' />
|
||||
return <QrSkeleton status='generating' />
|
||||
}
|
||||
|
||||
return <LnQRWith {...data.createWith} />
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[slashtagId]` on the table `users` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "slashtagId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users.slashtagId_unique" ON "users"("slashtagId");
|
|
@ -37,6 +37,7 @@ model User {
|
|||
checkedNotesAt DateTime?
|
||||
fiatCurrency String @default("USD")
|
||||
pubkey String? @unique
|
||||
slashtagId String? @unique
|
||||
trust Float @default(0)
|
||||
upvoteTrust Float @default(0)
|
||||
lastSeenAt DateTime?
|
||||
|
|
|
@ -206,6 +206,10 @@ div[contenteditable]:disabled,
|
|||
fill: #212529;
|
||||
}
|
||||
|
||||
.btn-grey-medium svg {
|
||||
fill: #212529;
|
||||
}
|
||||
|
||||
.fresh {
|
||||
background-color: var(--theme-clickToContextColor);
|
||||
border-radius: .4rem;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70 88.9103V70.1764L130 54V72.7339L70 88.9103ZM130 100.355L70 116.532V97.6448L130 81.4685V100.355ZM130 127.824L70 144V125.266L130 109.09V127.824Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.525 0.563842C165.505 6.37341 205.246 55.6284 199.436 110.525C193.627 165.505 144.372 205.246 89.4754 199.436C34.4951 193.627 -5.24572 144.372 0.563842 89.4754C6.37341 34.4951 55.6284 -5.24572 110.525 0.563842ZM108.335 21.4446C65.1426 16.8138 25.9912 48.4717 21.4446 91.6645C16.8138 134.857 48.4717 174.009 91.6645 178.555C134.857 183.186 174.009 151.528 178.555 108.335C183.186 65.1426 151.528 25.9912 108.335 21.4446Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 781 B |
Loading…
Reference in New Issue