diff --git a/.github/workflows/update-environments.yml b/.github/workflows/update-environments.yml index 88e6fd29c..a1917c0a0 100644 --- a/.github/workflows/update-environments.yml +++ b/.github/workflows/update-environments.yml @@ -30,7 +30,7 @@ jobs: * Stage fork: https://stage-fork--taho-development.netlify.app/ Read more: [Deployment to Production Flow](https://github.com/tahowallet/dapp/blob/main/docs/testing-env.md) - REVIEWERS: '["andreachapman"]' + REVIEWERS: '["michalinacienciala"]' - uses: studroid/label-pr-or-issue-action@ff48a93f6e1a8d8a6befdae900f54da173b17215 # v1.0.1 with: pr-or-issue-number: ${{ steps.pr.outputs.PULL_REQUEST_NUMBER }} @@ -78,7 +78,7 @@ jobs: * Production: https://taho-development.netlify.app/ (aka https://app.taho.xyz/) Read more: [Deployment to Production Flow](https://github.com/tahowallet/dapp/blob/main/docs/testing-env.md) - REVIEWERS: '["andreachapman"]' + REVIEWERS: '["michalinacienciala"]' - uses: studroid/label-pr-or-issue-action@ff48a93f6e1a8d8a6befdae900f54da173b17215 # v1.0.1 with: pr-or-issue-number: ${{ steps.pr.outputs.PULL_REQUEST_NUMBER }} diff --git a/package.json b/package.json index dced6cd58..4afaa3743 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "stream-http": "^3.2.0", "styled-jsx": "^5.1.2", "use-image": "^1.1.1", - "util": "^0.12.5" + "util": "^0.12.5", + "zod": "^3.22.4" }, "devDependencies": { "@babel/core": "^7.22.9", diff --git a/src/assets/questline-data.json b/src/assets/questline-data.json index 1773bd87a..06c73485b 100644 --- a/src/assets/questline-data.json +++ b/src/assets/questline-data.json @@ -13,8 +13,7 @@ { "id": "4_2", "name": "Increase your Gitcoin Passport score", - "description": "The more you increase your Gitcoin Passport score in a week, the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more you increase your Gitcoin Passport score in a week, the more of the weekly XP drop you will get." } ] }, @@ -31,14 +30,12 @@ { "id": "7_2", "name": "Mint W3ST", - "description": "The more W3ST you mint, the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more W3ST you mint, the more of the weekly XP drop you will get." }, { "id": "7_3", "name": "Collect post essence", - "description": "The more post essence you collect, the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more post essence you collect, the more of the weekly XP drop you will get." } ] }, @@ -51,6 +48,13 @@ "id": "8_1", "name": "Interact with unique contracts on Base", "description": "The more interactions you execute with different unique contracts on Base compared to other members of this realm in a week, the more of the weekly XP drop you will get." + }, + { + "id": "8_2", + "name": "Vampire Attack: Bridge funds from Arbitrum", + "description": "Bridge ETH or USDC from Arbitrum to Base using Hop Exchange", + "isNew": true, + "isAttack": true } ] }, @@ -67,8 +71,7 @@ { "id": "9_2", "name": "Earn fUSDC rewards using Fluidity", - "description": "The more fUSDC rewards you earn using Fluidity in a week, the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more fUSDC rewards you earn using Fluidity in a week, the more of the weekly XP drop you will get." } ] }, @@ -85,8 +88,7 @@ { "id": "19_2", "name": "Claim NFTs during the week using the Galxe space station", - "description": "The more NFTs you claim via a Galxe [`SpaceStation` contract](https://docs.galxe.com/developer/smart-contract/contracts/spacestation/), the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more NFTs you claim via a Galxe [`SpaceStation` contract](https://docs.galxe.com/developer/smart-contract/contracts/spacestation/), the more of the weekly XP drop you will get." } ] }, @@ -103,8 +105,7 @@ { "id": "22_2", "name": "Bridge tokens using FraxFerry", - "description": "The more unique tokens you bridge over FraxFerry, the more of the weekly XP drop you will get.", - "isNew": true + "description": "The more unique tokens you bridge over FraxFerry, the more of the weekly XP drop you will get." } ] } diff --git a/src/redux-state/selectors/wallet.ts b/src/redux-state/selectors/wallet.ts index b73aa21c3..8892ae40f 100644 --- a/src/redux-state/selectors/wallet.ts +++ b/src/redux-state/selectors/wallet.ts @@ -5,6 +5,7 @@ import { createWalletSelector } from "redux-state/selectors" export const selectWalletAddress = createWalletSelector("address") export const selectWalletAvatar = createWalletSelector("avatar") +export const selectWalletAvatarType = createWalletSelector("avatarType") export const selectWalletNameProperty = createWalletSelector("name") export const selectIsWalletConnected = createWalletSelector("isConnected") export const selectHasLoadedBalances = createWalletSelector("hasLoadedBalances") diff --git a/src/redux-state/slices/wallet.ts b/src/redux-state/slices/wallet.ts index 465456051..f6e98cb90 100644 --- a/src/redux-state/slices/wallet.ts +++ b/src/redux-state/slices/wallet.ts @@ -9,6 +9,7 @@ export type WalletState = { address: string name: string avatar: string + avatarType: string | null hasLoadedBalances: boolean balances: TokenBalances transactionStatus: { [id: string]: TransactionProgressStatus } @@ -19,6 +20,7 @@ const initialState: WalletState = { address: "", name: "", avatar: portrait, + avatarType: null, hasLoadedBalances: false, balances: { [TAHO_ADDRESS]: { @@ -41,12 +43,20 @@ const walletSlice = createSlice({ immerState, { payload, - }: { payload: { address: string; name?: string; avatar?: string } } + }: { + payload: { + address: string + name?: string + avatar?: string + avatarType?: string | null + } + } ) => { immerState.isConnected = true immerState.address = payload.address || immerState.address immerState.name = payload.name || immerState.name || "" immerState.avatar = payload.avatar || immerState.avatar || portrait + immerState.avatarType = payload.avatarType || null }, resetWalletState: () => initialState, updateBalances: ( diff --git a/src/redux-state/thunks/wallet.ts b/src/redux-state/thunks/wallet.ts index 3c1e6be2a..c04fb6817 100644 --- a/src/redux-state/thunks/wallet.ts +++ b/src/redux-state/thunks/wallet.ts @@ -1,4 +1,5 @@ import { + determineFetchedFileType, isClaimXpTransactionID, resolveAddressToWalletData, } from "shared/utils" @@ -35,7 +36,10 @@ export const fetchWalletName = createDappAsyncThunk( updateConnectedWallet({ address, ...(data.name ? { name: data.name } : {}), - ...(data.avatar ? { name: data.avatar } : {}), + ...(data.avatar ? { avatar: data.avatar } : {}), + avatarType: data.avatar + ? await determineFetchedFileType(data.avatar) + : null, }) ) diff --git a/src/shared/assets/attack-line.svg b/src/shared/assets/attack-line.svg new file mode 100644 index 000000000..8ee2c6289 --- /dev/null +++ b/src/shared/assets/attack-line.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/assets/attack.svg b/src/shared/assets/attack.svg new file mode 100644 index 000000000..7bb8a7d5d --- /dev/null +++ b/src/shared/assets/attack.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/components/Avatar.tsx b/src/shared/components/Avatar.tsx new file mode 100644 index 000000000..6a59b7875 --- /dev/null +++ b/src/shared/components/Avatar.tsx @@ -0,0 +1,47 @@ +import React, { CSSProperties } from "react" +import { + selectWalletAvatar, + selectWalletAvatarType, + useDappSelector, +} from "redux-state" + +type AvatarProps = { + width: string + style?: CSSProperties +} + +export default function Avatar({ width, style }: AvatarProps) { + const avatar = useDappSelector(selectWalletAvatar) + const avatarType = useDappSelector(selectWalletAvatarType) + + return ( + <> +
+ {avatarType !== "video/mp4" ? ( +
+ ) : ( +
+ + + ) +} diff --git a/src/shared/components/KonvaVideo.tsx b/src/shared/components/KonvaVideo.tsx new file mode 100644 index 000000000..a42f7897a --- /dev/null +++ b/src/shared/components/KonvaVideo.tsx @@ -0,0 +1,61 @@ +import React, { useEffect, useMemo, useRef } from "react" +import Konva from "konva" +import { Image } from "react-konva" +import { ImageConfig } from "konva/lib/shapes/Image" + +type KonvaVideoProps = { + x: number + y: number + height: number + width: number + src: string + loop?: boolean + videoProps?: Partial +} + +// Source: https://codesandbox.io/p/sandbox/react-konva-video-on-canvas-oygvf?file=%2Fsrc%2Findex.js%3A22%2C31 +export default function KonvaVideo({ + x, + y, + height, + width, + src, + loop = true, + videoProps, +}: KonvaVideoProps) { + const imageRef = useRef(null) + + const videoElement = useMemo(() => { + const element = document.createElement("video") + element.src = src + element.loop = loop + + return element + }, [src, loop]) + + // use Konva.Animation to redraw a layer + useEffect(() => { + if (!imageRef.current) return () => {} + + videoElement.play() + const imageLayer = imageRef.current.getLayer() + + const animation = new Konva.Animation(() => {}, imageLayer) + animation.start() + + return () => animation.stop() + }, [videoElement, imageRef]) + + return ( + + ) +} diff --git a/src/shared/components/RealmCutout/RealmCutout.tsx b/src/shared/components/RealmCutout/RealmCutout.tsx index 7a42f5bea..e1baec18e 100644 --- a/src/shared/components/RealmCutout/RealmCutout.tsx +++ b/src/shared/components/RealmCutout/RealmCutout.tsx @@ -5,7 +5,6 @@ import backgroundImg from "public/dapp_island_bg.webp" import { selectDisplayedRealmId, selectIsStakingRealmDisplayed, - selectWalletAvatar, useDappSelector, } from "redux-state" import RealmPin from "./RealmPin" @@ -18,7 +17,6 @@ const CUTOUT_RATIO = CUTOUT_HEIGHT / CUTOUT_WIDTH export default function RealmCutout() { const realmId = useDappSelector(selectDisplayedRealmId) const isStakedRealm = useDappSelector(selectIsStakingRealmDisplayed) - const walletAvatar = useDappSelector(selectWalletAvatar) if (!realmId) return null @@ -31,7 +29,7 @@ export default function RealmCutout() { <>
- {isStakedRealm && } + {isStakedRealm && }
Realm Pin - Avatar
) diff --git a/src/shared/constants/external-links.ts b/src/shared/constants/external-links.ts index b2f539468..ed4e13df7 100644 --- a/src/shared/constants/external-links.ts +++ b/src/shared/constants/external-links.ts @@ -3,7 +3,7 @@ export default { TWITTER: "https://twitter.com/taho_xyz", GITHUB: "https://github.com/tahowallet/extension", RULEBOOK: "https://docs.taho.xyz/app/", - WAITLIST: "https://tahobeta.deform.cc/subscapereferral", + WAITLIST: "https://tahobeta.deform.cc/openbeta", DOCS: "https://docs.taho.xyz/app/", BRAVE_SUPPORT: "https://support.brave.com/hc/en-us/articles/360023646212-How-do-I-configure-global-and-site-specific-Shields-settings-", diff --git a/src/shared/constants/realms.ts b/src/shared/constants/realms.ts index 97c218f5c..780703d5d 100644 --- a/src/shared/constants/realms.ts +++ b/src/shared/constants/realms.ts @@ -75,12 +75,13 @@ export const REALMS_WITH_CONTRACT_ADDRESS: { export const BASE_REALMS_MAP_DATA: RealmMapData[] = [ realm4, realm7, + realm8, realm9, realm19, realm22, ] -export const NEW_REALMS_MAP_DATA: RealmMapData[] = [realm8] +export const NEW_REALMS_MAP_DATA: RealmMapData[] = [] export const REALMS_MAP_DATA = [ ...BASE_REALMS_MAP_DATA, diff --git a/src/shared/hooks/reflect.ts b/src/shared/hooks/reflect.ts index 1568f853b..aab9c7e34 100644 --- a/src/shared/hooks/reflect.ts +++ b/src/shared/hooks/reflect.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react" +import { createContext, useContext, useEffect, useMemo, useState } from "react" import { selectDisplayedRealmId, selectRealmNameById, @@ -7,48 +7,88 @@ import { useDappSelector, } from "redux-state" import { usePresence, useSubscribe } from "@rocicorp/reflect/react" -import { getClientState, reflectInstance } from "shared/utils" +import { + ReflectInstance, + ReflectMutators, + getClientState, + mutators, +} from "shared/utils" import { getRealmMapData } from "shared/constants" import { RootState } from "redux-state/reducers" +import { Reflect } from "@rocicorp/reflect/client" +import { nanoid } from "@reduxjs/toolkit" +import { useWalletOnboarding } from "./wallets" + +export const reflectSingleton = + process.env.DISABLE_REFLECT === "true" + ? null + : new Reflect({ + userID: nanoid(), + roomID: "/", + server: process.env.REFLECT_SERVER ?? "", + mutators, + }) -export function useReflect() { - const name = useDappSelector(selectWalletName) - const stakingRealmId = useDappSelector(selectStakingRealmId) - const realmName = - useDappSelector((state: RootState) => - selectRealmNameById(state, stakingRealmId) - ) ?? null - - const realmMapData = stakingRealmId ? getRealmMapData(stakingRealmId) : null +const DEFAULT_BG_COLOR = "#2C2C2C" +const DEFAULT_TEXT_COLOR = "#FFF" - const [reflectInitialized, setReflectInitialized] = useState(false) +export const ReflectContext = createContext(null) - const stakingRealmColor = realmMapData?.color ?? "#2C2C2C" - const cursorTextColor = realmMapData?.cursorText ?? "#FFF" +export function useInitializeReflect() { + const reflect = useContext(ReflectContext) + const [initialized, setInitialized] = useState(false) useEffect(() => { - const initReflect = async () => { - if (reflectInitialized || !reflectInstance) return + const initialize = async () => { + if (!reflect || initialized) return - await reflectInstance.mutate.initClientState({ - id: reflectInstance.clientID, + setInitialized(true) + + await reflect.mutate.initClientState({ + id: reflect.clientID, cursor: null, userInfo: { - name, - realmName, - stakingRealmColor, - cursorTextColor, + name: "", + realmName: null, + stakingRealmColor: DEFAULT_BG_COLOR, + cursorTextColor: DEFAULT_TEXT_COLOR, }, isPresent: true, }) - - setReflectInitialized(true) } + initialize() + }, [reflect, initialized]) +} + +export function useReflect() { + const reflect = useContext(ReflectContext) + const name = useDappSelector(selectWalletName) + const stakingRealmId = useDappSelector(selectStakingRealmId) + const realmName = + useDappSelector((state: RootState) => + selectRealmNameById(state, stakingRealmId) + ) ?? null + + const { walletOnboarded } = useWalletOnboarding() + + const { color: stakingRealmColor, cursorText: cursorTextColor } = + useMemo(() => { + if (stakingRealmId) { + const { color, cursorText } = getRealmMapData(stakingRealmId) + return { color, cursorText } + } + return { + color: DEFAULT_BG_COLOR, + cursorText: DEFAULT_TEXT_COLOR, + } + }, [stakingRealmId]) + + useEffect(() => { const updateUserInfo = async () => { - if (!reflectInstance) return + if (!reflect) return - await reflectInstance.mutate.setUserInfo({ + await reflect.mutate.setUserInfo({ name, realmName, stakingRealmColor, @@ -56,26 +96,36 @@ export function useReflect() { }) } - initReflect() updateUserInfo() - }, [reflectInitialized, name, realmName, stakingRealmColor, cursorTextColor]) + }, [reflect, name, realmName, stakingRealmColor, cursorTextColor]) useEffect(() => { const handleReflectCursor = async (e: MouseEvent) => { - if (!reflectInstance) return + if (!reflect) return - await reflectInstance.mutate.setCursor({ x: e.clientX, y: e.clientY }) + await reflect.mutate.setCursor({ x: e.clientX, y: e.clientY }) } window.addEventListener("mousemove", handleReflectCursor) return () => window.removeEventListener("mousemove", handleReflectCursor) - }, []) + }, [reflect]) + + useEffect(() => { + const updateReflectPresence = async () => { + if (!reflect) return + + await reflect.mutate.setUserPresence(!!walletOnboarded) + } + + updateReflectPresence() + }, [walletOnboarded, reflect]) } export function useReflectPresence() { - const presentClientsdIds = usePresence(reflectInstance) + const reflect = useContext(ReflectContext) + const presentClientsdIds = usePresence(reflect) const presentClients = useSubscribe( - reflectInstance, + reflect, async (tx) => { const clients = await Promise.all( presentClientsdIds.map(async (clientID) => { @@ -96,8 +146,9 @@ export function useReflectPresence() { } export function useReflectCurrentUser() { + const reflect = useContext(ReflectContext) return useSubscribe( - reflectInstance, + reflect, async (tx) => { const currentUser = await getClientState(tx, tx.clientID) return currentUser @@ -107,8 +158,6 @@ export function useReflectCurrentUser() { } export function useReflectCursors() { - useReflect() - const reflectClients = useReflectPresence() const currentUser = useReflectCurrentUser() const realmModalOpened = useDappSelector(selectDisplayedRealmId) @@ -137,7 +186,9 @@ export function useReflectCursors() { ) // Hide current user cursor when the realm modal is opened - return !realmModalOpened + const visibleCursors = !realmModalOpened ? visibleClients : visibleClients.filter((client) => client.id !== currentUser?.id) + + return { visibleCursors, currentUser } } diff --git a/src/shared/hooks/wallets.ts b/src/shared/hooks/wallets.ts index 66143493e..d3c7269a7 100644 --- a/src/shared/hooks/wallets.ts +++ b/src/shared/hooks/wallets.ts @@ -23,7 +23,6 @@ import { } from "shared/constants" import { Network } from "@ethersproject/networks" import { Logger, defineReadOnly } from "ethers/lib/utils" -import { reflectInstance } from "shared/utils" import { useAssistant } from "./assistant" import { useInterval, useLocalStorageChange } from "./helpers" @@ -169,16 +168,6 @@ export function useWalletOnboarding(): { const { value, updateStorage } = useLocalStorageChange(LOCAL_STORAGE_WALLET) - useEffect(() => { - const updateReflectPresence = async () => { - if (!reflectInstance) return - - await reflectInstance.mutate.setUserPresence(!!value) - } - - updateReflectPresence() - }, [value]) - return { walletOnboarded: value, updateWalletOnboarding: updateStorage } } diff --git a/src/shared/types/quest.ts b/src/shared/types/quest.ts index 24bd3da39..9ca97ee12 100644 --- a/src/shared/types/quest.ts +++ b/src/shared/types/quest.ts @@ -3,4 +3,5 @@ export type Quest = { name: string description: string isNew?: boolean + isAttack?: boolean } diff --git a/src/shared/types/reflect.ts b/src/shared/types/reflect.ts index 9f77bd931..3bf0369c9 100644 --- a/src/shared/types/reflect.ts +++ b/src/shared/types/reflect.ts @@ -1,18 +1,24 @@ -export type ReflectCursor = { - x: number - y: number -} +import { z } from "zod" -export type ReflectUserInfo = { - name: string - realmName: string | null - stakingRealmColor: string - cursorTextColor: string -} +export const reflectCursorSchema = z.object({ + x: z.number(), + y: z.number(), +}) -export type ReflectClient = { - id: string - cursor: ReflectCursor | null - userInfo: ReflectUserInfo - isPresent: boolean -} +export const reflectUserInfoSchema = z.object({ + name: z.string(), + realmName: z.union([z.string(), z.null()]), + stakingRealmColor: z.string(), + cursorTextColor: z.string(), +}) + +export const reflectClientSchema = z.object({ + id: z.string(), + cursor: z.union([reflectCursorSchema, z.null()]), + userInfo: reflectUserInfoSchema, + isPresent: z.boolean(), +}) + +export type ReflectCursor = z.infer +export type ReflectUserInfo = z.infer +export type ReflectClient = z.infer diff --git a/src/shared/utils/misc.ts b/src/shared/utils/misc.ts index 972471ca1..9e41bf73c 100644 --- a/src/shared/utils/misc.ts +++ b/src/shared/utils/misc.ts @@ -61,3 +61,13 @@ export function createImageElement(source: string) { return image } + +// Source: https://stackoverflow.com/questions/38679681/getting-a-file-type-from-url +export async function determineFetchedFileType( + url: string +): Promise { + const response = await fetch(url, { method: "HEAD" }) + const fileType = response.headers.get("Content-Type") + + return fileType +} diff --git a/src/shared/utils/names.ts b/src/shared/utils/names.ts index 2d4269fc5..d2d6ad90e 100644 --- a/src/shared/utils/names.ts +++ b/src/shared/utils/names.ts @@ -5,6 +5,7 @@ import { isValidUNSDomainName, resolveAddressToUNS, resolveUNS } from "./uns" type WalletData = { name?: string avatar?: string + avatarType?: string | null } type NameWithProvider = WalletData & { @@ -78,9 +79,9 @@ const resolveAddressToWalletDataWithoutCache = async (address: string) => { const cachedResult = (await resolveAddressPromiseCache[normalizedAddress]) ?? {} - const { name, avatar } = cachedResult + const { name, avatar, avatarType } = cachedResult - return name ? { name, avatar } : null + return name ? { name, avatar, avatarType } : null } export const resolveAddressToWalletData = async ( @@ -97,7 +98,9 @@ export const resolveAddressToWalletData = async ( const data = await resolveAddressToWalletDataWithoutCache(normalizedAddress) - return data ? { name: data.name, avatar: data.avatar } : null + return data + ? { name: data.name, avatar: data.avatar, avatarType: data.avatarType } + : null } export const resolveNameToAddress = async (addressOrName: string) => { diff --git a/src/shared/utils/reflect.ts b/src/shared/utils/reflect.ts index 4c2059886..340aebff6 100644 --- a/src/shared/utils/reflect.ts +++ b/src/shared/utils/reflect.ts @@ -2,14 +2,27 @@ import type { ReflectServerOptions } from "@rocicorp/reflect/server" import { WriteTransaction } from "@rocicorp/reflect" import { generate } from "@rocicorp/rails" import { Reflect } from "@rocicorp/reflect/client" -import { ReflectClient, ReflectCursor, ReflectUserInfo } from "shared/types" -import { nanoid } from "@reduxjs/toolkit" +import { + ReflectClient, + ReflectCursor, + ReflectUserInfo, + reflectClientSchema, +} from "shared/types" +import { Schema } from "zod" + +// Source: https://github.com/rocicorp/reflect-draw/blob/main/src/datamodel/zod.ts +function parseReflectState(schema: Schema) { + return process.env.NODE_ENV !== "production" ? schema.parse : (val: T) => val +} export const { init: initClientState, get: getClientState, update: updateClientState, -} = generate("client-state") +} = generate( + "client-state", + parseReflectState(reflectClientSchema) +) async function setCursor( tx: WriteTransaction, @@ -49,16 +62,6 @@ export const mutators = { export type ReflectMutators = typeof mutators export type ReflectInstance = Reflect -export const reflectInstance = - process.env.DISABLE_REFLECT === "true" - ? null - : new Reflect({ - userID: nanoid(), - roomID: "/", - server: process.env.REFLECT_SERVER ?? "", - mutators, - }) - const makeOptions = (): ReflectServerOptions => ({ mutators, logLevel: "debug", diff --git a/src/shared/utils/shift.ts b/src/shared/utils/shift.ts index cc7108634..b07198cab 100644 --- a/src/shared/utils/shift.ts +++ b/src/shared/utils/shift.ts @@ -35,7 +35,7 @@ export function getNewQuestLabelShift(realmId: string) { case "7": return { x: 900, y: 300 } case "8": - return { x: 700, y: 150 } + return { x: 850, y: 300 } case "9": return { x: 850, y: 350 } case "19": diff --git a/src/ui/DApps/DesktopDApp.tsx b/src/ui/DApps/DesktopDApp.tsx index 801b223ac..0d9b0ac49 100644 --- a/src/ui/DApps/DesktopDApp.tsx +++ b/src/ui/DApps/DesktopDApp.tsx @@ -1,11 +1,15 @@ import React from "react" import { + ReflectContext, + reflectSingleton, useBalanceFetch, useConnect, useGameDataFetch, useGameLoadDataFetch, + useInitializeReflect, usePopulationFetch, + useReflect, useWallet, useWalletChange, useWalletOnboarding, @@ -14,9 +18,12 @@ import Onboarding from "ui/Onboarding" import PrivacyPolicy from "../../shared/components/PrivacyPolicy" import IslandView from "./IslandView" -export default function DesktopDApp() { - const { walletOnboarded } = useWalletOnboarding() +function DesktopDAppContent() { + useInitializeReflect() + useReflect() + const { isConnected } = useConnect() + const { walletOnboarded } = useWalletOnboarding() useWallet() useGameLoadDataFetch() @@ -33,3 +40,11 @@ export default function DesktopDApp() { ) } + +export default function DesktopDApp() { + return ( + + + + ) +} diff --git a/src/ui/Island/InteractiveIsland.tsx b/src/ui/Island/InteractiveIsland.tsx index d2a879584..cfb65e476 100644 --- a/src/ui/Island/InteractiveIsland.tsx +++ b/src/ui/Island/InteractiveIsland.tsx @@ -27,6 +27,7 @@ import Background from "./Background" import Realms from "./IslandRealms" import RealmPin from "./IslandRealmsDetails/RealmPin" import Clouds from "./Clouds" +import AttackLine from "./IslandRealmsDetails/AttackLine" function InteractiveIsland() { const settingsRef = useRef({ minScale: 0 }) @@ -167,6 +168,7 @@ function InteractiveIsland() { + diff --git a/src/ui/Island/IslandPresence.tsx b/src/ui/Island/IslandPresence.tsx index 76754eebc..85858ca37 100644 --- a/src/ui/Island/IslandPresence.tsx +++ b/src/ui/Island/IslandPresence.tsx @@ -1,13 +1,18 @@ import React from "react" -import { useReflectCurrentUser, useReflectCursors } from "shared/hooks" +import { useReflectCursors } from "shared/hooks" import IslandCursor from "./Cursor" export default function IslandPresence() { - const visibleCursors = useReflectCursors() - const currentUser = useReflectCurrentUser() + const { visibleCursors, currentUser } = useReflectCursors() const reflectEnabled = process.env.SHOW_REFLECT === "true" - if (!visibleCursors || !visibleCursors.length || !reflectEnabled) return null + if ( + !visibleCursors || + !visibleCursors.length || + !currentUser || + !reflectEnabled + ) + return null return visibleCursors.map(({ id, cursor, userInfo }) => { if (!cursor) return null @@ -17,7 +22,7 @@ export default function IslandPresence() { key={id} cursor={cursor} userInfo={userInfo} - extraCursor={id !== currentUser?.id} + extraCursor={id !== currentUser.id} /> ) }) diff --git a/src/ui/Island/IslandRealmsDetails/AttackLine.tsx b/src/ui/Island/IslandRealmsDetails/AttackLine.tsx new file mode 100644 index 000000000..850cdc303 --- /dev/null +++ b/src/ui/Island/IslandRealmsDetails/AttackLine.tsx @@ -0,0 +1,20 @@ +import React from "react" +import { Image } from "react-konva" +import attackLineImg from "shared/assets/attack-line.svg" +import { FIGMA_FACTOR } from "shared/constants" +import useImage from "use-image" + +export default function VampireQuest() { + const [attackLine] = useImage(attackLineImg) + + return ( + + ) +} diff --git a/src/ui/Island/IslandRealmsDetails/RealmPin.tsx b/src/ui/Island/IslandRealmsDetails/RealmPin.tsx index bb6865d43..04b6df5b4 100644 --- a/src/ui/Island/IslandRealmsDetails/RealmPin.tsx +++ b/src/ui/Island/IslandRealmsDetails/RealmPin.tsx @@ -5,17 +5,56 @@ import pin from "shared/assets/realm-pin.svg" import { selectStakingRealmId, selectWalletAvatar, + selectWalletAvatarType, useDappSelector, } from "redux-state" import { FIGMA_FACTOR, getRealmMapData } from "shared/constants" import { getPinShift } from "shared/utils" +// import KonvaVideo from "shared/components/KonvaVideo" +import portraitImg from "shared/assets/portrait.png" + +type RealmPinAvatarProps = { + x: number + y: number +} + +function RealmPinAvatar({ x, y }: RealmPinAvatarProps) { + const avatar = useDappSelector(selectWalletAvatar) + const avatarType = useDappSelector(selectWalletAvatarType) + + const [avatarImage] = useImage(avatar) + const [portrait] = useImage(portraitImg) + + // TODO: implement video avatar support + // if (avatarType === "video/mp4") { + // return ( + // + // ) + // } + + return ( + + ) +} export default function RealmPin() { const stakingRealmId = useDappSelector(selectStakingRealmId) - const avatar = useDappSelector(selectWalletAvatar) const [pinImage] = useImage(pin) - const [avatarImage] = useImage(avatar) const stakingRealm = stakingRealmId && getRealmMapData(stakingRealmId) @@ -34,14 +73,7 @@ export default function RealmPin() { height={92 * FIGMA_FACTOR.Y} width={108 * FIGMA_FACTOR.X} /> - + ) } diff --git a/src/ui/Island/RealmDetails/Quests/QuestItem.tsx b/src/ui/Island/RealmDetails/Quests/QuestItem.tsx index 4a6e84f71..47424f9ef 100644 --- a/src/ui/Island/RealmDetails/Quests/QuestItem.tsx +++ b/src/ui/Island/RealmDetails/Quests/QuestItem.tsx @@ -3,11 +3,18 @@ import Markdown from "react-markdown" import Accordion from "shared/components/Accordion" import starIcon from "shared/assets/icons/star.svg" import newQuestLabel from "shared/assets/new-quest-label.svg" +import attackLabel from "shared/assets/attack.svg" import Icon from "shared/components/Icon" import { Quest } from "shared/types" import { useDisplayedQuests } from "shared/hooks/quest" -export default function QuestItem({ id, name, description, isNew }: Quest) { +export default function QuestItem({ + id, + name, + description, + isNew, + isAttack, +}: Quest) { const { isQuestDisplayed, updateDisplayedQuest } = useDisplayedQuests() const handleQuestClick = useCallback(() => { @@ -17,7 +24,7 @@ export default function QuestItem({ id, name, description, isNew }: Quest) { return (
- {isNew && !isQuestDisplayed(id) && ( + {isNew && !isAttack && !isQuestDisplayed(id) && ( )} + {isAttack && ( + + )}
- {quests.map(({ id, name, description, isNew }) => ( + {quests.map(({ id, name, description, isNew, isAttack }) => ( ))}
diff --git a/src/ui/Nav/AccountInfo.tsx b/src/ui/Nav/AccountInfo.tsx index 2d64278a6..140c0eb67 100644 --- a/src/ui/Nav/AccountInfo.tsx +++ b/src/ui/Nav/AccountInfo.tsx @@ -1,13 +1,13 @@ import React, { useRef, useState } from "react" import { useDappSelector, - selectWalletAvatar, selectWalletName, selectStakingRealmId, selectRealmById, } from "redux-state" import RealmIcon from "shared/components/RealmIcon" import { getRealmColor } from "shared/constants" +import Avatar from "shared/components/Avatar" import AccountDropdown from "./AccountDropdown" export default function AccountInfo() { @@ -15,7 +15,6 @@ export default function AccountInfo() { const dropdownTriggerRef = useRef(null) const name = useDappSelector(selectWalletName) - const avatar = useDappSelector(selectWalletAvatar) const realmId = useDappSelector(selectStakingRealmId) const realm = useDappSelector((state) => selectRealmById(state, realmId)) const color = realmId && getRealmColor(realmId) @@ -43,7 +42,15 @@ export default function AccountInfo() { ref={dropdownTriggerRef} > {name} -
+