diff --git a/.ebstalk.apps.env/scoutgame.env b/.ebstalk.apps.env/scoutgame.env index 8d95df5ed3..5816bb640a 100644 --- a/.ebstalk.apps.env/scoutgame.env +++ b/.ebstalk.apps.env/scoutgame.env @@ -27,6 +27,10 @@ REACT_APP_GITHUB_CLIENT_ID="{{pull:secretsmanager:/io.cv.app/prd/github:SecretSt GITHUB_CLIENT_SECRET="{{pull:secretsmanager:/io.cv.app/prd/github:SecretString:waitlist_oauth_client_secret}}" GITHUB_ACCESS_TOKEN="{{pull:secretsmanager:/io.cv.app/prd/github:SecretString:scoutgame_github_access_token}}" BUILDER_SMART_CONTRACT_OWNER_PRIVKEY="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_owner_privkey}}" + +NEYNAR_SIGNER_ID="{{pull:secretsmanager:/io.cv.app/prd/neynar:SecretString:neynar_signer_id}}" +NEYNAR_SIGNER_PUBLIC_KEY="{{pull:secretsmanager:/io.cv.app/prd/neynar:SecretString:neynar_signer_public_key}}" + SCOUTGAME_S3_BUCKET="scoutgame.public" NFT_ARTWORK_S3_PATH="prd" REACT_APP_SCOUTGAME_INVITE_CODE="1337" diff --git a/apps/scoutgame/components/common/NFTPurchaseDialog/hooks/useDecentTransaction.tsx b/apps/scoutgame/components/common/NFTPurchaseDialog/hooks/useDecentTransaction.tsx index 4f17a7de8b..660dc0f101 100644 --- a/apps/scoutgame/components/common/NFTPurchaseDialog/hooks/useDecentTransaction.tsx +++ b/apps/scoutgame/components/common/NFTPurchaseDialog/hooks/useDecentTransaction.tsx @@ -1,12 +1,10 @@ -import env from '@beam-australia/react-env'; import { log } from '@charmverse/core/log'; -import { ActionType } from '@decent.xyz/box-common'; import type { BoxActionRequest, BoxActionResponse } from '@decent.xyz/box-common'; +import { ActionType } from '@decent.xyz/box-common'; import { + builderNftChain, getBuilderContractAddress, - usdcOptimismMainnetContractAddress, getDecentApiKey, - builderNftChain, optimismUsdcContractAddress } from '@packages/scoutgame/builderNfts/constants'; import { GET } from '@packages/utils/http'; @@ -72,7 +70,6 @@ export function useDecentTransaction({ dstChainId: builderNftChain.id, slippage: 1, actionType: ActionType.NftMint, - // @ts-ignore actionConfig: { chainId: optimism.id, contractAddress: getBuilderContractAddress(), diff --git a/apps/scoutgamecron/src/scripts/welcomeWaitlistTier.ts b/apps/scoutgamecron/src/scripts/welcomeWaitlistTier.ts new file mode 100644 index 0000000000..9d5a7f6468 --- /dev/null +++ b/apps/scoutgamecron/src/scripts/welcomeWaitlistTier.ts @@ -0,0 +1,66 @@ +import { log } from "@charmverse/core/log"; +import { Prisma, prisma } from "@charmverse/core/prisma-client"; +import { ConnectWaitlistTier, getWaitlistRange } from "@packages/scoutgame/waitlist/scoring/constants"; +import { welcomeFromWaitlistToScoutgame } from '@packages/scoutgame/waitlist/welcomeToScoutgame'; + + +async function welcomeWaitlistTier(tier: ConnectWaitlistTier) { + + const tierInfo = getWaitlistRange(tier); + + const whereQuery: Prisma.ConnectWaitlistSlotWhereInput = tier === 'legendary' ? { + percentile: { + gte: tierInfo.min, + } + } : tier === 'common' ? { + percentile: { + lte: tierInfo.max + } + + } + // Other tiers have a minMax range + : { + percentile: { + gte: tierInfo.min, + lte: tierInfo.max + } + }; + + const existingScouts = await prisma.scout.findMany({ + where: { + farcasterId: { + gte: 1 + } + }, + select: { + farcasterId: true + } + }) + + // Only message users who are not already scouts + const users = await prisma.connectWaitlistSlot.findMany({ + where: {...whereQuery, fid: {notIn: existingScouts.map(scout => scout.farcasterId).filter(Boolean) as number[]}, isPartnerAccount: {not: true}}, + orderBy: { + fid: 'asc' + }, + skip: 2 + }); + + const totalUsersInTier = users.length; + + log.info(`Processing ${totalUsersInTier} users in tier ${tier}`); + + + const limit = totalUsersInTier; + // const limit = 1; + + for (let i = 0; i < limit; i++) { + const user = users[i]; + console.log(`Processing FID:${user.fid} ${user.username} user ${i + 1} of ${totalUsersInTier}`); + await welcomeFromWaitlistToScoutgame({fid: user.fid}); + } + +} + + +// welcomeWaitlistTier('rare').then(console.log).catch(console.error); \ No newline at end of file diff --git a/apps/scoutgamecron/src/tasks/processBuilderActivity/__tests__/processBuilderActivity.spec.ts b/apps/scoutgamecron/src/tasks/processBuilderActivity/__tests__/processBuilderActivity.spec.ts index 9867818b35..f736b2410c 100644 --- a/apps/scoutgamecron/src/tasks/processBuilderActivity/__tests__/processBuilderActivity.spec.ts +++ b/apps/scoutgamecron/src/tasks/processBuilderActivity/__tests__/processBuilderActivity.spec.ts @@ -1,5 +1,6 @@ import { prisma } from '@charmverse/core/prisma-client'; import { jest } from '@jest/globals'; +import type { Season } from '@packages/scoutgame/dates'; import { mockRepo, mockBuilder } from '@packages/scoutgame/testing/database'; import { mockSeason } from '@packages/scoutgame/testing/generators'; import { DateTime } from 'luxon'; @@ -66,7 +67,7 @@ describe('processBuilderActivity', () => { builderId: builder.id, githubUser: builder.githubUser, createdAfter: new Date(), - season: mockSeason + season: mockSeason as Season }); const builderEvents = await prisma.builderEvent.count({ @@ -114,7 +115,7 @@ describe('processBuilderActivity', () => { builderId: builder.id, githubUser: builder.githubUser, createdAfter: new Date(), - season: mockSeason + season: mockSeason as Season }); const builderEvents = await prisma.builderEvent.count({ diff --git a/apps/scoutgamecron/src/tasks/processBuilderActivity/github/getPullRequestsByUser.ts b/apps/scoutgamecron/src/tasks/processBuilderActivity/github/getPullRequestsByUser.ts index 7998bd6193..6835fbab08 100644 --- a/apps/scoutgamecron/src/tasks/processBuilderActivity/github/getPullRequestsByUser.ts +++ b/apps/scoutgamecron/src/tasks/processBuilderActivity/github/getPullRequestsByUser.ts @@ -90,7 +90,7 @@ export async function getPullRequestsByUser({ login, after }: { login: string; a while (hasNextPage) { try { - // @ts-ignore + // @ts-expect-error tbd what error is const { search } = await octokit.graphql.paginate(prSearchQuery, { queryString, first: 100, diff --git a/apps/waitlist/lib/scoring/__tests__/refreshPercentilesForEveryone.spec.ts b/apps/waitlist/lib/scoring/__tests__/refreshPercentilesForEveryone.spec.ts index b7083683c0..87ff4865dd 100644 --- a/apps/waitlist/lib/scoring/__tests__/refreshPercentilesForEveryone.spec.ts +++ b/apps/waitlist/lib/scoring/__tests__/refreshPercentilesForEveryone.spec.ts @@ -4,7 +4,7 @@ import { randomLargeInt, randomIntFromInterval } from '@packages/scoutgame/testi import type { ConnectWaitlistTier, TierChange } from '@packages/scoutgame/waitlist/scoring/constants'; import { refreshUserScore } from '@packages/scoutgame/waitlist/scoring/refreshUserScore'; -import { refreshPercentilesForEveryone } from '../refreshPercentilesForEveryone'; // Adjust the import to the correct module +import { refreshPercentilesForEveryone } from '../refreshPercentilesForEveryone'; // Function to shuffle an array deterministically using a seeded random number generator function seededShuffle(array: number[], seed: number): number[] { @@ -92,7 +92,7 @@ describe('refreshPercentilesForEveryone', () => { // Everyone starts in the 'common' tier // Now, 70% of 150 records should be out of the common tier - expect(tierChangeResults.length).toBe(104); + expect(tierChangeResults.length).toBe(106); const firstChangedUser = tierChangeResults[0]; expect(fids.indexOf(firstChangedUser.fid)).toBe(131); @@ -153,7 +153,7 @@ describe('refreshPercentilesForEveryone', () => { expect(thirdUserWaitlistSlot.percentile).toBe(thirdChangedUser.percentile); - expect(fourthChangedUser.percentile).toBe(50); + expect(fourthChangedUser.percentile).toBe(53); expect(fourthChangedUser.score).toBe(210); expect(fourthChangedUser.newTier).toBe('rare'); expect(fourthChangedUser.tierChange).toBe('up'); @@ -249,7 +249,7 @@ describe('refreshPercentilesForEveryone', () => { // Everyone starts in the 'common' tier // Now, 70% of 150 records should be out of the common tier - expect(tierChangeResults.length).toBe(104); + expect(tierChangeResults.length).toBe(106); const firstChangedUser = tierChangeResults[0]; expect(fids.indexOf(firstChangedUser.fid)).toBe(131); @@ -310,7 +310,7 @@ describe('refreshPercentilesForEveryone', () => { expect(thirdUserWaitlistSlot.percentile).toBe(thirdChangedUser.percentile); - expect(fourthChangedUser.percentile).toBe(50); + expect(fourthChangedUser.percentile).toBe(53); expect(fourthChangedUser.score).toBe(210); expect(fourthChangedUser.newTier).toBe('rare'); expect(fourthChangedUser.tierChange).toBe('up'); @@ -506,8 +506,8 @@ describe('refreshPercentilesForEveryone', () => { expect(tierChangeResults.some((tierChange) => fidsWithReferral.includes(tierChange.fid))); // Everyone starts in the 'common' tier - // Now, 70% (-1) of 100 records should be out of the common tier - expect(tierChangeResults.length).toBe(69); + // Now, 70% of 100 records should be out of the common tier + expect(tierChangeResults.length).toBe(70); const firstChangedUser = tierChangeResults[0]; expect(expectedFids.indexOf(firstChangedUser.fid)).toBe(50); @@ -568,7 +568,7 @@ describe('refreshPercentilesForEveryone', () => { expect(thirdUserWaitlistSlot.percentile).toBe(thirdChangedUser.percentile); - expect(fourthChangedUser.percentile).toBe(50); + expect(fourthChangedUser.percentile).toBe(59); expect(fourthChangedUser.score).toBe(-909); expect(fourthChangedUser.newTier).toBe('rare'); expect(fourthChangedUser.tierChange).toBe('up'); diff --git a/apps/waitlist/lib/scoring/notifyNewScore.ts b/apps/waitlist/lib/scoring/notifyNewScore.ts index b0d82a6c06..cb0d6f31ea 100644 --- a/apps/waitlist/lib/scoring/notifyNewScore.ts +++ b/apps/waitlist/lib/scoring/notifyNewScore.ts @@ -1,7 +1,7 @@ +import { NEYNAR_SIGNER_ID } from '@packages/farcaster/constants'; +import { writeToFarcaster } from '@packages/farcaster/messaging/writeToFarcaster'; import type { ConnectWaitlistTier } from '@packages/scoutgame/waitlist/scoring/constants'; -import { baseUrl, isDevEnv, isProdEnv, isStagingEnv, isTestEnv } from '@root/config/constants'; -import { NEYNAR_SIGNER_ID } from '@root/lib/farcaster/constants'; -import { writeToFarcaster } from '@root/lib/farcaster/messaging/writeToFarcaster'; +import { baseUrl, isDevEnv, isProdEnv, isStagingEnv, isTestEnv } from '@packages/utils/env'; import type { TierChangeResult } from './refreshPercentilesForEveryone'; diff --git a/apps/waitlist/lib/scoring/refreshPercentilesForEveryone.ts b/apps/waitlist/lib/scoring/refreshPercentilesForEveryone.ts index e8d639da46..836aa0a0bc 100644 --- a/apps/waitlist/lib/scoring/refreshPercentilesForEveryone.ts +++ b/apps/waitlist/lib/scoring/refreshPercentilesForEveryone.ts @@ -11,10 +11,6 @@ import { roundNumberInRange } from '@packages/utils/numbers'; import { notifyNewScore } from './notifyNewScore'; -// const prisma = new PrismaClient({ -// log: ['query'] -// }); - export type TierChangeResult = Pick & { newTier: ConnectWaitlistTier; tierChange: TierChange; diff --git a/apps/waitlist/package.json b/apps/waitlist/package.json index 54b7023064..291bbf64f0 100644 --- a/apps/waitlist/package.json +++ b/apps/waitlist/package.json @@ -11,7 +11,10 @@ "typecheck": "../../node_modules/typescript/bin/tsc --project ./tsconfig.json --noEmit" }, "dependencies": { + "@packages/farcaster": "^0.0.0", "@packages/github": "file:../../packages/github", + "@packages/scoutgame": "^0.0.0", + "@packages/utils": "^1.0.0", "next-safe-action": "~7.4.2" } } diff --git a/package-lock.json b/package-lock.json index 158255a05e..c7b6125487 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1171,7 +1171,10 @@ "apps/waitlist": { "version": "0.1.0", "dependencies": { + "@packages/farcaster": "^0.0.0", "@packages/github": "file:../../packages/github", + "@packages/scoutgame": "^0.0.0", + "@packages/utils": "^1.0.0", "next-safe-action": "~7.4.2" } }, @@ -68237,6 +68240,7 @@ "name": "@packages/scoutgame", "version": "0.0.0", "dependencies": { + "@packages/farcaster": "^0.0.0", "@packages/github": "^0.0.0", "@packages/onchain": "^0.0.0", "@packages/utils": "^1.0.0" @@ -80705,6 +80709,7 @@ "@packages/scoutgame": { "version": "file:packages/scoutgame", "requires": { + "@packages/farcaster": "^0.0.0", "@packages/github": "^0.0.0", "@packages/onchain": "^0.0.0", "@packages/utils": "^1.0.0" @@ -117654,7 +117659,10 @@ "waitlist": { "version": "file:apps/waitlist", "requires": { + "@packages/farcaster": "^0.0.0", "@packages/github": "file:../../packages/github", + "@packages/scoutgame": "^0.0.0", + "@packages/utils": "^1.0.0", "next-safe-action": "~7.4.2" } }, diff --git a/packages/farcaster/src/constants.ts b/packages/farcaster/src/constants.ts new file mode 100644 index 0000000000..616da70464 --- /dev/null +++ b/packages/farcaster/src/constants.ts @@ -0,0 +1,6 @@ +export const NEYNAR_API_BASE_URL = 'https://api.neynar.com/v2/farcaster'; + +export const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY as string; + +export const NEYNAR_SIGNER_ID = process.env.NEYNAR_SIGNER_ID as string; +export const NEYNAR_SIGNER_PUBLIC_KEY = process.env.NEYNAR_SIGNER_PUBLIC_KEY as string; diff --git a/lib/farcaster/lookupUserByCustodyAddress.ts b/packages/farcaster/src/lookupUserByCustodyAddress.ts similarity index 92% rename from lib/farcaster/lookupUserByCustodyAddress.ts rename to packages/farcaster/src/lookupUserByCustodyAddress.ts index 04b407f488..3c893535ed 100644 --- a/lib/farcaster/lookupUserByCustodyAddress.ts +++ b/packages/farcaster/src/lookupUserByCustodyAddress.ts @@ -1,5 +1,5 @@ import type { User as FarcasterUserProfileFromNeynar } from '@neynar/nodejs-sdk/build/neynar-api/v2/openapi-farcaster'; -import { GET } from '@root/adapters/http'; +import { GET } from '@packages/utils/http'; import { NEYNAR_API_BASE_URL, NEYNAR_API_KEY } from './constants'; diff --git a/lib/farcaster/messaging/abi/keyGatewayAbi.ts b/packages/farcaster/src/messaging/abi/keyGatewayAbi.ts similarity index 100% rename from lib/farcaster/messaging/abi/keyGatewayAbi.ts rename to packages/farcaster/src/messaging/abi/keyGatewayAbi.ts diff --git a/lib/farcaster/messaging/abi/signedKeyRequestMetadataAbi.ts b/packages/farcaster/src/messaging/abi/signedKeyRequestMetadataAbi.ts similarity index 100% rename from lib/farcaster/messaging/abi/signedKeyRequestMetadataAbi.ts rename to packages/farcaster/src/messaging/abi/signedKeyRequestMetadataAbi.ts diff --git a/lib/farcaster/messaging/getApprovedSigner.ts b/packages/farcaster/src/messaging/getApprovedSigner.ts similarity index 96% rename from lib/farcaster/messaging/getApprovedSigner.ts rename to packages/farcaster/src/messaging/getApprovedSigner.ts index c34451b373..36a8b72485 100644 --- a/lib/farcaster/messaging/getApprovedSigner.ts +++ b/packages/farcaster/src/messaging/getApprovedSigner.ts @@ -3,10 +3,11 @@ import { InvalidInputError } from '@charmverse/core/errors'; import { log } from '@charmverse/core/log'; import type { Signer } from '@neynar/nodejs-sdk/build/neynar-api/v2/openapi-farcaster'; -import { GET, POST } from '@root/adapters/http'; -import { getPublicClient } from '@root/lib/blockchain/publicClient'; -import { getWalletClient } from '@root/lib/blockchain/walletClient'; -import { sleep } from '@root/lib/utils/sleep'; +import { getPublicClient } from '@packages/onchain/getPublicClient'; +import { getWalletClient } from '@packages/onchain/getWalletClient'; +import { GET, POST } from '@packages/utils/http'; +import { sleep } from '@packages/utils/sleep'; +import type { Address } from 'viem'; import { encodeAbiParameters } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; import { optimism } from 'viem/chains'; @@ -220,7 +221,7 @@ export async function getApprovedSignerId(): Promise<{ signerId: string; signerP abi: keyGatewayAbi, // ABI of the contract functionName: 'addFor', // Function to call args: [ - farcasterDeveloper.custody_address as `0x${string}`, // fidOwner address + farcasterDeveloper.custody_address as Address, // fidOwner address 1, // keyType (uint32) signerPublicKey as `0x${string}`, // key (bytes) 1, // metadataType (uint8) diff --git a/lib/farcaster/messaging/writeToFarcaster.ts b/packages/farcaster/src/messaging/writeToFarcaster.ts similarity index 66% rename from lib/farcaster/messaging/writeToFarcaster.ts rename to packages/farcaster/src/messaging/writeToFarcaster.ts index e405ee93e3..c751f85b6a 100644 --- a/lib/farcaster/messaging/writeToFarcaster.ts +++ b/packages/farcaster/src/messaging/writeToFarcaster.ts @@ -1,15 +1,22 @@ -// docs.neynar.com/reference/publish-message - import { ExternalServiceError } from '@charmverse/core/errors'; import { log } from '@charmverse/core/log'; -import type { Cast } from '@neynar/nodejs-sdk/build/neynar-api/v2/openapi-farcaster'; -import { POST } from '@root/adapters/http'; -import { NEYNAR_API_BASE_URL, NEYNAR_API_KEY } from '@root/lib/farcaster/constants'; +import { POST } from '@packages/utils/http'; -// TBD - https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers +import { NEYNAR_API_BASE_URL, NEYNAR_API_KEY } from '../constants'; -// https://docs.neynar.com/reference/post-cast -// https://github.com/neynarxyz/farcaster-examples/tree/main/gm-bot +// Copied from @neynar/nodejs-sdk/build/neynar-api/v2/openapi-farcaster - Fails in CI so ported the type here +type Cast = { + hash: string; + parent_hash: string | null; + parent_url: string | null; + root_parent_url: string | null; + parent_author: any; + author: any; + text: string; + timestamp: string; + embeds: any[]; + type?: any; +}; export async function writeToFarcaster({ neynarSignerId, diff --git a/packages/scoutgame/package.json b/packages/scoutgame/package.json index 7f6fb0141c..da23c9bc2b 100644 --- a/packages/scoutgame/package.json +++ b/packages/scoutgame/package.json @@ -11,6 +11,7 @@ "./builderNfts/artwork/generateNftImage": "./src/builderNfts/artwork/generateNftImage.tsx" }, "dependencies": { + "@packages/farcaster": "^0.0.0", "@packages/onchain": "^0.0.0", "@packages/utils": "^1.0.0", "@packages/github": "^0.0.0" diff --git a/packages/scoutgame/src/waitlist/scoring/__tests__/getWaitlistRange.spec.ts b/packages/scoutgame/src/waitlist/scoring/__tests__/getWaitlistRange.spec.ts new file mode 100644 index 0000000000..d0b80b2ce3 --- /dev/null +++ b/packages/scoutgame/src/waitlist/scoring/__tests__/getWaitlistRange.spec.ts @@ -0,0 +1,22 @@ +import { getWaitlistRange, waitlistTiers } from '../constants'; + +describe('getWaitlistRange', () => { + it('should return the correct range for each tier', () => { + const expectedRanges = { + common: { min: 1, max: 30 }, + rare: { min: 31, max: 60 }, + epic: { min: 61, max: 80 }, + mythic: { min: 81, max: 95 }, + legendary: { min: 96, max: 100 } + }; + + waitlistTiers.forEach((tier) => { + const { min, max } = getWaitlistRange(tier); + expect({ min, max }).toEqual(expectedRanges[tier]); + }); + }); + + it('should throw an error for an invalid tier', () => { + expect(() => getWaitlistRange('invalid-tier' as any)).toThrow('Invalid tier: invalid-tier'); + }); +}); diff --git a/packages/scoutgame/src/waitlist/scoring/constants.ts b/packages/scoutgame/src/waitlist/scoring/constants.ts index 394e4e9aad..3a76238d32 100644 --- a/packages/scoutgame/src/waitlist/scoring/constants.ts +++ b/packages/scoutgame/src/waitlist/scoring/constants.ts @@ -17,7 +17,7 @@ export const tierDistribution: TierDistributionType[] = [ { tier: 'common', threshold: 1, - totalPercentSize: 40, + totalPercentSize: 30, imageText: '/images/levels/common.png', badge: '/images/levels/common-badge.png', badgeText: '/images/levels/common-badge-text.png' @@ -25,7 +25,7 @@ export const tierDistribution: TierDistributionType[] = [ { tier: 'rare', threshold: 31, - totalPercentSize: 20, + totalPercentSize: 30, imageText: '/images/levels/rare.png', badge: '/images/levels/rare-badge.png', badgeText: '/images/levels/rare-badge-text.png' @@ -93,3 +93,15 @@ export function findHighestNumberInArray messageGroup.tier === tier); + if (!group) { + throw new Error('Tier not found.'); + } + // Get a random message from the group's messages + const randomMessage = group.messages[Math.floor(Math.random() * group.messages.length)]; + // Interpolate @username with the real username + return randomMessage.replace(/@username/g, `@${realUsername}`); +} + +export async function welcomeFromWaitlistToScoutgame({ fid }: { fid: number }): Promise { + const waitlistSlot = await prisma.connectWaitlistSlot.findUniqueOrThrow({ + where: { + fid + } + }); + + if (!waitlistSlot.username) { + throw new Error('Username not found.'); + } + + const tier = getTier(waitlistSlot.percentile); + + const message = getMessage(tier, waitlistSlot.username as string); + + await writeToFarcaster({ + neynarSignerId: process.env.NEYNAR_SIGNER_ID as string, + text: message, + embedUrl: `https://scoutgame.xyz` + }); +} diff --git a/packages/utils/package.json b/packages/utils/package.json index 488c0e4a63..ab70c05f7f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -13,6 +13,8 @@ "./types": "./src/types.ts", "./dates": "./src/dates.ts", "./http": "./src/http.ts", + "./sleep": "./src/sleep.ts", + "./env": "./src/env.ts", "./strings": "./src/strings.ts" } } diff --git a/packages/utils/src/env.ts b/packages/utils/src/env.ts new file mode 100644 index 0000000000..4f68e28790 --- /dev/null +++ b/packages/utils/src/env.ts @@ -0,0 +1,12 @@ +import env from '@beam-australia/react-env'; + +// Note: NODE_ENV can only be 'development' or 'production' according to Next.js, but we don't want to mix them with test env +export const isTestEnv = (env('APP_ENV') ?? process.env.REACT_APP_APP_ENV ?? process.env.NODE_ENV) === 'test'; +export const isStagingEnv = (env('APP_ENV') ?? process.env.REACT_APP_APP_ENV) === 'staging'; +export const isProdEnv = process.env.NODE_ENV === 'production' && !isTestEnv && !isStagingEnv; +export const isDevEnv = + (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') && !isProdEnv && !isStagingEnv && !isTestEnv; + +export const isNodeEnv = typeof window === 'undefined'; +export const appEnv = isProdEnv ? 'production' : isStagingEnv ? 'staging' : isTestEnv ? 'test' : 'development'; +export const baseUrl = process.env.DOMAIN as string | undefined; diff --git a/packages/utils/src/sleep.ts b/packages/utils/src/sleep.ts new file mode 100644 index 0000000000..af916a7f4a --- /dev/null +++ b/packages/utils/src/sleep.ts @@ -0,0 +1,5 @@ +export function sleep(timeMs: number) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs); + }); +}