Compare commits

..
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
44c9ccd65d Version Packages (#1963)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-16 10:21:09 -08:00
Manan TankandGitHub 7912c26323 [react] use custom theme context in styled components (#1954) 2023-11-16 17:53:44 +00:00
69d68f5a25 [RN/React] Enables FB and Apple sign in for embedded wallet (#1945)
Co-authored-by: Manan Tank <[email protected]>
2023-11-16 13:31:22 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
836be52724 Version Packages (#1959)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-15 20:26:55 -08:00
Joaquim VergesandGitHub 9f993dcc1e [EmbeddedWallet] fix switch chains logic (#1958) 2023-11-15 16:56:19 -08:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
61760a180f Version Packages (#1946)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-15 15:20:43 -08:00
Joaquim VergesandGitHub a3f3e160e0 [SDK] Performance improvements for getContract (#1936) 2023-11-15 23:01:25 +00:00
Joaquim VergesandGitHub b1190914a9 [SmartWallet, React] Smart wallet performance and auto connect fixes (#1950) 2023-11-15 18:37:30 +00:00
Kien NgoandGitHub e79a952061 [React-core] Fix caching issue with useTotalCirculatingSupply (#1951) 2023-11-15 14:34:57 +00:00
iketwandGitHub 480ed721d1 Enforces tsdoc/syntax code comments (#1949) 2023-11-15 04:30:13 +00:00
42c7436827 [React/Wallets] Deprecates PaperWallet in favor of EmbeddedWallet (#1948)
Co-authored-by: ElasticBottle <[email protected]>
2023-11-15 03:33:10 +00:00
Adam MajmudarandGitHub 16e9e8b64d [Wallets] Add EngineWallet & EngineSigner (#1939) 2023-11-15 00:59:58 +00:00
Adam MajmudarandGitHub 23ab897e32 Add support for EIP-2612 to engine relayer (#1947) 2023-11-14 23:20:44 +00:00
iketwandGitHub 6fcdec7c12 [SDK] Fixes all tsdoc warnings (#1944) 2023-11-14 20:04:17 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
b734f05525 Version Packages (#1919)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-14 11:30:08 -08:00
iketwandGitHub 30ea9c6bf7 [All] Warns tsdoc formatting and fixes React, RN and ReactCore (#1943) 2023-11-14 19:19:46 +00:00
iketwandGitHub 6de3a19b86 [RN] Updated safe-area-context package to 4.7.4 (#1942) 2023-11-14 18:09:58 +00:00
iketwandGitHub 06e59cf916 [React/RN] Move shortenAdress util to react-core (#1929) 2023-11-14 15:48:52 +00:00
Joaquim VergesandGitHub 3f3a484a3a [SmartWallet] remove deprecated fallback (#1937) 2023-11-14 01:39:23 +00:00
05e52114bf [auth] update fastify login route response to match express/next (#1927)
Signed-off-by: Alec Ananian <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2023-11-13 16:42:41 -08:00
iketwandGitHub 1a421d3b5d [RN] Adds Apple sign in option to embedded wallet (#1912) 2023-11-10 21:59:10 +00:00
iketwandGitHub 97fdbc8f64 [RN] Refactor WC receiver for SW and EW (#1925) 2023-11-10 21:26:17 +00:00
ebf4025718 [wallets]: refactor for ease of adding oauth provider (#1923)
Co-authored-by: ikethirdweb <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
2023-11-10 19:13:27 +00:00
a81a5285b7 [wallets, React SDK] Add FB and Apple social login (#1920)
Co-authored-by: ikethirdweb <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
2023-11-10 18:07:25 +00:00
95c9d56ea4 [CLI] fixed url redirect for pre-built deploy command (#1918)
Co-authored-by: ikethirdweb <[email protected]>
2023-11-09 22:20:25 +00:00
18552f3a22 [React Native] Option to hide [Switch to Personal Wallet] button (#1908)
Co-authored-by: ikethirdweb <[email protected]>
2023-11-09 22:09:47 +00:00
275 changed files with 3990 additions and 2649 deletions
+30
View File
@@ -1,5 +1,35 @@
# @thirdweb-dev/auth
## 4.1.8
### Patch Changes
- Updated dependencies [[`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091)]:
- @thirdweb-dev/wallets@2.1.5
## 4.1.7
### Patch Changes
- Updated dependencies [[`9f993dcc`](https://github.com/thirdweb-dev/js/commit/9f993dcc1e60cc087850b8fb9ce09524073ce0b5)]:
- @thirdweb-dev/wallets@2.1.4
## 4.1.6
### Patch Changes
- Updated dependencies [[`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6), [`480ed721`](https://github.com/thirdweb-dev/js/commit/480ed721d1c736049fa8b6ee4cbdbdf3cce18abd), [`16e9e8b6`](https://github.com/thirdweb-dev/js/commit/16e9e8b64d7d1a97a5b32fc855479e992b7ed3a8), [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c)]:
- @thirdweb-dev/wallets@2.1.3
## 4.1.5
### Patch Changes
- [#1927](https://github.com/thirdweb-dev/js/pull/1927) [`05e52114`](https://github.com/thirdweb-dev/js/commit/05e52114bf4f0ee352f393ed20115a904a18c310) Thanks [@alecananian](https://github.com/alecananian)! - Update Fastify login route response to match Express/Next
- Updated dependencies [[`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b), [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e), [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3), [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8)]:
- @thirdweb-dev/wallets@2.1.2
## 4.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "4.1.4",
"version": "4.1.8",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+5 -2
View File
@@ -22,7 +22,9 @@ export const loginHandler = (
schema: {
body: LoginPayloadBodySchema,
response: {
200: z.any(),
200: z.object({
token: z.string(),
}),
},
},
handler: async (req, res) => {
@@ -104,7 +106,8 @@ export const loginHandler = (
},
);
return res.status(200).send({ success: true });
// Send token in body and as cookie for frontend and backend use cases
return res.status(200).send({ token });
},
});
};
+38
View File
@@ -1,5 +1,43 @@
# thirdweb
## 0.13.16
### Patch Changes
- Updated dependencies [[`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091)]:
- @thirdweb-dev/wallets@2.1.5
- @thirdweb-dev/auth@4.1.8
## 0.13.15
### Patch Changes
- Updated dependencies [[`9f993dcc`](https://github.com/thirdweb-dev/js/commit/9f993dcc1e60cc087850b8fb9ce09524073ce0b5)]:
- @thirdweb-dev/wallets@2.1.4
- @thirdweb-dev/auth@4.1.7
## 0.13.14
### Patch Changes
- Updated dependencies [[`23ab897e`](https://github.com/thirdweb-dev/js/commit/23ab897e329cdd5b316fbe554ea0f64add91f2b4), [`a3f3e160`](https://github.com/thirdweb-dev/js/commit/a3f3e160e07328655a4f0332f41f4edd210a1a8a), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`480ed721`](https://github.com/thirdweb-dev/js/commit/480ed721d1c736049fa8b6ee4cbdbdf3cce18abd), [`16e9e8b6`](https://github.com/thirdweb-dev/js/commit/16e9e8b64d7d1a97a5b32fc855479e992b7ed3a8), [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c)]:
- @thirdweb-dev/sdk@4.0.13
- @thirdweb-dev/storage@2.0.4
- @thirdweb-dev/wallets@2.1.3
- @thirdweb-dev/auth@4.1.6
## 0.13.13
### Patch Changes
- [#1918](https://github.com/thirdweb-dev/js/pull/1918) [`95c9d56e`](https://github.com/thirdweb-dev/js/commit/95c9d56ea47ef57113679196efe8f9114c6fbb21) Thanks [@saminacodes](https://github.com/saminacodes)! - Fix url redirect for pre-built deploy command
- Updated dependencies [[`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b), [`05e52114`](https://github.com/thirdweb-dev/js/commit/05e52114bf4f0ee352f393ed20115a904a18c310), [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e), [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3), [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8), [`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b)]:
- @thirdweb-dev/wallets@2.1.2
- @thirdweb-dev/auth@4.1.5
- @thirdweb-dev/storage@2.0.3
- @thirdweb-dev/sdk@4.0.12
## 0.13.12
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.13.12",
"version": "0.13.16",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -30,7 +30,7 @@ export default function generateDashboardUrl(
const foundMapping = mapping[name as keyof typeof mapping];
return foundMapping
? `https://thirdweb.com/dashboard/${foundMapping}`
? `https://thirdweb.com/thirdweb.eth/${foundMapping}`
: undefined;
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
module.exports = {
root: true,
extends: ["thirdweb"],
plugins: ["better-tree-shaking"],
plugins: ["better-tree-shaking", "eslint-plugin-tsdoc"],
rules: {
"tsdoc/syntax": "error",
"better-tree-shaking/no-top-level-side-effects": "error",
},
};
+46
View File
@@ -1,5 +1,51 @@
# @thirdweb-dev/react-core
## 4.1.6
### Patch Changes
- Updated dependencies [[`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091)]:
- @thirdweb-dev/wallets@2.1.5
- @thirdweb-dev/auth@4.1.8
## 4.1.5
### Patch Changes
- Updated dependencies [[`9f993dcc`](https://github.com/thirdweb-dev/js/commit/9f993dcc1e60cc087850b8fb9ce09524073ce0b5)]:
- @thirdweb-dev/wallets@2.1.4
- @thirdweb-dev/auth@4.1.7
## 4.1.4
### Patch Changes
- [#1951](https://github.com/thirdweb-dev/js/pull/1951) [`e79a9520`](https://github.com/thirdweb-dev/js/commit/e79a95206116ea09de0126fdbe30fb4413fdff28) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Fix caching issue with useTotalCirculatingSupply
- [#1944](https://github.com/thirdweb-dev/js/pull/1944) [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- [#1950](https://github.com/thirdweb-dev/js/pull/1950) [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Smart Wallet autoconnect fixes and performance improvements
- Updated dependencies [[`23ab897e`](https://github.com/thirdweb-dev/js/commit/23ab897e329cdd5b316fbe554ea0f64add91f2b4), [`a3f3e160`](https://github.com/thirdweb-dev/js/commit/a3f3e160e07328655a4f0332f41f4edd210a1a8a), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`480ed721`](https://github.com/thirdweb-dev/js/commit/480ed721d1c736049fa8b6ee4cbdbdf3cce18abd), [`16e9e8b6`](https://github.com/thirdweb-dev/js/commit/16e9e8b64d7d1a97a5b32fc855479e992b7ed3a8), [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c)]:
- @thirdweb-dev/sdk@4.0.13
- @thirdweb-dev/storage@2.0.4
- @thirdweb-dev/wallets@2.1.3
- @thirdweb-dev/auth@4.1.6
## 4.1.3
### Patch Changes
- [#1943](https://github.com/thirdweb-dev/js/pull/1943) [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- [#1929](https://github.com/thirdweb-dev/js/pull/1929) [`06e59cf9`](https://github.com/thirdweb-dev/js/commit/06e59cf91647e4080829d49f8cb91a4d8fdc87f7) Thanks [@iketw](https://github.com/iketw)! - Move addresses' utils to the core package
- Updated dependencies [[`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b), [`05e52114`](https://github.com/thirdweb-dev/js/commit/05e52114bf4f0ee352f393ed20115a904a18c310), [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e), [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3), [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8), [`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b)]:
- @thirdweb-dev/wallets@2.1.2
- @thirdweb-dev/auth@4.1.5
- @thirdweb-dev/storage@2.0.3
- @thirdweb-dev/sdk@4.0.12
## 4.1.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "4.1.2",
"version": "4.1.6",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -335,7 +335,7 @@ export function ThirdwebWalletProvider(
},
);
} catch (e) {
console.error("Failed to auto connect wallet");
console.error("Failed to auto connect personal wallet");
console.error(e);
setConnectionStatus("disconnected");
return;
@@ -366,9 +366,14 @@ export function ThirdwebWalletProvider(
} catch (e) {
console.error("Failed to auto connect wallet");
console.error(e);
lastConnectedWalletStorage.removeItem(
LAST_CONNECTED_WALLET_STORAGE_KEY,
);
if (
e instanceof Error &&
e.message === autoConnectTimeoutErrorMessage
) {
lastConnectedWalletStorage.removeItem(
LAST_CONNECTED_WALLET_STORAGE_KEY,
);
}
setConnectionStatus("disconnected");
}
}
+3 -3
View File
@@ -43,13 +43,13 @@ export type WalletConfig<I extends WalletInstance = WalletInstance> = {
/**
* If true, this wallet will be tagged as "recommended" in ConnectWallet Modal and will be shown at the top of the list
* @default false
* @defaultValue false
*/
recommended?: boolean;
/**
* If the wallet can sign transactions without user interaction, set this to true.
* @default false
* @defaultValue false
*/
isHeadless?: boolean;
};
@@ -154,7 +154,7 @@ export type ConnectUIProps<I extends WalletInstance = WalletInstance> = {
/**
* Called when the wallet is connected but it's
* part of another wallet's connection flow.
* @param walleInstance the instance of the connected wallet
* @param walleInstance - the instance of the connected wallet
*/
onLocallyConnected?: (walleInstance: WalletInstance) => void;
};
@@ -28,7 +28,7 @@ import { constants, utils } from "ethers";
import invariant from "tiny-invariant";
/**
* The options to be passed as the second parameter to the {@link useClaimIneligibilityReasons}` hook.
* The options to be passed as the second parameter to the {@link useClaimIneligibilityReasons} hook.
*
* @beta
*/
@@ -193,7 +193,10 @@ export function useTotalCirculatingSupply(
const { erc721, erc1155 } = getErcs(contract);
return useQueryWithNetwork<BigNumber>(
cacheKeys.contract.nft.query.totalCirculatingSupply(contractAddress),
cacheKeys.contract.nft.query.totalCirculatingSupply(
contractAddress,
tokenId ?? undefined,
),
async () => {
requiredParamInvariant(contract, "No Contract instance provided");
@@ -718,7 +721,7 @@ export function useBurnNFT<TContract extends NFTContract>(
/**
* Set shared metadata
* TODO add docs
* @private
* @internal
*/
export function useSetSharedMetadata<TContract extends NFTContract>(
contract: RequiredParam<TContract>,
+7
View File
@@ -257,6 +257,13 @@ export type { ThirdwebSDKProviderProps } from "./providers/types";
// utils
export { invalidateContractAndBalances } from "./utils/cache-keys";
export {
isEnsName,
isPossibleEVMAddress,
shortenAddress,
shortenIfAddress,
shortenString,
} from "./utils/addresses";
// types
export type {
+39 -4
View File
@@ -1,12 +1,33 @@
import { utils } from "ethers";
export function shortenString(str: string, extraShort?: true) {
export function shortenString(str: string, extraShort: boolean = true) {
return `${str.substring(0, extraShort ? 4 : 6)}...${str.substring(
str.length - (extraShort ? 3 : 4),
)}`;
}
export function shortenAddress(address: string, extraShort?: true): string {
/**
* This function shortens an address if it is a valid EVM address.
*
* @param address - The address to shorten
* @param extraShort - If true, show the first 4 and last 3 characters
* @returns The shortened address
*
* @remarks
* Note that this function will not check if the address is an ENS.
*
* @example Calling shortenAddress with extraShort set to true
* ```ts
* const address = shortenIfAddress("0x1234567890123456789012345678901234567890", true); // result will be "0x1234...890"
* ```
*/
export function shortenAddress(
address?: string,
extraShort: boolean = true,
): string {
if (!address) {
return "";
}
try {
const formattedAddress = utils.getAddress(address);
return shortenString(formattedAddress, extraShort);
@@ -17,10 +38,24 @@ export function shortenAddress(address: string, extraShort?: true): string {
export function shortenIfAddress(
address?: string | null | false,
extraShort?: true,
extraShort: boolean = true,
): string {
if (typeof address === "string" && address.length > 0) {
if (address && utils.isAddress(address)) {
return shortenAddress(address, extraShort);
}
return address || "";
}
export function isEnsName(name: string): boolean {
return name?.endsWith(".eth");
}
export function isPossibleEVMAddress(address?: string, ignoreEns?: boolean) {
if (!address) {
return false;
}
if (isEnsName(address) && !ignoreEns) {
return true;
}
return utils.isAddress(address);
}
@@ -182,10 +182,12 @@ export const cacheKeys = {
),
totalCirculatingSupply: (
contractAddress: RequiredParam<ContractAddress>,
tokenId?: BigNumberish,
) =>
createContractCacheKey(contractAddress, [
"query",
"totalCirculatingSupply",
tokenId ?? "0",
]),
totalCount: (contractAddress: RequiredParam<ContractAddress>) =>
createContractCacheKey(contractAddress, ["query", "totalCount"]),
@@ -1,5 +1,13 @@
# @thirdweb-dev/react-native-compat
## 0.4.6
## 0.4.5
## 0.4.4
## 0.4.3
## 0.4.2
## 0.4.1
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/react-native-compat",
"description": "Shims for Thirdweb in React Native Projects",
"version": "0.4.2",
"version": "0.4.6",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+2 -1
View File
@@ -1,8 +1,9 @@
module.exports = {
root: true,
extends: ["thirdweb"],
plugins: ["i18next"],
plugins: ["i18next", "eslint-plugin-tsdoc"],
rules: {
"tsdoc/syntax": "error",
"i18next/no-literal-string": 2,
},
settings: {
+107
View File
@@ -1,5 +1,112 @@
# @thirdweb-dev/react-native
## 0.4.6
### Patch Changes
- [#1945](https://github.com/thirdweb-dev/js/pull/1945) [`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091) Thanks [@iketw](https://github.com/iketw)! - Enable Facebook Sign In for embedded wallet
- Updated dependencies [[`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091)]:
- @thirdweb-dev/wallets@2.1.5
- @thirdweb-dev/react-core@4.1.6
## 0.4.5
### Patch Changes
- Updated dependencies [[`9f993dcc`](https://github.com/thirdweb-dev/js/commit/9f993dcc1e60cc087850b8fb9ce09524073ce0b5)]:
- @thirdweb-dev/wallets@2.1.4
- @thirdweb-dev/react-core@4.1.5
## 0.4.4
### Patch Changes
- [#1944](https://github.com/thirdweb-dev/js/pull/1944) [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- Updated dependencies [[`23ab897e`](https://github.com/thirdweb-dev/js/commit/23ab897e329cdd5b316fbe554ea0f64add91f2b4), [`a3f3e160`](https://github.com/thirdweb-dev/js/commit/a3f3e160e07328655a4f0332f41f4edd210a1a8a), [`e79a9520`](https://github.com/thirdweb-dev/js/commit/e79a95206116ea09de0126fdbe30fb4413fdff28), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`480ed721`](https://github.com/thirdweb-dev/js/commit/480ed721d1c736049fa8b6ee4cbdbdf3cce18abd), [`16e9e8b6`](https://github.com/thirdweb-dev/js/commit/16e9e8b64d7d1a97a5b32fc855479e992b7ed3a8), [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c)]:
- @thirdweb-dev/sdk@4.0.13
- @thirdweb-dev/react-core@4.1.4
- @thirdweb-dev/storage@2.0.4
- @thirdweb-dev/wallets@2.1.3
## 0.4.3
### Patch Changes
- [#1908](https://github.com/thirdweb-dev/js/pull/1908) [`18552f3a`](https://github.com/thirdweb-dev/js/commit/18552f3a225d1c46f0ff19c2b1c9cc44f84e33ac) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add `hideSwitchToPersonalWallet` prop on `ConnectWallet` component to hide the "Switch to Personal wallet" option in the ConnectWallet modal which is shown when wallet is connected as a Smart Wallet
- [#1912](https://github.com/thirdweb-dev/js/pull/1912) [`1a421d3b`](https://github.com/thirdweb-dev/js/commit/1a421d3b5d837eade5b7344118a7148ddea35517) Thanks [@iketw](https://github.com/iketw)! - Adds Apple as a social sign in option for EmbeddedWallet
You can now do:
```typescript
embeddedWallet({
auth: {
options: ['apple'],
redirectUrl: 'your-apps-redirect-url://',
},
}),
```
- [#1943](https://github.com/thirdweb-dev/js/pull/1943) [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- [#1929](https://github.com/thirdweb-dev/js/pull/1929) [`06e59cf9`](https://github.com/thirdweb-dev/js/commit/06e59cf91647e4080829d49f8cb91a4d8fdc87f7) Thanks [@iketw](https://github.com/iketw)! - Move addresses' utils to the core package
- [#1925](https://github.com/thirdweb-dev/js/pull/1925) [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8) Thanks [@iketw](https://github.com/iketw)! - [RN] Adds walletConnectReceiver prop to EmbeddedWallet and SmartWallet
You can pass your own WalletConnect projectId and metadata (highly recommended):
```typescript
embeddedWallet({
auth: {
options: ['email', 'google'],
redirectUrl: 'redirect-url',
},
walletConnectReceiver: {
walletConnectWalletMetadata?: WCMetadata;
walletConnectV2ProjectId?: string;
walletConnectV2RelayUrl?: string;
},
}),
smartWallet(localWallet(), {
factoryAddress: 'factory-address',
gasless: true,
walletConnectReceiver: {
walletConnectWalletMetadata?: WCMetadata;
walletConnectV2ProjectId?: string;
walletConnectV2RelayUrl?: string;
},
}),
```
You can just pass `true` to the prop and these props will default to our internal WalletConnect projectId and metadata.
```typescript
embeddedWallet({
auth: {
options: ['email', 'google'],
redirectUrl: 'redirect-url',
},
walletConnectReceiver: true
}),
smartWallet(localWallet(), {
factoryAddress: 'factory-address',
gasless: true,
walletConnectReceiver: true
}),
```
- [#1942](https://github.com/thirdweb-dev/js/pull/1942) [`6de3a19b`](https://github.com/thirdweb-dev/js/commit/6de3a19b86476cf5f7d67223a366379c4fdb9b74) Thanks [@iketw](https://github.com/iketw)! - Updated react-native-safe-area-context to 4.74
- Updated dependencies [[`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b), [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e), [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3), [`06e59cf9`](https://github.com/thirdweb-dev/js/commit/06e59cf91647e4080829d49f8cb91a4d8fdc87f7), [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8), [`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 0.4.2
### Patch Changes
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.4.2",
"version": "0.4.6",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -70,6 +70,7 @@
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-inclusive-language": "^2.2.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-tsdoc": "^0.2.16",
"ethers": "^5.7.2",
"expo": "^47.0.14",
"jest": "^29.6.2",
@@ -79,7 +80,7 @@
"react-native-camera": "^4.2.1",
"react-native-device-info": "10.6.0",
"react-native-mmkv": "2.5.1",
"react-native-safe-area-context": "4.5.3",
"react-native-safe-area-context": "4.7.4",
"react-native-webview": "12.1.0",
"rimraf": "^3.0.2",
"typedoc": "^0.25.2",
@@ -93,7 +94,7 @@
"react-native-inappbrowser-reborn": "^3.7.0",
"react-native-quick-base64": "^2.0.7",
"react-native-quick-crypto": "^0.6.1",
"react-native-safe-area-context": "4.5.3",
"react-native-safe-area-context": "4.7.4",
"react-native-webview": "12.1.0"
},
"peerDependencies": {
@@ -73,7 +73,7 @@ export class noopStorage implements IAsyncStorage {
/**
* Returns a new instance of AsyncLocalStorage
*
* @param name Name to namespace the storage with
* @param name - Name to namespace the storage with
* @returns A new instance of AsyncLocalStorage
*/
export function createAsyncLocalStorage(name: string) {
@@ -83,7 +83,7 @@ export function createAsyncLocalStorage(name: string) {
/**
* Returns a new instance of LocalStorage
*
* @param name Name to namespace the storage with
* @param name - Name to namespace the storage with
* @returns A new instance of LocalStorage
*/
export function createSyncStorage(name: string) {
@@ -31,7 +31,7 @@ export class SecureStorage implements ISecureStorage {
/**
* Returns a new instance of SecureStorage implemented by Expo SecureStore
*
* @param name Name to namespace the storage with
* @param name - Name to namespace the storage with
* @returns A new instance of SecureStorage
*/
export function createSecureStorage(name: string) {
@@ -1,3 +1,14 @@
export const APPLE_ICON = `<svg fill="#959CA0" height="40px" width="40px" version="1.1" id="Capa_1" viewBox="0 0 24 24" xml:space="preserve" stroke="#959CA0"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path d="M15.769,0c0.053,0,0.106,0,0.162,0c0.13,1.606-0.483,2.806-1.228,3.675c-0.731,0.863-1.732,1.7-3.351,1.573 c-0.108-1.583,0.506-2.694,1.25-3.561C13.292,0.879,14.557,0.16,15.769,0z"></path> <path d="M20.67,16.716c0,0.016,0,0.03,0,0.045c-0.455,1.378-1.104,2.559-1.896,3.655c-0.723,0.995-1.609,2.334-3.191,2.334 c-1.367,0-2.275-0.879-3.676-0.903c-1.482-0.024-2.297,0.735-3.652,0.926c-0.155,0-0.31,0-0.462,0 c-0.995-0.144-1.798-0.932-2.383-1.642c-1.725-2.098-3.058-4.808-3.306-8.276c0-0.34,0-0.679,0-1.019 c0.105-2.482,1.311-4.5,2.914-5.478c0.846-0.52,2.009-0.963,3.304-0.765c0.555,0.086,1.122,0.276,1.619,0.464 c0.471,0.181,1.06,0.502,1.618,0.485c0.378-0.011,0.754-0.208,1.135-0.347c1.116-0.403,2.21-0.865,3.652-0.648 c1.733,0.262,2.963,1.032,3.723,2.22c-1.466,0.933-2.625,2.339-2.427,4.74C17.818,14.688,19.086,15.964,20.67,16.716z"></path> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> </g> </g></svg>`;
export const FACEBOOK_ICON = `<svg xml:space="preserve" viewBox="0 0 40 40">
<linearGradient id="a" x1="-277.375" x2="-277.375" y1="406.6018" y2="407.5726" gradientTransform="matrix(40 0 0 -39.7778 11115.001 16212.334)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0062e0"/>
<stop offset="1" stop-color="#19afff"/>
</linearGradient>
<path fill="url(#a)" d="M16.7 39.8C7.2 38.1 0 29.9 0 20 0 9 9 0 20 0s20 9 20 20c0 9.9-7.2 18.1-16.7 19.8l-1.1-.9h-4.4l-1.1.9z"/>
<path fill="#fff" d="m27.8 25.6.9-5.6h-5.3v-3.9c0-1.6.6-2.8 3-2.8H29V8.2c-1.4-.2-3-.4-4.4-.4-4.6 0-7.8 2.8-7.8 7.8V20h-5v5.6h5v14.1c1.1.2 2.2.3 3.3.3 1.1 0 2.2-.1 3.3-.3V25.6h4.4z"/>
</svg>`;
export const GOOGLE_ICON = `<svg height={height} viewBox="0 0 24 24" width={width}>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
@@ -36,7 +36,7 @@ export type ConnectWalletProps = {
/**
* Hide option to request testnet funds for testnets in dropdown
*
* @default false
* @defaultValue false
*/
hideTestnetFaucet?: boolean;
@@ -47,7 +47,7 @@ export type ConnectWalletProps = {
* Please, note that if you support multiple networks in your app this prop should
* be set to `false` to allow users to switch between networks.
*
* @default false
* @defaultValue false
*/
switchToActiveChain?: boolean;
@@ -69,6 +69,13 @@ export type ConnectWalletProps = {
* ```
*/
displayBalanceToken?: Record<number, string>;
/**
* Hide the "switch to Personal wallet" option in the wallet modal which is shown when wallet is connected to a Smart Wallet
*
* @defaultValue false
*/
hideSwitchToPersonalWallet?: boolean;
} & ConnectWalletButtonProps;
export const ConnectWallet = ({
@@ -84,6 +91,7 @@ export const ConnectWallet = ({
termsOfServiceUrl,
privacyPolicyUrl,
supportedTokens,
hideSwitchToPersonalWallet,
}: ConnectWalletProps) => {
const globalTheme = useGlobalTheme();
const l = useLocale();
@@ -163,6 +171,7 @@ export const ConnectWallet = ({
hideTestnetFaucet={hideTestnetFaucet}
supportedTokens={supportedTokensMemo}
displayBalanceToken={displayBalanceToken}
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
/>
)
) : (
@@ -1,5 +1,4 @@
import { useWallet } from "@thirdweb-dev/react-core";
import { IWalletConnectReceiver } from "@thirdweb-dev/wallets";
import { useCallback, useEffect, useRef, useState } from "react";
import Box from "../base/Box";
import BaseButton from "../base/BaseButton";
@@ -14,8 +13,13 @@ import WalletConnectIcon from "../../assets/wallet-connect";
import QrCodeIcon from "../../assets/qr-code";
import { QRCodeScan } from "./QRCodeScan";
import { useLocale } from "../../providers/ui-context-provider";
import { useWalletConnectHandler } from "../../providers/context-provider";
const ConnectAppField = () => {
const ConnectAppField = ({
onConnectAppTriggered,
}: {
onConnectAppTriggered?: () => void;
}) => {
const theme = useTheme();
const l = useLocale();
const [showWCInput, setShowWCInput] = useState(false);
@@ -24,22 +28,19 @@ const ConnectAppField = () => {
const wallet = useWallet();
const [showQRCodeScan, setShowQRCodeScan] = useState(false);
const wcUriRef = useRef<string | undefined>();
const walletConnectHandler = useWalletConnectHandler();
const getAppMeta = useCallback(() => {
if (wallet && "isWCReceiverEnabled" in wallet) {
const sessions = (
wallet as unknown as IWalletConnectReceiver
).getActiveSessions();
if (Object.keys(sessions).length > 0) {
setAppMeta({
name: sessions[0].peer.metadata.name,
iconUrl: sessions[0].peer.metadata.icons[0],
});
}
const sessions = walletConnectHandler?.getActiveSessions();
if (sessions && Object.keys(sessions).length > 0) {
setAppMeta({
name: sessions[0].peer.metadata.name,
iconUrl: sessions[0].peer.metadata.icons[0],
});
}
}, [wallet]);
}, [walletConnectHandler]);
const onSmartWalletWCMessage = useCallback(
const onWalletConnectMessage = useCallback(
({ type }: { type: string }) => {
if (type === "session_approved") {
getAppMeta();
@@ -52,22 +53,22 @@ const ConnectAppField = () => {
useEffect(() => {
if (wallet) {
wallet.addListener("message", onSmartWalletWCMessage);
wallet.addListener("message", onWalletConnectMessage);
getAppMeta();
}
return () => {
if (wallet) {
wallet.removeListener("message", onSmartWalletWCMessage);
wallet.removeListener("message", onWalletConnectMessage);
}
};
}, [getAppMeta, onSmartWalletWCMessage, wallet]);
}, [getAppMeta, onWalletConnectMessage, wallet]);
const onConnectDappPress = () => {
if (appMeta) {
reset();
(wallet as unknown as IWalletConnectReceiver).disconnectSession();
walletConnectHandler?.disconnectSession();
} else {
setShowWCInput(true);
}
@@ -103,10 +104,14 @@ const ConnectAppField = () => {
const uriToUse = uri || wcUri;
if (uriToUse?.startsWith("wc:")) {
(wallet as unknown as IWalletConnectReceiver).connectApp(uriToUse);
if (!uriToUse?.startsWith("wc:")) {
return;
}
onConnectAppTriggered?.();
walletConnectHandler?.connectApp(uriToUse);
setWCUri(undefined);
wcUriRef.current = undefined;
};
@@ -135,6 +140,7 @@ const ConnectAppField = () => {
style: {
color: theme.colors.textPrimary,
fontFamily: theme.textVariants.defaults.fontFamily,
flex: 1,
},
}}
containerProps={{
@@ -30,6 +30,8 @@ import { SupportedTokens } from "../SendFunds/defaultTokens";
import { ActiveDot } from "../base";
import { EmbeddedWallet } from "../../wallets/wallets/embedded/EmbeddedWallet";
import { useLocale } from "../../providers/ui-context-provider";
import { isWalletConnectReceiverEnabled } from "../../wallets/utils";
import ConnectAppField from "./ConnectAppField";
const MODAL_HEIGHT = Dimensions.get("window").height * 0.7;
const DEVICE_WIDTH = Dimensions.get("window").width;
@@ -42,6 +44,7 @@ export const ConnectWalletDetailsModal = ({
hideTestnetFaucet,
supportedTokens,
displayBalanceToken,
hideSwitchToPersonalWallet,
}: {
isVisible: boolean;
onClosePress: () => void;
@@ -50,6 +53,7 @@ export const ConnectWalletDetailsModal = ({
hideTestnetFaucet?: boolean;
supportedTokens: SupportedTokens;
displayBalanceToken?: Record<number, string>;
hideSwitchToPersonalWallet?: boolean;
}) => {
const l = useLocale();
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
@@ -131,6 +135,7 @@ export const ConnectWalletDetailsModal = ({
return (
<SmartWalletAdditionalActions
onExportPress={onExportLocalWalletPress}
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
/>
);
}
@@ -203,11 +208,15 @@ export const ConnectWalletDetailsModal = ({
return null;
}, [
activeWallet?.walletId,
isImportModalVisible,
onExportLocalWalletPress,
onWalletImported,
smartWallet,
l,
onExportLocalWalletPress,
l.connect_wallet_details.additional_actions,
l.connect_wallet_details.backup_wallet,
l.connect_wallet_details.import_wallet,
l.local_wallet.this_is_a_temporary_wallet,
isImportModalVisible,
onWalletImported,
hideSwitchToPersonalWallet,
]);
return (
@@ -296,6 +305,9 @@ export const ConnectWalletDetailsModal = ({
}}
/>
)}
{isWalletConnectReceiverEnabled(activeWallet) ? (
<ConnectAppField onConnectAppTriggered={onClosePress} />
) : null}
{getAdditionalActions()}
{extraRows ? extraRows({}) : null}
{addressCopied === true ? (
@@ -10,12 +10,15 @@ import { useState } from "react";
import BaseButton from "../base/BaseButton";
import Box from "../base/Box";
import { ModalHeaderTextClose } from "../base/modal/ModalHeaderTextClose";
import { useAddress, useWallet } from "@thirdweb-dev/react-core";
import {
shortenAddress,
useAddress,
useWallet,
} from "@thirdweb-dev/react-core";
import { PasswordInput } from "../PasswordInput";
import * as FileSystem from "expo-file-system";
import { SmartWallet } from "@thirdweb-dev/wallets";
import { usePersonalWalletAddress } from "../../wallets/hooks/usePersonalWalletAddress";
import { shortenWalletAddress } from "../../utils/addresses";
import { LocalWallet } from "../../wallets/wallets/LocalWallet";
import { TWModal } from "../base/modal/TWModal";
import { useLocale } from "../../providers/ui-context-provider";
@@ -162,7 +165,7 @@ export const ExportLocalWalletModal = ({
{l.local_wallet.wallet_address}
</Text>
<Text variant="bodySmallSecondary">
{shortenWalletAddress(
{shortenAddress(
personalWalletAddress ? personalWalletAddress : address,
)}
</Text>
@@ -173,16 +176,17 @@ export const ExportLocalWalletModal = ({
<Text variant="bodySmall" color="red" mt="xs" textAlign="left">
{error}
</Text>
<Box flexDirection="row" justifyContent="flex-end" mt="lg">
<Box flexDirection="row" justifyContent="flex-end" mt="xs">
<BaseButton
backgroundColor="white"
backgroundColor="accentButtonColor"
borderColor="accentButtonColor"
style={styles.modalButton}
onPress={onContinuePress}
>
{isExporting ? (
<ActivityIndicator size="small" color="black" />
) : (
<Text variant="bodySmall" color="black">
<Text variant="bodySmall" color="accentButtonTextColor">
{l.connect_wallet_details.backup}
</Text>
)}
@@ -2,18 +2,19 @@ import { useWallet } from "@thirdweb-dev/react-core";
import Box from "../base/Box";
import BaseButton from "../base/BaseButton";
import Text from "../base/Text";
import { IWalletConnectReceiver } from "@thirdweb-dev/wallets";
import {
CLOSE_MODAL_STATE,
WalletConnectSessionProposalModal,
} from "../../utils/modalTypes";
import { useLocale, useModalState } from "../../providers/ui-context-provider";
import { useWalletConnectHandler } from "../../providers/context-provider";
export const SessionProposalModal = () => {
const l = useLocale();
const { modalState, setModalState } = useModalState();
const { data: proposalData } =
modalState as WalletConnectSessionProposalModal;
const walletConnectHandler = useWalletConnectHandler();
const wallet = useWallet();
@@ -41,7 +42,7 @@ export const SessionProposalModal = () => {
minWidth={100}
borderColor="border"
onPress={async () => {
(wallet as unknown as IWalletConnectReceiver).rejectSession();
walletConnectHandler?.rejectSession();
onClose();
}}
>
@@ -61,9 +62,7 @@ export const SessionProposalModal = () => {
if (!wallet) {
throw new Error("Wallet not connected.");
}
await (
wallet as unknown as IWalletConnectReceiver
).approveSession();
await walletConnectHandler?.approveSession();
onClose();
}}
>
@@ -1,19 +1,16 @@
import { useChain, useWallet } from "@thirdweb-dev/react-core";
import { shortenAddress, useChain, useWallet } from "@thirdweb-dev/react-core";
import Box from "../base/Box";
import BaseButton from "../base/BaseButton";
import Text from "../base/Text";
import {
EIP155_SIGNING_METHODS,
IWalletConnectReceiver,
} from "@thirdweb-dev/wallets";
import { EIP155_SIGNING_METHODS } from "@thirdweb-dev/wallets";
import { useLocale, useModalState } from "../../providers/ui-context-provider";
import {
CLOSE_MODAL_STATE,
WalletConnectSessionRequestModal,
} from "../../utils/modalTypes";
import { ActivityIndicator, Dimensions } from "react-native";
import { shortenWalletAddress } from "../../utils/addresses";
import { useCallback, useState } from "react";
import { useWalletConnectHandler } from "../../providers/context-provider";
const getTitle = (method: string) => {
switch (method) {
@@ -34,6 +31,7 @@ export const SessionRequestModal = () => {
const { modalState, setModalState } = useModalState();
const { data: requestData } = modalState as WalletConnectSessionRequestModal;
const [approvingRequest, setApprovingRequest] = useState(false);
const walletConnectHandler = useWalletConnectHandler();
const wallet = useWallet();
const chain = useChain();
@@ -48,12 +46,10 @@ export const SessionRequestModal = () => {
}
setApprovingRequest(true);
(wallet as unknown as IWalletConnectReceiver)
.approveRequest()
.finally(() => {
setApprovingRequest(false);
onClose();
});
walletConnectHandler?.approveEIP155Request().finally(() => {
setApprovingRequest(false);
onClose();
});
};
const getContent = useCallback(() => {
@@ -86,10 +82,10 @@ export const SessionRequestModal = () => {
return (
<Box>
<Text variant="bodySmall" textAlign="left">
{`${l.common.from}: ${shortenWalletAddress(from)}`}
{`${l.common.from}: ${shortenAddress(from)}`}
</Text>
<Text variant="bodySmall" textAlign="left" mt="sm">
{`${l.common.to}: ${shortenWalletAddress(to)}`}
{`${l.common.to}: ${shortenAddress(to)}`}
</Text>
</Box>
);
@@ -126,7 +122,7 @@ export const SessionRequestModal = () => {
minWidth={100}
borderColor="border"
onPress={async () => {
(wallet as unknown as IWalletConnectReceiver).rejectRequest();
walletConnectHandler?.rejectEIP155Request();
onClose();
}}
>
@@ -11,15 +11,16 @@ import { useWalletContext, useWallet } from "@thirdweb-dev/react-core";
import { useEffect, useState } from "react";
import { useSmartWallet } from "../../providers/context-provider";
import RightArrowIcon from "../../assets/right-arrow";
import ConnectAppField from "./ConnectAppField";
import DisconnectIcon from "../../assets/disconnect";
import { IconTextButton } from "../base/IconTextButton";
import { useGlobalTheme, useLocale } from "../../providers/ui-context-provider";
export const SmartWalletAdditionalActions = ({
onExportPress,
hideSwitchToPersonalWallet,
}: {
onExportPress: () => void;
hideSwitchToPersonalWallet?: boolean;
}) => {
const l = useLocale();
const { setConnectedWallet } = useWalletContext();
@@ -61,25 +62,24 @@ export const SmartWalletAdditionalActions = ({
return (
<>
<IconTextButton
mt="xs"
text={
showSmartWallet
? l.smart_wallet.switch_to_smart
: l.smart_wallet.switch_to_personal
}
icon={
<DisconnectIcon
width={14}
height={14}
color={theme.colors.iconPrimary}
/>
}
onPress={onWalletPress}
/>
{!showSmartWallet && smartWallet?.enableConnectApp ? (
<ConnectAppField />
) : null}
{(showSmartWallet || !hideSwitchToPersonalWallet) && (
<IconTextButton
mt="xs"
text={
showSmartWallet
? l.smart_wallet.switch_to_smart
: l.smart_wallet.switch_to_personal
}
icon={
<DisconnectIcon
width={14}
height={14}
color={theme.colors.iconPrimary}
/>
}
onPress={onWalletPress}
/>
)}
{wallet?.walletId === walletIds.localWallet ||
activeWallet?.walletId === walletIds.localWallet ? (
<>
@@ -36,6 +36,12 @@ export type ConnectWalletDetailsProps = {
* ```
*/
displayBalanceToken?: Record<number, string>;
/**
* Hide the "switch to Personal wallet" option in the dropdown which is shown when wallet is connected to a Smart Wallet
*
* @defaultValue false
*/
hideSwitchToPersonalWallet?: boolean;
};
export const WalletDetailsButton = ({
@@ -45,6 +51,7 @@ export const WalletDetailsButton = ({
hideTestnetFaucet,
supportedTokens,
displayBalanceToken,
hideSwitchToPersonalWallet,
}: ConnectWalletDetailsProps) => {
const l = useLocale();
const activeWallet = useWallet();
@@ -76,6 +83,7 @@ export const WalletDetailsButton = ({
hideTestnetFaucet={hideTestnetFaucet}
supportedTokens={supportedTokens}
displayBalanceToken={displayBalanceToken}
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
/>
{detailsButton ? (
detailsButton({ onPress })
@@ -1,7 +1,7 @@
import { walletIds } from "@thirdweb-dev/wallets";
import { ModalHeaderTextClose } from "../../base/modal/ModalHeaderTextClose";
import { WalletConfig } from "@thirdweb-dev/react-core";
import { ReactNode, useState } from "react";
import { ReactNode, useMemo, useState } from "react";
import Box from "../../base/Box";
import Text from "../../base/Text";
import ThirdwebLogo from "../../../assets/thirdweb-logo";
@@ -33,7 +33,6 @@ export function ChooseWallet({
onChooseWallet,
modalTitleIconUrl,
onClose,
excludeWalletIds = [],
termsOfServiceUrl,
privacyPolicyUrl,
}: ChooseWalletProps) {
@@ -49,14 +48,25 @@ export function ChooseWallet({
w.id === walletIds.magicLink ||
(w.id === walletIds.embeddedWallet && w.selectUI),
);
const connectionWallets = wallets
.filter(
const walletsToDisplay = useMemo(() => {
const filteredWallets = wallets.filter(
(wallet) =>
wallet.id !== walletIds.magicLink &&
wallet.id !== walletIds.embeddedWallet &&
wallet.id !== walletIds.localWallet,
)
.slice(0, 2);
);
const trueItems = filteredWallets.filter(
(item) => item.recommended === true,
);
const falseItems = filteredWallets.filter(
(item) => item.recommended !== true,
);
const sortedWallets = [...trueItems, ...falseItems];
return sortedWallets;
}, [wallets]);
const showToSPrivacyPolicy = termsOfServiceUrl || privacyPolicyUrl;
@@ -74,7 +84,7 @@ export function ChooseWallet({
};
const onSingleWalletPress = () => {
onChooseWallet(connectionWallets[0]);
onChooseWallet(walletsToDisplay[0]);
};
const onBackPress = () => {
@@ -135,13 +145,7 @@ export function ChooseWallet({
{!emailWallet || isConnectAWalletEnabled ? (
<>
<ChooseWalletContent
wallets={wallets}
excludeWalletIds={[
...excludeWalletIds,
walletIds.localWallet,
walletIds.magicLink,
walletIds.embeddedWallet,
]}
wallets={walletsToDisplay}
onChooseWallet={onChooseWallet}
/>
@@ -176,7 +180,7 @@ export function ChooseWallet({
) : null}
{emailWallet &&
!isConnectAWalletEnabled &&
(guestWallet || connectionWallets.length > 0) ? (
(guestWallet || walletsToDisplay.length > 0) ? (
<Box
mt="md"
marginHorizontal="xl"
@@ -193,15 +197,15 @@ export function ChooseWallet({
) : null}
{emailWallet &&
!isConnectAWalletEnabled &&
connectionWallets.length > 0 ? (
connectionWallets.length === 1 ? (
walletsToDisplay.length > 0 ? (
walletsToDisplay.length === 1 ? (
<WalletButton
marginHorizontal="xl"
paddingHorizontal="none"
paddingVertical="none"
mt="md"
walletIconUrl={connectionWallets[0].meta.iconURL}
name={connectionWallets[0].meta.name}
walletIconUrl={walletsToDisplay[0].meta.iconURL}
name={walletsToDisplay[0].meta.name}
onPress={onSingleWalletPress}
/>
) : (
@@ -217,7 +221,10 @@ export function ChooseWallet({
borderWidth={1}
onPress={onConnectAWalletPress}
>
{connectionWallets.map((wallet) => {
{walletsToDisplay.map((wallet, index) => {
if (index > 1) {
return null;
}
return (
<Box key={wallet.meta.name} mr="xxs">
<ImageSvgUri
@@ -1,4 +1,3 @@
import { useMemo } from "react";
import type { WalletConfig } from "@thirdweb-dev/react-core";
import { Dimensions, ScrollView, View } from "react-native";
import { WalletButton } from "../../base/WalletButton";
@@ -7,7 +6,6 @@ import { useTheme } from "@shopify/restyle";
interface ChooseWalletContentProps {
wallets: WalletConfig[];
excludeWalletIds?: string[];
onChooseWallet: (wallet: WalletConfig, data?: any) => void;
}
@@ -15,27 +13,10 @@ const MAX_HEIGHT = Dimensions.get("window").height * 0.3;
export const ChooseWalletContent = ({
wallets,
excludeWalletIds,
onChooseWallet,
}: ChooseWalletContentProps) => {
const theme = useTheme();
const walletsToDisplay = useMemo(() => {
const filteredWallets = wallets.filter(
(w) => !!!excludeWalletIds?.find((ewId) => ewId === w.id),
);
const trueItems = filteredWallets.filter(
(item) => item.recommended === true,
);
const falseItems = filteredWallets.filter(
(item) => item.recommended !== true,
);
const sortedWallets = [...trueItems, ...falseItems];
return sortedWallets;
}, [wallets, excludeWalletIds]);
return (
<View style={{ flexDirection: "column", maxHeight: MAX_HEIGHT }}>
<ScrollView
@@ -45,9 +26,8 @@ export const ChooseWalletContent = ({
paddingHorizontal: 16,
}}
>
{walletsToDisplay.map((item, index) => {
const marginBottom =
index === walletsToDisplay.length - 1 ? "none" : "xxs";
{wallets.map((item, index) => {
const marginBottom = index === wallets.length - 1 ? "none" : "xxs";
return (
<Box key={item.id}>
{item.selectUI ? (
@@ -13,12 +13,12 @@ export type ConnectWalletButtonProps = {
theme?: ThemeProviderProps["theme"];
/**
* Set a custom title for the button
* @default "Connect Wallet"
* @defaultValue "Connect Wallet"
*/
buttonTitle?: string;
/**
* Set a custom title for the Connect Wallet modal
* @default "Choose your wallet"
* @defaultValue "Choose your wallet"
*/
modalTitle?: string;
/**
@@ -188,14 +188,21 @@ export const LocalWalletImportModal = ({
{l.common.password}
</Text>
<PasswordInput onChangeText={onChangeText} />
<Text variant="bodySmall" color="red" mt="xs" textAlign="left">
{error}
</Text>
{error ? (
<Text
variant="bodySmall"
color="red"
marginVertical="xxs"
textAlign="left"
>
{error}
</Text>
) : null}
<Box
mb="xs"
flexDirection="row"
justifyContent="center"
alignItems="center"
mt="sm"
>
<Box height={1} flex={1} backgroundColor="border" />
<Text variant="bodySmall" textAlign="center" marginHorizontal="xxs">
@@ -214,23 +221,19 @@ export const LocalWalletImportModal = ({
fontFamily: theme.textVariants.defaults.fontFamily,
},
}}
containerProps={{ pl: "xxs" }}
containerProps={{ pl: "xxs", mt: "sm" }}
/>
<Box
flexDirection="row"
justifyContent="center"
paddingHorizontal="md"
mt="lg"
>
<Box flexDirection="row" justifyContent="flex-end" mt="lg">
<BaseButton
backgroundColor="white"
backgroundColor="accentButtonColor"
borderColor="accentButtonColor"
style={styles.modalButton}
onPress={onImportPress}
>
{isImporting ? (
<ActivityIndicator size="small" color="black" />
<ActivityIndicator size="small" color="accentButtonTextColor" />
) : (
<Text variant="bodySmall" color="black">
<Text variant="bodySmall" color="accentButtonTextColor">
{l.common.import}
</Text>
)}
@@ -338,6 +338,7 @@ const SendFundsForm = ({
<BaseButton
mt="md"
backgroundColor="accentButtonColor"
borderColor="accentButtonColor"
flexDirection="row"
alignItems="center"
justifyContent="center"
@@ -1,5 +1,5 @@
import { shortenAddress } from "@thirdweb-dev/react-core";
import { Theme } from "../../styles/theme";
import { shortenWalletAddress } from "../../utils/addresses";
import Text from "./Text";
type AddressProps = {
@@ -16,7 +16,7 @@ export const AddressDisplay = ({
}: AddressProps) => {
return (
<Text variant={variant} {...props}>
{shortenWalletAddress(address, extraShort)}
{shortenAddress(address, extraShort)}
</Text>
);
};
@@ -85,7 +85,7 @@ export const NetworkButton = ({
>
<Box flexDirection="row" alignItems="center">
<ChainIcon chainIconUrl={chain?.icon?.url} size={28} />
<Box ml="md" alignItems="flex-start" justifyContent="center">
<Box ml="sm" alignItems="flex-start" justifyContent="center">
<Text variant="bodyLarge">
{chain?.name || l.common.unknown_network}
</Text>
@@ -161,8 +161,10 @@ export const SwitchChainModal = ({
return (
<NetworkButton
mt="xxs"
padding="none"
key={chain.chainId}
backgroundColor="backgroundHighlight"
backgroundColor="transparent"
borderColor="transparent"
chain={chain}
enableSwitchModal={false}
switchChainOnPress={true}
@@ -0,0 +1,45 @@
import BaseButton from "./BaseButton";
import ImageSvgUri from "./ImageSvgUri";
import Text from "./Text";
type SquareButtonProps = {
onPress: () => void;
iconUrl: string;
size: number;
name?: string;
} & (typeof BaseButton)["arguments"];
export const SquareButton = ({
onPress,
iconUrl,
name,
size,
...props
}: SquareButtonProps) => {
return (
<BaseButton
flexDirection="column"
alignItems="center"
justifyContent="center"
borderWidth={1}
borderRadius="sm"
borderColor="border"
padding="sm"
onPress={onPress}
{...props}
>
<ImageSvgUri imageUrl={iconUrl} width={size} height={size} />
{name ? (
<Text
variant="bodySmallSecondary"
numberOfLines={1}
mt="xs"
fontSize={14}
lineHeight={16}
>
{name}
</Text>
) : null}
</BaseButton>
);
};
@@ -1,53 +0,0 @@
import { useGlobalTheme } from "../../providers/ui-context-provider";
import BaseButton from "./BaseButton";
import Box from "./Box";
import ImageSvgUri from "./ImageSvgUri";
import Text from "./Text";
type WalletButtonSquareProps = {
onPress: () => void;
walletIconUrl: string;
size: number;
name: string;
} & (typeof BaseButton)["arguments"];
export const WalletButtonSquare = ({
onPress,
walletIconUrl,
name,
size,
}: WalletButtonSquareProps) => {
const theme = useGlobalTheme();
const marginVertical = theme.spacing.xs;
return (
<BaseButton
flexDirection="column"
alignItems="center"
justifyContent="center"
marginVertical="xxs"
borderWidth={0}
borderRadius="sm"
width={size - marginVertical * 2}
height={size - marginVertical * 2}
onPress={onPress}
>
<Box borderWidth={0} borderRadius="lg">
<ImageSvgUri
imageUrl={walletIconUrl}
width={size * 0.6}
height={size * 0.6}
/>
</Box>
<Text
variant="bodySmallSecondary"
numberOfLines={1}
mt="xs"
fontSize={14}
lineHeight={16}
>
{name}
</Text>
</BaseButton>
);
};
+3
View File
@@ -74,6 +74,9 @@ export const _en = {
request_new_code: "Request new code",
sign_in: "Sign In",
sign_in_google: "Sign in with Google",
sign_in_facebook: "Sign in with Facebook",
sign_in_apple: "Sign in with Apple",
sign_in_email: "Sign in with Email",
enter_your_email: "Enter your email address",
forgot_password: "Forgot password",
enter_account_recovery_code: "Enter account recovery code",
+3
View File
@@ -74,6 +74,9 @@ export const _es = {
request_new_code: "Solicitar nuevo código",
sign_in: "Iniciar Sesión",
sign_in_google: "Iniciar sesión con Google",
sign_in_facebook: "Iniciar sesión con Facebook",
sign_in_apple: "Iniciar sesión con Apple",
sign_in_email: "Iniciar sesión con Email",
enter_your_email: "Ingresa tu dirección de correo electrónico",
forgot_password: "Olvidé mi contraseña",
enter_account_recovery_code: "Ingresa el código de recuperación de cuenta",
+3
View File
@@ -75,6 +75,9 @@ export const _ja = {
request_new_code: "新しいコードをリクエスト",
sign_in: "サインイン",
sign_in_google: "Googleでサインイン",
sign_in_facebook: "Facebookでサインイン",
sign_in_apple: "Appleでサインイン",
sign_in_email: "メールアドレスでサインイン",
enter_your_email: "メールアドレスを入力",
forgot_password: "パスワードを忘れた場合",
enter_account_recovery_code: "アカウント回復コードを入力",
-1
View File
@@ -42,7 +42,6 @@ export { Web3Button } from "./components/Web3Button";
// utilities
export * from "./utils/uri";
export * from "./utils/addresses";
export { createSyncStorage as createLocalStorage } from "../core/AsyncStorage";
export { createAsyncLocalStorage } from "../core/AsyncStorage";
export { createSecureStorage } from "../core/SecureStorage";
@@ -1,4 +1,10 @@
import { SmartWallet, walletIds } from "@thirdweb-dev/wallets";
import {
SmartWallet,
WalletConnectHandler,
WalletConnectReceiverConfig,
WalletConnectV2Handler,
walletIds,
} from "@thirdweb-dev/wallets";
import {
createContext,
useCallback,
@@ -8,13 +14,15 @@ import {
} from "react";
import { useWalletConnectListener } from "../wallets/hooks/useWalletConnectListener";
import { MagicLink } from "../wallets/wallets/MagicLink";
import { useWalletContext } from "@thirdweb-dev/react-core";
import { useWallet, useWalletContext } from "@thirdweb-dev/react-core";
import { isWalletConnectReceiverEnabled } from "../wallets/utils";
type DappContextType = {
smartWallet?: SmartWallet;
setSmartWallet?: (value?: SmartWallet) => void;
magicLink?: MagicLink;
setMagicLink?: (value?: MagicLink) => void;
walletConnectHandler?: WalletConnectHandler;
};
const DappContext = createContext<DappContextType>({});
@@ -22,15 +30,50 @@ const DappContext = createContext<DappContextType>({});
export const DappContextProvider = (props: React.PropsWithChildren) => {
const [smartWallet, setSmartWallet] = useState<SmartWallet | undefined>();
const [magicLink, setMagicLink] = useState<MagicLink | undefined>();
const [walletConnectHandler, setWalletConnectHandler] =
useState<WalletConnectHandler>();
const createdWalletInstance = useWalletContext().createdWalletInstance;
const activeWallet = useWallet();
useEffect(() => {
if (createdWalletInstance?.walletId === walletIds.magicLink) {
if (!createdWalletInstance) {
return;
}
if (createdWalletInstance.walletId === walletIds.magicLink) {
setMagicLink(createdWalletInstance as MagicLink);
}
}, [createdWalletInstance, setMagicLink]);
useWalletConnectListener();
useEffect(() => {
if (!activeWallet) {
return;
}
const initWCHandler = async () => {
const wcReceiverOptions =
activeWallet?.getOptions() as WalletConnectReceiverConfig;
const handler = new WalletConnectV2Handler(
{
walletConnectReceiver: {
...(wcReceiverOptions?.walletConnectReceiver === true
? {}
: wcReceiverOptions?.walletConnectReceiver),
},
},
activeWallet,
);
await handler.init();
setWalletConnectHandler(handler);
};
if (isWalletConnectReceiverEnabled(activeWallet)) {
initWCHandler();
}
}, [activeWallet]);
useWalletConnectListener(walletConnectHandler);
const magicSDK = useCallback(() => {
if (magicLink) {
@@ -48,6 +91,7 @@ export const DappContextProvider = (props: React.PropsWithChildren) => {
setSmartWallet,
magicLink: magicLink,
setMagicLink: setMagicLink,
walletConnectHandler: walletConnectHandler,
}}
>
{props.children}
@@ -74,3 +118,9 @@ export const useMagicLink = () => {
setMagicLink: context.setMagicLink,
} as const;
};
export const useWalletConnectHandler = () => {
const context = useContext(DappContext);
return context.walletConnectHandler;
};
@@ -52,7 +52,7 @@ interface ThirdwebProviderProps<TChains extends Chain[]>
* />
* ```
*
* * ```jsx
* ```jsx
* import { en } from "@thirdweb-dev/react-native";
*
* <ThirdwebProvider
@@ -86,6 +86,7 @@ interface ThirdwebProviderProps<TChains extends Chain[]>
* @example
* You can wrap your application with the provider as follows:
*
* ```jsx
* import { ThirdwebProvider } from "@thirdweb-dev/react-native";
*
* const App = () => {
@@ -95,7 +96,7 @@ interface ThirdwebProviderProps<TChains extends Chain[]>
* </ThirdwebProvider>
* );
* };
*
* ```
*/
export const ThirdwebProvider = <
TChains extends Chain[] = typeof defaultChains,
@@ -26,6 +26,7 @@ export type Palette = {
black: string;
white: string;
red: string;
transparent: string;
warning: string;
@@ -38,6 +39,7 @@ export const paletteBase = {
red: "red",
warning: "#F46565",
error: "#D85252",
transparent: "transparent",
};
export const paletteLight: Palette = {
@@ -1,54 +0,0 @@
import { utils } from "ethers";
/**
* Shortens an EVM compatible address to show the first 6 and last 4 characters
*
* @param str The address to shorten
* @param extraShort If true, show the first 4 and last 3 characters
* @returns The shortened address
*/
export function shortenWalletAddress(str?: string, extraShort = true) {
if (!str) {
return str;
}
return `${str.substring(0, extraShort ? 4 : 6)}...${str.substring(
str.length - (extraShort ? 3 : 4),
)}`;
}
/**
* This function shortens an address if it is a valid EVM address.
*
* @param address The address to shorten
* @param extraShort If true, show the first 4 and last 3 characters
* @returns The shortened address
* @example
* ```ts
* const address = shortenIfAddress("0x1234567890123456789012345678901234567890", true); // result will be "0x1234...890"
* ```
*/
export function shortenIfAddress(address?: string, extraShort?: true): string {
if (!address) {
return "";
}
if (isPossibleEVMAddress(address, true)) {
return shortenWalletAddress(address, extraShort) || "";
}
return address;
}
export function isEnsName(name: string): boolean {
return name?.endsWith(".eth");
}
// if a string is a valid address or ens name
export function isPossibleEVMAddress(address?: string, ignoreEns?: boolean) {
if (!address) {
return false;
}
if (isEnsName(address) && !ignoreEns) {
return true;
}
return utils.isAddress(address);
}
+2 -2
View File
@@ -4,8 +4,8 @@ import { WCMeta } from "../wallets/types/wc";
/**
* Build a WalletConnect display URI from a wc:// uri + a wallet specific link
*
* @param uri The wc:// uri
* @param links The wallet specific links
* @param uri - The wc:// uri
* @param links - The wallet specific links
* @returns The WalletConnect display URI
*/
export function formatWalletConnectDisplayUri(
@@ -23,12 +23,12 @@ export type CoinbaseWalletConnectorOptions = WalletMobileSDKProviderOptions &
ConfigurationParams & {
/**
* Fallback Ethereum JSON RPC URL
* @default ""
* @defaultValue ""
*/
jsonRpcUrl?: string;
/**
* Fallback Ethereum Chain ID
* @default 1
* @defaultValue 1
*/
chainId?: number;
};
@@ -19,8 +19,11 @@ import {
import { getEthersSigner } from "./embedded/signer";
import { logoutUser } from "./embedded/helpers/auth/logout";
import {
clearConnectedAuthStrategy,
clearConnectedEmail,
getConnectedAuthStrategy,
getConnectedEmail,
saveConnectedAuthStrategy,
saveConnectedEmail,
} from "./embedded/helpers/storage/local";
import {
@@ -39,11 +42,15 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
email?: string;
connectedAuthStrategy?: AuthParams["strategy"];
constructor(options: EmbeddedWalletConnectorOptions) {
super();
this.options = options;
this.email = getConnectedEmail();
this.connectedAuthStrategy = getConnectedAuthStrategy();
}
async connect(options?: EmbeddedWalletConnectionArgs) {
@@ -59,14 +66,12 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
await this.switchChain(options.chainId);
}
if (this.email) {
saveConnectedEmail(this.email);
}
return this.getAddress();
}
async authenticate(params: AuthParams): Promise<AuthResult> {
const strategy = params.strategy;
this.connectedAuthStrategy = strategy;
switch (strategy) {
case "email_verification": {
return await this.validateEmailOTP({
@@ -81,6 +86,18 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
redirectUrl: params.redirectUrl,
});
}
case "facebook": {
return this.socialLogin({
provider: AuthProvider.FACEBOOK,
redirectUrl: params.redirectUrl,
});
}
case "apple": {
return this.socialLogin({
provider: AuthProvider.APPLE,
redirectUrl: params.redirectUrl,
});
}
case "jwt": {
return this.customJwt({
jwt: params.jwt,
@@ -160,7 +177,6 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
this.options.clientId,
);
this.email = email;
saveConnectedEmail(email);
return {
user: {
@@ -179,11 +195,11 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
);
if (error instanceof Error) {
throw new Error(
`Error logging in with ${oauthOption.provider}: ${error.message}`,
`Error signing in with ${oauthOption.provider}: ${error.message}`,
);
} else {
throw new Error(
`An unknown error occurred logging in with ${oauthOption.provider}`,
`An unknown error occurred signing in with ${oauthOption.provider}`,
);
}
}
@@ -216,6 +232,7 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
async disconnect(): Promise<void> {
clearConnectedEmail();
clearConnectedAuthStrategy();
await logoutUser(this.options.clientId);
await this.onDisconnect();
this.signer = undefined;
@@ -262,6 +279,13 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
);
}
if (this.email) {
saveConnectedEmail(this.email);
}
if (this.connectedAuthStrategy) {
saveConnectedAuthStrategy(this.connectedAuthStrategy);
}
return signer;
}
@@ -335,6 +359,10 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
getEmail() {
return this.email;
}
getConnectedAuthStrategy() {
return this.connectedAuthStrategy;
}
}
function assertUnreachable(x: never): never {
@@ -26,7 +26,7 @@ import { isDeviceSharePresentForUser } from "./helpers/storage/local";
import { Auth } from "aws-amplify";
import {
ROUTE_AUTH_JWT_CALLBACK,
ROUTE_HEADLESS_GOOGLE_LOGIN,
ROUTE_HEADLESS_OAUTH_LOGIN,
} from "./helpers/constants";
import { AuthOptions, OauthOption, VerifiedTokenResponse } from "../types";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
@@ -171,17 +171,16 @@ export async function validateEmailOTP(options: {
}
export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
const headlessLoginLinkWithParams = `${ROUTE_HEADLESS_GOOGLE_LOGIN}?authProvider=${encodeURIComponent(
oauthOptions.provider,
)}&baseUrl=${encodeURIComponent(
"https://embedded-wallet.thirdweb.com",
const encodedProvider = encodeURIComponent(oauthOptions.provider);
const headlessLoginLinkWithParams = `${ROUTE_HEADLESS_OAUTH_LOGIN}?authProvider=${encodedProvider}&baseUrl=${encodeURIComponent(
`https://embedded-wallet.thirdweb.com`,
)}&platform=${encodeURIComponent("mobile")}`;
const resp = await fetch(headlessLoginLinkWithParams);
if (!resp.ok) {
const error = await resp.json();
throw new Error(`Error getting headless login link: ${error.message}`);
throw new Error(`Error getting headless sign in link: ${error.message}`);
}
const json = await resp.json();
@@ -192,7 +191,7 @@ export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
clientId || "",
)}&platform=${encodeURIComponent("mobile")}&redirectUrl=${encodeURIComponent(
oauthOptions.redirectUrl,
)}`;
)}&authOption=${encodedProvider}`;
const result = await InAppBrowser.openAuth(
completeLoginUrl,
@@ -207,16 +206,23 @@ export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
},
);
if (result.type === "cancel") {
throw new Error(`Sign in cancelled`);
}
if (result.type !== "success") {
throw new Error("Error signing in. Please try again later.");
throw new Error(`Can't sign in with ${oauthOptions.provider}: ${result}`);
}
const decodedUrl = decodeURIComponent(result.url);
const parts = decodedUrl.split("?authResult=");
if (parts.length < 2) {
throw new Error("Malformed response from the login redirect");
// assume error
const error = decodedUrl.split("?error=")?.[1];
throw new Error(`Something went wrong: ${error}`);
}
const authResult = parts[1];
const { storedToken } = JSON.parse(authResult);
@@ -35,6 +35,6 @@ export const ROUTE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedde
export const ROUTE_VALIDATE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-thirdweb-email-otp`;
export const ROUTE_IS_VALID_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/is-thirdweb-email-otp-valid`;
export const ROUTE_HEADLESS_GOOGLE_LOGIN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/headless-oauth-login-link`;
export const ROUTE_HEADLESS_OAUTH_LOGIN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/headless-oauth-login-link`;
export const ROUTE_AUTH_COGNITO_ID_TOKEN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/cognito-id-token`;
@@ -6,8 +6,11 @@ import {
} from "@paperxyz/embedded-wallet-service-sdk";
import { MMKV } from "react-native-mmkv";
import { DEVICE_SHARE_MISSING_MESSAGE } from "../constants";
import { AuthParams } from "../../../types";
const CONNECTED_EMAIL_LOCAL_STORAGE_NAME = "embedded-wallet-connected-email";
const CONNECTED_AUTH_STRATEGY_LOCAL_STORAGE_NAME =
"embedded-wallet-connected-auth-params";
const storage = new MMKV();
@@ -34,6 +37,23 @@ export function clearConnectedEmail() {
removeItemInAsyncStorage(CONNECTED_EMAIL_LOCAL_STORAGE_NAME);
}
export function getConnectedAuthStrategy(): AuthParams["strategy"] | undefined {
return getItemFromAsyncStorage(CONNECTED_AUTH_STRATEGY_LOCAL_STORAGE_NAME) as
| AuthParams["strategy"]
| undefined;
}
export function saveConnectedAuthStrategy(authStrategy: string) {
setItemInAsyncStorage(
CONNECTED_AUTH_STRATEGY_LOCAL_STORAGE_NAME,
authStrategy,
);
}
export function clearConnectedAuthStrategy() {
removeItemInAsyncStorage(CONNECTED_AUTH_STRATEGY_LOCAL_STORAGE_NAME);
}
export async function isDeviceSharePresentForUser(
clientId: string,
walletUserId?: string,
@@ -91,9 +91,9 @@ function createWalletShares(): {
/**
* Store user's wallet shares. Encrypts authShare and recoveryShare as given clientSide as well.
* @param {string} walletAddress the user's wallet address. Note that for each logged in user and clientId, we have a single walletAddress. This will error if we attempt to store shares for user's with an existing wallet different from the walletAddress
* @param {string} authShare the *unencrypted* authShare for the user
* @param {string} recoveryShare the *unencrypted* recovery share for the user
* @param walletAddress - the user's wallet address. Note that for each logged in user and clientId, we have a single walletAddress. This will error if we attempt to store shares for user's with an existing wallet different from the walletAddress
* @param authShare - the *unencrypted* authShare for the user
* @param recoveryShare - the *unencrypted* recovery share for the user
* @throws if another walletAddress already exists
*/
export async function storeShares<R extends string | undefined>({
@@ -46,9 +46,9 @@ export async function getExistingUserEtherJsWallet(clientId: string) {
/**
*
* @param deviceShare retrieves the current share associated with the user's device.
* @param deviceShare - retrieves the current share associated with the user's device.
* @returns the requested shares
* @throws if attempting to get {@param deviceShare} when it's not present
* @throws if attempting to get deviceShare when it's not present
*/
export async function getShares<
A extends boolean,
@@ -62,8 +62,8 @@ type EmailVerificationAuthParams = {
recoveryCode?: string;
};
type GoogleAuthParams = {
strategy: "google";
type SocialAuthParams = {
strategy: "google" | "facebook" | "apple";
redirectUrl: string;
};
@@ -76,7 +76,7 @@ type JwtAuthParams = {
// this is the input to 'authenticate'
export type AuthParams =
| EmailVerificationAuthParams
| GoogleAuthParams
| SocialAuthParams
| JwtAuthParams;
// TODO typed based off AuthParams["strategy"]
@@ -8,7 +8,7 @@ import { useCoinbaseWalletListener } from "./useCoinbaseWalletListener";
* const connect = useCoinbaseWallet('org.reactjs.native.example.test15://');
* connect();
*
* @param callbackURL The Universal Link used by Coinbase Wallet to return responses to your application.
* @param callbackURL - The Universal Link used by Coinbase Wallet to return responses to your application.
* @returns connect function to connect to the Coinbase wallet
*/
export function useCoinbaseWallet(callbackURL: URL) {
@@ -2,63 +2,79 @@ import { useWallet } from "@thirdweb-dev/react-core";
import { useCallback, useEffect } from "react";
import { useModalState } from "../../providers/ui-context-provider";
import {
IWalletConnectReceiver,
WCProposal,
WCRequest,
WalletConnectHandler,
} from "@thirdweb-dev/wallets";
/**
* Registers listeners for wallet connect if the active wallet
* is a smart wallet and has connect to app enabled
* is also a WalletConnectReceiver
*/
export function useWalletConnectListener() {
export function useWalletConnectListener(
walletConnectHandler?: WalletConnectHandler,
) {
const activeWallet = useWallet();
const { setModalState } = useModalState();
const onSmartWalletWCMessage = useCallback(
({ type, data }: { type: string; data?: unknown }) => {
switch (type) {
case "session_proposal":
setModalState({
view: "WalletConnectSessionProposalModal",
caller: "WCSessionProposalListener",
data: data as WCProposal,
isOpen: true,
isSheet: false,
});
break;
case "session_delete":
// reset();
break;
case "session_request":
setModalState({
view: "WalletConnectSessionRequestModal",
caller: "WCSessionRequestListener",
data: data as WCRequest,
isOpen: true,
isSheet: false,
});
break;
default:
// method not implemented
}
const onSessionProposal = useCallback(
(proposal: WCProposal) => {
setModalState({
view: "WalletConnectSessionProposalModal",
caller: "WCSessionProposalListener",
data: proposal,
isOpen: true,
isSheet: false,
});
},
[setModalState],
);
const onSessionRequest = useCallback(
(proposal: WCRequest) => {
setModalState({
view: "WalletConnectSessionRequestModal",
caller: "WCSessionRequestListener",
data: proposal,
isOpen: true,
isSheet: false,
});
},
[setModalState],
);
const onSessionDelete = useCallback(() => {
// consider clearing local storage
}, []);
useEffect(() => {
if (
activeWallet &&
"isWCReceiverEnabled" in activeWallet &&
(activeWallet as unknown as IWalletConnectReceiver).isWCReceiverEnabled()
) {
activeWallet.addListener("message", onSmartWalletWCMessage);
if (!walletConnectHandler) {
return;
}
walletConnectHandler.addListener("session_proposal", onSessionProposal);
walletConnectHandler.addListener("session_delete", onSessionDelete);
walletConnectHandler.addListener("session_request", onSessionRequest);
// consider adding switch_chain as well
return () => {
if (activeWallet) {
activeWallet.removeListener("message", onSmartWalletWCMessage);
if (walletConnectHandler) {
walletConnectHandler.removeListener(
"session_proposal",
onSessionProposal,
);
walletConnectHandler.removeListener("session_delete", onSessionDelete);
walletConnectHandler.removeListener(
"session_request",
onSessionRequest,
);
}
};
}, [activeWallet, onSmartWalletWCMessage]);
}, [
activeWallet,
onSessionDelete,
onSessionProposal,
onSessionRequest,
walletConnectHandler,
]);
}
@@ -0,0 +1,30 @@
import {
APPLE_ICON,
EMAIL_WALLET_ICON,
FACEBOOK_ICON,
GOOGLE_ICON,
} from "../../assets";
import { LocaleType } from "../../i18n/types";
export type AuthOption = "email" | SocialLogin;
export type SocialLogin = "google" | "apple" | "facebook";
export type AuthOptionDictionary = Record<AuthOption, string>;
export const AUTH_OPTIONS_ICONS: AuthOptionDictionary = {
google: GOOGLE_ICON,
email: EMAIL_WALLET_ICON,
apple: APPLE_ICON,
facebook: FACEBOOK_ICON,
};
export const AUTH_OPTIONS_TEXT: Record<
AuthOption,
keyof LocaleType["embedded_wallet"]
> = {
google: "sign_in_google",
email: "sign_in_email",
apple: "sign_in_apple",
facebook: "sign_in_facebook",
};
@@ -0,0 +1,6 @@
import { WalletInstance } from "@thirdweb-dev/react-core";
export const isWalletConnectReceiverEnabled = (wallet?: WalletInstance) => {
const options = wallet?.getOptions();
return options && "walletConnectReceiver" in options;
};
@@ -10,14 +10,18 @@ import Box from "../../../components/base/Box";
import Text from "../../../components/base/Text";
import BaseButton from "../../../components/base/BaseButton";
import { TextInput } from "../../../components/base/TextInput";
import { GOOGLE_ICON } from "../../../assets/svgs";
import { WalletButton } from "../../../components/base/WalletButton";
import {
useGlobalTheme,
useLocale,
} from "../../../providers/ui-context-provider";
import { EmbeddedWalletConfig } from "./embedded-wallet";
import { AuthProvider } from "@thirdweb-dev/wallets";
import {
AUTH_OPTIONS_ICONS,
AUTH_OPTIONS_TEXT,
AuthOption,
} from "../../types/embedded-wallet";
import { SquareButton } from "../../../components/base/SquareButton";
/**
* UI for selecting wallet - this UI is rendered in the wallet selection screen
@@ -37,7 +41,8 @@ export const EmailSelectionUI: React.FC<
const supportedWallets = useWallets();
const isEmailEnabled = auth?.options.includes("email");
const isGoogleEnabled = auth?.options.includes("google");
const socialLogins = auth?.options.filter((o) => o !== "email");
const isSocialLoginsEnabled = socialLogins && socialLogins.length > 0;
useEffect(() => {
const emailWalletInstance = createWalletInstance(
@@ -81,12 +86,12 @@ export const EmailSelectionUI: React.FC<
}
};
const onGoogleSignInPress = () => {
const onProviderPress = (authOption: Omit<AuthOption, "email">) => {
onSelect({
email: emailInput,
emailWallet,
oauthOptions: {
provider: AuthProvider.GOOGLE,
provider: authOption,
redirectUrl: auth?.redirectUrl,
},
});
@@ -94,21 +99,39 @@ export const EmailSelectionUI: React.FC<
return (
<Box paddingHorizontal="xl" mt="lg">
{isGoogleEnabled ? (
{isSocialLoginsEnabled ? (
<Box justifyContent="center">
<WalletButton
iconHeight={28}
iconWidth={28}
borderRadius="lg"
borderWidth={1}
borderColor="buttonBackgroundColor"
backgroundColor="buttonBackgroundColor"
nameColor="buttonTextColor"
justifyContent="center"
name={l.embedded_wallet.sign_in_google}
walletIconUrl={GOOGLE_ICON}
onPress={onGoogleSignInPress}
/>
<Box justifyContent="center" flexDirection="row">
{socialLogins.length === 1 ? (
<WalletButton
iconHeight={28}
iconWidth={28}
borderRadius="lg"
borderWidth={1}
borderColor="backgroundHighlight"
backgroundColor="backgroundHighlight"
nameColor="textPrimary"
flex={1}
justifyContent="center"
name={l.embedded_wallet[AUTH_OPTIONS_TEXT[socialLogins[0]]]}
walletIconUrl={AUTH_OPTIONS_ICONS[socialLogins[0]]}
onPress={() => onProviderPress(socialLogins[0])}
/>
) : (
<Box flexDirection="row" width="100%">
{socialLogins.map((provider, index) => (
<SquareButton
key={provider}
ml={index === 0 ? 0 : "md"}
flex={1}
onPress={() => onProviderPress(provider)}
iconUrl={AUTH_OPTIONS_ICONS[provider]}
size={32}
/>
))}
</Box>
)}
</Box>
{isEmailEnabled && supportedWallets.length === 1 ? (
<Box
mb="md"
@@ -128,7 +151,7 @@ export const EmailSelectionUI: React.FC<
<Box height={1} flex={1} backgroundColor="border" />
</Box>
) : (
<Box mt="md" />
<Box mt={isEmailEnabled ? "md" : "none"} />
)}
</Box>
) : null}
@@ -167,8 +190,8 @@ export const EmailSelectionUI: React.FC<
paddingVertical="md"
borderRadius="lg"
borderWidth={1}
borderColor="border"
backgroundColor="accentButtonColor"
borderColor="accentButtonColor"
onPress={handleNetworkCall}
>
{isFetching ? (
@@ -7,9 +7,9 @@ import {
useSetConnectedWallet,
useSetConnectionStatus,
} from "@thirdweb-dev/react-core";
import { GOOGLE_ICON } from "../../../assets";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import { Box, Text, WalletButton } from "../../../components/base";
import { AUTH_OPTIONS_ICONS, SocialLogin } from "../../types/embedded-wallet";
export const EmbeddedSocialConnection: React.FC<
ConnectUIProps<EmbeddedWallet>
@@ -18,19 +18,19 @@ export const EmbeddedSocialConnection: React.FC<
const setConnectionStatus = useSetConnectionStatus();
const [errorMessage, setErrorMessage] = useState<string>();
const handleGoogleLogin = useCallback(() => {
const handleSocialLogin = useCallback(() => {
setErrorMessage("");
setTimeout(async () => {
const emailWallet = selectionData.emailWallet as EmbeddedWallet;
const embeddedWallet = selectionData.emailWallet as EmbeddedWallet;
try {
const authResult = await emailWallet.authenticate({
strategy: "google",
const authResult = await embeddedWallet.authenticate({
strategy: selectionData.oauthOptions?.provider as SocialLogin,
redirectUrl: selectionData.oauthOptions?.redirectUrl,
});
const response = await emailWallet.connect({ authResult });
const response = await embeddedWallet.connect({ authResult });
if (response) {
if (onLocallyConnected) {
@@ -41,12 +41,15 @@ export const EmbeddedSocialConnection: React.FC<
}
} else {
setErrorMessage(
response || "Error login in. Please try again later.",
response || "Error signing in. Please try again later.",
);
}
} catch (error) {
console.error("Error logging in with google: ", error);
setErrorMessage(`Error login in. ${error}`);
console.error(
`Error signing in with ${selectionData.oauthOptions?.provider}: `,
error,
);
setErrorMessage(`${error}`);
}
}, 0);
}, [
@@ -58,8 +61,8 @@ export const EmbeddedSocialConnection: React.FC<
]);
useEffect(() => {
handleGoogleLogin();
}, [handleGoogleLogin]);
handleSocialLogin();
}, [handleSocialLogin]);
return (
<Box marginHorizontal="xl" height={200}>
@@ -71,7 +74,11 @@ export const EmbeddedSocialConnection: React.FC<
borderRadius="lg"
justifyContent="center"
name="Sign In"
walletIconUrl={GOOGLE_ICON}
walletIconUrl={
AUTH_OPTIONS_ICONS[
selectionData.oauthOptions?.provider as SocialLogin
]
}
/>
}
subHeaderText={""}
@@ -3,6 +3,7 @@ import {
AbstractClientWallet,
EmbeddedWalletAdditionalOptions,
walletIds,
WalletConnectReceiverConfig,
} from "@thirdweb-dev/wallets";
import type { EmbeddedWalletConnector } from "../../connectors/embedded-wallet/embedded-connector";
import {
@@ -10,9 +11,12 @@ import {
EmbeddedWalletConnectionArgs,
} from "../../connectors/embedded-wallet/types";
import { EMAIL_WALLET_ICON } from "../../../assets/svgs";
import { WalletMeta } from "@thirdweb-dev/wallets/dist/declarations/src/evm/wallets/base";
import { AUTH_OPTIONS_ICONS } from "../../types/embedded-wallet";
export type EmbeddedWalletOptions =
WalletOptions<EmbeddedWalletAdditionalOptions>;
export type EmbeddedWalletOptions = WalletOptions<
EmbeddedWalletAdditionalOptions & WalletConnectReceiverConfig
>;
export class EmbeddedWallet extends AbstractClientWallet<
EmbeddedWalletOptions,
@@ -77,6 +81,22 @@ export class EmbeddedWallet extends AbstractClientWallet<
return connector.authenticate(params);
}
getMeta(): WalletMeta {
const strategy = this.connector?.getConnectedAuthStrategy();
const meta = (this.constructor as typeof AbstractClientWallet).meta;
switch (strategy) {
case "facebook":
case "apple":
case "google":
return {
...meta,
iconURL: AUTH_OPTIONS_ICONS[strategy],
};
default:
return meta;
}
}
onConnected = () => {
this.emit("message", { type: "connected" });
};
@@ -50,7 +50,7 @@ export const EnterPassword = ({
};
const onForgotPress = () => {
// console.log("[TODO] Implement", setErrorMessage);
// [TODO] Forgot password
};
return (
@@ -6,15 +6,15 @@ import {
} from "@thirdweb-dev/react-core";
import { EmbeddedConnectionUI } from "./EmbeddedConnectionUI";
import { EmailSelectionUI } from "./EmbeddedSelectionUI";
type AuthOption = "email" | "google";
import { AuthOption } from "../../types/embedded-wallet";
import { WalletConnectReceiverConfig } from "@thirdweb-dev/wallets";
export type EmbeddedWalletConfig = {
auth?: {
options: AuthOption[];
redirectUrl?: string;
};
};
} & WalletConnectReceiverConfig;
export const embeddedWallet = (
config?: EmbeddedWalletConfig,
@@ -36,6 +36,7 @@ export const embeddedWallet = (
create(options: WalletOptions) {
return new EmbeddedWallet({
...options,
...config,
clientId: options.clientId || "",
});
},
@@ -23,7 +23,7 @@ import { useGlobalTheme, useLocale } from "../../providers/ui-context-provider";
/**
*
* @param magicLinkOptions Options available to create a magic link wallet
* @param magicLinkOptions - Options available to create a magic link wallet
* @returns A WalletConfig<MagicLink> object
*
* @deprecated We have deprecated magicLink in favor of our embeddedWallet which adds support for more sign in methods.
@@ -1,6 +1,5 @@
import type { WalletConfig, WalletOptions } from "@thirdweb-dev/react-core";
import { SmartWallet, createAsyncLocalStorage } from "@thirdweb-dev/wallets";
import { createSyncStorage } from "../../../core/AsyncStorage";
import { SmartWalletConfig } from "../types/smart-wallet";
import { SmartWalletFlow } from "../../components/ConnectWalletFlow/SmartWallet/SmartWalletFlow";
@@ -17,7 +16,6 @@ export const smartWallet = (
...options,
...config,
walletStorage: createAsyncLocalStorage("smart-wallet"),
wcStorage: createSyncStorage("smart-wallet"),
}),
connectUI(props) {
return <SmartWalletFlow {...props} personalWalletConfig={wallet} />;
+2 -1
View File
@@ -1,8 +1,9 @@
module.exports = {
root: true,
extends: ["thirdweb", "plugin:i18next/recommended"],
plugins: ["better-tree-shaking"],
plugins: ["better-tree-shaking", "eslint-plugin-tsdoc"],
rules: {
"tsdoc/syntax": "error",
"@next/next/no-img-element": "off",
"better-tree-shaking/no-top-level-side-effects": "error",
"i18next/no-literal-string": 2,
+60
View File
@@ -1,5 +1,65 @@
# @thirdweb-dev/react
## 4.1.6
### Patch Changes
- [#1954](https://github.com/thirdweb-dev/js/pull/1954) [`7912c263`](https://github.com/thirdweb-dev/js/commit/7912c263237708e9d2e9471873db7679e783b6f8) Thanks [@MananTank](https://github.com/MananTank)! - use Custom Theme context in styled components to prevent theme conflicts
- [#1945](https://github.com/thirdweb-dev/js/pull/1945) [`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091) Thanks [@iketw](https://github.com/iketw)! - Enable Facebook Sign In for embedded wallet
- Updated dependencies [[`69d68f5a`](https://github.com/thirdweb-dev/js/commit/69d68f5a25877bf5ea694719d2cf4ca8388f3091)]:
- @thirdweb-dev/wallets@2.1.5
- @thirdweb-dev/react-core@4.1.6
## 4.1.5
### Patch Changes
- Updated dependencies [[`9f993dcc`](https://github.com/thirdweb-dev/js/commit/9f993dcc1e60cc087850b8fb9ce09524073ce0b5)]:
- @thirdweb-dev/wallets@2.1.4
- @thirdweb-dev/react-core@4.1.5
## 4.1.4
### Patch Changes
- [#1944](https://github.com/thirdweb-dev/js/pull/1944) [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- [#1948](https://github.com/thirdweb-dev/js/pull/1948) [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6) Thanks [@iketw](https://github.com/iketw)! - Deprecates PaperWallet in favor of EmbeddedWallet
- Updated dependencies [[`23ab897e`](https://github.com/thirdweb-dev/js/commit/23ab897e329cdd5b316fbe554ea0f64add91f2b4), [`a3f3e160`](https://github.com/thirdweb-dev/js/commit/a3f3e160e07328655a4f0332f41f4edd210a1a8a), [`e79a9520`](https://github.com/thirdweb-dev/js/commit/e79a95206116ea09de0126fdbe30fb4413fdff28), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`42c74368`](https://github.com/thirdweb-dev/js/commit/42c743682739de77c264c11cc1692ed752f929a6), [`6fcdec7c`](https://github.com/thirdweb-dev/js/commit/6fcdec7c12e4a3c0d10f3a67771e19cfad5c8ab5), [`480ed721`](https://github.com/thirdweb-dev/js/commit/480ed721d1c736049fa8b6ee4cbdbdf3cce18abd), [`16e9e8b6`](https://github.com/thirdweb-dev/js/commit/16e9e8b64d7d1a97a5b32fc855479e992b7ed3a8), [`b1190914`](https://github.com/thirdweb-dev/js/commit/b1190914a97a9c4d9019c4ce8ee49ad345aed18c)]:
- @thirdweb-dev/sdk@4.0.13
- @thirdweb-dev/react-core@4.1.4
- @thirdweb-dev/wallets@2.1.3
## 4.1.3
### Patch Changes
- [#1943](https://github.com/thirdweb-dev/js/pull/1943) [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3) Thanks [@iketw](https://github.com/iketw)! - Adds tsdoc warnings to enforce a common comments format
- [#1929](https://github.com/thirdweb-dev/js/pull/1929) [`06e59cf9`](https://github.com/thirdweb-dev/js/commit/06e59cf91647e4080829d49f8cb91a4d8fdc87f7) Thanks [@iketw](https://github.com/iketw)! - Move addresses' utils to the core package
- [#1920](https://github.com/thirdweb-dev/js/pull/1920) [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e) Thanks [@ElasticBottle](https://github.com/ElasticBottle)! - feat: add apple auth to embedded wallet
```
<ThirdwebProvider
supportedWallets={[
embeddedWallet({
auth: {
options: ["google", "email", "apple"], // <- new apple option!
}
})
]}
>
```
- Updated dependencies [[`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b), [`a81a5285`](https://github.com/thirdweb-dev/js/commit/a81a5285b7ef4ddf47fd2779ab80ebbef4bd9e0e), [`30ea9c6b`](https://github.com/thirdweb-dev/js/commit/30ea9c6bf74b483a98592a1d8d64589b7f1b22a3), [`06e59cf9`](https://github.com/thirdweb-dev/js/commit/06e59cf91647e4080829d49f8cb91a4d8fdc87f7), [`97fdbc8f`](https://github.com/thirdweb-dev/js/commit/97fdbc8f6443dcd08a7610e4437bd4c9d6b0e8c8), [`3f3a484a`](https://github.com/thirdweb-dev/js/commit/3f3a484a3aa6e9f784e4d44458461bac0d3d4c6b)]:
- @thirdweb-dev/wallets@2.1.2
- @thirdweb-dev/react-core@4.1.3
- @thirdweb-dev/sdk@4.0.12
## 4.1.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "4.1.2",
"version": "4.1.6",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+11 -12
View File
@@ -1,7 +1,6 @@
import type { Theme } from "../design-system";
import styled from "@emotion/styled";
import { Chain } from "@thirdweb-dev/chains";
import { useStorage } from "@thirdweb-dev/react-core";
import { StyledDiv } from "../design-system/elements";
const defaultChainIcon =
"ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/ethereum/512.png";
@@ -52,13 +51,13 @@ export const ChainIcon: React.FC<{
);
};
const ActiveDot = styled.div<{ theme?: Theme }>`
width: 28%;
height: 28%;
border-radius: 50%;
position: absolute;
top: 60%;
right: 0px;
background-color: #00d395;
box-shadow: 0 0 0 2px var(--bg);
`;
const ActiveDot = /* @__PURE__ */ StyledDiv({
width: "28%",
height: "28%",
borderRadius: "50%",
position: "absolute",
top: "60%",
right: 0,
backgroundColor: "#00d395",
boxShadow: `0 0 0 2px var(--bg)`,
});
+7 -4
View File
@@ -1,4 +1,4 @@
import type { Theme } from "../design-system";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
import { useClipboard } from "../evm/components/hooks/useCopyClipboard";
import { ToolTip } from "./Tooltip";
import styled from "@emotion/styled";
@@ -29,6 +29,9 @@ export const CopyIcon: React.FC<{
);
};
const CheckIconStyled = /* @__PURE__ */ styled(CheckIcon)<{ theme?: Theme }>`
color: ${(p) => p.theme.colors.success};
`;
const CheckIconStyled = /* @__PURE__ */ styled(CheckIcon)(() => {
const theme = useCustomTheme();
return {
color: theme.colors.success,
};
});
+32 -29
View File
@@ -1,12 +1,14 @@
import { useState } from "react";
import { UploadIcon } from "@radix-ui/react-icons";
import { Theme, iconSize, radius, spacing } from "../design-system";
import { iconSize, radius, spacing } from "../design-system";
import styled from "@emotion/styled";
import { Spacer } from "./Spacer";
import { isMobile } from "../evm/utils/isMobile";
import type { IconFC } from "../wallet/ConnectWallet/icons/types";
import { Text } from "./text";
import { Container } from "./basic";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const DragNDrop: React.FC<{
extension: string;
@@ -132,37 +134,38 @@ export const DragNDrop: React.FC<{
);
};
const UploadIconSecondary = /* @__PURE__ */ styled(UploadIcon)<{
theme?: Theme;
}>`
color: ${(props) => props.theme.colors.secondaryIconColor};
transition:
transform 200ms ease,
color 200ms ease;
`;
const UploadIconSecondary = /* @__PURE__ */ styled(UploadIcon)(() => {
const theme = useCustomTheme();
return {
color: theme.colors.secondaryIconColor,
transition: "transform 200ms ease, color 200ms ease",
};
});
const DropContainer = styled.div<{ theme?: Theme }>`
border: 2px solid ${(p) => p.theme.colors.borderColor};
border-radius: ${radius.md};
padding: ${spacing.xl} ${spacing.md};
display: flex;
align-items: center;
flex-direction: column;
cursor: pointer;
transition: border-color 200ms ease;
const DropContainer = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
border: `2px solid ${theme.colors.borderColor}`,
borderRadius: radius.md,
padding: `${spacing.xl} ${spacing.md}`,
display: "flex",
alignItems: "center",
flexDirection: "column",
cursor: "pointer",
transition: "border-color 200ms ease",
&:hover,
&[data-is-dragging="true"] {
border-color: ${(p) => p.theme.colors.accentText};
svg {
color: ${(p) => p.theme.colors.accentText};
}
}
"&:hover, &[data-is-dragging='true']": {
borderColor: theme.colors.accentText,
svg: {
color: theme.colors.accentText,
},
},
&[data-error="true"] {
border-color: ${(p) => p.theme.colors.danger};
}
`;
"&[data-error='true']": {
borderColor: theme.colors.danger,
},
};
});
const CheckCircleIcon: IconFC = (props) => (
<svg
+4 -4
View File
@@ -1,6 +1,6 @@
import styled from "@emotion/styled";
import { fadeInAnimation } from "../design-system/animations";
import { StyledDiv } from "../design-system/elements";
export const FadeIn = styled.div`
animation: ${fadeInAnimation} 0.15s ease-in;
`;
export const FadeIn = /* @__PURE__ */ StyledDiv({
animation: `${fadeInAnimation} 0.15s ease-in`,
});
+53 -61
View File
@@ -1,11 +1,4 @@
import {
Theme,
media,
spacing,
shadow,
radius,
iconSize,
} from "../design-system";
import { media, spacing, shadow, radius, iconSize } from "../design-system";
import {
wideModalMaxHeight,
modalMaxWidthCompact,
@@ -17,11 +10,12 @@ import { Overlay } from "./Overlay";
import { noScrollBar } from "./basic";
import { IconButton } from "./buttons";
import { keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import * as Dialog from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import { DynamicHeight } from "./DynamicHeight";
import { useEffect, useRef, useState } from "react";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
import { StyledDiv } from "../design-system/elements";
export const Modal: React.FC<{
trigger?: React.ReactNode;
@@ -127,16 +121,15 @@ export const Modal: React.FC<{
);
};
export const CrossContainer = styled.div`
position: absolute;
top: ${spacing.lg};
right: ${spacing.lg};
transform: translateX(15%);
${media.mobile} {
right: ${spacing.md};
}
`;
export const CrossContainer = /* @__PURE__ */ StyledDiv({
position: "absolute",
top: spacing.lg,
right: spacing.lg,
transform: "translateX(15%)",
[media.mobile]: {
right: spacing.md,
},
});
const modalAnimationDesktop = keyframes`
from {
@@ -160,46 +153,45 @@ const modalAnimationMobile = keyframes`
}
`;
const DialogContent = styled.div<{ theme?: Theme }>`
z-index: 10000;
background: ${(p) => p.theme.colors.modalBg};
--bg: ${(p) => p.theme.colors.modalBg};
color: ${(p) => p.theme.colors.primaryText};
border-radius: ${radius.xl};
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - 40px);
box-sizing: border-box;
animation: ${modalAnimationDesktop} 300ms ease;
box-shadow: ${shadow.lg};
line-height: normal;
border: 1px solid ${(p) => p.theme.colors.borderColor};
outline: none;
overflow: hidden;
font-family: ${(p) => p.theme.fontFamily};
const DialogContent = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
${noScrollBar}
/* open from bottom on mobile */
${media.mobile} {
top: auto;
bottom: 0;
left: 0;
right: 0;
max-width: 100vw;
transform: none;
width: 100vw;
animation: ${modalAnimationMobile} 0.35s cubic-bezier(0.15, 1.15, 0.6, 1);
border-radius: ${radius.xxl};
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
max-width: none !important;
}
& *::selection {
background-color: ${(p) => p.theme.colors.selectedTextBg};
color: ${(p) => p.theme.colors.selectedTextColor};
}
`;
return {
zIndex: 10000,
background: theme.colors.modalBg,
"--bg": theme.colors.modalBg,
color: theme.colors.primaryText,
borderRadius: radius.xl,
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "calc(100vw - 40px)",
boxSizing: "border-box",
animation: `${modalAnimationDesktop} 300ms ease`,
boxShadow: shadow.lg,
lineHeight: "normal",
border: `1px solid ${theme.colors.borderColor}`,
outline: "none",
overflow: "hidden",
fontFamily: theme.fontFamily,
[media.mobile]: {
top: "auto",
bottom: 0,
left: 0,
right: 0,
transform: "none",
width: "100vw",
animation: `${modalAnimationMobile} 0.35s cubic-bezier(0.15, 1.15, 0.6, 1)`,
borderRadius: radius.xxl,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
maxWidth: "none !important",
},
"& *::selection": {
backgroundColor: theme.colors.selectedTextBg,
color: theme.colors.selectedTextColor,
},
...noScrollBar,
};
});
+21 -17
View File
@@ -3,6 +3,7 @@ import { Input } from "./formElements";
import { Container } from "./basic";
import { media, fontSize, spacing } from "../design-system";
import styled from "@emotion/styled";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export function OTPInput(props: {
digits: number;
@@ -115,20 +116,23 @@ export function OTPInput(props: {
);
}
const OTPInputBox = /* @__PURE__ */ styled(Input)`
appearance: none;
-webkit-appearance: none;
width: 40px;
height: 40px;
text-align: center;
font-size: ${fontSize.md};
padding: ${spacing.xs};
${media.mobile} {
width: 35px;
height: 35px;
}
&[data-verify-status="invalid"] {
color: ${(p) => p.theme.colors.danger};
border-color: ${(p) => p.theme.colors.danger};
}
`;
const OTPInputBox = /* @__PURE__ */ styled(Input)(() => {
const theme = useCustomTheme();
return {
appearance: "none",
WebkitAppearance: "none",
width: "40px",
height: "40px",
textAlign: "center",
fontSize: fontSize.md,
padding: spacing.xs,
[media.mobile]: {
width: "35px",
height: "35px",
},
"&[data-verify-status='invalid']": {
color: theme.colors.danger,
borderColor: theme.colors.danger,
},
};
});
+13 -10
View File
@@ -1,6 +1,6 @@
import type { Theme } from "../design-system";
import { keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
const overlayEnter = keyframes`
from {
@@ -11,11 +11,14 @@ const overlayEnter = keyframes`
}
`;
export const Overlay = styled.div<{ theme?: Theme }>`
background-color: ${(p) => p.theme.colors.modalOverlayBg};
z-index: 9999;
position: fixed;
inset: 0;
animation: ${overlayEnter} 400ms cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(10px);
`;
export const Overlay = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
backgroundColor: theme.colors.modalOverlayBg,
zIndex: 9999,
position: "fixed",
inset: 0,
animation: `${overlayEnter} 400ms cubic-bezier(0.16, 1, 0.3, 1)`,
backdropFilter: "blur(10px)",
};
});
+23 -24
View File
@@ -1,14 +1,9 @@
import {
fontSize,
radius,
shadow,
spacing,
type Theme,
} from "../design-system";
import { fontSize, radius, shadow, spacing } from "../design-system";
import { keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import * as RXPopover from "@radix-ui/react-popover";
import { Container } from "./basic";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export type PopoverProps = {
children: React.ReactNode;
@@ -53,21 +48,25 @@ from {
}
`;
const PopoverContent = /* @__PURE__ */ (() => styled(RXPopover.Content)<{
theme?: Theme;
}>`
border-radius: ${radius.sm};
padding: ${spacing.sm} ${spacing.md};
background-color: ${(p) => p.theme.colors.tooltipBg};
box-shadow: ${shadow.md};
will-change: transform, opacity;
animation: ${slideUpAndFade} 400ms cubic-bezier(0.16, 1, 0.3, 1);
color: ${(p) => p.theme.colors.tooltipText};
font-size: ${fontSize.md};
`)();
const PopoverContent = /* @__PURE__ */ (() =>
styled(RXPopover.Content)(() => {
const theme = useCustomTheme();
return {
borderRadius: radius.sm,
padding: `${spacing.sm} ${spacing.md}`,
backgroundColor: theme.colors.tooltipBg,
boxShadow: shadow.md,
willChange: "transform, opacity",
animation: `${slideUpAndFade} 400ms cubic-bezier(0.16, 1, 0.3, 1)`,
color: theme.colors.tooltipText,
fontSize: fontSize.md,
};
}))();
const PopoverArrow = /* @__PURE__ */ (() => styled(RXPopover.Arrow)<{
theme?: Theme;
}>`
fill: ${(p) => p.theme.colors.tooltipBg};
`)();
const PopoverArrow = /* @__PURE__ */ (() =>
styled(RXPopover.Arrow)(() => {
const theme = useCustomTheme();
return {
fill: theme.colors.tooltipBg,
};
}))();
+92 -94
View File
@@ -1,9 +1,10 @@
import { keyframes } from "@emotion/react";
import { Theme, radius } from "../design-system";
import styled from "@emotion/styled";
import { radius } from "../design-system";
import QRCodeUtil from "qrcode";
import React, { ReactElement, useMemo } from "react";
import { fadeInAnimation } from "../design-system/animations";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const QRCode: React.FC<{
qrCodeUri?: string;
@@ -48,23 +49,26 @@ export const QRCode: React.FC<{
);
};
const IconContainer = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-content: center;
z-index: 1000;
`;
const IconContainer = /* @__PURE__ */ StyledDiv({
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
justifyContent: "center",
alignContent: "center",
zIndex: 1000,
});
const QRCodeContainer = styled.div<{ theme?: Theme }>`
animation: ${fadeInAnimation} 600ms ease;
--ck-qr-dot-color: ${(p) => p.theme.colors.primaryText};
--ck-body-background: ${(p) => p.theme.colors.modalBg};
--ck-qr-background: ${(p) => p.theme.colors.modalBg};
`;
const QRCodeContainer = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
animation: `${fadeInAnimation} 600ms ease`,
"--ck-qr-dot-color": theme.colors.primaryText,
"--ck-body-background": theme.colors.modalBg,
"--ck-qr-background": theme.colors.modalBg,
};
});
const generateMatrix = (
value: string,
@@ -98,82 +102,76 @@ export const PlaceholderKeyframes = keyframes`
100%{ background-position: -100% 0; }
`;
export const QRPlaceholder = styled.div<{ theme?: Theme }>`
--color: ${(p) => p.theme.colors.skeletonBg};
--bg: ${(p) => p.theme.colors.modalBg};
overflow: hidden;
position: relative;
display: flex;
align-items: center;
justify-content: center;
border-radius: ${radius.md};
> div {
z-index: 4;
position: relative;
width: 28%;
height: 28%;
border-radius: 5px;
background: var(--bg);
box-shadow: 0 0 0 7px var(--bg);
}
> span {
z-index: 4;
position: absolute;
background: var(--color);
border-radius: 12px;
width: 13.25%;
height: 13.25%;
box-shadow: 0 0 0 4px var(--bg);
&:before {
content: "";
position: absolute;
inset: 9px;
border-radius: 3px;
box-shadow: 0 0 0 4px var(--bg);
}
&[data-v1] {
top: 0;
left: 0;
}
&[data-v2] {
top: 0;
right: 0;
}
&[data-v3] {
bottom: 0;
left: 0;
}
}
&:before {
z-index: 3;
content: "";
position: absolute;
inset: 0;
background: repeat;
background-size: 1.888% 1.888%;
background-image: radial-gradient(var(--color) 41%, transparent 41%);
}
&:after {
z-index: 100;
content: "";
position: absolute;
inset: 0;
transform: scale(1.5) rotate(45deg);
background-image: linear-gradient(
90deg,
transparent 50%,
${(p) => p.theme.colors.skeletonBg},
transparent
);
background-size: 200% 100%;
animation: ${PlaceholderKeyframes} 1000ms linear infinite both;
}
`;
export const QRPlaceholder = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
"--color": theme.colors.skeletonBg,
"--bg": theme.colors.modalBg,
overflow: "hidden",
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: radius.md,
"> div": {
zIndex: 4,
position: "relative",
width: "28%",
height: "28%",
borderRadius: "5px",
background: "var(--bg)",
boxShadow: "0 0 0 7px var(--bg)",
},
"> span": {
zIndex: 4,
position: "absolute",
background: "var(--color)",
borderRadius: "12px",
width: "13.25%",
height: "13.25%",
boxShadow: "0 0 0 4px var(--bg)",
"&:before": {
content: '""',
position: "absolute",
inset: "9px",
borderRadius: "3px",
boxShadow: "0 0 0 4px var(--bg)",
},
"&[data-v1]": {
top: 0,
left: 0,
},
"&[data-v2]": {
top: 0,
right: 0,
},
"&[data-v3]": {
bottom: 0,
left: 0,
},
},
"&:before": {
zIndex: 3,
content: '""',
position: "absolute",
inset: 0,
background: "repeat",
backgroundSize: "1.888% 1.888%",
backgroundImage: "radial-gradient(var(--color) 41%, transparent 41%)",
},
"&::after": {
zIndex: 100,
content: '""',
position: "absolute",
inset: 0,
transform: "scale(1.5) rotate(45deg)",
background:
"linear-gradient(90deg, transparent 50%, var(--color), transparent)",
backgroundSize: "200% 100%",
animation: `${PlaceholderKeyframes} 1000ms linear infinite both`,
},
};
});
export function QRCodeRenderer({
ecl = "M",
+12 -8
View File
@@ -1,6 +1,7 @@
import { radius, Theme } from "../design-system";
import { radius } from "../design-system";
import { keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const Skeleton: React.FC<{
height: string;
@@ -25,9 +26,12 @@ const skeletonAnimation = keyframes`
}
`;
const SkeletonDiv = styled.div<{ theme?: Theme }>`
background-size: 200% 200%;
background-color: ${(p) => p.theme.colors.skeletonBg};
animation: ${skeletonAnimation} 500ms ease-in-out infinite alternate;
border-radius: ${radius.sm};
`;
const SkeletonDiv = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
backgroundSize: "200% 200%",
backgroundColor: theme.colors.skeletonBg,
animation: `${skeletonAnimation} 500ms ease-in-out infinite alternate`,
borderRadius: radius.sm,
};
});
+15 -15
View File
@@ -1,12 +1,13 @@
import { iconSize, type Theme } from "../design-system";
import { keyframes, useTheme } from "@emotion/react";
import styled from "@emotion/styled";
import { keyframes } from "@emotion/react";
import { StyledCircle, StyledSvg } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const Spinner: React.FC<{
color: keyof Theme["colors"];
size: keyof typeof iconSize;
}> = (props) => {
const theme = useTheme() as Theme;
const theme = useCustomTheme();
return (
<Svg
style={{
@@ -29,7 +30,7 @@ export const Spinner: React.FC<{
// animations
const dashAnimation = keyframes`
0% {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
@@ -44,19 +45,18 @@ const dashAnimation = keyframes`
`;
const rotateAnimation = keyframes`
100% {
100% {
transform: rotate(360deg);
}
`;
// styles
const Svg = styled.svg`
animation: ${rotateAnimation} 2s linear infinite;
width: 1em;
height: 1em;
`;
const Svg = /* @__PURE__ */ StyledSvg({
animation: `${rotateAnimation} 2s linear infinite`,
width: "1em",
height: "1em",
});
const Circle = styled.circle`
stroke-linecap: round;
animation: ${dashAnimation} 1.5s ease-in-out infinite;
`;
const Circle = /* @__PURE__ */ StyledCircle({
strokeLinecap: "round",
animation: `${dashAnimation} 1.5s ease-in-out infinite`,
});
+20 -18
View File
@@ -1,5 +1,6 @@
import styled from "@emotion/styled";
import { fontSize, spacing, Theme } from "../design-system";
import { fontSize, spacing } from "../design-system";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const TextDivider = (props: {
text: string;
@@ -16,19 +17,20 @@ export const TextDivider = (props: {
);
};
export const TextDividerEl = styled.div<{ theme?: Theme }>`
display: flex;
align-items: center;
color: ${(p) => p.theme.colors.secondaryText};
font-size: ${fontSize.sm};
&::before,
&::after {
content: "";
flex: 1;
border-bottom: 1px solid ${(p) => p.theme.colors.separatorLine};
}
span {
margin: 0 1rem;
}
`;
export const TextDividerEl = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
display: "flex",
alignItems: "center",
color: theme.colors.secondaryText,
fontSize: fontSize.sm,
"&::before, &::after": {
content: '""',
flex: 1,
borderBottom: `1px solid ${theme.colors.separatorLine}`,
},
span: {
margin: "0 16px",
},
};
});
+27 -23
View File
@@ -1,7 +1,8 @@
import { fontSize, radius, shadow, spacing, Theme } from "../design-system";
import { fontSize, radius, shadow, spacing } from "../design-system";
import { keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import * as RadixTooltip from "@radix-ui/react-tooltip";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const ToolTip: React.FC<{
children: React.ReactNode;
@@ -40,26 +41,29 @@ to {
}
`;
const TooltipContent = /* @__PURE__ */ (() => styled(RadixTooltip.Content)<{
theme?: Theme;
}>`
background: ${(p) => p.theme.colors.tooltipBg};
color: ${(p) => p.theme.colors.tooltipText};
border-radius: ${radius.sm};
line-height: normal;
padding: ${spacing.sm} ${spacing.md};
font-size: ${fontSize.sm};
box-shadow: ${shadow.sm};
user-select: none;
will-change: transform, opacity;
animation: ${slideUpAndFade} 200ms cubic-bezier(0.16, 1, 0.3, 1);
z-index: 999999999999999;
max-width: 300px;
line-height: 1.5;
`)();
const TooltipContent = /* @__PURE__ */ (() =>
styled(RadixTooltip.Content)(() => {
const theme = useCustomTheme();
return {
background: theme.colors.tooltipBg,
color: theme.colors.tooltipText,
borderRadius: radius.sm,
padding: `${spacing.sm} ${spacing.md}`,
fontSize: fontSize.sm,
boxShadow: shadow.sm,
userSelect: "none",
willChange: "transform, opacity",
animation: `${slideUpAndFade} 200ms cubic-bezier(0.16, 1, 0.3, 1)`,
zIndex: 999999999999999,
maxWidth: "300px",
lineHeight: 1.5,
};
}))();
const TooltipArrow = /* @__PURE__ */ (() => styled(RadixTooltip.Arrow)<{
theme?: Theme;
}>`
fill: ${(p) => p.theme.colors.tooltipBg};
`)();
const TooltipArrow = /* @__PURE__ */ (() =>
styled(RadixTooltip.Arrow)(() => {
const theme = useCustomTheme();
return {
fill: theme.colors.tooltipBg,
};
}))();
+52 -43
View File
@@ -1,4 +1,3 @@
import styled from "@emotion/styled";
import { Theme, iconSize, spacing } from "../design-system";
import { BackButton, ModalTitle } from "./modalElements";
import { Img } from "./Img";
@@ -7,22 +6,28 @@ import {
floatDownAnimation,
floatUpAnimation,
} from "../design-system/animations";
import { StyledDiv } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
import type { CSSObject } from "@emotion/react";
export const ScreenBottomContainer = styled.div<{ theme?: Theme }>`
border-top: 1px solid ${(p) => p.theme.colors.separatorLine};
display: flex;
flex-direction: column;
gap: ${spacing.lg};
padding: ${spacing.lg};
`;
export const ScreenBottomContainer = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
borderTop: `1px solid ${theme.colors.separatorLine}`,
display: "flex",
flexDirection: "column",
gap: spacing.lg,
padding: spacing.lg,
};
});
export const noScrollBar = `
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0px;
display: none;
}
`;
export const noScrollBar: CSSObject = /* @__PURE__ */ {
scrollbarWidth: "none",
"&::-webkit-scrollbar": {
width: 0,
display: "none",
},
};
export function ModalHeader(props: {
onBack?: () => void;
@@ -59,12 +64,13 @@ export function ModalHeader(props: {
);
}
export const Line = styled.div<{
theme?: Theme;
}>`
height: 1px;
background: ${(p) => p.theme.colors.separatorLine};
`;
export const Line = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
height: "1px",
background: theme.colors.separatorLine,
};
});
export function Container(props: {
animate?: "fadein" | "floatup" | "floatdown";
@@ -165,26 +171,29 @@ export function Container(props: {
);
}
const Box = styled.div<{ theme?: Theme; color?: keyof Theme["colors"] }>`
color: ${(p) => (p.color ? p.theme.colors[p.color] : "inherit")};
type BoxProps = {
color?: keyof Theme["colors"];
};
&[data-animate="fadein"] {
opacity: 0;
animation: ${fadeInAnimation} 350ms ease forwards;
}
&[data-animate="floatup"] {
opacity: 0;
animation: ${floatUpAnimation} 350ms ease forwards;
}
&[data-animate="floatdown"] {
opacity: 0;
animation: ${floatDownAnimation} 350ms ease forwards;
}
&[data-scrolly="true"] {
overflow-y: auto;
${noScrollBar}
}
`;
const Box = /* @__PURE__ */ StyledDiv((props: BoxProps) => {
const theme = useCustomTheme();
return {
color: props.color ? theme.colors[props.color] : "inherit",
"&[data-animate='fadein']": {
opacity: 0,
animation: `${fadeInAnimation} 350ms ease forwards`,
},
"&[data-animate='floatup']": {
opacity: 0,
animation: `${floatUpAnimation} 350ms ease forwards`,
},
"&[data-animate='floatdown']": {
opacity: 0,
animation: `${floatDownAnimation} 350ms ease forwards`,
},
"&[data-scrolly='true']": {
overflowY: "auto",
...noScrollBar,
},
};
});
+122 -115
View File
@@ -1,127 +1,134 @@
import { fontSize, radius, spacing, Theme } from "../design-system";
import styled from "@emotion/styled";
import { StyledButton } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const Button = styled.button<{
type ButtonProps = {
variant: "primary" | "secondary" | "link" | "accent" | "outline";
theme?: Theme;
fullWidth?: boolean;
gap?: keyof typeof spacing;
}>`
all: unset;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: ${radius.md};
padding: ${fontSize.sm} ${fontSize.sm};
font-size: ${fontSize.md};
font-weight: 500;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
line-height: normal;
flex-shrink: 0;
transition: border 200ms ease;
gap: ${(p) => (p.gap && spacing[p.gap]) || 0};
};
${(p) => p.fullWidth && `width: 100%;`};
background: ${(p) => {
switch (p.variant) {
case "primary":
return p.theme.colors.primaryButtonBg;
case "accent":
return p.theme.colors.accentButtonBg;
case "secondary":
return p.theme.colors.secondaryButtonBg;
default:
return "none";
}
}};
color: ${(p) => {
switch (p.variant) {
case "primary":
return p.theme.colors.primaryButtonText;
case "accent":
return p.theme.colors.accentButtonText;
case "secondary":
return p.theme.colors.secondaryButtonText;
case "outline":
return p.theme.colors.secondaryButtonText;
case "link":
return p.theme.colors.accentText;
default:
return p.theme.colors.primaryButtonText;
}
}};
${(p) => {
if (p.variant === "outline") {
return `
border: 1.5px solid ${p.theme.colors.borderColor};
&:hover {
border-color: ${p.theme.colors.accentText};
export const Button = /* @__PURE__ */ StyledButton((props: ButtonProps) => {
const theme = useCustomTheme();
return {
all: "unset",
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: radius.md,
padding: `${fontSize.sm} ${fontSize.sm}`,
fontSize: fontSize.md,
fontWeight: 500,
boxSizing: "border-box",
WebkitTapHighlightColor: "transparent",
lineHeight: "normal",
flexShrink: 0,
transition: "border 200ms ease",
gap: (props.gap && spacing[props.gap]) || 0,
width: props.fullWidth ? "100%" : undefined,
background: (() => {
switch (props.variant) {
case "primary":
return theme.colors.primaryButtonBg;
case "accent":
return theme.colors.accentButtonBg;
case "secondary":
return theme.colors.secondaryButtonBg;
default:
return "none";
}
})(),
color: (() => {
switch (props.variant) {
case "primary":
return theme.colors.primaryButtonText;
case "accent":
return theme.colors.accentButtonText;
case "secondary":
return theme.colors.secondaryButtonText;
case "outline":
return theme.colors.secondaryButtonText;
case "link":
return theme.colors.accentText;
default:
return theme.colors.primaryButtonText;
}
})(),
"&:active": {
transform: "translateY(1px)",
},
"&[disabled]": {
cursor: "not-allowed",
},
...(() => {
if (props.variant === "outline") {
return {
border: `1.5px solid ${theme.colors.borderColor}`,
"&:hover": {
borderColor: theme.colors.accentText,
},
};
}
`;
}
}}
${(p) => {
if (p.variant === "link") {
return `
padding: 0;
&:hover {
color: ${p.theme.colors.primaryText};
}`;
}
}}
if (props.variant === "secondary") {
return {
"&:hover": {
background: theme.colors.secondaryButtonHoverBg,
},
};
}
/* pressed effect */
&:active {
transform: translateY(1px);
}
if (props.variant === "link") {
return {
padding: 0,
"&:hover": {
color: theme.colors.primaryText,
},
};
}
})(),
};
});
&[disabled] {
cursor: not-allowed;
}
`;
export const IconButton = /* @__PURE__ */ StyledButton(() => {
const theme = useCustomTheme();
return {
all: "unset",
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: radius.sm,
WebkitTapHighlightColor: "transparent",
color: theme.colors.secondaryIconColor,
padding: "2px",
transition: "background 200ms ease, color 200ms ease",
"&:hover": {
background: theme.colors.secondaryIconHoverBg,
color: theme.colors.secondaryIconHoverColor,
},
};
});
export const IconButton = styled.button<{
theme?: Theme;
}>`
all: unset;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: ${radius.sm};
-webkit-tap-highlight-color: transparent;
color: ${(p) => p.theme.colors.secondaryIconColor};
padding: 2px;
transition:
background 0.2s ease,
color 0.2s ease;
&:hover {
background: ${(p) => p.theme.colors.secondaryIconHoverBg};
color: ${(p) => p.theme.colors.secondaryIconHoverColor};
}
`;
export const InputButton = styled.button<{ theme?: Theme }>`
all: unset;
display: flex;
align-items: center;
justify-content: center;
border-radius: ${radius.sm};
padding: ${spacing.sm};
cursor: pointer;
-webkit-tap-highlight-color: transparent;
color: ${(p) => p.theme.colors.secondaryText};
&:hover {
color: ${(p) => p.theme.colors.primaryText};
}
&[disabled] {
cursor: not-allowed;
}
`;
export const InputButton = /* @__PURE__ */ StyledButton(() => {
const theme = useCustomTheme();
return {
all: "unset",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: radius.sm,
padding: spacing.sm,
cursor: "pointer",
WebkitTapHighlightColor: "transparent",
color: theme.colors.secondaryText,
"&:hover": {
color: theme.colors.primaryText,
},
"&[disabled]": {
cursor: "not-allowed",
},
};
});
+90 -103
View File
@@ -1,113 +1,100 @@
import { fontSize, radius, Theme, spacing } from "../design-system";
import styled from "@emotion/styled";
import { StyledDiv, StyledInput, StyledLabel } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const Label = styled.label<{
theme?: Theme;
type LabelProps = {
color?: keyof Theme["colors"];
}>`
font-size: ${fontSize.sm};
color: ${(p) => p.theme.colors[p.color || "primaryText"]};
display: block;
font-weight: 500;
`;
};
export const Input = styled.input<{
export const Label = /* @__PURE__ */ StyledLabel((props: LabelProps) => {
const theme = useCustomTheme();
return {
fontSize: fontSize.sm,
color: theme.colors[props.color || "primaryText"],
display: "block",
fontWeight: 500,
};
});
type InputProps = {
variant: "outline" | "transparent";
sm?: boolean;
theme?: Theme;
}>`
font-size: ${fontSize.md};
display: block;
padding: ${(p) => (p.sm ? spacing.sm : fontSize.sm)};
box-sizing: border-box;
width: 100%;
outline: none;
border: none;
border-radius: 6px;
color: ${(p) => p.theme.colors.primaryText};
-webkit-appearance: none;
appearance: none;
background: transparent;
};
&::placeholder {
color: ${(p) => p.theme.colors.secondaryText};
}
box-shadow: 0 0 0 1.5px
${(p) => {
switch (p.variant) {
case "outline":
return p.theme.colors.borderColor;
default:
return "transparent";
}
}};
/* when browser auto-fills the input */
&:-webkit-autofill {
-webkit-text-fill-color: ${(p) => p.theme.colors.primaryText};
-webkit-box-shadow: 0 0 0px 1000px ${(p) => p.theme.colors.inputAutofillBg}
inset !important;
box-shadow: 0 0 0px 1000px ${(p) => p.theme.colors.inputAutofillBg} inset !important;
transition: background-color 5000s ease-in-out 0s;
}
&:-webkit-autofill:focus {
-webkit-box-shadow:
0 0 0px 1000px ${(p) => p.theme.colors.inputAutofillBg} inset,
0 0 0 2px ${(p) => p.theme.colors.accentText} !important;
box-shadow:
0 0 0px 1000px ${(p) => p.theme.colors.inputAutofillBg} inset,
0 0 0 2px ${(p) => p.theme.colors.accentText} !important;
}
&:focus {
box-shadow: 0 0 0 2px ${(p) => p.theme.colors.accentText};
}
/* show overflow ellipsis for long text - but not if it's a type="password" */
&:not([type="password"]) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&[data-error="true"] {
box-shadow: 0 0 0 2px ${(p) => p.theme.colors.danger} !important;
}
&[disabled] {
cursor: not-allowed;
}
&[type="number"]::-webkit-outer-spin-button,
&[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
&[type="number"] {
appearance: none;
-moz-appearance: textfield;
}
`;
export const Input = /* @__PURE__ */ StyledInput((props: InputProps) => {
const theme = useCustomTheme();
return {
fontSize: fontSize.md,
display: "block",
padding: props.sm ? spacing.sm : fontSize.sm,
boxSizing: "border-box",
width: "100%",
outline: "none",
border: "none",
borderRadius: "6px",
color: theme.colors.primaryText,
WebkitAppearance: "none",
appearance: "none",
background: "transparent",
"&::placeholder": {
color: theme.colors.secondaryText,
},
boxShadow: `0 0 0 1.5px ${
props.variant === "outline" ? theme.colors.borderColor : "transparent"
}`,
"&:-webkit-autofill": {
WebkitTextFillColor: theme.colors.primaryText,
WebkitBoxShadow: `0 0 0px 1000px ${theme.colors.inputAutofillBg} inset !important`,
boxShadow: `0 0 0px 1000px ${theme.colors.inputAutofillBg} inset !important`,
transition: "background-color 5000s ease-in-out 0s",
},
"&:-webkit-autofill:focus": {
WebkitBoxShadow: `0 0 0px 1000px ${theme.colors.inputAutofillBg} inset, 0 0 0 2px ${theme.colors.accentText} !important`,
boxShadow: `0 0 0px 1000px ${theme.colors.inputAutofillBg} inset, 0 0 0 2px ${theme.colors.accentText} !important`,
},
"&:focus": {
boxShadow: `0 0 0 2px ${theme.colors.accentText}`,
},
"&:not([type='password'])": {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
"&[data-error='true']": {
boxShadow: `0 0 0 2px ${theme.colors.danger} !important`,
},
"&[disabled]": {
cursor: "not-allowed",
},
"&[type='number']::-webkit-outer-spin-button, &[type='number']::-webkit-inner-spin-button":
{
WebkitAppearance: "none",
margin: 0,
},
"&[type='number']": {
appearance: "none",
MozAppearance: "textfield",
},
};
});
// for rendering a input and a button side by side
export const InputContainer = styled.div<{ theme?: Theme }>`
display: flex;
border-radius: ${radius.sm};
box-shadow: 0 0 0px 1.5px ${(p) => p.theme.colors.borderColor};
/* show focus ring on container instead of input */
&:focus-within {
box-shadow: 0 0 0px 2px ${(p) => p.theme.colors.accentText};
}
input:focus {
box-shadow: none;
}
/* show error ring on container instead of input */
&[data-error="true"] {
box-shadow: 0 0 0px 2px ${(p) => p.theme.colors.danger};
}
`;
export const InputContainer = /* @__PURE__ */ StyledDiv(() => {
const theme = useCustomTheme();
return {
display: "flex",
borderRadius: radius.sm,
boxShadow: `0 0 0px 1.5px ${theme.colors.borderColor}`,
"&:focus-within": {
boxShadow: `0 0 0px 2px ${theme.colors.accentText}`,
},
"input:focus": {
boxShadow: "none",
},
// show error ring on container instead of input
"&[data-error='true']": {
boxShadow: `0 0 0px 2px ${theme.colors.danger}`,
},
};
});
+58 -44
View File
@@ -1,38 +1,47 @@
import { type Theme, fontSize, media } from "../design-system";
import { fontSize, media } from "../design-system";
import { iconSize } from "../design-system";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
import { StyledAnchor, StyledH2, StyledP } from "../design-system/elements";
import { IconButton } from "./buttons";
import styled from "@emotion/styled";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
export const ModalTitle = /* @__PURE__ */ styled.h2<{
theme?: Theme;
centerOnMobile?: boolean;
}>`
margin: 0;
font-weight: 600;
font-size: ${fontSize.lg};
color: ${(p) => p.theme.colors.primaryText};
line-height: 1.3;
text-align: left;
${media.mobile} {
text-align: ${(p) => (p.centerOnMobile ? "center" : "left")};
}
`;
export const ModalTitle = /* @__PURE__ */ StyledH2(
(props: { centerOnMobile?: boolean }) => {
const theme = useCustomTheme();
return {
margin: 0,
fontWeight: 600,
fontSize: fontSize.lg,
color: theme.colors.primaryText,
lineHeight: 1.3,
textAlign: "left",
[media.mobile]: {
textAlign: props.centerOnMobile ? "center" : "left",
},
};
},
);
export const ModalDescription = styled.p<{
theme?: Theme;
type ModalDescriptionProps = {
centerOnMobile?: boolean;
sm?: boolean;
}>`
all: unset;
display: block;
font-size: ${(p) => (p.sm ? fontSize.sm : fontSize.md)};
color: ${(p) => p.theme.colors.secondaryText};
line-height: 1.5;
${media.mobile} {
text-align: ${(p) => (p.centerOnMobile ? "center" : "left")};
}
`;
};
export const ModalDescription = /* @__PURE__ */ StyledP(
(props: ModalDescriptionProps) => {
const theme = useCustomTheme();
return {
all: "unset",
display: "block",
fontSize: props.sm ? fontSize.sm : fontSize.md,
color: theme.colors.secondaryText,
lineHeight: 1.5,
[media.mobile]: {
textAlign: props.centerOnMobile ? "center" : "left",
},
};
},
);
export const BackButton: React.FC<{
onClick: () => void;
@@ -49,19 +58,24 @@ export const BackButton: React.FC<{
);
};
export const HelperLink = styled.a<{ theme?: Theme; md?: boolean }>`
all: unset;
cursor: pointer;
color: ${(p) => p.theme.colors.accentText};
font-size: ${(p) => (p.md ? fontSize.md : fontSize.sm)};
text-decoration: none;
display: block;
line-height: 1.5;
${media.mobile} {
text-align: center;
}
&:hover {
color: ${(p) => p.theme.colors.primaryText};
text-decoration: none;
}
`;
export const HelperLink = /* @__PURE__ */ StyledAnchor(
(props: { md?: boolean }) => {
const theme = useCustomTheme();
return {
all: "unset",
cursor: "pointer",
color: theme.colors.accentText,
fontSize: props.md ? fontSize.md : fontSize.sm,
textDecoration: "none",
display: "block",
lineHeight: 1.5,
[media.mobile]: {
textAlign: "center",
},
"&:hover": {
color: theme.colors.primaryText,
textDecoration: "none",
},
};
},
);
+42 -32
View File
@@ -1,7 +1,8 @@
import styled from "@emotion/styled";
import { fontSize, Theme } from "../design-system";
import { StyledAnchor, StyledSpan } from "../design-system/elements";
import { useCustomTheme } from "../design-system/CustomThemeProvider";
export const Text = styled.span<{
type TextProps = {
theme?: Theme;
color?: keyof Theme["colors"];
center?: boolean;
@@ -10,21 +11,26 @@ export const Text = styled.span<{
weight?: 400 | 500 | 600 | 700;
multiline?: boolean;
balance?: boolean;
}>`
font-size: ${(p) => fontSize[p.size || "md"]};
color: ${(p) => p.theme.colors[p.color || "secondaryText"]};
margin: 0;
display: ${(p) => (p.inline ? "inline" : "block")};
font-weight: ${(p) => p.weight || 500};
line-height: ${(p) => (p.multiline ? 1.5 : "normal")};
${(p) => (p.center ? `text-align: center;` : "")};
text-wrap: ${(p) => (p.balance ? "balance" : "inherit")};
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
`;
};
export const Link = styled.a<{
export const Text = /* @__PURE__ */ StyledSpan((p: TextProps) => {
const theme = useCustomTheme();
return {
fontSize: fontSize[p.size || "md"],
color: theme.colors[p.color || "secondaryText"],
margin: 0,
display: p.inline ? "inline" : "block",
fontWeight: p.weight || 500,
lineHeight: p.multiline ? 1.5 : "normal",
textAlign: p.center ? "center" : "left",
textWrap: p.balance ? "balance" : "inherit",
maxWidth: "100%",
overflow: "hidden",
textOverflow: "ellipsis",
};
});
type LinkProps = {
theme?: Theme;
size?: keyof typeof fontSize;
weight?: 400 | 500 | 600 | 700;
@@ -32,20 +38,24 @@ export const Link = styled.a<{
center?: boolean;
color?: keyof Theme["colors"];
hoverColor?: keyof Theme["colors"];
}>`
all: unset;
cursor: pointer;
color: ${(p) => p.theme.colors[p.color || "accentText"]};
font-size: ${(p) => fontSize[p.size || "md"]};
text-decoration: none;
text-align: ${(p) => (p.center ? "center" : "left")};
display: ${(p) => (p.inline ? "inline" : "block")};
font-weight: ${(p) => p.weight || 500};
line-height: normal;
transition: color 0.2s ease;
};
&:hover {
color: ${(p) => p.theme.colors[p.hoverColor || "primaryText"]};
text-decoration: none;
}
`;
export const Link = /* @__PURE__ */ StyledAnchor((p: LinkProps) => {
const theme = useCustomTheme();
return {
all: "unset",
cursor: "pointer",
color: theme.colors[p.color || "accentText"],
fontSize: fontSize[p.size || "md"],
textDecoration: "none",
textAlign: p.center ? "center" : "left",
display: p.inline ? "inline" : "block",
fontWeight: p.weight || 500,
lineHeight: "normal",
transition: "color 0.2s ease",
"&:hover": {
color: theme.colors[p.hoverColor || "primaryText"],
textDecoration: "none",
},
};
});
@@ -1,5 +1,7 @@
import { ThemeProvider } from "@emotion/react";
import { Theme, darkThemeObj, lightThemeObj } from "./index";
import { createContext, useContext } from "react";
const CustomThemeCtx = /* @__PURE__ */ createContext(darkThemeObj);
export function CustomThemeProvider(props: {
children: React.ReactNode;
@@ -14,5 +16,13 @@ export function CustomThemeProvider(props: {
themeObj = theme;
}
return <ThemeProvider theme={themeObj}>{children}</ThemeProvider>;
return (
<CustomThemeCtx.Provider value={themeObj}>
{children}
</CustomThemeCtx.Provider>
);
}
export function useCustomTheme() {
return useContext(CustomThemeCtx);
}
@@ -0,0 +1,14 @@
import styled from "@emotion/styled";
export const StyledDiv = /* @__PURE__ */ styled.div;
export const StyledSvg = /* @__PURE__ */ styled.svg;
export const StyledCircle = /* @__PURE__ */ styled.circle;
export const StyledSpan = /* @__PURE__ */ styled.span;
export const StyledAnchor = /* @__PURE__ */ styled.a;
export const StyledButton = /* @__PURE__ */ styled.button;
export const StyledLabel = /* @__PURE__ */ styled.label;
export const StyledInput = /* @__PURE__ */ styled.input;
export const StyledH2 = /* @__PURE__ */ styled.h2;
export const StyledP = /* @__PURE__ */ styled.p;
export const StyledUl = /* @__PURE__ */ styled.ul;
export const StyledSelect = /* @__PURE__ */ styled.select;
+13 -7
View File
@@ -142,26 +142,32 @@ export const shadow = {
xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
};
export function lightTheme(overrides: ThemeOverrides): Theme {
export function lightTheme(overrides?: ThemeOverrides): Theme {
if (!overrides) {
return lightThemeObj;
}
return applyThemeOverrides(lightThemeObj, overrides);
}
export function darkTheme(overrides: ThemeOverrides): Theme {
export function darkTheme(overrides?: ThemeOverrides): Theme {
if (!overrides) {
return darkThemeObj;
}
return applyThemeOverrides(darkThemeObj, overrides);
}
export function applyThemeOverrides(
baseTheme: Theme,
themeOverides: ThemeOverrides,
themeOverrides: ThemeOverrides,
): Theme {
const theme = { ...baseTheme };
if (themeOverides.colors) {
theme.colors = { ...theme.colors, ...themeOverides.colors };
if (themeOverrides.colors) {
theme.colors = { ...theme.colors, ...themeOverrides.colors };
}
if (themeOverides.fontFamily) {
theme.fontFamily = themeOverides.fontFamily;
if (themeOverrides.fontFamily) {
theme.fontFamily = themeOverrides.fontFamily;
}
return theme;
@@ -1,7 +1,7 @@
import { Popover } from "../../../components/Popover";
import { Spinner } from "../../../components/Spinner";
import { Button } from "../../../components/buttons";
import { Theme, ThemeObjectOrType } from "../../../design-system";
import { Theme } from "../../../design-system";
import {
ConnectWallet,
ConnectWalletProps,
@@ -20,8 +20,10 @@ import type { SmartContract } from "@thirdweb-dev/sdk";
import type { CallOverrides, ContractInterface } from "ethers";
import { PropsWithChildren, useState } from "react";
import invariant from "tiny-invariant";
import { CustomThemeProvider } from "../../../design-system/CustomThemeProvider";
import { useTheme } from "@emotion/react";
import {
CustomThemeProvider,
useCustomTheme,
} from "../../../design-system/CustomThemeProvider";
import { useTWLocale } from "../../providers/locale-provider";
type ActionFn = (contract: SmartContract) => any;
@@ -106,7 +108,7 @@ export const Web3Button = <TAction extends ActionFn>(
const requiresConfirmation = !useIsHeadlessWallet();
const { contract } = useContract(contractAddress, contractAbi || "custom");
const contextTheme = useTheme() as ThemeObjectOrType;
const contextTheme = useCustomTheme();
const theme = props.theme || contextTheme || "dark";
const locale = useTWLocale();
@@ -2,6 +2,7 @@ import {
useCreateWalletInstance,
useSetConnectedWallet,
useSetConnectionStatus,
useWallet,
useWalletContext,
useWallets,
} from "@thirdweb-dev/react-core";
@@ -10,8 +11,13 @@ import {
type AuthParams,
EmbeddedWallet,
} from "@thirdweb-dev/wallets";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import { embeddedWallet } from "../../../wallet/wallets/embeddedWallet/embeddedWallet";
import {
UseQueryResult,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
/**
* Hook to authenticate and connect to an embedded wallet
@@ -72,3 +78,30 @@ export function useEmbeddedWallet() {
sendVerificationEmail,
};
}
export function useEmbeddedWalletUserEmail(): UseQueryResult<
string | undefined
> {
const wallet = useWallet();
const queryClient = useQueryClient();
const emailQuery = useQuery<string | undefined, string>(
[wallet?.walletId, "embeddedWallet-email"],
() => {
if (wallet && wallet.walletId === walletIds.embeddedWallet) {
return (wallet as EmbeddedWallet).getEmail();
}
},
{
retry: false,
enabled: wallet?.walletId === walletIds.embeddedWallet,
},
);
// Invalidate the query when the wallet changes
useEffect(() => {
queryClient.invalidateQueries([wallet?.walletId, "embeddedWallet-email"]);
}, [wallet, queryClient]);
return emailQuery;
}
+5 -2
View File
@@ -117,6 +117,7 @@ export function enDefault() {
approveTransactionInSafe: "Approve transaction in Safe",
tryAgain: "Try Again",
failedToSignIn: "Failed to Sign in",
inProgress: "Awaiting Confirmation",
},
},
},
@@ -197,14 +198,16 @@ export function enDefault() {
},
embeddedWallet: {
signInWithGoogle: "Sign in with Google",
signInWithFacebook: "Sign in with Facebook",
signInWithApple: "Sign in with Apple",
emailPlaceholder: "Enter your email address",
submitEmail: "Continue",
signIn: "Sign in",
emailRequired: "Email address is required",
invalidEmail: "Invalid email address",
googleLoginScreen: {
socialLoginScreen: {
title: "Sign in",
instruction: "Select your Google account in the pop-up",
instruction: "Sign into your account in the pop-up",
failed: "Failed to sign in",
retry: "Retry",
},
+5 -2
View File
@@ -120,6 +120,7 @@ export function esDefault(): ThirdwebLocale {
approveTransactionInSafe: "Aprobar transacción en Safe",
tryAgain: "Intentar de nuevo",
failedToSignIn: "Error al iniciar sesión",
inProgress: "Esperando confirmación",
},
},
},
@@ -201,14 +202,16 @@ export function esDefault(): ThirdwebLocale {
},
embeddedWallet: {
signInWithGoogle: "Iniciar sesión con Google",
signInWithFacebook: "Iniciar sesión con Facebook",
signInWithApple: "Iniciar sesión con Apple",
emailPlaceholder: "Ingresa tu dirección de correo electrónico",
submitEmail: "Continuar",
signIn: "Iniciar sesión",
emailRequired: "Se requiere dirección de correo electrónico",
invalidEmail: "Dirección de correo electrónico inválida",
googleLoginScreen: {
socialLoginScreen: {
title: "Iniciar sesión",
instruction: "Selecciona tu cuenta de Google en la ventana emergente",
instruction: "Inicie sesión en su cuenta en la ventana abierta",
failed: "Error al iniciar sesión",
retry: "Reintentar",
},
+5 -2
View File
@@ -119,6 +119,7 @@ export function jaDefault(): ThirdwebLocale {
approveTransactionInSafe: "Safeで取引を承認",
tryAgain: "再試行",
failedToSignIn: "サインインに失敗しました",
inProgress: "確認待ち",
},
},
},
@@ -200,14 +201,16 @@ export function jaDefault(): ThirdwebLocale {
},
embeddedWallet: {
signInWithGoogle: "Googleでサインイン",
signInWithFacebook: "Facebookでサインイン",
signInWithApple: "Appleでサインイン",
emailPlaceholder: "メールアドレスを入力してください",
submitEmail: "続ける",
emailRequired: "メールアドレスが必要です",
invalidEmail: "無効なメールアドレス",
signIn: "サインイン",
googleLoginScreen: {
socialLoginScreen: {
title: "サインイン",
instruction: "ポップアップ内でGoogleアカウントを選択してください",
instruction: "ポップアップウィンドウでアカウントにサインインします", // TODO: check if this is correct
failed: "サインインに失敗しました",
retry: "再試行",
},
-26
View File
@@ -1,26 +0,0 @@
import { utils } from "ethers";
export function shortenString(str: string, extraShort?: true) {
return `${str.substring(0, extraShort ? 4 : 6)}...${str.substring(
str.length - (extraShort ? 3 : 4),
)}`;
}
export function shortenAddress(address: string, extraShort?: true): string {
try {
const formattedAddress = utils.getAddress(address);
return shortenString(formattedAddress, extraShort);
} catch {
return address;
}
}
export function shortenIfAddress(
address?: string | null | false,
extraShort?: true,
): string {
if (typeof address === "string" && address.length > 0) {
return shortenAddress(address, extraShort);
}
return address || "";
}
@@ -5,8 +5,8 @@ export type DeepPartial<T extends Record<string, any>> = {
/**
* Create a new object with given overrides object applied on top of given base object without mutating the base and overrides object
*
* @param defaultObj the object to use as the base object
* @param overrides the object to use as the overrides
* @param defaultObj - the object to use as the base object
* @param overrides - the object to use as the overrides
*/
export function immutableOverride<T extends Record<string, any>>(
defaultObj: T,

Some files were not shown because too many files have changed in this diff Show More