13 | ),
14 |
15 | // Will possibly be used for filtering. See OptionValue for a comment on
16 | // the naming of this prop.
17 | "data-picker-category": category
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/addresses/picker/NameHint.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { Typography } from "antd";
5 |
6 | import { useTranslation, Trans } from "react-i18next";
7 |
8 | import { KristName } from "@api/types";
9 | import { ContextualAddress } from "@comp/addresses/ContextualAddress";
10 |
11 | const { Text } = Typography;
12 |
13 | interface Props {
14 | name?: KristName;
15 | }
16 |
17 | export function NameHint({ name }: Props): JSX.Element {
18 | const { t } = useTranslation();
19 |
20 | return
21 | {name
22 | ? (
23 |
24 | Owner:
25 |
26 | )
27 | : {t("addressPicker.nameHintNotFound")}}
28 | ;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/addresses/picker/VerifiedHint.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { VerifiedAddress, VerifiedAddressLink } from "@comp/addresses/VerifiedAddress";
5 |
6 | interface Props {
7 | address: string;
8 | verified: VerifiedAddress;
9 | }
10 |
11 | export function VerifiedHint({ address, verified }: Props): JSX.Element {
12 | return
13 |
14 | ;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/addresses/picker/WalletHint.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useTranslation, Trans } from "react-i18next";
5 |
6 | import { Wallet } from "@wallets";
7 | import { ContextualAddress } from "@comp/addresses/ContextualAddress";
8 |
9 | interface Props {
10 | wallet: Wallet;
11 | }
12 |
13 | export function WalletHint({ wallet }: Props): JSX.Element {
14 | const { t } = useTranslation();
15 |
16 | return
17 |
18 | Owner:
19 |
20 | ;
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/auth/AuthMasterPasswordModal.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useRef } from "react";
5 | import { Modal, Input } from "antd";
6 |
7 | import { useTFns } from "@utils/i18n";
8 |
9 | import { useAuthForm } from "./AuthForm";
10 |
11 | interface Props {
12 | visible: boolean;
13 | encrypt?: boolean;
14 | onCancel: () => void;
15 | onSubmit: () => void;
16 | }
17 |
18 | export function AuthMasterPasswordModal({
19 | visible,
20 | encrypt,
21 | onCancel,
22 | onSubmit
23 | }: Props): JSX.Element {
24 | const { t, tStr } = useTFns("masterPassword.");
25 | const inputRef = useRef(null);
26 |
27 | const { form, submit, reset } = useAuthForm(encrypt, onSubmit, inputRef);
28 |
29 | return { reset(); onCancel(); }}
38 | onOk={submit}
39 | >
40 | {form}
41 | ;
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/auth/AuthorisedAction.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import React, { FC, useContext } from "react";
5 | import { message } from "antd";
6 |
7 | import i18n from "@utils/i18n";
8 |
9 | import { AuthContext, PromptAuthFn } from "./AuthContext";
10 |
11 | interface Props {
12 | encrypt?: boolean;
13 | onAuthed?: () => void;
14 | children: React.ReactNode;
15 | }
16 |
17 | export const AuthorisedAction: FC = ({ encrypt, onAuthed, children }) => {
18 | const promptAuth = useContext(AuthContext);
19 |
20 | // This is used to pass the 'onClick' prop down to the child. The child MUST
21 | // support the onClick prop.
22 | // NOTE: If the child is a custom component, make sure it passes `...props`
23 | // down to its child.
24 | const child = React.Children.only(children) as React.ReactElement;
25 |
26 | // Wrap the single child element and override onClick
27 | return React.cloneElement(child, { onClick: (e: MouseEvent) => {
28 | e.preventDefault();
29 | promptAuth?.(encrypt, onAuthed);
30 | }});
31 | };
32 |
33 | export const useAuth = (): PromptAuthFn =>
34 | useContext(AuthContext) || (() =>
35 | message.error(i18n.t("masterPassword.earlyAuthError")));
36 |
--------------------------------------------------------------------------------
/src/components/auth/FakeUsernameInput.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { Input } from "antd";
5 |
6 | /// Fake username field for master password inputs, to trick autofill.
7 | export function FakeUsernameInput(): JSX.Element {
8 | return ;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/auth/MasterPasswordInput.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { Input } from "antd";
5 |
6 | interface Props {
7 | inputRef?: React.Ref;
8 | placeholder: string;
9 | tabIndex?: number;
10 | autoFocus?: boolean;
11 | }
12 |
13 | export function getMasterPasswordInput({ inputRef, placeholder, tabIndex, autoFocus }: Props): JSX.Element {
14 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/auth/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 |
5 | export * from "./AuthorisedAction";
6 |
--------------------------------------------------------------------------------
/src/components/krist/KristSymbol.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import Icon from "@ant-design/icons";
5 |
6 | export const KristSymbolSvg = (): JSX.Element => (
7 |
10 | );
11 | export const KristSymbol = (props: any): JSX.Element =>
12 | ;
13 |
--------------------------------------------------------------------------------
/src/components/krist/KristValue.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .krist-value {
7 | font-size: 100%;
8 |
9 | white-space: nowrap;
10 |
11 | .anticon svg {
12 | /* Hack to make it consistent with Lato */
13 | position: relative;
14 | bottom: 0.125em;
15 | font-size: 0.75em;
16 | color: @text-color-secondary;
17 | }
18 |
19 | .krist-value-amount {
20 | font-weight: bold;
21 | }
22 |
23 | .krist-currency-long {
24 | color: @text-color-secondary;
25 |
26 | &::before {
27 | content: " ";
28 | }
29 | }
30 |
31 | &.krist-value-green {
32 | color: @kw-green;
33 |
34 | .anticon svg, .krist-currency-long {
35 | color: fade(@kw-green, 75%);
36 | }
37 | }
38 |
39 | &.krist-value-zero {
40 | color: @text-color-secondary;
41 |
42 | .anticon svg, .krist-currency-long {
43 | color: fade(@text-color-secondary, 60%);
44 | }
45 | }
46 | }
47 |
48 | // The currency symbol appears too dark when inside a button
49 | .ant-btn.ant-btn-primary .krist-value .anticon svg {
50 | color: fade(@text-color, 70%);
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/krist/KristValue.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import React from "react";
5 | import classNames from "classnames";
6 |
7 | import { useSelector } from "react-redux";
8 | import { RootState } from "@store";
9 |
10 | import { KristSymbol } from "./KristSymbol";
11 |
12 | import "./KristValue.less";
13 |
14 | interface OwnProps {
15 | icon?: React.ReactNode;
16 | value?: number;
17 | long?: boolean;
18 | hideNullish?: boolean;
19 | green?: boolean;
20 | highlightZero?: boolean;
21 | }
22 | type Props = React.HTMLProps & OwnProps;
23 |
24 | export const KristValue = ({
25 | icon,
26 | value,
27 | long,
28 | hideNullish,
29 | green,
30 | highlightZero,
31 | ...props
32 | }: Props): JSX.Element | null => {
33 | const currencySymbol = useSelector((s: RootState) => s.node.currency.currency_symbol);
34 |
35 | if (hideNullish && (value === undefined || value === null)) return null;
36 |
37 | const classes = classNames("krist-value", props.className, {
38 | "krist-value-green": green,
39 | "krist-value-zero": highlightZero && value === 0
40 | });
41 |
42 | return (
43 |
44 | {icon || ((currencySymbol || "KST") === "KST" && )}
45 | {(value || 0).toLocaleString()}
46 | {long && {currencySymbol || "KST"}}
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/krist/MarkdownLink.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { FC } from "react";
5 | import { Link } from "react-router-dom";
6 |
7 | import { useSyncNode } from "@api";
8 |
9 | // Allow overriding a link to make it open in a new tab and start with baseURL.
10 | // This is usually used by the markdown renderers.
11 | export function useMarkdownLink(baseURL?: string): FC {
12 | // Default for baseURL if not specified
13 | const syncNode = useSyncNode();
14 | const base = baseURL || syncNode;
15 |
16 | return ({ title, href, children }) => {
17 | // Force the link to start with baseURL/syncNode if it's relative
18 | const absLink = href.startsWith("/")
19 | ? base + href
20 | : href;
21 |
22 | return
27 | {children}
28 | ;
29 | };
30 | }
31 |
32 | export function useRelativeMarkdownLink(): FC {
33 | return ({ title, href, children }) => {
34 | return
35 | {children}
36 | ;
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/names/KristNameLink.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import classNames from "classnames";
5 | import { Typography } from "antd";
6 |
7 | import { ConditionalLink } from "@comp/ConditionalLink";
8 |
9 | import { useNameSuffix } from "@utils/krist";
10 | import { useBooleanSetting } from "@utils/settings";
11 |
12 | const { Text } = Typography;
13 |
14 | interface OwnProps {
15 | name: string;
16 | text?: string;
17 | noLink?: boolean;
18 | neverCopyable?: boolean;
19 | }
20 | type Props = React.HTMLProps & OwnProps;
21 |
22 | export function KristNameLink({ name, text, noLink, neverCopyable, ...props }: Props): JSX.Element | null {
23 | const nameSuffix = useNameSuffix();
24 | const nameCopyButtons = useBooleanSetting("nameCopyButtons");
25 | const copyNameSuffixes = useBooleanSetting("copyNameSuffixes");
26 |
27 | if (!name) return null;
28 | const nameWithSuffix = `${name}.${nameSuffix}`;
29 | const content = text || nameWithSuffix;
30 |
31 | const copyable = !neverCopyable && nameCopyButtons
32 | ? { text: copyNameSuffixes ? nameWithSuffix : name }
33 | : undefined;
34 |
35 | const classes = classNames("krist-name", props.className);
36 |
37 | return
38 |
43 | {content}
44 |
45 | ;
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/names/NameARecordLink.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .name-a-record-link {
7 | background: @kw-darker;
8 | border-radius: @border-radius-base;
9 |
10 | display: inline-block;
11 | margin-top: @padding-xs;
12 | padding: 0.25rem @padding-xs;
13 |
14 | font-size: @font-size-base * 0.9;
15 | font-family: monospace;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/names/NameARecordLink.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import classNames from "classnames";
5 |
6 | import { useNameSuffix, stripNameSuffix } from "@utils/krist";
7 |
8 | import { KristNameLink } from "./KristNameLink";
9 |
10 | import "./NameARecordLink.less";
11 |
12 | interface Props {
13 | a?: string;
14 | className?: string;
15 | }
16 |
17 | export function NameARecordLink({ a, className }: Props): JSX.Element | null {
18 | const nameSuffix = useNameSuffix();
19 |
20 | if (!a) return null;
21 |
22 | const classes = classNames("name-a-record-link", className);
23 |
24 | // I don't have a citation for this other than a vague memory, but there are
25 | // (as of writing this) 45 names in the database whose A records begin with
26 | // `$` and then point to another name. There is an additional 1 name that
27 | // actually points to a domain, but still begins with `$` and ends with the
28 | // name suffix. 40 of these names end in the `.kst` suffix. Since I cannot
29 | // find any specification or documentation on it right now, I support both
30 | // formats. The suffix is stripped if it is present.
31 | if (a.startsWith("$")) {
32 | // Probably a name redirect
33 | const withoutPrefix = a.replace(/^\$/, "");
34 | const nameWithoutSuffix = stripNameSuffix(nameSuffix, withoutPrefix);
35 |
36 | return ;
42 | }
43 |
44 | return
45 | {a}
46 | ;
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/styles/ConditionalLink.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .conditional-link-disabled {
7 | color: @primary-color;
8 | cursor: pointer;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/styles/DateTime.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .date-time {
7 | &-secondary, &-secondary a, &-secondary time {
8 | color: @text-color-secondary;
9 | }
10 |
11 | &-small, &-small a, &-small time {
12 | font-size: 90%;
13 |
14 | @media (max-width: @screen-xl) {
15 | font-size: 85%;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/styles/HelpIcon.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .kw-help-icon {
7 | display: inline-block;
8 | margin-left: @padding-xs;
9 |
10 | font-size: 90%;
11 |
12 | color: @text-color-secondary;
13 | cursor: pointer;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/styles/OptionalField.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .optional-field {
7 | &.optional-field-unset {
8 | color: @text-color-secondary;
9 | font-style: italic;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/styles/SmallCopyable.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | .small-copyable {
5 | border: 0;
6 | background: transparent;
7 | padding: 0;
8 | line-height: inherit;
9 | display: inline-block;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/styles/Statistic.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .kw-statistic {
7 | &-title {
8 | color: @kw-text-secondary;
9 | display: block;
10 | }
11 |
12 | &-value {
13 | font-size: @heading-3-size;
14 |
15 | .ant-typography-copy {
16 | line-height: 1 !important;
17 | margin-left: @padding-xs;
18 |
19 | .anticon {
20 | font-size: @font-size-base;
21 | vertical-align: 0;
22 | }
23 | }
24 | }
25 |
26 | &-green &-value {
27 | color: @kw-green;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/transactions/SendTransactionModalLink.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { FC, useState, useCallback } from "react";
5 |
6 | import { AuthorisedAction } from "@comp/auth/AuthorisedAction";
7 | import { SendTransactionModal } from "@pages/transactions/send/SendTransactionModal";
8 |
9 | import { Wallet } from "@wallets";
10 |
11 | interface Props {
12 | from?: Wallet | string;
13 | to?: string;
14 | }
15 |
16 | export const SendTransactionModalLink: FC = ({
17 | from,
18 | to,
19 | children
20 | }): JSX.Element => {
21 | const [modalVisible, setModalVisible] = useState(false);
22 |
23 | return <>
24 | setModalVisible(true)}>
25 | {children}
26 |
27 |
28 |
35 | >;
36 | };
37 |
38 | export type OpenSendTxFn = (from?: Wallet | string, to?: string) => void;
39 | export type SendTxHookRes = [
40 | OpenSendTxFn,
41 | JSX.Element | null,
42 | (visible: boolean) => void
43 | ];
44 |
45 | interface FromTo {
46 | from?: Wallet | string;
47 | to?: string;
48 | }
49 |
50 | export function useSendTransactionModal(): SendTxHookRes {
51 | const [opened, setOpened] = useState(false);
52 | const [visible, setVisible] = useState(false);
53 | const [fromTo, setFromTo] = useState({});
54 |
55 | const open = useCallback((from?: Wallet | string, to?: string) => {
56 | setFromTo({ from, to });
57 | setVisible(true);
58 | setOpened(true);
59 | }, []);
60 |
61 | const modal = opened
62 | ?
66 | : null;
67 |
68 | return [open, modal, setVisible];
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/transactions/TransactionConciseMetadata.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .transaction-concise-metadata {
7 | color: @kw-text-tertiary;
8 | font-family: monospace;
9 | font-size: 85%;
10 |
11 | &-truncated::after {
12 | content: "\2026";
13 | color: @text-color-secondary;
14 | user-select: none;
15 | }
16 | }
17 |
18 | a.transaction-concise-metadata {
19 | &:hover, &:active, &:focus {
20 | color: @text-color;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/transactions/TransactionConciseMetadata.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import classNames from "classnames";
5 |
6 | import { Link } from "react-router-dom";
7 |
8 | import { KristTransaction } from "@api/types";
9 | import { useNameSuffix, stripNameFromMetadata } from "@utils/krist";
10 |
11 | import "./TransactionConciseMetadata.less";
12 |
13 | interface Props {
14 | transaction?: KristTransaction;
15 | metadata?: string;
16 | limit?: number;
17 | className?: string;
18 | }
19 |
20 | /**
21 | * Trims the name and metaname from the start of metadata, and truncates it
22 | * to a specified amount of characters.
23 | */
24 | export function TransactionConciseMetadata({
25 | transaction: tx,
26 | metadata,
27 | limit = 30,
28 | className
29 | }: Props): JSX.Element | null {
30 | const nameSuffix = useNameSuffix();
31 |
32 | // Don't render anything if there's no metadata (after the hooks)
33 | const meta = metadata || tx?.metadata;
34 | if (!meta) return null;
35 |
36 | // Strip the name from the start of the transaction metadata, if it is present
37 | const hasName = tx && (tx.sent_name || tx.sent_metaname);
38 | const withoutName = hasName
39 | ? stripNameFromMetadata(nameSuffix, meta)
40 | : meta;
41 |
42 | // Trim it down to the limit if necessary
43 | const wasTruncated = withoutName.length > limit;
44 | const truncated = wasTruncated ? withoutName.substr(0, limit) : withoutName;
45 |
46 | const classes = classNames("transaction-concise-metadata", className, {
47 | "transaction-concise-metadata-truncated": wasTruncated
48 | });
49 |
50 | // Link to the transaction if it is available
51 | return tx
52 | ? (
53 |
57 | {truncated}
58 |
59 | )
60 | : {truncated};
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/transactions/TransactionSummary.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { Row } from "antd";
5 |
6 | import { useTranslation } from "react-i18next";
7 | import { Link } from "react-router-dom";
8 |
9 | import { useWallets } from "@wallets";
10 |
11 | import { KristTransaction } from "@api/types";
12 | import { TransactionItem } from "./TransactionItem";
13 |
14 | import "./TransactionSummary.less";
15 |
16 | interface Props {
17 | transactions?: KristTransaction[];
18 |
19 | seeMoreCount?: number;
20 | seeMoreKey?: string;
21 | seeMoreLink?: string;
22 | }
23 |
24 | export function TransactionSummary({ transactions, seeMoreCount, seeMoreKey, seeMoreLink }: Props): JSX.Element {
25 | const { t } = useTranslation();
26 | const { walletAddressMap } = useWallets();
27 |
28 | return <>
29 | {transactions && transactions.map(t => (
30 |
35 | ))}
36 |
37 | {seeMoreCount !== undefined &&
38 |
39 | {t(seeMoreKey || "transactionSummary.seeMore", { count: seeMoreCount })}
40 |
41 | }
42 | >;
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/transactions/TransactionType.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .transaction-type {
7 | &, a {
8 | user-select: none;
9 |
10 | font-weight: bold;
11 | color: @text-color-secondary;
12 | }
13 |
14 | &-transferred, &-name_transferred {
15 | &, a { color: @kw-primary; }
16 | }
17 | &-sent, &-name_sent, &-name_purchased {
18 | &, a { color: @kw-orange; }
19 | }
20 | &-received, &-mined, &-name_received {
21 | &, a { color: @kw-green; }
22 | }
23 | &-name_a_record {
24 | &, a { color: @kw-purple; }
25 | }
26 | &-bumped {
27 | &, a { color: @kw-text-tertiary; }
28 | }
29 |
30 | &-no-link {
31 | &, a { cursor: default; }
32 | }
33 |
34 | @media (max-width: @screen-xl) {
35 | font-size: 90%;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/types.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 |
5 | /** CopyConfig from ant-design (antd/lib/typography/Base.d.ts) */
6 | export interface CopyConfig {
7 | text?: string;
8 | onCopy?: () => void;
9 | icon?: React.ReactNode;
10 | tooltips?: boolean | React.ReactNode;
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/wallets/SelectWalletFormat.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { Select } from "antd";
5 |
6 | import { useTranslation } from "react-i18next";
7 |
8 | import { WalletFormatName, ADVANCED_FORMATS } from "@wallets";
9 | import { useBooleanSetting } from "@utils/settings";
10 |
11 | interface Props {
12 | initialFormat: WalletFormatName;
13 | }
14 |
15 | export function SelectWalletFormat({ initialFormat }: Props): JSX.Element {
16 | const advancedWalletFormats = useBooleanSetting("walletFormats");
17 | const { t } = useTranslation();
18 |
19 | return ;
30 | }
31 |
--------------------------------------------------------------------------------
/src/global/AppHotkeys.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 |
5 | import { useHistory } from "react-router-dom";
6 | import { GlobalHotKeys } from "react-hotkeys";
7 |
8 | export function AppHotkeys(): JSX.Element {
9 | const history = useHistory();
10 |
11 | return history.push("/dev")
15 | }}
16 | />;
17 | }
18 |
--------------------------------------------------------------------------------
/src/global/AppLoading.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 |
5 | export function AppLoading(): JSX.Element {
6 | return
7 | {/* Spinner */}
8 |
9 |
10 | {/* Loading hint */}
11 | {/* NOTE: This is not translated, as usually this component is shown when
12 | the translations are being loaded! */}
13 | setsetstsetsetestLoading KristWeb...
14 |
;
15 | }
16 |
--------------------------------------------------------------------------------
/src/global/AppServices.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { StorageBroadcast } from "./StorageBroadcast";
5 | import { LegacyMigration } from "./legacy/LegacyMigration";
6 | import { SyncWallets } from "@comp/wallets/SyncWallets";
7 | import { ForcedAuth } from "./ForcedAuth";
8 | import { WebsocketService } from "./ws/WebsocketService";
9 | import { SyncMOTD } from "./ws/SyncMOTD";
10 | import { AppHotkeys } from "./AppHotkeys";
11 | import { PurchaseKristHandler } from "./PurchaseKrist";
12 | import { AdvanceTip } from "@pages/dashboard/TipsCard";
13 |
14 | export function AppServices(): JSX.Element {
15 | return <>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | >;
26 | }
27 |
--------------------------------------------------------------------------------
/src/global/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { FC } from "react";
5 | import { Alert } from "antd";
6 |
7 | import { useTFns } from "@utils/i18n";
8 |
9 | import * as Sentry from "@sentry/react";
10 | import { errorReporting } from "@utils";
11 |
12 | interface Props {
13 | name: string;
14 | }
15 |
16 | export const ErrorBoundary: FC = ({ name, children }) => {
17 | return }
19 | onError={console.error}
20 |
21 | // Add the boundary name to the scope
22 | beforeCapture={scope => {
23 | scope.setTag("error-boundary", name);
24 | }}
25 | >
26 | {children}
27 | ;
28 | };
29 |
30 | function ErrorFallback(): JSX.Element {
31 | const { tStr } = useTFns("errorBoundary.");
32 |
33 | return
39 |
{tStr("description")}
40 |
41 | {/* If Sentry error reporting is enabled, add a message saying the error
42 | * was automatically reported. */}
43 | {errorReporting && (
44 |
{tStr("sentryNote")}
45 | )}
46 | >}
47 | />;
48 | }
49 |
--------------------------------------------------------------------------------
/src/global/ForcedAuth.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { message } from "antd";
5 | import { useTranslation, TFunction } from "react-i18next";
6 |
7 | import { authMasterPassword, useMasterPassword } from "@wallets";
8 |
9 | import { useMountEffect } from "@utils/hooks";
10 | import { criticalError } from "@utils";
11 |
12 | async function forceAuth(t: TFunction, salt: string, tester: string): Promise {
13 | try {
14 | const password = localStorage.getItem("forcedAuth");
15 | if (!password) return;
16 |
17 | await authMasterPassword(salt, tester, password);
18 | message.warning(t("masterPassword.forcedAuthWarning"));
19 | } catch (e) {
20 | criticalError(e);
21 | }
22 | }
23 |
24 | /** For development purposes, check the presence of a local storage key
25 | * containing the master password, and automatically authenticate with it. */
26 | export function ForcedAuth(): JSX.Element | null {
27 | const { isAuthed, hasMasterPassword, salt, tester }
28 | = useMasterPassword();
29 |
30 | const { t } = useTranslation();
31 |
32 | useMountEffect(() => {
33 | if (isAuthed || !hasMasterPassword || !salt || !tester) return;
34 | forceAuth(t, salt, tester);
35 | });
36 |
37 | return null;
38 | }
39 |
--------------------------------------------------------------------------------
/src/global/compat/CompatCheckModal.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../../App.less";
5 |
6 | .compat-check-modal {
7 | .ant-modal-confirm-btns {
8 | display: none;
9 | }
10 |
11 | .browser-choices {
12 | display: flex;
13 | flex-direction: row;
14 |
15 | // Remove the offset from the confirm modal body padding
16 | margin-left: -38px;
17 | padding-top: @margin-sm;
18 |
19 | a {
20 | flex: 1;
21 |
22 | display: flex;
23 | flex-direction: column;
24 |
25 | padding: @margin-sm;
26 |
27 | color: @text-color;
28 | text-align: center;
29 | background: transparent;
30 | border-radius: @border-radius-base;
31 | transition: all @animation-duration-base ease;
32 |
33 | &:hover {
34 | background: @kw-lighter;
35 | }
36 |
37 | img {
38 | width: 96px;
39 | margin: 0 auto @margin-md auto;
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/global/compat/localStorage.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 |
5 | // Implementation sourced from MDN:
6 | // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#feature-detecting_localstorage
7 | export function localStorageAvailable(
8 | type: "localStorage" | "sessionStorage" = "localStorage"
9 | ): boolean {
10 | let storage;
11 | try {
12 | storage = window[type];
13 | const x = "__storage_test__";
14 | storage.setItem(x, x);
15 | storage.removeItem(x);
16 | return true;
17 | } catch(e) {
18 | return e instanceof DOMException && (
19 | // everything except Firefox
20 | e.code === 22 ||
21 | // Firefox
22 | e.code === 1014 ||
23 | // test name field too, because code might not be present
24 | // everything except Firefox
25 | e.name === "QuotaExceededError" ||
26 | // Firefox
27 | e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
28 | // acknowledge QuotaExceededError only if there's something already stored
29 | (!!storage && storage.length !== 0);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/global/ws/SyncDetailedWork.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useEffect } from "react";
5 |
6 | import { useSelector } from "react-redux";
7 | import { RootState } from "@store";
8 | import * as nodeActions from "@actions/NodeActions";
9 |
10 | import { store } from "@app";
11 |
12 | import * as api from "@api";
13 | import { KristWorkDetailed } from "@api/types";
14 |
15 | import { criticalError } from "@utils";
16 |
17 | import Debug from "debug";
18 | const debug = Debug("kristweb:sync-work");
19 |
20 | export async function updateDetailedWork(): Promise {
21 | debug("updating detailed work");
22 | const data = await api.get("work/detailed");
23 |
24 | debug("work: %d", data.work);
25 | store.dispatch(nodeActions.setDetailedWork(data));
26 | }
27 |
28 | /** Sync the detailed work with the Krist node on startup. */
29 | export function SyncDetailedWork(): JSX.Element | null {
30 | const { lastBlockID } = useSelector((s: RootState) => s.node);
31 |
32 | useEffect(() => {
33 | updateDetailedWork().catch(criticalError);
34 | }, [lastBlockID]);
35 |
36 | return null;
37 | }
38 |
--------------------------------------------------------------------------------
/src/global/ws/WebsocketProvider.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { FC, createContext, useState, Dispatch, SetStateAction } from "react";
5 |
6 | import { WebsocketConnection } from "./WebsocketConnection";
7 |
8 | import Debug from "debug";
9 | const debug = Debug("kristweb:websocket-provider");
10 |
11 | export interface WSContextType {
12 | connection?: WebsocketConnection;
13 | setConnection?: Dispatch>;
14 | }
15 | export const WebsocketContext = createContext({});
16 |
17 | export const WebsocketProvider: FC = ({ children }): JSX.Element => {
18 | const [connection, setConnection] = useState();
19 |
20 | debug("ws provider re-rendering");
21 |
22 | return
23 | {children}
24 | ;
25 | };
26 |
--------------------------------------------------------------------------------
/src/global/ws/WebsocketService.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useEffect, useContext } from "react";
5 |
6 | import { WebsocketContext } from "./WebsocketProvider";
7 | import { WebsocketConnection } from "./WebsocketConnection";
8 |
9 | import * as api from "@api";
10 | import { useWallets } from "@wallets";
11 |
12 | import Debug from "debug";
13 | const debug = Debug("kristweb:websocket-service");
14 |
15 | export function WebsocketService(): JSX.Element | null {
16 | const { wallets } = useWallets();
17 | const syncNode = api.useSyncNode();
18 |
19 | const { connection, setConnection } = useContext(WebsocketContext);
20 |
21 | // On first render, or if the sync node changes, create the websocket
22 | // connection
23 | useEffect(() => {
24 | // Don't reconnect if we already have a connection and the sync node hasn't
25 | // changed (prevents infinite loops)
26 | if (connection && connection.syncNode === syncNode) return;
27 |
28 | // Close any existing connections
29 | if (connection) connection.forceClose();
30 |
31 | if (!setConnection) {
32 | debug("ws provider setConnection is missing!");
33 | return;
34 | }
35 |
36 | // Connect to the Krist websocket server
37 | setConnection(new WebsocketConnection(syncNode));
38 |
39 | // On unmount, force close the existing connection
40 | return () => {
41 | if (connection) connection.forceClose();
42 | };
43 | }, [syncNode, connection, setConnection]);
44 |
45 | // If the wallets change, let the websocket service know so that it can keep
46 | // track of events related to any new wallets
47 | useEffect(() => {
48 | if (connection) connection.setWallets(wallets);
49 | }, [wallets, connection]);
50 |
51 | return null;
52 | }
53 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2020-2021 Drew Lemmy
2 | * This file is part of KristWeb 2 under AGPL-3.0.
3 | * Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt */
4 | body {
5 | margin: 0;
6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
8 | sans-serif;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | }
12 |
13 | code {
14 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
15 | monospace;
16 | }
17 |
--------------------------------------------------------------------------------
/src/krist/api/login.ts:
--------------------------------------------------------------------------------
1 | import { TranslatedError } from "@utils/i18n";
2 |
3 | import { store } from "@app";
4 |
5 | import { decryptWallet, syncWallet, Wallet } from "@wallets";
6 | import * as api from "@api";
7 |
8 | export async function loginWallet(wallet: Wallet): Promise {
9 | const masterPassword = store.getState().masterPassword.masterPassword;
10 | if (!masterPassword) throw new TranslatedError("myWallets.login.masterPasswordRequired");
11 |
12 | const decrypted = await decryptWallet(masterPassword, wallet);
13 | if (!decrypted) throw new TranslatedError("myWallets.login.errorWalletDecrypt");
14 |
15 | try {
16 | // Call /login to force create the address
17 | const { authed } = await api.post("/login", {
18 | privatekey: decrypted.privatekey
19 | });
20 | if (!authed) throw new TranslatedError("myWallets.login.errorAuthFailed");
21 |
22 | // Fetch the updated address and store it in the Redux store
23 | syncWallet(wallet);
24 | } catch (e) {
25 | console.error(e);
26 | throw new TranslatedError("myWallets.login.errorAuthFailed");
27 | }
28 | }
29 |
30 | interface LoginRes {
31 | authed: boolean;
32 | address?: string;
33 | }
34 |
--------------------------------------------------------------------------------
/src/krist/api/search.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { KristAddress, KristBlock, KristName, KristTransaction } from "./types";
5 | import * as api from ".";
6 |
7 | export interface SearchQueryMatch {
8 | originalQuery: string;
9 | matchedAddress: boolean;
10 | matchedName: boolean;
11 | matchedBlock: boolean;
12 | matchedTransaction: boolean;
13 | strippedName: string;
14 | }
15 |
16 | export interface SearchResult {
17 | query: SearchQueryMatch;
18 |
19 | matches: {
20 | exactAddress: KristAddress | false;
21 | exactName: KristName | false;
22 | exactBlock: KristBlock | false;
23 | exactTransaction: KristTransaction | false;
24 | };
25 | }
26 |
27 | export interface SearchExtendedResult {
28 | query: SearchQueryMatch;
29 |
30 | matches: {
31 | transactions: {
32 | addressInvolved: number | false;
33 | nameInvolved: number | false;
34 | metadata: number | false;
35 | };
36 | };
37 | }
38 |
39 | export async function search(query?: string): Promise {
40 | if (!query) return;
41 |
42 | return api.get(
43 | "search?q=" + encodeURIComponent(query),
44 |
45 | // Don't show the rate limit notification if it is hit, a message will be
46 | // shown in the search box instead
47 | { ignoreRateLimit: true }
48 | );
49 | }
50 |
51 | export async function searchExtended(query?: string): Promise {
52 | if (!query || query.length < 3) return;
53 |
54 | return api.get(
55 | "search/extended?q=" + encodeURIComponent(query),
56 |
57 | // Don't show the rate limit notification if it is hit, a message will be
58 | // shown in the search box instead
59 | { ignoreRateLimit: true }
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/src/krist/api/transactions.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { TranslatedError } from "@utils/i18n";
5 |
6 | import { KristTransaction } from "./types";
7 | import * as api from ".";
8 |
9 | import { Wallet, decryptWallet } from "@wallets";
10 |
11 | interface MakeTransactionResponse {
12 | transaction: KristTransaction;
13 | }
14 |
15 | export async function makeTransaction(
16 | masterPassword: string,
17 | from: Wallet,
18 | to: string,
19 | amount: number,
20 | metadata?: string
21 | ): Promise {
22 | // Attempt to decrypt the wallet to get the privatekey
23 | const decrypted = await decryptWallet(masterPassword, from);
24 | if (!decrypted)
25 | throw new TranslatedError("sendTransaction.errorWalletDecrypt");
26 | const { privatekey } = decrypted;
27 |
28 | const { transaction } = await api.post(
29 | "/transactions",
30 | {
31 | privatekey, to, amount,
32 | metadata: metadata || undefined // Clean up empty strings
33 | }
34 | );
35 |
36 | return transaction;
37 | }
38 |
--------------------------------------------------------------------------------
/src/krist/contacts/Contact.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | export interface Contact {
5 | // UUID for this contact
6 | id: string;
7 |
8 | address: string;
9 | label?: string;
10 | isName?: boolean;
11 | }
12 |
13 | export interface ContactMap { [key: string]: Contact }
14 |
15 | /** Properties of Contact that are required to create a new contact. */
16 | export type ContactNewKeys = "address" | "label" | "isName";
17 | export type ContactNew = Pick;
18 |
19 | /** Properties of Contact that are allowed to be updated. */
20 | export type ContactUpdatableKeys = "address" | "label" | "isName";
21 | export const CONTACT_UPDATABLE_KEYS: ContactUpdatableKeys[]
22 | = ["address", "label", "isName"];
23 | export type ContactUpdatable = Pick;
24 |
--------------------------------------------------------------------------------
/src/krist/contacts/functions/addContact.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { v4 as uuid } from "uuid";
5 |
6 | import { store } from "@app";
7 | import * as actions from "@actions/ContactsActions";
8 |
9 | import { Contact, ContactNew, saveContact } from "..";
10 | import { broadcastAddContact } from "@global/StorageBroadcast";
11 |
12 | /**
13 | * Adds a new contact, saving it to locale storage, and dispatching the changes
14 | * to the Redux store.
15 | *
16 | * @param contact - The information for the new contact.
17 | */
18 | export function addContact(contact: ContactNew): Contact {
19 | const id = uuid();
20 |
21 | const newContact = {
22 | id,
23 | address: contact.address,
24 | label: contact.label?.trim() || undefined,
25 | isName: contact.isName
26 | };
27 |
28 | // Save the contact to local storage
29 | saveContact(newContact);
30 | broadcastAddContact(newContact.id); // Broadcast changes to other tabs
31 |
32 | // Dispatch the changes to the Redux store
33 | store.dispatch(actions.addContact(newContact));
34 |
35 | return newContact;
36 | }
37 |
--------------------------------------------------------------------------------
/src/krist/contacts/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | export * from "./Contact";
5 | export * from "./functions/addContact";
6 | export * from "./functions/editContact";
7 | export * from "./contactStorage";
8 | export * from "./utils";
9 |
--------------------------------------------------------------------------------
/src/krist/contacts/utils.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useSelector, shallowEqual } from "react-redux";
5 | import { RootState } from "@store";
6 |
7 | import { Contact, ContactMap } from ".";
8 |
9 | export type ContactAddressMap = Record;
10 |
11 | export interface ContactsHookResponse {
12 | contacts: ContactMap;
13 | contactAddressMap: ContactAddressMap;
14 |
15 | contactAddressList: string[];
16 | joinedContactAddressList: string;
17 | }
18 |
19 | /** Hook that fetches the contacts from the Redux store. */
20 | export function useContacts(): ContactsHookResponse {
21 | const contacts = useSelector((s: RootState) => s.contacts.contacts, shallowEqual);
22 |
23 | const contactAddressMap: ContactAddressMap = {};
24 | const contactAddressList: string[] = [];
25 |
26 | for (const id in contacts) {
27 | const contact = contacts[id];
28 | const address = contact.address;
29 |
30 | contactAddressMap[address] = contact;
31 | contactAddressList.push(address);
32 | }
33 |
34 | const joinedContactAddressList = contactAddressList.join(",");
35 |
36 | return {
37 | contacts, contactAddressMap,
38 | contactAddressList, joinedContactAddressList
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/src/krist/wallets/Wallet.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { WalletFormatName } from ".";
5 |
6 | export interface Wallet {
7 | // UUID for this wallet
8 | id: string;
9 |
10 | // User assignable data
11 | label?: string;
12 | category?: string;
13 |
14 | // Login info
15 | encPassword: string; // Encrypted with master password, decrypted on-demand
16 | encPrivatekey: string; // The password with the password + wallet format applied
17 | username?: string;
18 | format: WalletFormatName;
19 |
20 | // Fetched from API
21 | address: string;
22 | balance?: number;
23 | names?: number;
24 | firstSeen?: string;
25 | lastSynced?: string;
26 |
27 | dontSave?: boolean; // Used to avoid saving when syncing
28 | }
29 |
30 | export interface WalletMap { [key: string]: Wallet }
31 |
32 | /** Properties of Wallet that are required to create a new wallet. */
33 | export type WalletNewKeys = "label" | "category" | "username" | "format" | "dontSave";
34 | export type WalletNew = Pick;
35 |
36 | /** Properties of Wallet that are allowed to be updated. */
37 | export type WalletUpdatableKeys
38 | = "label" | "category" | "encPassword" | "encPrivatekey" | "username" | "format" | "address";
39 | export const WALLET_UPDATABLE_KEYS: WalletUpdatableKeys[]
40 | = ["label", "category", "encPassword", "encPrivatekey", "username", "format", "address"];
41 | export type WalletUpdatable = Pick;
42 |
43 | /** Properties of Wallet that are allowed to be synced. */
44 | export type WalletSyncableKeys
45 | = "balance" | "names" | "firstSeen" | "lastSynced";
46 | export const WALLET_SYNCABLE_KEYS: WalletSyncableKeys[]
47 | = ["balance", "names", "firstSeen", "lastSynced"];
48 | export type WalletSyncable = Pick;
49 |
--------------------------------------------------------------------------------
/src/krist/wallets/functions/resetMasterPassword.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { store } from "@app";
5 |
6 | import { getWalletKey } from "@wallets";
7 |
8 | export function resetMasterPassword(): void {
9 | // Remove the master password from local storage
10 | localStorage.removeItem("salt2");
11 | localStorage.removeItem("tester2");
12 |
13 | // Find and remove all the wallets
14 | const wallets = store.getState().wallets.wallets;
15 | for (const id in wallets) {
16 | const key = getWalletKey(id);
17 | localStorage.removeItem(key);
18 | }
19 |
20 | // Reload the page and return to the dashboard
21 | location.href = "/";
22 | }
23 |
--------------------------------------------------------------------------------
/src/krist/wallets/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | export * from "./Wallet";
5 | export * from "./walletFormats";
6 | export * from "./functions/addWallet";
7 | export * from "./functions/editWallet";
8 | export * from "./functions/syncWallets";
9 | export * from "./functions/decryptWallet";
10 | export * from "./functions/recalculateWallets";
11 | export * from "./functions/resetMasterPassword";
12 | export * from "./masterPassword";
13 | export * from "./walletStorage";
14 | export * from "./utils";
15 |
--------------------------------------------------------------------------------
/src/krist/wallets/walletFormats.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { sha256 } from "@utils/crypto";
5 |
6 | export interface WalletFormat {
7 | (password: string, username?: string): Promise;
8 | }
9 |
10 | export type WalletFormatName = "kristwallet" | "kristwallet_username_appendhashes" | "kristwallet_username" | "jwalelset" | "api";
11 | export const WALLET_FORMATS: Record = {
12 | "kristwallet": async password =>
13 | await sha256("KRISTWALLET" + password) + "-000",
14 |
15 | "kristwallet_username_appendhashes": async (password, username) =>
16 | await sha256("KRISTWALLETEXTENSION" + await sha256(await sha256(username || "") + "^" + await sha256(password))) + "-000",
17 |
18 | "kristwallet_username": async (password, username) =>
19 | await sha256(await sha256(username || "") + "^" + await sha256(password)),
20 |
21 | "jwalelset": async password =>
22 | await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(password)))))))))))))))))),
23 |
24 | "api": async password => password
25 | };
26 | export const ADVANCED_FORMATS: WalletFormatName[] = [
27 | "kristwallet_username_appendhashes", "kristwallet_username", "jwalelset"
28 | ];
29 |
30 | export const applyWalletFormat =
31 | (format: WalletFormatName, password: string, username?: string): Promise =>
32 | WALLET_FORMATS[format](password, username);
33 |
34 | export const formatNeedsUsername = (format: WalletFormatName): boolean =>
35 | WALLET_FORMATS[format].length === 2;
36 |
--------------------------------------------------------------------------------
/src/layout/AppLayout.less:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | @import (reference) "../App.less";
5 |
6 | .site-layout {
7 | min-height: calc(100vh - @layout-header-height);
8 |
9 | margin-left: @kw-sidebar-width;
10 |
11 | &.site-layout-mobile {
12 | margin-left: 0;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/layout/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020-2021 Drew Lemmy
2 | // This file is part of KristWeb 2 under AGPL-3.0.
3 | // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
4 | import { useState } from "react";
5 | import { Layout, Grid } from "antd";
6 |
7 | import { AppHeader } from "./nav/AppHeader";
8 | import { Sidebar } from "./sidebar/Sidebar";
9 | import { AppRouter } from "../global/AppRouter";
10 |
11 | import { TopMenuProvider } from "./nav/TopMenu";
12 |
13 | import "./AppLayout.less";
14 |
15 | const { useBreakpoint } = Grid;
16 |
17 | export function AppLayout(): JSX.Element {
18 | const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
19 | const bps = useBreakpoint();
20 |
21 | return
22 |
23 |
27 |
28 |
29 |
33 |
34 | {/* Fade out the background when the sidebar is open on mobile */}
35 | {!bps.md &&