streaks
This commit is contained in:
		
							parent
							
								
									a6ce93c2bb
								
							
						
					
					
						commit
						072e60c954
					
				@ -181,6 +181,15 @@ export default {
 | 
				
			|||||||
            GROUP BY "userId", created_at`
 | 
					            GROUP BY "userId", created_at`
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (meFull.noteCowboyHat) {
 | 
				
			||||||
 | 
					          queries.push(
 | 
				
			||||||
 | 
					            `SELECT id::text, updated_at AS "sortTime", 0 as "earnedSats", 'Streak' AS type
 | 
				
			||||||
 | 
					            FROM "Streak"
 | 
				
			||||||
 | 
					            WHERE "userId" = $1
 | 
				
			||||||
 | 
					            AND updated_at <= $2`
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // we do all this crazy subquery stuff to make 'reward' islands
 | 
					      // we do all this crazy subquery stuff to make 'reward' islands
 | 
				
			||||||
@ -227,6 +236,17 @@ export default {
 | 
				
			|||||||
  JobChanged: {
 | 
					  JobChanged: {
 | 
				
			||||||
    item: async (n, args, { models }) => getItem(n, { id: n.id }, { models })
 | 
					    item: async (n, args, { models }) => getItem(n, { id: n.id }, { models })
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  Streak: {
 | 
				
			||||||
 | 
					    days: async (n, args, { models }) => {
 | 
				
			||||||
 | 
					      const res = await models.$queryRaw`
 | 
				
			||||||
 | 
					        SELECT "endedAt" - "startedAt" AS days
 | 
				
			||||||
 | 
					        FROM "Streak"
 | 
				
			||||||
 | 
					        WHERE id = ${Number(n.id)} AND "endedAt" IS NOT NULL
 | 
				
			||||||
 | 
					      `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return res.length ? res[0].days : null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  Earn: {
 | 
					  Earn: {
 | 
				
			||||||
    sources: async (n, args, { me, models }) => {
 | 
					    sources: async (n, args, { me, models }) => {
 | 
				
			||||||
      const [sources] = await models.$queryRaw(`
 | 
					      const [sources] = await models.$queryRaw(`
 | 
				
			||||||
 | 
				
			|||||||
@ -304,6 +304,21 @@ export default {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (user.noteCowboyHat) {
 | 
				
			||||||
 | 
					        const streak = await models.streak.findFirst({
 | 
				
			||||||
 | 
					          where: {
 | 
				
			||||||
 | 
					            userId: me.id,
 | 
				
			||||||
 | 
					            updatedAt: {
 | 
				
			||||||
 | 
					              gt: lastChecked
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (streak) {
 | 
				
			||||||
 | 
					          return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return false
 | 
					      return false
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    searchUsers: async (parent, { q, limit, similarity }, { models }) => {
 | 
					    searchUsers: async (parent, { q, limit, similarity }, { models }) => {
 | 
				
			||||||
@ -475,6 +490,15 @@ export default {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    streak: async (user, args, { models }) => {
 | 
				
			||||||
 | 
					      const res = await models.$queryRaw`
 | 
				
			||||||
 | 
					        SELECT (now_utc() at time zone 'America/Chicago')::date - "startedAt" AS days
 | 
				
			||||||
 | 
					        FROM "Streak"
 | 
				
			||||||
 | 
					        WHERE "userId" = ${user.id} AND "endedAt" IS NULL
 | 
				
			||||||
 | 
					      `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return res.length ? res[0].days : null
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    stacked: async (user, { when }, { models }) => {
 | 
					    stacked: async (user, { when }, { models }) => {
 | 
				
			||||||
      if (user.stacked) {
 | 
					      if (user.stacked) {
 | 
				
			||||||
        return user.stacked
 | 
					        return user.stacked
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,12 @@ export default gql`
 | 
				
			|||||||
    tips: Int!
 | 
					    tips: Int!
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  type Streak {
 | 
				
			||||||
 | 
					    sortTime: String!
 | 
				
			||||||
 | 
					    days: Int
 | 
				
			||||||
 | 
					    id: ID!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type Earn {
 | 
					  type Earn {
 | 
				
			||||||
    earnedSats: Int!
 | 
					    earnedSats: Int!
 | 
				
			||||||
    sortTime: String!
 | 
					    sortTime: String!
 | 
				
			||||||
@ -56,6 +62,7 @@ export default gql`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  union Notification = Reply | Votification | Mention
 | 
					  union Notification = Reply | Votification | Mention
 | 
				
			||||||
    | Invitification | Earn | JobChanged | InvoicePaid | Referral
 | 
					    | Invitification | Earn | JobChanged | InvoicePaid | Referral
 | 
				
			||||||
 | 
					    | Streak
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type Notifications {
 | 
					  type Notifications {
 | 
				
			||||||
    lastChecked: String
 | 
					    lastChecked: String
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ export default gql`
 | 
				
			|||||||
    setName(name: String!): Boolean
 | 
					    setName(name: String!): Boolean
 | 
				
			||||||
    setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, noteItemSats: Boolean!,
 | 
					    setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, noteItemSats: Boolean!,
 | 
				
			||||||
      noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
 | 
					      noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
 | 
				
			||||||
      noteInvites: Boolean!, noteJobIndicator: Boolean!, hideInvoiceDesc: Boolean!, hideFromTopUsers: Boolean!,
 | 
					      noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!, hideFromTopUsers: Boolean!,
 | 
				
			||||||
      wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!]): User
 | 
					      wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!]): User
 | 
				
			||||||
    setPhoto(photoId: ID!): Int!
 | 
					    setPhoto(photoId: ID!): Int!
 | 
				
			||||||
    upsertBio(bio: String!): User!
 | 
					    upsertBio(bio: String!): User!
 | 
				
			||||||
@ -58,6 +58,7 @@ export default gql`
 | 
				
			|||||||
    bio: Item
 | 
					    bio: Item
 | 
				
			||||||
    bioId: Int
 | 
					    bioId: Int
 | 
				
			||||||
    photoId: Int
 | 
					    photoId: Int
 | 
				
			||||||
 | 
					    streak: Int
 | 
				
			||||||
    sats: Int!
 | 
					    sats: Int!
 | 
				
			||||||
    upvotePopover: Boolean!
 | 
					    upvotePopover: Boolean!
 | 
				
			||||||
    tipPopover: Boolean!
 | 
					    tipPopover: Boolean!
 | 
				
			||||||
@ -68,6 +69,7 @@ export default gql`
 | 
				
			|||||||
    noteDeposits: Boolean!
 | 
					    noteDeposits: Boolean!
 | 
				
			||||||
    noteInvites: Boolean!
 | 
					    noteInvites: Boolean!
 | 
				
			||||||
    noteJobIndicator: Boolean!
 | 
					    noteJobIndicator: Boolean!
 | 
				
			||||||
 | 
					    noteCowboyHat: Boolean!
 | 
				
			||||||
    hideInvoiceDesc: Boolean!
 | 
					    hideInvoiceDesc: Boolean!
 | 
				
			||||||
    hideFromTopUsers: Boolean!
 | 
					    hideFromTopUsers: Boolean!
 | 
				
			||||||
    wildWestMode: Boolean!
 | 
					    wildWestMode: Boolean!
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ import { Badge } from 'react-bootstrap'
 | 
				
			|||||||
import { abbrNum } from '../lib/format'
 | 
					import { abbrNum } from '../lib/format'
 | 
				
			||||||
import Share from './share'
 | 
					import Share from './share'
 | 
				
			||||||
import { DeleteDropdown } from './delete'
 | 
					import { DeleteDropdown } from './delete'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Parent ({ item, rootText }) {
 | 
					function Parent ({ item, rootText }) {
 | 
				
			||||||
  const ParentFrag = () => (
 | 
					  const ParentFrag = () => (
 | 
				
			||||||
@ -135,7 +136,10 @@ export default function Comment ({
 | 
				
			|||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
              <span> \ </span>
 | 
					              <span> \ </span>
 | 
				
			||||||
              <Link href={`/${item.user.name}`} passHref>
 | 
					              <Link href={`/${item.user.name}`} passHref>
 | 
				
			||||||
                <a>@{item.user.name}<span className='text-boost font-weight-bold'>{op && ' OP'}</span></a>
 | 
					                <a className='d-inline-flex align-items-center'>
 | 
				
			||||||
 | 
					                  @{item.user.name}<CowboyHat className='ml-1 fill-grey' streak={item.user.streak} height={12} width={12} />
 | 
				
			||||||
 | 
					                  {op && <span className='text-boost font-weight-bold ml-1'>OP</span>}
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
              <span> </span>
 | 
					              <span> </span>
 | 
				
			||||||
              <Link href={`/items/${item.id}`} passHref>
 | 
					              <Link href={`/items/${item.id}`} passHref>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								components/cowboy-hat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								components/cowboy-hat.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap'
 | 
				
			||||||
 | 
					import CowboyHatIcon from '../svgs/cowboy.svg'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function CowboyHat ({ streak, badge, className = 'ml-1', height = 16, width = 16 }) {
 | 
				
			||||||
 | 
					  if (!streak) {
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <HatTooltip overlayText={`${streak} days`}>
 | 
				
			||||||
 | 
					      {badge
 | 
				
			||||||
 | 
					        ? (
 | 
				
			||||||
 | 
					          <Badge variant='grey-medium' className='ml-2 d-inline-flex align-items-center'>
 | 
				
			||||||
 | 
					            <CowboyHatIcon className={className} height={height} width={width} />
 | 
				
			||||||
 | 
					            <span className='ml-1'>{streak}</span>
 | 
				
			||||||
 | 
					          </Badge>)
 | 
				
			||||||
 | 
					        : <CowboyHatIcon className={className} height={height} width={width} />}
 | 
				
			||||||
 | 
					    </HatTooltip>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function HatTooltip ({ children, overlayText, placement }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <OverlayTrigger
 | 
				
			||||||
 | 
					      placement={placement || 'bottom'}
 | 
				
			||||||
 | 
					      overlay={
 | 
				
			||||||
 | 
					        <Tooltip>
 | 
				
			||||||
 | 
					          {overlayText || '1 sat'}
 | 
				
			||||||
 | 
					        </Tooltip>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      trigger={['hover', 'focus']}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </OverlayTrigger>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -15,6 +15,7 @@ import { abbrNum } from '../lib/format'
 | 
				
			|||||||
import NoteIcon from '../svgs/notification-4-fill.svg'
 | 
					import NoteIcon from '../svgs/notification-4-fill.svg'
 | 
				
			||||||
import { useQuery, gql } from '@apollo/client'
 | 
					import { useQuery, gql } from '@apollo/client'
 | 
				
			||||||
import LightningIcon from '../svgs/bolt.svg'
 | 
					import LightningIcon from '../svgs/bolt.svg'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function WalletSummary ({ me }) {
 | 
					function WalletSummary ({ me }) {
 | 
				
			||||||
  if (!me) return null
 | 
					  if (!me) return null
 | 
				
			||||||
@ -72,7 +73,9 @@ export default function Header ({ sub }) {
 | 
				
			|||||||
            <NavDropdown
 | 
					            <NavDropdown
 | 
				
			||||||
              className={styles.dropdown} title={
 | 
					              className={styles.dropdown} title={
 | 
				
			||||||
                <Link href={`/${me?.name}`} passHref>
 | 
					                <Link href={`/${me?.name}`} passHref>
 | 
				
			||||||
                  <Nav.Link eventKey={me?.name} as='div' className='p-0' onClick={e => e.preventDefault()}>{`@${me?.name}`}</Nav.Link>
 | 
					                  <Nav.Link eventKey={me?.name} as='div' className='p-0 d-flex align-items-center' onClick={e => e.preventDefault()}>
 | 
				
			||||||
 | 
					                    {`@${me?.name}`}<CowboyHat streak={me.streak} />
 | 
				
			||||||
 | 
					                  </Nav.Link>
 | 
				
			||||||
                </Link>
 | 
					                </Link>
 | 
				
			||||||
              } alignRight
 | 
					              } alignRight
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import Link from 'next/link'
 | 
				
			|||||||
import { timeSince } from '../lib/time'
 | 
					import { timeSince } from '../lib/time'
 | 
				
			||||||
import EmailIcon from '../svgs/mail-open-line.svg'
 | 
					import EmailIcon from '../svgs/mail-open-line.svg'
 | 
				
			||||||
import Share from './share'
 | 
					import Share from './share'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function ItemJob ({ item, toc, rank, children }) {
 | 
					export default function ItemJob ({ item, toc, rank, children }) {
 | 
				
			||||||
  const isEmail = Yup.string().email().isValidSync(item.url)
 | 
					  const isEmail = Yup.string().email().isValidSync(item.url)
 | 
				
			||||||
@ -52,7 +53,9 @@ export default function ItemJob ({ item, toc, rank, children }) {
 | 
				
			|||||||
            <span> \ </span>
 | 
					            <span> \ </span>
 | 
				
			||||||
            <span>
 | 
					            <span>
 | 
				
			||||||
              <Link href={`/${item.user.name}`} passHref>
 | 
					              <Link href={`/${item.user.name}`} passHref>
 | 
				
			||||||
                <a>@{item.user.name}</a>
 | 
					                <a className='d-inline-flex align-items-center'>
 | 
				
			||||||
 | 
					                  @{item.user.name}<CowboyHat className='ml-1 fill-grey' streak={item.user.streak} height={12} width={12} />
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
              <span> </span>
 | 
					              <span> </span>
 | 
				
			||||||
              <Link href={`/items/${item.id}`} passHref>
 | 
					              <Link href={`/items/${item.id}`} passHref>
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ import Flag from '../svgs/flag-fill.svg'
 | 
				
			|||||||
import Share from './share'
 | 
					import Share from './share'
 | 
				
			||||||
import { abbrNum } from '../lib/format'
 | 
					import { abbrNum } from '../lib/format'
 | 
				
			||||||
import { DeleteDropdown } from './delete'
 | 
					import { DeleteDropdown } from './delete'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SearchTitle ({ title }) {
 | 
					export function SearchTitle ({ title }) {
 | 
				
			||||||
  return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
 | 
					  return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
 | 
				
			||||||
@ -115,7 +116,7 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
 | 
				
			|||||||
            <span> \ </span>
 | 
					            <span> \ </span>
 | 
				
			||||||
            <span>
 | 
					            <span>
 | 
				
			||||||
              <Link href={`/${item.user.name}`} passHref>
 | 
					              <Link href={`/${item.user.name}`} passHref>
 | 
				
			||||||
                <a>@{item.user.name}</a>
 | 
					                <a className='d-inline-flex align-items-center'>@{item.user.name}<CowboyHat className='ml-1 fill-grey' streak={item.user.streak} height={12} width={12} /></a>
 | 
				
			||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
              <span> </span>
 | 
					              <span> </span>
 | 
				
			||||||
              <Link href={`/items/${item.id}`} passHref>
 | 
					              <Link href={`/items/${item.id}`} passHref>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,8 @@ import Link from 'next/link'
 | 
				
			|||||||
import Check from '../svgs/check-double-line.svg'
 | 
					import Check from '../svgs/check-double-line.svg'
 | 
				
			||||||
import HandCoin from '../svgs/hand-coin-fill.svg'
 | 
					import HandCoin from '../svgs/hand-coin-fill.svg'
 | 
				
			||||||
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
 | 
					import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
 | 
				
			||||||
 | 
					import CowboyHatIcon from '../svgs/cowboy.svg'
 | 
				
			||||||
 | 
					import BaldIcon from '../svgs/bald.svg'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: oh man, this is a mess ... each notification type should just be a component ...
 | 
					// TODO: oh man, this is a mess ... each notification type should just be a component ...
 | 
				
			||||||
function Notification ({ n }) {
 | 
					function Notification ({ n }) {
 | 
				
			||||||
@ -20,7 +22,7 @@ function Notification ({ n }) {
 | 
				
			|||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className='clickToContext'
 | 
					      className='clickToContext'
 | 
				
			||||||
      onClick={e => {
 | 
					      onClick={e => {
 | 
				
			||||||
        if (n.__typename === 'Earn' || n.__typename === 'Referral') {
 | 
					        if (n.__typename === 'Earn' || n.__typename === 'Referral' || n.__typename === 'Streak') {
 | 
				
			||||||
          return
 | 
					          return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -103,6 +105,8 @@ function Notification ({ n }) {
 | 
				
			|||||||
                  <Check className='fill-info mr-1' />{n.earnedSats} sats were deposited in your account
 | 
					                  <Check className='fill-info mr-1' />{n.earnedSats} sats were deposited in your account
 | 
				
			||||||
                  <small className='text-muted ml-1'>{timeSince(new Date(n.sortTime))}</small>
 | 
					                  <small className='text-muted ml-1'>{timeSince(new Date(n.sortTime))}</small>
 | 
				
			||||||
                </div>)
 | 
					                </div>)
 | 
				
			||||||
 | 
					              : n.__typename === 'Streak'
 | 
				
			||||||
 | 
					                ? <Streak n={n} />
 | 
				
			||||||
                : (
 | 
					                : (
 | 
				
			||||||
                  <>
 | 
					                  <>
 | 
				
			||||||
                    {n.__typename === 'Votification' &&
 | 
					                    {n.__typename === 'Votification' &&
 | 
				
			||||||
@ -136,6 +140,45 @@ function Notification ({ n }) {
 | 
				
			|||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Streak ({ n }) {
 | 
				
			||||||
 | 
					  function blurb (n) {
 | 
				
			||||||
 | 
					    const index = Number(n.id) % 6
 | 
				
			||||||
 | 
					    const FOUND_BLURBS = [
 | 
				
			||||||
 | 
					      'The harsh frontier is no place for the unprepared. This hat will protect you from the sun, dust, and other elements Mother Nature throws your way.',
 | 
				
			||||||
 | 
					      'A cowboy is nothing without a cowboy hat. Take good care of it, and it will protect you from the sun, dust, and other elements on your journey.',
 | 
				
			||||||
 | 
					      "This is not just a hat, it's a matter of survival. Take care of this essential tool, and it will shield you from the scorching sun and the elements.",
 | 
				
			||||||
 | 
					      "A cowboy hat isn't just a fashion statement. It's your last defense against the unforgiving elements of the Wild West. Hang onto it tight.",
 | 
				
			||||||
 | 
					      "A good cowboy hat is worth its weight in gold, shielding you from the sun, wind, and dust of the western frontier. Don't lose it.",
 | 
				
			||||||
 | 
					      'Your cowboy hat is the key to your survival in the wild west. Treat it with respect and it will protect you from the elements.'
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const LOST_BLURBS = [
 | 
				
			||||||
 | 
					      'your cowboy hat was taken by the wind storm that blew in from the west. No worries, a true cowboy always finds another hat.',
 | 
				
			||||||
 | 
					      "you left your trusty cowboy hat in the saloon before leaving town. You'll need a replacement for the long journey west.",
 | 
				
			||||||
 | 
					      'you lost your cowboy hat in a wild shoot-out on the outskirts of town. Tough luck, tIme to start searching for another one.',
 | 
				
			||||||
 | 
					      'you ran out of food and had to trade your hat for supplies. Better start looking for another hat.',
 | 
				
			||||||
 | 
					      "your hat was stolen by a mischievous prairie dog. You won't catch the dog, but you can always find another hat.",
 | 
				
			||||||
 | 
					      'you lost your hat while crossing the river on your journey west. Maybe you can find a replacement hat in the next town.'
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (n.days) {
 | 
				
			||||||
 | 
					      return `After ${n.days} days, ` + LOST_BLURBS[index]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return FOUND_BLURBS[index]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='d-flex font-weight-bold ml-2 py-1'>
 | 
				
			||||||
 | 
					      <div style={{ fontSize: '2rem' }}>{n.days ? <BaldIcon className='fill-grey' height={40} width={40} /> : <CowboyHatIcon className='fill-grey' height={40} width={40} />}</div>
 | 
				
			||||||
 | 
					      <div className='ml-1 p-1'>
 | 
				
			||||||
 | 
					        you {n.days ? 'lost your' : 'found a'} cowboy hat
 | 
				
			||||||
 | 
					        <div><small style={{ lineHeight: '140%', display: 'inline-block' }}>{blurb(n)}</small></div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Notifications ({ notifications, earn, cursor, lastChecked, variables }) {
 | 
					export default function Notifications ({ notifications, earn, cursor, lastChecked, variables }) {
 | 
				
			||||||
  const { data, fetchMore } = useQuery(NOTIFICATIONS, { variables })
 | 
					  const { data, fetchMore } = useQuery(NOTIFICATIONS, { variables })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ import LightningIcon from '../svgs/bolt.svg'
 | 
				
			|||||||
import ModalButton from './modal-button'
 | 
					import ModalButton from './modal-button'
 | 
				
			||||||
import { encodeLNUrl } from '../lib/lnurl'
 | 
					import { encodeLNUrl } from '../lib/lnurl'
 | 
				
			||||||
import Avatar from './avatar'
 | 
					import Avatar from './avatar'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function UserHeader ({ user }) {
 | 
					export default function UserHeader ({ user }) {
 | 
				
			||||||
  const [editting, setEditting] = useState(false)
 | 
					  const [editting, setEditting] = useState(false)
 | 
				
			||||||
@ -132,7 +133,7 @@ export default function UserHeader ({ user }) {
 | 
				
			|||||||
              )
 | 
					              )
 | 
				
			||||||
            : (
 | 
					            : (
 | 
				
			||||||
              <div className='d-flex align-items-center mb-2'>
 | 
					              <div className='d-flex align-items-center mb-2'>
 | 
				
			||||||
                <div className={styles.username}>@{user.name}</div>
 | 
					                <div className={styles.username}>@{user.name}<CowboyHat className='' streak={user.streak} badge /></div>
 | 
				
			||||||
                {isMe &&
 | 
					                {isMe &&
 | 
				
			||||||
                  <Button className='py-0' style={{ lineHeight: '1.25' }} 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>
 | 
					              </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import Link from 'next/link'
 | 
					import Link from 'next/link'
 | 
				
			||||||
import { Image } from 'react-bootstrap'
 | 
					import { Image } from 'react-bootstrap'
 | 
				
			||||||
import { abbrNum } from '../lib/format'
 | 
					import { abbrNum } from '../lib/format'
 | 
				
			||||||
 | 
					import CowboyHat from './cowboy-hat'
 | 
				
			||||||
import styles from './item.module.css'
 | 
					import styles from './item.module.css'
 | 
				
			||||||
import userStyles from './user-header.module.css'
 | 
					import userStyles from './user-header.module.css'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,8 +19,8 @@ export default function UserList ({ users }) {
 | 
				
			|||||||
        </Link>
 | 
					        </Link>
 | 
				
			||||||
        <div className={styles.hunk}>
 | 
					        <div className={styles.hunk}>
 | 
				
			||||||
          <Link href={`/${user.name}`} passHref>
 | 
					          <Link href={`/${user.name}`} passHref>
 | 
				
			||||||
            <a className={`${styles.title} text-reset`}>
 | 
					            <a className={`${styles.title} d-inline-flex align-items-center text-reset`}>
 | 
				
			||||||
              @{user.name}
 | 
					              @{user.name}<CowboyHat className='ml-1 fill-grey' height={14} width={14} streak={user.streak} />
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
          </Link>
 | 
					          </Link>
 | 
				
			||||||
          <div className={styles.other}>
 | 
					          <div className={styles.other}>
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ export const COMMENT_FIELDS = gql`
 | 
				
			|||||||
    text
 | 
					    text
 | 
				
			||||||
    user {
 | 
					    user {
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      id
 | 
					      id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    sats
 | 
					    sats
 | 
				
			||||||
@ -29,6 +30,7 @@ export const COMMENT_FIELDS = gql`
 | 
				
			|||||||
      bountyPaidTo
 | 
					      bountyPaidTo
 | 
				
			||||||
      user {
 | 
					      user {
 | 
				
			||||||
        name
 | 
					        name
 | 
				
			||||||
 | 
					        streak
 | 
				
			||||||
        id
 | 
					        id
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ export const INVITE_FIELDS = gql`
 | 
				
			|||||||
    revoked
 | 
					    revoked
 | 
				
			||||||
    user {
 | 
					    user {
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      id
 | 
					      id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    poor
 | 
					    poor
 | 
				
			||||||
 | 
				
			|||||||
@ -11,10 +11,12 @@ export const ITEM_FIELDS = gql`
 | 
				
			|||||||
    url
 | 
					    url
 | 
				
			||||||
    user {
 | 
					    user {
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      id
 | 
					      id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    fwdUser {
 | 
					    fwdUser {
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      id
 | 
					      id
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    sats
 | 
					    sats
 | 
				
			||||||
@ -51,6 +53,7 @@ export const ITEM_FIELDS = gql`
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      user {
 | 
					      user {
 | 
				
			||||||
        name
 | 
					        name
 | 
				
			||||||
 | 
					        streak
 | 
				
			||||||
        id
 | 
					        id
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,11 @@ export const NOTIFICATIONS = gql`
 | 
				
			|||||||
            text
 | 
					            text
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        ... on Streak {
 | 
				
			||||||
 | 
					          id
 | 
				
			||||||
 | 
					          sortTime
 | 
				
			||||||
 | 
					          days
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        ... on Earn {
 | 
					        ... on Earn {
 | 
				
			||||||
          sortTime
 | 
					          sortTime
 | 
				
			||||||
          earnedSats
 | 
					          earnedSats
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ export const ME = gql`
 | 
				
			|||||||
    me {
 | 
					    me {
 | 
				
			||||||
      id
 | 
					      id
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      sats
 | 
					      sats
 | 
				
			||||||
      stacked
 | 
					      stacked
 | 
				
			||||||
      freePosts
 | 
					      freePosts
 | 
				
			||||||
@ -24,6 +25,7 @@ export const ME = gql`
 | 
				
			|||||||
      noteDeposits
 | 
					      noteDeposits
 | 
				
			||||||
      noteInvites
 | 
					      noteInvites
 | 
				
			||||||
      noteJobIndicator
 | 
					      noteJobIndicator
 | 
				
			||||||
 | 
					      noteCowboyHat
 | 
				
			||||||
      hideInvoiceDesc
 | 
					      hideInvoiceDesc
 | 
				
			||||||
      hideFromTopUsers
 | 
					      hideFromTopUsers
 | 
				
			||||||
      wildWestMode
 | 
					      wildWestMode
 | 
				
			||||||
@ -44,6 +46,7 @@ export const SETTINGS_FIELDS = gql`
 | 
				
			|||||||
    noteDeposits
 | 
					    noteDeposits
 | 
				
			||||||
    noteInvites
 | 
					    noteInvites
 | 
				
			||||||
    noteJobIndicator
 | 
					    noteJobIndicator
 | 
				
			||||||
 | 
					    noteCowboyHat
 | 
				
			||||||
    hideInvoiceDesc
 | 
					    hideInvoiceDesc
 | 
				
			||||||
    hideFromTopUsers
 | 
					    hideFromTopUsers
 | 
				
			||||||
    nostrPubkey
 | 
					    nostrPubkey
 | 
				
			||||||
@ -72,12 +75,12 @@ gql`
 | 
				
			|||||||
${SETTINGS_FIELDS}
 | 
					${SETTINGS_FIELDS}
 | 
				
			||||||
mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $noteItemSats: Boolean!,
 | 
					mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $noteItemSats: Boolean!,
 | 
				
			||||||
  $noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
 | 
					  $noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
 | 
				
			||||||
  $noteInvites: Boolean!, $noteJobIndicator: Boolean!, $hideInvoiceDesc: Boolean!, $hideFromTopUsers: Boolean!,
 | 
					  $noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!, $hideFromTopUsers: Boolean!,
 | 
				
			||||||
  $wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!]) {
 | 
					  $wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!]) {
 | 
				
			||||||
  setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping,  fiatCurrency: $fiatCurrency,
 | 
					  setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping,  fiatCurrency: $fiatCurrency,
 | 
				
			||||||
    noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
 | 
					    noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
 | 
				
			||||||
    noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
 | 
					    noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
 | 
				
			||||||
    noteJobIndicator: $noteJobIndicator, hideInvoiceDesc: $hideInvoiceDesc, hideFromTopUsers: $hideFromTopUsers,
 | 
					    noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc, hideFromTopUsers: $hideFromTopUsers,
 | 
				
			||||||
    wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays) {
 | 
					    wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays) {
 | 
				
			||||||
      ...SettingsFields
 | 
					      ...SettingsFields
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -103,6 +106,7 @@ gql`
 | 
				
			|||||||
  query searchUsers($q: String!, $limit: Int, $similarity: Float) {
 | 
					  query searchUsers($q: String!, $limit: Int, $similarity: Float) {
 | 
				
			||||||
    searchUsers(q: $q, limit: $limit, similarity: $similarity) {
 | 
					    searchUsers(q: $q, limit: $limit, similarity: $similarity) {
 | 
				
			||||||
      name
 | 
					      name
 | 
				
			||||||
 | 
					      streak
 | 
				
			||||||
      photoId
 | 
					      photoId
 | 
				
			||||||
      stacked
 | 
					      stacked
 | 
				
			||||||
      spent
 | 
					      spent
 | 
				
			||||||
@ -117,6 +121,7 @@ export const USER_FIELDS = gql`
 | 
				
			|||||||
    id
 | 
					    id
 | 
				
			||||||
    createdAt
 | 
					    createdAt
 | 
				
			||||||
    name
 | 
					    name
 | 
				
			||||||
 | 
					    streak
 | 
				
			||||||
    nitems
 | 
					    nitems
 | 
				
			||||||
    ncomments
 | 
					    ncomments
 | 
				
			||||||
    stacked
 | 
					    stacked
 | 
				
			||||||
@ -133,6 +138,7 @@ export const TOP_USERS = gql`
 | 
				
			|||||||
    topUsers(cursor: $cursor, when: $when, sort: $sort) {
 | 
					    topUsers(cursor: $cursor, when: $when, sort: $sort) {
 | 
				
			||||||
      users {
 | 
					      users {
 | 
				
			||||||
        name
 | 
					        name
 | 
				
			||||||
 | 
					        streak
 | 
				
			||||||
        photoId
 | 
					        photoId
 | 
				
			||||||
        stacked(when: $when)
 | 
					        stacked(when: $when)
 | 
				
			||||||
        spent(when: $when)
 | 
					        spent(when: $when)
 | 
				
			||||||
 | 
				
			|||||||
@ -102,6 +102,7 @@ export default function Settings ({ data: { settings } }) {
 | 
				
			|||||||
            noteDeposits: settings?.noteDeposits,
 | 
					            noteDeposits: settings?.noteDeposits,
 | 
				
			||||||
            noteInvites: settings?.noteInvites,
 | 
					            noteInvites: settings?.noteInvites,
 | 
				
			||||||
            noteJobIndicator: settings?.noteJobIndicator,
 | 
					            noteJobIndicator: settings?.noteJobIndicator,
 | 
				
			||||||
 | 
					            noteCowboyHat: settings?.noteCowboyHat,
 | 
				
			||||||
            hideInvoiceDesc: settings?.hideInvoiceDesc,
 | 
					            hideInvoiceDesc: settings?.hideInvoiceDesc,
 | 
				
			||||||
            hideFromTopUsers: settings?.hideFromTopUsers,
 | 
					            hideFromTopUsers: settings?.hideFromTopUsers,
 | 
				
			||||||
            wildWestMode: settings?.wildWestMode,
 | 
					            wildWestMode: settings?.wildWestMode,
 | 
				
			||||||
@ -216,6 +217,11 @@ export default function Settings ({ data: { settings } }) {
 | 
				
			|||||||
          <Checkbox
 | 
					          <Checkbox
 | 
				
			||||||
            label='there is a new job'
 | 
					            label='there is a new job'
 | 
				
			||||||
            name='noteJobIndicator'
 | 
					            name='noteJobIndicator'
 | 
				
			||||||
 | 
					            groupClassName='mb-0'
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <Checkbox
 | 
				
			||||||
 | 
					            label='I find or lose a cowboy hat'
 | 
				
			||||||
 | 
					            name='noteCowboyHat'
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
          <div className='form-label'>privacy</div>
 | 
					          <div className='form-label'>privacy</div>
 | 
				
			||||||
          <Checkbox
 | 
					          <Checkbox
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								prisma/migrations/20230131163315_streaks/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								prisma/migrations/20230131163315_streaks/migration.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					-- CreateTable
 | 
				
			||||||
 | 
					CREATE TABLE "Streak" (
 | 
				
			||||||
 | 
					    "id" SERIAL NOT NULL,
 | 
				
			||||||
 | 
					    "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					    "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					    "startedAt" DATE NOT NULL,
 | 
				
			||||||
 | 
					    "endedAt" DATE,
 | 
				
			||||||
 | 
					    "userId" INTEGER NOT NULL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PRIMARY KEY ("id")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- CreateIndex
 | 
				
			||||||
 | 
					CREATE INDEX "Streak.userId_index" ON "Streak"("userId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- CreateIndex
 | 
				
			||||||
 | 
					CREATE UNIQUE INDEX "Streak.startedAt_userId_unique" ON "Streak"("startedAt", "userId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- AddForeignKey
 | 
				
			||||||
 | 
					ALTER TABLE "Streak" ADD FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
 | 
				
			||||||
@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					-- AlterTable
 | 
				
			||||||
 | 
					ALTER TABLE "users" ADD COLUMN     "noteCowboyHat" BOOLEAN NOT NULL DEFAULT true;
 | 
				
			||||||
@ -70,6 +70,7 @@ model User {
 | 
				
			|||||||
  noteDeposits       Boolean @default(true)
 | 
					  noteDeposits       Boolean @default(true)
 | 
				
			||||||
  noteInvites        Boolean @default(true)
 | 
					  noteInvites        Boolean @default(true)
 | 
				
			||||||
  noteJobIndicator   Boolean @default(true)
 | 
					  noteJobIndicator   Boolean @default(true)
 | 
				
			||||||
 | 
					  noteCowboyHat      Boolean @default(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // privacy settings
 | 
					  // privacy settings
 | 
				
			||||||
  hideInvoiceDesc  Boolean @default(false)
 | 
					  hideInvoiceDesc  Boolean @default(false)
 | 
				
			||||||
@ -84,12 +85,26 @@ model User {
 | 
				
			|||||||
  PollVote    PollVote[]
 | 
					  PollVote    PollVote[]
 | 
				
			||||||
  Donation    Donation[]
 | 
					  Donation    Donation[]
 | 
				
			||||||
  ReferralAct ReferralAct[]
 | 
					  ReferralAct ReferralAct[]
 | 
				
			||||||
 | 
					  Streak      Streak[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @@index([createdAt])
 | 
					  @@index([createdAt])
 | 
				
			||||||
  @@index([inviteId])
 | 
					  @@index([inviteId])
 | 
				
			||||||
  @@map(name: "users")
 | 
					  @@map(name: "users")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					model Streak {
 | 
				
			||||||
 | 
					  id        Int       @id @default(autoincrement())
 | 
				
			||||||
 | 
					  createdAt DateTime  @default(now()) @map(name: "created_at")
 | 
				
			||||||
 | 
					  updatedAt DateTime  @default(now()) @updatedAt @map(name: "updated_at")
 | 
				
			||||||
 | 
					  startedAt DateTime  @db.Date
 | 
				
			||||||
 | 
					  endedAt   DateTime? @db.Date
 | 
				
			||||||
 | 
					  userId    Int
 | 
				
			||||||
 | 
					  user      User      @relation(fields: [userId], references: [id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @@unique([startedAt, userId])
 | 
				
			||||||
 | 
					  @@index([userId])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
model NostrRelay {
 | 
					model NostrRelay {
 | 
				
			||||||
  addr      String           @id
 | 
					  addr      String           @id
 | 
				
			||||||
  createdAt DateTime         @default(now()) @map(name: "created_at")
 | 
					  createdAt DateTime         @default(now()) @map(name: "created_at")
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								svgs/bald.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								svgs/bald.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288.69 325.52">
 | 
				
			||||||
 | 
					  <path class="cls-1" d="m275.55,123.09c-2.36-8.21-11.36-2.84-11.36-2.84,0,0-.16-.79-1.42-15.31-1.26-14.52-5.21-41.81-34.71-68.16-29.51-26.36-77.51-25.09-77.51-25.09h-12.28c-12.27-.02-38.34,4.73-59.96,16.56-21.61,11.84-33.29,34.56-39.6,51.13-6.31,16.57-8.52,47.18-8.52,47.18,0,0-6.31-3.32-8.84-2.05-2.52,1.26-6.94,3.15-2.52,21.46,4.42,18.3,15.15,30.92,18.14,36.76,3,5.84,8.52,4.42,9.63,3.47,1.1-.94,1.58-3.78,1.58-3.78,0,0,1.1,29.35,2.21,36.29,1.1,6.94,5.2,19.56,8.67,24.14,3.48,4.58,6.79,8.21,6.79,8.21l6.31-.32s-.32,3.47,0,6.15c.32,2.69,2.05,5.84,4.1,7.74,2.05,1.89,23.36,26.03,30.61,30.92,7.26,4.89,13.73,10.57,44,10.57s36.95-8.2,40.74-11.2,20.04-18.46,29.19-29.03c9.15-10.57,21.93-26.98,24.93-35.66s5.37-45.45,5.37-45.45c0,0,.63,1.27,2.68,1.11,2.05-.16,5.84-3.47,7.73-7.58,1.89-4.1,2.68-4.1,8.68-20.19,5.99-16.1,7.73-26.83,5.36-35.03Zm-200.2-.65c-1.08.06-1.56-.72-1.8-1.32-.24-.59.08-2.79.77-3.71s4.37-6.44,15.99-12.77c11.62-6.33,20.02-4.83,20.02-4.83,0,0,2.8.45,5.31,1.11,2.51.65,4.31,2.99,4.73,3.76.42.78.59,1.62,0,2.75-.6,1.14-1.92,1.26-8.37,1.44-6.46.18-11.9,1.49-17.7,3.65-5.8,2.15-10.58,5.91-13.69,7.95-3.11,2.03-4.19,1.91-5.26,1.97Zm27.73,47.96c-8.57,0-15.52-7.91-15.52-17.66s6.95-17.66,15.52-17.66,15.52,7.91,15.52,17.66-6.95,17.66-15.52,17.66Zm88.88,74.66c-3.22,2.29-7.61,2.6-18.21,0-10.59-2.6-33.26-1.24-36.17-1.05-2.91.18-9.47,1.18-12.07,1.79-2.6.62-4.96,1.51-8.92,1.1-3.97-.41-6.57-3.82-6.94-7.17-.37-3.34.62-9.84,5.58-17.96,4.95-8.11,13.76-18.58,37.32-18.58,0,0,11.05-1.11,22.5,4.71,11.46,5.82,18.71,13.81,20.38,20.69,1.67,6.87-.25,14.18-3.47,16.47Zm7.17-74.66c-8.57,0-15.52-7.91-15.52-17.66s6.95-17.66,15.52-17.66,15.52,7.91,15.52,17.66-6.95,17.66-15.52,17.66Zm28.96-49.28c-.24.6-.72,1.38-1.8,1.32-1.07-.06-2.15.06-5.26-1.97-3.11-2.04-7.89-5.8-13.69-7.95-5.8-2.16-11.24-3.47-17.7-3.65-6.45-.18-7.77-.3-8.37-1.44-.59-1.13-.41-1.97,0-2.75.42-.77,2.22-3.11,4.73-3.76,2.51-.66,5.31-1.11,5.31-1.11,0,0,8.4-1.5,20.02,4.83,11.62,6.33,15.3,11.85,15.99,12.77s1.01,3.12.77,3.71Z"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.0 KiB  | 
							
								
								
									
										21
									
								
								svgs/cowboy.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								svgs/cowboy.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					<svg
 | 
				
			||||||
 | 
						 xmlns="http://www.w3.org/2000/svg" viewBox="80 200 450 400">
 | 
				
			||||||
 | 
					<path class="st0" d="M499.9,315.6c-17.2-10.5-28.7,0.6-36.6,7.9c-7.9,7.2-26.6,30.8-36.6,38.1c-10,7.2-27.5,13.6-53.9,16.7
 | 
				
			||||||
 | 
						c-26.3,3.1-50.5,1.4-47.8,1c2.7-0.4,10.6-2.2,12.4-3c1.8-0.9,11.3-4.3,33.7-11.9c22.4-7.6,36.5-17,39.4-18.7
 | 
				
			||||||
 | 
						c2.9-1.7,5.9-3.9,5.4-5.2s-2.2-8.1-5.1-15.8c-2.9-7.7-8.5-29.4-10.1-34.2c-1.6-4.8-3-13.1-5.9-19.5s-4.4-21.3-11.3-38.5
 | 
				
			||||||
 | 
						c-6.9-17.2-11.8-19.5-31.7-27.7c-19.8-8.2-57.8-6.9-57.8-6.9s-41.1,1.3-59.5,9.2c-18.4,7.9-22.8,19-23.4,20.6
 | 
				
			||||||
 | 
						c-0.6,1.6-12.9,44.2-12.9,44.2l-27.4,93.1c0,0-6-5.6-10.7-10.8c-4.5-5-29.9-36.9-44-41.1c-14.1-4.2-20.4,0.8-26.2,5.5
 | 
				
			||||||
 | 
						c-5.8,4.7-11.1,18.3-8.5,32.7c2.6,14.4,29.8,45.3,51,59.7c21.2,14.4,40.8,21.5,43.6,23.1c2.8,1.6,6.5,2.8,16,15.2
 | 
				
			||||||
 | 
						s32.5,32.3,45.8,41.6c13.4,9.4,34.8,22.1,44.6,26.8c9.8,4.7,14.9,3.6,14.9,3.6c8.8,0,36.1-15.4,50.6-25.1
 | 
				
			||||||
 | 
						c14.4-9.7,32.3-25.1,34.1-26.6c1.6-1.3,3.2-3.4,3.7-3.9c0.7-0.8,6-8.1,15.3-17s14.6-12.4,14.6-12.4l0.2-1.8c0,0,27-9.2,53.4-30.3
 | 
				
			||||||
 | 
						c26.5-21.1,39.1-43.5,41.4-48.5C512.8,350.8,517.1,326,499.9,315.6L499.9,315.6z M366.5,413.2c-17,7.4-32,14.7-53.7,17.6
 | 
				
			||||||
 | 
						c-21.7,2.9-39.3,0.3-57.4-5.8c-18.1-6.1-38.3-16.6-49.2-23.6c-10.9-7-22.7-16.6-22.7-16.6s74.7,8.7,110.6,8.4
 | 
				
			||||||
 | 
						c36-0.3,71.5-2.9,83.3-4.2c11.8-1.3,32.8-4.2,32.8-4.2S383.5,405.7,366.5,413.2L366.5,413.2z"/>
 | 
				
			||||||
 | 
					<path class="st0" d="M415.7,449.1c0,0.1-0.2,1.2-6,4.5c-6.3,3.6-21.6,22.6-25.8,27.2c-4.2,4.7-27.5,22.3-27.5,22.3
 | 
				
			||||||
 | 
						S310.7,535,295.9,535s-43.1-21.3-58.4-31.3c-15.3-10-26.9-21.4-31.2-25.9c-4.2-4.5-13.8-15-20.5-21.7c-6.7-6.7-8.7-7-8.7-7
 | 
				
			||||||
 | 
						s-0.4,2.3,0,5.1c0.4,2.8,3.5,16.6,5.1,20.1s4.4,8.3,4.4,8.3s13.1,49.1,15.1,53.5s11.9,16.9,11.9,16.9l6,0.2l0.7,9
 | 
				
			||||||
 | 
						c0,0,0.7,0.9,2.5,2.5c1.8,1.6,18.6,20.4,22.3,23.6c3.6,3.2,15.6,15.6,28.3,17.8c12.7,2.2,23.3,2,40.8,0.9
 | 
				
			||||||
 | 
						c17.5-1.2,36.7-18.8,54.6-39.9c17.9-21.1,24.3-31.8,26.4-40.5s3.1-40.5,3.2-41.4c0.1-0.9,1.9,1.3,4.7,1.5c2.8,0.1,6.9-7.9,10.4-19.4
 | 
				
			||||||
 | 
						C416.6,456,415.8,449.5,415.7,449.1L415.7,449.1z M318.4,556.4c-6.8,7-16.8,12-20.7,12s-11.4-2.5-19.9-10.2
 | 
				
			||||||
 | 
						c-8.5-7.7-11.9-13-11.9-13s15.9,1.8,31,1.8s31-1.8,31-1.8S325.2,549.4,318.4,556.4L318.4,556.4z"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.0 KiB  | 
@ -10,6 +10,7 @@ const { earn } = require('./earn')
 | 
				
			|||||||
const { ApolloClient, HttpLink, InMemoryCache } = require('@apollo/client')
 | 
					const { ApolloClient, HttpLink, InMemoryCache } = require('@apollo/client')
 | 
				
			||||||
const { indexItem, indexAllItems } = require('./search')
 | 
					const { indexItem, indexAllItems } = require('./search')
 | 
				
			||||||
const { timestampItem } = require('./ots')
 | 
					const { timestampItem } = require('./ots')
 | 
				
			||||||
 | 
					const { computeStreaks } = require('./streak')
 | 
				
			||||||
const fetch = require('cross-fetch')
 | 
					const fetch = require('cross-fetch')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function work () {
 | 
					async function work () {
 | 
				
			||||||
@ -47,6 +48,7 @@ async function work () {
 | 
				
			|||||||
  await boss.work('indexAllItems', indexAllItems(args))
 | 
					  await boss.work('indexAllItems', indexAllItems(args))
 | 
				
			||||||
  await boss.work('auction', auction(args))
 | 
					  await boss.work('auction', auction(args))
 | 
				
			||||||
  await boss.work('earn', earn(args))
 | 
					  await boss.work('earn', earn(args))
 | 
				
			||||||
 | 
					  await boss.work('streak', computeStreaks(args))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log('working jobs')
 | 
					  console.log('working jobs')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										51
									
								
								worker/streak.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								worker/streak.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					function computeStreaks ({ models }) {
 | 
				
			||||||
 | 
					  return async function () {
 | 
				
			||||||
 | 
					    console.log('computing streaks')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get all eligible users in the last day
 | 
				
			||||||
 | 
					    // if the user doesn't have an active streak, add one
 | 
				
			||||||
 | 
					    // if they have an active streak but didn't maintain it, end it
 | 
				
			||||||
 | 
					    await models.$executeRaw(
 | 
				
			||||||
 | 
					      `WITH day_streaks (id) AS (
 | 
				
			||||||
 | 
					        SELECT "userId"
 | 
				
			||||||
 | 
					        FROM
 | 
				
			||||||
 | 
					        ((SELECT "userId", floor(sum("ItemAct".msats)/1000) as sats_spent
 | 
				
			||||||
 | 
					            FROM "ItemAct"
 | 
				
			||||||
 | 
					            WHERE (created_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago')::date = (now() AT TIME ZONE 'America/Chicago' - interval '1 day')::date
 | 
				
			||||||
 | 
					            GROUP BY "userId")
 | 
				
			||||||
 | 
					        UNION ALL
 | 
				
			||||||
 | 
					        (SELECT "userId", sats as sats_spent
 | 
				
			||||||
 | 
					            FROM "Donation"
 | 
				
			||||||
 | 
					            WHERE (created_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Chicago')::date = (now() AT TIME ZONE 'America/Chicago' - interval '1 day')::date
 | 
				
			||||||
 | 
					        )) spending
 | 
				
			||||||
 | 
					        GROUP BY "userId"
 | 
				
			||||||
 | 
					        HAVING sum(sats_spent) >= 100
 | 
				
			||||||
 | 
					      ), existing_streaks (id) AS (
 | 
				
			||||||
 | 
					          SELECT "userId"
 | 
				
			||||||
 | 
					          FROM "Streak"
 | 
				
			||||||
 | 
					          WHERE "Streak"."endedAt" IS NULL
 | 
				
			||||||
 | 
					      ), new_streaks (id) AS (
 | 
				
			||||||
 | 
					          SELECT day_streaks.id
 | 
				
			||||||
 | 
					          FROM day_streaks
 | 
				
			||||||
 | 
					          LEFT JOIN existing_streaks ON existing_streaks.id = day_streaks.id
 | 
				
			||||||
 | 
					          WHERE existing_streaks.id IS NULL
 | 
				
			||||||
 | 
					      ), ending_streaks (id) AS (
 | 
				
			||||||
 | 
					          SELECT existing_streaks.id
 | 
				
			||||||
 | 
					          FROM existing_streaks
 | 
				
			||||||
 | 
					          LEFT JOIN day_streaks ON existing_streaks.id = day_streaks.id
 | 
				
			||||||
 | 
					          WHERE day_streaks.id IS NULL
 | 
				
			||||||
 | 
					      ), streak_insert AS (
 | 
				
			||||||
 | 
					          INSERT INTO "Streak" ("userId", "startedAt", created_at, updated_at)
 | 
				
			||||||
 | 
					          SELECT id, (now() AT TIME ZONE 'America/Chicago' - interval '1 day')::date, now_utc(), now_utc()
 | 
				
			||||||
 | 
					          FROM new_streaks
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      UPDATE "Streak"
 | 
				
			||||||
 | 
					      SET "endedAt" = (now() AT TIME ZONE 'America/Chicago' - interval '1 day')::date, updated_at = now_utc()
 | 
				
			||||||
 | 
					      FROM ending_streaks
 | 
				
			||||||
 | 
					      WHERE ending_streaks.id = "Streak"."userId"`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log('done computing streaks')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = { computeStreaks }
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user