Add Opt-in to Display Linked Accounts in Profile (#826)
* Add display linked accounts to settings * Apply suggestions from code review Co-authored-by: ekzyis <ek@stacker.news> * small styling enhancements --------- Co-authored-by: ekzyis <ek@stacker.news> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
parent
5c3c7fb185
commit
b3498fe277
|
@ -503,10 +503,15 @@ export default {
|
|||
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
||||
}
|
||||
await models.account.delete({ where: { id: account.id } })
|
||||
if (authType === 'twitter') {
|
||||
await models.user.update({ where: { id: me.id }, data: { hideTwitter: true, twitterId: null } })
|
||||
} else {
|
||||
await models.user.update({ where: { id: me.id }, data: { hideGithub: true, githubId: null } })
|
||||
}
|
||||
} else if (authType === 'lightning') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { pubkey: null } })
|
||||
} else if (authType === 'nostr') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { nostrAuthPubkey: null } })
|
||||
user = await models.user.update({ where: { id: me.id }, data: { hideNostr: true, nostrAuthPubkey: null } })
|
||||
} else if (authType === 'email') {
|
||||
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
||||
} else {
|
||||
|
@ -811,6 +816,24 @@ export default {
|
|||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
githubId: async (user, args, { me }) => {
|
||||
if ((!me || me.id !== user.id) && user.hideGithub) {
|
||||
return null
|
||||
}
|
||||
return user.githubId
|
||||
},
|
||||
twitterId: async (user, args, { models, me }) => {
|
||||
if ((!me || me.id !== user.id) && user.hideTwitter) {
|
||||
return null
|
||||
}
|
||||
return user.twitterId
|
||||
},
|
||||
nostrAuthPubkey: async (user, args, { models, me }) => {
|
||||
if ((!me || me.id !== user.id) && user.hideNostr) {
|
||||
return null
|
||||
}
|
||||
return user.nostrAuthPubkey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ export default gql`
|
|||
greeterMode: Boolean!
|
||||
hideBookmarks: Boolean!
|
||||
hideCowboyHat: Boolean!
|
||||
hideGithub: Boolean!
|
||||
hideNostr: Boolean!
|
||||
hideTwitter: Boolean!
|
||||
hideFromTopUsers: Boolean!
|
||||
hideInvoiceDesc: Boolean!
|
||||
hideIsContributor: Boolean!
|
||||
|
@ -119,6 +122,9 @@ export default gql`
|
|||
greeterMode: Boolean!
|
||||
hideBookmarks: Boolean!
|
||||
hideCowboyHat: Boolean!
|
||||
hideGithub: Boolean!
|
||||
hideNostr: Boolean!
|
||||
hideTwitter: Boolean!
|
||||
hideFromTopUsers: Boolean!
|
||||
hideInvoiceDesc: Boolean!
|
||||
hideIsContributor: Boolean!
|
||||
|
@ -156,5 +162,8 @@ export default gql`
|
|||
streak: Int
|
||||
maxStreak: Int
|
||||
isContributor: Boolean
|
||||
githubId: String
|
||||
twitterId: String
|
||||
nostrAuthPubkey: String
|
||||
}
|
||||
`
|
||||
|
|
|
@ -24,6 +24,10 @@ import CodeIcon from '../svgs/terminal-box-fill.svg'
|
|||
import MuteDropdownItem from './mute'
|
||||
import copy from 'clipboard-copy'
|
||||
import { useToast } from './toast'
|
||||
import { hexToBech32 } from '../lib/nostr'
|
||||
import NostrIcon from '../svgs/nostr.svg'
|
||||
import GithubIcon from '../svgs/github-fill.svg'
|
||||
import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||
|
||||
export default function UserHeader ({ user }) {
|
||||
const router = useRouter()
|
||||
|
@ -180,8 +184,36 @@ function HeaderNym ({ user, isMe }) {
|
|||
: <NymView user={user} isMe={isMe} setEditting={setEditting} />
|
||||
}
|
||||
|
||||
function SocialLink ({ name, id }) {
|
||||
const className = `${styles.social} text-reset`
|
||||
if (name === 'Nostr') {
|
||||
const npub = hexToBech32(id)
|
||||
return (
|
||||
<Link className={className} target='_blank' href={`https://nostr.com/${npub}`} rel='noreferrer'>
|
||||
<NostrIcon width={20} height={20} className='me-1' />
|
||||
{npub.slice(0, 10)}...{npub.slice(-10)}
|
||||
</Link>
|
||||
)
|
||||
} else if (name === 'Github') {
|
||||
return (
|
||||
<Link className={className} target='_blank' href={`https://github.com/${id}`} rel='noreferrer'>
|
||||
<GithubIcon width={20} height={20} className='me-1' />
|
||||
{id}
|
||||
</Link>
|
||||
)
|
||||
} else if (name === 'Twitter') {
|
||||
return (
|
||||
<Link className={className} target='_blank' href={`https://twitter.com/${id}`} rel='noreferrer'>
|
||||
<TwitterIcon width={20} height={20} className='me-1' />
|
||||
@{id}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function HeaderHeader ({ user }) {
|
||||
const me = useMe()
|
||||
|
||||
const showModal = useShowModal()
|
||||
const toaster = useToast()
|
||||
|
||||
|
@ -229,8 +261,24 @@ function HeaderHeader ({ user }) {
|
|||
? <Link href={`/items/${user.since}`} className='ms-1'>#{user.since}</Link>
|
||||
: <span>never</span>}
|
||||
</small>
|
||||
{user.optional.maxStreak !== null && <small className='text-muted d-flex-inline'>longest cowboy streak: {user.optional.maxStreak}</small>}
|
||||
{user.optional.isContributor && <small className='text-muted d-flex align-items-center'><CodeIcon className='me-1' height={16} width={16} /> verified stacker.news contributor</small>}
|
||||
{user.optional.maxStreak !== null &&
|
||||
<small className='text-muted d-flex-inline'>longest cowboy streak: {user.optional.maxStreak}</small>}
|
||||
{user.optional.isContributor &&
|
||||
<small className='text-muted d-flex align-items-center'>
|
||||
<CodeIcon className='me-1' height={16} width={16} /> verified stacker.news contributor
|
||||
</small>}
|
||||
{user.optional.nostrAuthPubkey !== null && !me.privates?.hideNostr &&
|
||||
<small className='text-muted d-flex-inline'>
|
||||
<SocialLink name='Nostr' id={user.optional.nostrAuthPubkey} />
|
||||
</small>}
|
||||
{user.optional.githubId !== null && !me?.privates?.hideGithub &&
|
||||
<small className='text-muted d-flex-inline'>
|
||||
<SocialLink name='Github' id={user.optional.githubId} />
|
||||
</small>}
|
||||
{user.optional.twitterId !== null && !me?.privates?.hideTwitter &&
|
||||
<small className='text-muted d-flex-inline'>
|
||||
<SocialLink name='Twitter' id={user.optional.twitterId} />
|
||||
</small>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,3 +31,9 @@
|
|||
font-size: 150%;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.social {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
|
@ -15,6 +15,9 @@ export const ME = gql`
|
|||
greeterMode
|
||||
hideCowboyHat
|
||||
hideFromTopUsers
|
||||
hideGithub
|
||||
hideNostr
|
||||
hideTwitter
|
||||
hideInvoiceDesc
|
||||
hideIsContributor
|
||||
hideWalletBalance
|
||||
|
@ -47,6 +50,9 @@ export const ME = gql`
|
|||
isContributor
|
||||
stacked
|
||||
streak
|
||||
githubId
|
||||
nostrAuthPubkey
|
||||
twitterId
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
@ -73,6 +79,9 @@ export const SETTINGS_FIELDS = gql`
|
|||
hideFromTopUsers
|
||||
hideCowboyHat
|
||||
hideBookmarks
|
||||
hideGithub
|
||||
hideNostr
|
||||
hideTwitter
|
||||
hideIsContributor
|
||||
imgproxyOnly
|
||||
hideWalletBalance
|
||||
|
@ -182,6 +191,9 @@ export const USER_FIELDS = gql`
|
|||
streak
|
||||
maxStreak
|
||||
isContributor
|
||||
githubId
|
||||
nostrAuthPubkey
|
||||
twitterId
|
||||
}
|
||||
}`
|
||||
|
||||
|
|
|
@ -476,6 +476,9 @@ export const settingsSchema = object({
|
|||
).max(NOSTR_MAX_RELAY_NUM,
|
||||
({ max, value }) => `${Math.abs(max - value.length)} too many`),
|
||||
hideBookmarks: boolean(),
|
||||
hideGithub: boolean(),
|
||||
hideNostr: boolean(),
|
||||
hideTwitter: boolean(),
|
||||
hideWalletBalance: boolean(),
|
||||
diagnostics: boolean(),
|
||||
hideIsContributor: boolean()
|
||||
|
|
|
@ -12,6 +12,30 @@ import { NodeNextRequest } from 'next/dist/server/base-http/node'
|
|||
import { schnorr } from '@noble/curves/secp256k1'
|
||||
import { sendUserNotification } from '../../../api/webPush'
|
||||
|
||||
/**
|
||||
* Stores userIds in user table
|
||||
* @returns {Partial<import('next-auth').EventCallbacks>}
|
||||
* */
|
||||
function getEventCallbacks () {
|
||||
return {
|
||||
async linkAccount ({ user, profile, account }) {
|
||||
if (account.provider === 'github') {
|
||||
await prisma.user.update({ where: { id: user.id }, data: { githubId: profile.name } })
|
||||
} else if (account.provider === 'twitter') {
|
||||
await prisma.user.update({ where: { id: user.id }, data: { twitterId: profile.name } })
|
||||
}
|
||||
},
|
||||
async signIn ({ user, profile, account, isNewUser }) {
|
||||
if (account.provider === 'github') {
|
||||
await prisma.user.update({ where: { id: user.id }, data: { githubId: profile.name } })
|
||||
} else if (account.provider === 'twitter') {
|
||||
await prisma.user.update({ where: { id: user.id }, data: { twitterId: profile.name } })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Partial<import('next-auth').CallbacksOptions>} */
|
||||
function getCallbacks (req) {
|
||||
return {
|
||||
/**
|
||||
|
@ -136,6 +160,7 @@ async function nostrEventAuth (event) {
|
|||
return { k1, pubkey }
|
||||
}
|
||||
|
||||
/** @type {import('next-auth/providers').Provider[]} */
|
||||
const providers = [
|
||||
CredentialsProvider({
|
||||
id: 'lightning',
|
||||
|
@ -164,7 +189,7 @@ const providers = [
|
|||
url: 'https://github.com/login/oauth/authorize',
|
||||
params: { scope: '' }
|
||||
},
|
||||
profile: profile => {
|
||||
profile (profile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.login
|
||||
|
@ -174,7 +199,7 @@ const providers = [
|
|||
TwitterProvider({
|
||||
clientId: process.env.TWITTER_ID,
|
||||
clientSecret: process.env.TWITTER_SECRET,
|
||||
profile: profile => {
|
||||
profile (profile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.screen_name
|
||||
|
@ -188,6 +213,7 @@ const providers = [
|
|||
})
|
||||
]
|
||||
|
||||
/** @returns {import('next-auth').AuthOptions} */
|
||||
export const getAuthOptions = req => ({
|
||||
callbacks: getCallbacks(req),
|
||||
providers,
|
||||
|
@ -199,7 +225,8 @@ export const getAuthOptions = req => ({
|
|||
signIn: '/login',
|
||||
verifyRequest: '/email',
|
||||
error: '/auth/error'
|
||||
}
|
||||
},
|
||||
events: getEventCallbacks()
|
||||
})
|
||||
|
||||
export default async (req, res) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import Alert from 'react-bootstrap/Alert'
|
|||
import Button from 'react-bootstrap/Button'
|
||||
import InputGroup from 'react-bootstrap/InputGroup'
|
||||
import { CenterLayout } from '../../components/layout'
|
||||
import { useState } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import { getGetServerSideProps } from '../../api/ssrApollo'
|
||||
import LoginButton from '../../components/login-button'
|
||||
|
@ -52,7 +52,7 @@ export default function Settings ({ ssrData }) {
|
|||
const logger = useLogger()
|
||||
|
||||
const { data } = useQuery(SETTINGS)
|
||||
const { settings: { privates: settings } } = data || ssrData
|
||||
const { settings: { privates: settings } } = useMemo(() => data ?? ssrData, [data, ssrData])
|
||||
if (!data && !ssrData) return <PageLoading />
|
||||
|
||||
return (
|
||||
|
@ -79,6 +79,9 @@ export default function Settings ({ ssrData }) {
|
|||
autoDropBolt11s: settings?.autoDropBolt11s,
|
||||
hideFromTopUsers: settings?.hideFromTopUsers,
|
||||
hideCowboyHat: settings?.hideCowboyHat,
|
||||
hideGithub: settings?.hideGithub,
|
||||
hideNostr: settings?.hideNostr,
|
||||
hideTwitter: settings?.hideTwitter,
|
||||
imgproxyOnly: settings?.imgproxyOnly,
|
||||
wildWestMode: settings?.wildWestMode,
|
||||
greeterMode: settings?.greeterMode,
|
||||
|
@ -284,6 +287,66 @@ export default function Settings ({ ssrData }) {
|
|||
name='hideBookmarks'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
disabled={me.optional.githubId === null}
|
||||
label={
|
||||
<div className='d-flex align-items-center'>hide my linked github profile
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>Linked accounts are hidden from your profile by default</li>
|
||||
<li>uncheck this to display your github on your profile</li>
|
||||
{me.optional.githubId === null &&
|
||||
<div className='my-2'>
|
||||
<li><i>You don't seem to have a linked github account</i></li>
|
||||
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||
</div>}
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
name='hideGithub'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
disabled={me.optional.nostrAuthPubkey === null}
|
||||
label={
|
||||
<div className='d-flex align-items-center'>hide my linked nostr profile
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>Linked accounts are hidden from your profile by default</li>
|
||||
<li>Uncheck this to display your npub on your profile</li>
|
||||
{me.optional.nostrAuthPubkey === null &&
|
||||
<div className='my-2'>
|
||||
<li>You don't seem to have a linked nostr account</li>
|
||||
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||
</div>}
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
name='hideNostr'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<Checkbox
|
||||
disabled={me.optional.twitterId === null}
|
||||
label={
|
||||
<div className='d-flex align-items-center'>hide my linked twitter profile
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>Linked accounts are hidden from your profile by default</li>
|
||||
<li>Uncheck this to display your twitter on your profile</li>
|
||||
{me.optional.twitterId === null &&
|
||||
<div className='my-2'>
|
||||
<i>You don't seem to have a linked twitter account</i>
|
||||
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||
</div>}
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
name='hideTwitter'
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
{me.optional?.isContributor &&
|
||||
<Checkbox
|
||||
label={<>hide that I'm a stacker.news contributor</>}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "hideGithub" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "hideNostr" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "hideTwitter" BOOLEAN NOT NULL DEFAULT true;
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "githubId" TEXT,
|
||||
ADD COLUMN "twitterId" TEXT;
|
|
@ -92,6 +92,11 @@ model User {
|
|||
Session Session[]
|
||||
itemForwards ItemForward[]
|
||||
hideBookmarks Boolean @default(false)
|
||||
hideGithub Boolean @default(true)
|
||||
hideNostr Boolean @default(true)
|
||||
hideTwitter Boolean @default(true)
|
||||
githubId String?
|
||||
twitterId String?
|
||||
followers UserSubscription[] @relation("follower")
|
||||
followees UserSubscription[] @relation("followee")
|
||||
hideWelcomeBanner Boolean @default(false)
|
||||
|
|
Loading…
Reference in New Issue