Skip to content

Commit

Permalink
Merge pull request #616 from alleslabs/feat/saved-accounts-table
Browse files Browse the repository at this point in the history
Feat/saved accounts table
  • Loading branch information
songwongtp committed Nov 9, 2023
2 parents c0d3dcf + 89315cc commit 7770e13
Show file tree
Hide file tree
Showing 16 changed files with 499 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#616](https://github.com/alleslabs/celatone-frontend/pull/616) Add table for saved accounts and add save, remove and edit modal
- [#613](https://github.com/alleslabs/celatone-frontend/pull/613) Add saved accounts modal ui
- [#611](https://github.com/alleslabs/celatone-frontend/pull/611) Add saved accounts page
- [#608](https://github.com/alleslabs/celatone-frontend/pull/608) Deprecate stone 10
Expand Down
12 changes: 11 additions & 1 deletion src/lib/app-provider/contexts/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
useCodeStore,
useContractStore,
usePublicProjectStore,
useAccountStore,
} from "lib/providers/store";
import { formatUserKey } from "lib/utils";

Expand Down Expand Up @@ -51,6 +52,7 @@ export const AppProvider = observer(({ children }: AppProviderProps) => {
const { setCodeUserKey, isCodeUserKeyExist } = useCodeStore();
const { setContractUserKey, isContractUserKeyExist } = useContractStore();
const { setProjectUserKey, isProjectUserKeyExist } = usePublicProjectStore();
const { setAccountUserKey, isAccountUserKeyExist } = useAccountStore();
const { setModalTheme } = useModalTheme();

const [currentChainName, setCurrentChainName] = useState<string>();
Expand Down Expand Up @@ -85,8 +87,15 @@ export const AppProvider = observer(({ children }: AppProviderProps) => {
setCodeUserKey(userKey);
setContractUserKey(userKey);
setProjectUserKey(userKey);
setAccountUserKey(userKey);
}
}, [currentChainName, setCodeUserKey, setContractUserKey, setProjectUserKey]);
}, [
currentChainName,
setCodeUserKey,
setContractUserKey,
setProjectUserKey,
setAccountUserKey,
]);

// Disable "Leave page" alert
useEffect(() => {
Expand All @@ -113,6 +122,7 @@ export const AppProvider = observer(({ children }: AppProviderProps) => {
!isCodeUserKeyExist() ||
!isContractUserKeyExist() ||
!isProjectUserKeyExist() ||
!isAccountUserKeyExist() ||
!currentChainId
)
return <LoadingOverlay />;
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/json-schema/EditSchemaButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ export const EditSchemaButtons = ({
trigger={
<Tooltip label="Delete your attached schema">
<IconButton
variant="ghost-gray"
variant="ghost-gray-icon"
size="sm"
color="gray.600"
icon={<CustomIcon name="delete" boxSize={4} />}
aria-label="delete schema"
/>
Expand Down
16 changes: 11 additions & 5 deletions src/lib/components/modal/ActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function ActionModal({
otherVariant = "outline-primary",
noCloseButton = false,
closeOnOverlayClick = true,
buttonRemark = "Information will be stored locally on your device.",
buttonRemark,
}: ActionModalProps) {
const { isOpen, onOpen, onClose } = useDisclosure();

Expand Down Expand Up @@ -92,7 +92,11 @@ export function ActionModal({
<Box w="full">
<Flex alignItems="center" gap={3}>
<CustomIcon name={icon} color={iconColor} boxSize={5} />
<Heading as="h5" variant={{ base: "h6", md: "h5" }}>
<Heading
as="h5"
variant={{ base: "h6", md: "h5" }}
wordBreak="break-word"
>
{title}
</Heading>
</Flex>
Expand Down Expand Up @@ -131,9 +135,11 @@ export function ActionModal({
{otherBtnTitle}
</Button>
</Flex>
<Text variant="body3" color="text.dark">
{buttonRemark}
</Text>
{buttonRemark && (
<Text variant="body3" color="text.dark">
{buttonRemark}
</Text>
)}
</Flex>
</ModalFooter>
</ModalContent>
Expand Down
117 changes: 117 additions & 0 deletions src/lib/components/modal/account/EditSavedAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Flex, Text } from "@chakra-ui/react";
import { useCallback, useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";

import { ActionModal } from "../ActionModal";
import { useCelatoneApp } from "lib/app-provider";
import { ExplorerLink } from "lib/components/ExplorerLink";
import { ControllerInput, ControllerTextarea } from "lib/components/forms";
import { useGetMaxLengthError, useHandleAccountSave } from "lib/hooks";
import type { AccountLocalInfo } from "lib/stores/account";

import type { SaveAccountDetail } from "./SaveNewAccount";

interface EditSavedAccountModalProps {
account: AccountLocalInfo;
triggerElement: JSX.Element;
}

export const EditSavedAccountModal = ({
account,
triggerElement,
}: EditSavedAccountModalProps) => {
const { constants } = useCelatoneApp();
const getMaxLengthError = useGetMaxLengthError();
const defaultValues = useMemo(() => {
return {
address: account.address,
name: account.name ?? "",
description: account.description ?? "",
};
}, [account]);

const {
control,
watch,
reset,
formState: { errors },
} = useForm<SaveAccountDetail>({
defaultValues,
mode: "all",
});

const addressState = watch("address");
const nameState = watch("name");
const descriptionState = watch("description");

const resetForm = useCallback(
() => reset(defaultValues),
[defaultValues, reset]
);

useEffect(() => {
resetForm();
}, [resetForm]);

const handleSave = useHandleAccountSave({
title: `Updated Saved Account!`,
address: addressState,
name: nameState,
description: descriptionState,
actions: () => {},
});

return (
<ActionModal
title="Edit account"
icon="edit-solid"
headerContent={
<Flex gap={4} alignItems="center" pt={6}>
<Text variant="body2" fontWeight={500} color="text.dark">
Account Address
</Text>
<ExplorerLink value={account.address} type="user_address" />
</Flex>
}
trigger={triggerElement}
mainBtnTitle="Save"
mainAction={handleSave}
disabledMain={!!errors.name || !!errors.description}
otherBtnTitle="Cancel"
otherAction={resetForm}
closeOnOverlayClick={false}
>
<Flex direction="column" gap={6}>
<ControllerInput
name="name"
control={control}
label="Account Name"
variant="fixed-floating"
// placeholder="ex. Celatone Account 1"
labelBgColor="gray.900"
rules={{
maxLength: constants.maxAccountNameLength,
}}
error={
errors.name && getMaxLengthError(nameState.length, "account_name")
}
/>
<ControllerTextarea
name="description"
control={control}
label="Account Description"
// placeholder="Help understanding what this account do ..."
variant="fixed-floating"
labelBgColor="gray.900"
rules={{
maxLength: constants.maxAccountDescriptionLength,
}}
error={
errors.description &&
getMaxLengthError(descriptionState.length, "account_desc")
}
/>
</Flex>
</ActionModal>
);
};
84 changes: 84 additions & 0 deletions src/lib/components/modal/account/RemoveSavedAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
useToast,
Text,
chakra,
IconButton,
Highlight,
} from "@chakra-ui/react";
import { useCallback } from "react";

import { ActionModal } from "../ActionModal";
import { CustomIcon } from "lib/components/icon";
import { useAccountStore } from "lib/providers/store";
import type { AccountLocalInfo } from "lib/stores/account";
import { truncate } from "lib/utils";

const StyledIconButton = chakra(IconButton, {
baseStyle: {
display: "flex",
alignItems: "center",
fontSize: "22px",
borderRadius: "36px",
},
});

interface RemoveSavedAccountModalProps {
account: AccountLocalInfo;
trigger?: JSX.Element;
}
export function RemoveSavedAccountModal({
account,
trigger = (
<StyledIconButton
icon={<CustomIcon name="delete" />}
variant="ghost-gray"
/>
),
}: RemoveSavedAccountModalProps) {
const toast = useToast();
const { removeSavedAccount } = useAccountStore();
const handleRemove = useCallback(() => {
removeSavedAccount(account.address);

toast({
title: `Removed \u2018${account.name}\u2019 from Saved Codes`,
status: "success",
duration: 5000,
isClosable: false,
position: "bottom-right",
icon: <CustomIcon name="check-circle-solid" color="success.main" />,
});
}, [removeSavedAccount, account.address, account.name, toast]);
return (
<ActionModal
title={
account.name
? `Remove account \u2018${account.name}\u2019?`
: `Remove account \u2018${truncate(account.address)}\u2019 ?`
}
icon="delete-solid"
iconColor="error.light"
mainVariant="error"
mainBtnTitle="Yes, Remove It"
mainAction={handleRemove}
otherBtnTitle="No, Keep It"
trigger={trigger}
>
<Text>
<Highlight
query={[
account.name ?? "",
truncate(account.address),
"Saved Accounts",
]}
styles={{ fontWeight: "bold", color: "inherit" }}
>
{`This action will remove \u2018${
account.name ?? truncate(account.address)
}\u2019 from Saved Accounts.
You can save this address again later, but you will need to add its new account name and description.`}
</Highlight>
</Text>
</ActionModal>
);
}
8 changes: 4 additions & 4 deletions src/lib/components/modal/account/SaveNewAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useGetMaxLengthError, useHandleAccountSave } from "lib/hooks";
import { useAccountStore } from "lib/providers/store";
import type { Addr } from "lib/types";

interface SaveNewAccountDetail {
export interface SaveAccountDetail {
address: Addr;
name: string;
description: string;
Expand All @@ -31,7 +31,7 @@ export function SaveNewAccountModal({ buttonProps }: SaveNewAccountModalProps) {
const getMaxLengthError = useGetMaxLengthError();
const { isAccountSaved } = useAccountStore();

const defaultValues: SaveNewAccountDetail = {
const defaultValues: SaveAccountDetail = {
address: "" as Addr,
name: "",
description: "",
Expand All @@ -42,7 +42,7 @@ export function SaveNewAccountModal({ buttonProps }: SaveNewAccountModalProps) {
watch,
reset,
formState: { errors },
} = useForm<SaveNewAccountDetail>({
} = useForm<SaveAccountDetail>({
defaultValues,
mode: "all",
});
Expand Down Expand Up @@ -161,7 +161,7 @@ export function SaveNewAccountModal({ buttonProps }: SaveNewAccountModalProps) {
}}
error={
errors.description &&
getMaxLengthError(descriptionState.length, "contract_desc")
getMaxLengthError(descriptionState.length, "account_desc")
}
/>
</Flex>
Expand Down
24 changes: 24 additions & 0 deletions src/lib/components/state/ZeroState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CustomIcon } from "../icon";
import { useInternalNavigate } from "lib/app-provider";
import { SaveNewContractModal } from "lib/components/modal/contract";
import { ADMIN_SPECIAL_SLUG, INSTANTIATED_LIST_NAME } from "lib/data";
import { SaveAccountButton } from "lib/pages/saved-accounts/components/SaveAccountButton";
import type { LVPair } from "lib/types";
import { formatSlugName } from "lib/utils";

Expand Down Expand Up @@ -86,3 +87,26 @@ export const ZeroState = ({ list, isReadOnly }: ZeroStateProps) => {
</Flex>
);
};

export const AccountZeroState = () => (
<Flex
alignItems="center"
gap={4}
color="text.dark"
direction="column"
my={12}
py={8}
borderY="1px solid"
borderColor="gray.700"
>
<StateImage imageVariant="empty" />
<Flex align="center">You don’t have any saved accounts.</Flex>
<Flex align="center" gap={4}>
Save an account and entering the account address through
<SaveAccountButton />
</Flex>
<Flex align="center">
Saved accounts are stored locally on your device..
</Flex>
</Flex>
);
28 changes: 28 additions & 0 deletions src/lib/components/table/accounts/SavedAccountsNameCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { EditableCell } from "../EditableCell";
import { useCelatoneApp } from "lib/app-provider";
import { useHandleAccountSave } from "lib/hooks";
import type { AccountLocalInfo } from "lib/stores/account";

interface SaveAccountsNameCellProps {
account: AccountLocalInfo;
}
export const SaveAccountsNameCell = ({
account,
}: SaveAccountsNameCellProps) => {
const { constants } = useCelatoneApp();
const onSave = useHandleAccountSave({
title: "Changed name successfully!",
address: account.address,
name: account.name ?? "",
description: account.description,
actions: () => {},
});
return (
<EditableCell
initialValue={account.name}
defaultValue="Untitled Name"
maxLength={constants.maxAccountNameLength}
onSave={onSave}
/>
);
};
Loading

3 comments on commit 7770e13

@vercel
Copy link

@vercel vercel bot commented on 7770e13 Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7770e13 Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7770e13 Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.