├── static ├── .gitkeep ├── fonts │ ├── fonts.css │ └── SourceSansPro │ │ ├── SourceSansPro-Bold.ttf │ │ ├── SourceSansPro-Black.ttf │ │ ├── SourceSansPro-Italic.ttf │ │ ├── SourceSansPro-Light.ttf │ │ ├── SourceSansPro-Regular.ttf │ │ ├── SourceSansPro-SemiBold.ttf │ │ ├── SourceSansPro-BlackItalic.ttf │ │ ├── SourceSansPro-BoldItalic.ttf │ │ ├── SourceSansPro-ExtraLight.ttf │ │ ├── SourceSansPro-LightItalic.ttf │ │ ├── SourceSansPro-SemiBoldItalic.ttf │ │ ├── SourceSansPro-ExtraLightItalic.ttf │ │ └── stylesheet.css ├── favicon-128.png ├── favicon-32.png ├── favicon-64.png ├── noscript │ ├── chrome.jpg │ ├── firefox.jpg │ ├── safari.jpg │ └── style.css └── env.js ├── src ├── constants │ ├── all-assets-id.js │ ├── admin-const.js │ ├── sale-states.js │ ├── poll-states.js │ ├── review-states.js │ ├── sale-definition-types.js │ ├── documents-policies.js │ ├── asset-request-types.js │ ├── stellar-types.js │ ├── limit-types.js │ ├── asset-policies.js │ ├── operation-type.js │ ├── stats-op-types.js │ ├── key-value.js │ ├── limits-request-states.js │ ├── request-types.js │ ├── issuance-request-states.js │ ├── sorts-criteria.js │ ├── fee-types.js │ ├── user-types.js │ ├── user-states.js │ ├── blob-types.js │ ├── asset-pair-policies.js │ ├── defaults.js │ ├── request-states.js │ ├── document-types.js │ └── index.js ├── components │ ├── common │ │ ├── SocialLinks │ │ │ ├── index.js │ │ │ └── SocialLinks.vue │ │ ├── SyndicateMember │ │ │ ├── index.js │ │ │ └── SyndicateMember.vue │ │ ├── index.js │ │ ├── formatters │ │ │ ├── index.js │ │ │ ├── VerboseFormatter.vue │ │ │ ├── AssetPoliciesFormatter.vue │ │ │ └── MarkdownFormatter.vue │ │ ├── Tabs │ │ │ ├── index.js │ │ │ └── tabs.scss │ │ ├── getters │ │ │ ├── ImgGetter.vue │ │ │ ├── index.js │ │ │ ├── UserDocLinkGetter.vue │ │ │ ├── link_getter.mixin.js │ │ │ ├── OperationCounterparty.vue │ │ │ ├── accountTypeLocalizer.js │ │ │ ├── DocLinkGetter.vue │ │ │ └── EmailGetter.vue │ │ ├── fields │ │ │ ├── index.js │ │ │ ├── DataField.vue │ │ │ └── scss │ │ │ │ └── _fields-variables.scss │ │ ├── details │ │ │ └── Detail.Row.vue │ │ ├── modals │ │ │ └── Modal.vue │ │ └── CollectionLoader.vue │ ├── User │ │ ├── Trades │ │ │ ├── components │ │ │ │ ├── OrderBook │ │ │ │ │ └── index.js │ │ │ │ └── PriceChart │ │ │ │ │ └── index.js │ │ │ ├── Trades.vue │ │ │ ├── Trades.Index.vue │ │ │ └── models │ │ │ │ └── AssetPair.js │ │ ├── Sales │ │ │ ├── components │ │ │ │ └── SaleManager │ │ │ │ │ ├── index.js │ │ │ │ │ └── SaleManager.ParticipantsTab │ │ │ │ │ ├── index.js │ │ │ │ │ └── SaleManager.ParticipantsTab.vue │ │ │ ├── SaleRequests │ │ │ │ ├── components │ │ │ │ │ └── SaleRequestManager │ │ │ │ │ │ └── index.js │ │ │ │ ├── SaleRequests.vue │ │ │ │ ├── SaleRequests.Index.vue │ │ │ │ └── SaleRequests.Show.vue │ │ │ ├── Sales.Index.vue │ │ │ ├── Sales.Show.vue │ │ │ └── Sales.vue │ │ ├── Users │ │ │ ├── components │ │ │ │ ├── UserDetails │ │ │ │ │ ├── index.js │ │ │ │ │ ├── UserDetails.GeneralKycViewer.vue │ │ │ │ │ ├── UserDetails.VerifiedKycViewer.vue │ │ │ │ │ ├── UserDetails.ExternalDetailsViewer.vue │ │ │ │ │ ├── UserDetails.Account.vue │ │ │ │ │ └── UserDetails.AccreditedKycViewer.vue │ │ │ │ └── UserAccountDetails.vue │ │ │ ├── Users.Show.vue │ │ │ ├── Users.Index.vue │ │ │ └── Users.vue │ │ ├── KycRequests │ │ │ ├── queue │ │ │ │ ├── constants │ │ │ │ │ ├── decision-actions.js │ │ │ │ │ └── decision-states.js │ │ │ │ ├── components │ │ │ │ │ └── ReviewDecisionViewer.vue │ │ │ │ └── wrappers │ │ │ │ │ └── ReviewDecision.js │ │ │ ├── KycRequests.Index.vue │ │ │ └── KycRequests.vue │ │ ├── Assets │ │ │ ├── AssetPairs │ │ │ │ ├── AssetPairs.vue │ │ │ │ ├── AssetPairs.New.vue │ │ │ │ ├── AssetPairs.Show.vue │ │ │ │ └── AssetPairs.Index.vue │ │ │ ├── Withdrawals │ │ │ │ ├── Withdrawals.vue │ │ │ │ └── Withdrawals.Index.vue │ │ │ ├── SystemAssets │ │ │ │ ├── SystemAssets.vue │ │ │ │ ├── SystemAssets.New.vue │ │ │ │ ├── SystemAssets.Show.vue │ │ │ │ └── SystemAssets.Index.vue │ │ │ ├── AssetRequests │ │ │ │ ├── AssetRequests.vue │ │ │ │ └── AssetRequests.Index.vue │ │ │ ├── Assets.vue │ │ │ └── Issuance │ │ │ │ ├── Issuance.vue │ │ │ │ └── components │ │ │ │ └── issuance.scss │ │ ├── Polls │ │ │ ├── PollRequests │ │ │ │ ├── PollRequests.vue │ │ │ │ ├── PollRequests.Index.vue │ │ │ │ └── PollRequests.Show.vue │ │ │ ├── Polls.Index.vue │ │ │ ├── Polls.Show.vue │ │ │ ├── Polls.vue │ │ │ └── components │ │ │ │ ├── PollViewer.vue │ │ │ │ └── PollViewerVoters.vue │ │ ├── OfflineOperations │ │ │ ├── PreIssuanceRequests │ │ │ │ └── PreIssuanceRequests.vue │ │ │ ├── Preissuance │ │ │ │ └── Preissuance.vue │ │ │ ├── ChangeAssetIssuer │ │ │ │ └── ChangeAssetIssuer.vue │ │ │ └── OfflineOperations.vue │ │ ├── Admins │ │ │ ├── Admins.New.vue │ │ │ ├── Admins.Show.vue │ │ │ ├── Admins.vue │ │ │ └── Admins.Index.vue │ │ ├── KycRecoveryRequests │ │ │ ├── KycRecoveryRequests.Index.vue │ │ │ └── KycRecoveryRequests.vue │ │ ├── Fees │ │ │ ├── Fees.Index.vue │ │ │ └── Fees.vue │ │ ├── KeyValue │ │ │ ├── KeyValue.Index.vue │ │ │ └── KeyValue.vue │ │ ├── RolesAndRules │ │ │ ├── RolesAndRules.index.vue │ │ │ └── RolesAndRules.vue │ │ ├── Limits │ │ │ ├── Limits.Index.vue │ │ │ ├── Limits.vue │ │ │ ├── Limits.Requests.vue │ │ │ └── components │ │ │ │ └── Limits.UserLimits.vue │ │ ├── User.vue │ │ ├── components │ │ │ └── UserHeader.vue │ │ └── Operations │ │ │ └── OperationDetails.vue │ ├── App │ │ ├── filters │ │ │ ├── formatMoney.js │ │ │ ├── formatNumber.js │ │ │ ├── globalizeLimitType.js │ │ │ ├── globalizeStatsOpType.js │ │ │ └── globalizeRequestStateI.js │ │ ├── scss │ │ │ ├── _modals.scss │ │ │ ├── _forms.scss │ │ │ ├── app.scss │ │ │ ├── _typography.scss │ │ │ ├── _data-representations.scss │ │ │ ├── _defaults.scss │ │ │ └── _lists.scss │ │ └── components │ │ │ ├── AppFooter.vue │ │ │ ├── Loader.vue │ │ │ ├── IERestrictionMessage.vue │ │ │ └── LanguagePicker.vue │ ├── Auth │ │ └── Auth.vue │ └── settings │ │ └── Settings.vue ├── assets │ ├── ico │ │ ├── opera32.png │ │ ├── firefox32.png │ │ └── chrome-32png.png │ ├── img │ │ └── logo-white.png │ ├── mdi │ │ └── AccountStarIcon.js │ ├── style │ │ └── _info-blocks.scss │ └── scss │ │ └── _colors.scss ├── utils │ ├── verbozify.js │ ├── un-camel-case.js │ ├── xdrTypeFromValue.js │ ├── clearObject.js │ ├── xdrEnumToConstant.js │ ├── file_writer.js │ ├── parseXdrTxResponse.js │ ├── ErrorTracker.js │ ├── file-reader.js │ ├── kyc-tempater.js │ ├── keyServer.js │ └── bus.js ├── router │ ├── helpers │ │ ├── isLoggedIn.js │ │ └── navigationGuards.js │ ├── routes.js │ ├── index.js │ └── routes │ │ └── AuthRoutes.js ├── js │ ├── records │ │ ├── rule.record.js │ │ ├── role.record.js │ │ ├── user.record.js │ │ ├── assetPair.record.js │ │ ├── keyValue.record.js │ │ ├── movement.record.js │ │ ├── limits.record.js │ │ └── fees.record.js │ ├── errors │ │ ├── index.js │ │ └── runtime-errors.js │ └── modals │ │ ├── flow-blocking-modal.mixin.js │ │ └── confirmation_message.js ├── mixins │ └── datalist.mixin.js ├── store │ ├── actions.js │ ├── modules │ │ ├── loader.js │ │ ├── assets.js │ │ ├── tfa.js │ │ ├── email-books.js │ │ └── idle-handler.js │ ├── wrappers │ │ └── balance.js │ ├── types.js │ ├── getters.js │ ├── index.js │ ├── plugins.js │ ├── state.js │ └── mutations.js ├── apiHelper │ ├── index.js │ ├── responseHandlers │ │ └── requests │ │ │ ├── CreatePreIssuanceRequest.js │ │ │ ├── IssuanceCreateRequest.js │ │ │ ├── ReviewableRequest.js │ │ │ ├── KycRecoveryRequest.js │ │ │ ├── ChangeRoleRequest.js │ │ │ └── AssetRequest.js │ ├── tfa.js │ ├── assets.js │ └── users.js ├── api.js ├── validators.js └── config.js ├── docs └── images │ ├── vue-logo.png │ ├── home-page.png │ └── screenshot1.png ├── .eslintignore ├── .editorconfig ├── webpack ├── utils.js ├── test.conf.js ├── local.conf.js └── base.conf.js ├── envs ├── local.js └── prod.js ├── .ci ├── build.sh └── publish.sh ├── .gitattributes ├── .gitignore ├── jsconfig.json ├── parseXdrEnvelope.js ├── Dockerfile ├── .babelrc ├── .gitlab-ci.yml ├── .eslintrc.js └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/constants/all-assets-id.js: -------------------------------------------------------------------------------- 1 | export const ALL_ASSETS_ID = 'all' 2 | -------------------------------------------------------------------------------- /src/components/common/SocialLinks/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './SocialLinks' 2 | -------------------------------------------------------------------------------- /static/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | /* SourceSansPro */ 2 | @import "SourceSansPro/stylesheet.css"; 3 | -------------------------------------------------------------------------------- /src/components/User/Trades/components/OrderBook/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './OrderBook' 2 | -------------------------------------------------------------------------------- /src/components/common/SyndicateMember/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './SyndicateMember' 2 | -------------------------------------------------------------------------------- /static/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/favicon-128.png -------------------------------------------------------------------------------- /static/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/favicon-32.png -------------------------------------------------------------------------------- /static/favicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/favicon-64.png -------------------------------------------------------------------------------- /docs/images/vue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/docs/images/vue-logo.png -------------------------------------------------------------------------------- /src/components/User/Sales/components/SaleManager/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './SaleManager' 2 | -------------------------------------------------------------------------------- /docs/images/home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/docs/images/home-page.png -------------------------------------------------------------------------------- /docs/images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/docs/images/screenshot1.png -------------------------------------------------------------------------------- /src/assets/ico/opera32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/src/assets/ico/opera32.png -------------------------------------------------------------------------------- /src/components/User/Trades/components/PriceChart/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './PriceChart.Fetcher' 2 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './UserDetails.vue' 2 | -------------------------------------------------------------------------------- /static/noscript/chrome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/noscript/chrome.jpg -------------------------------------------------------------------------------- /static/noscript/firefox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/noscript/firefox.jpg -------------------------------------------------------------------------------- /static/noscript/safari.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/noscript/safari.jpg -------------------------------------------------------------------------------- /src/assets/ico/firefox32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/src/assets/ico/firefox32.png -------------------------------------------------------------------------------- /src/assets/img/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/src/assets/img/logo-white.png -------------------------------------------------------------------------------- /src/assets/ico/chrome-32png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/src/assets/ico/chrome-32png.png -------------------------------------------------------------------------------- /src/constants/admin-const.js: -------------------------------------------------------------------------------- 1 | export const ADMIN_CONST = Object.freeze({ 2 | RULE_ID: 1, 3 | ROLE_ID: 1, 4 | }) 5 | -------------------------------------------------------------------------------- /src/components/User/Sales/SaleRequests/components/SaleRequestManager/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './SaleRequestManager.vue' 2 | -------------------------------------------------------------------------------- /src/constants/sale-states.js: -------------------------------------------------------------------------------- 1 | export const SALE_STATES = Object.freeze({ 2 | open: 1, 3 | closed: 2, 4 | cancelled: 3, 5 | }) 6 | -------------------------------------------------------------------------------- /src/components/User/Users/Users.Show.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/utils/verbozify.js: -------------------------------------------------------------------------------- 1 | export function verbozify (str) { 2 | return str.charAt(0).toUpperCase() + str.slice(1).split('_').join(' ') 3 | } 4 | -------------------------------------------------------------------------------- /src/components/User/Sales/components/SaleManager/SaleManager.ParticipantsTab/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './SaleManager.ParticipantsTab' 2 | -------------------------------------------------------------------------------- /static/env.js: -------------------------------------------------------------------------------- 1 | // document.ENV will rewrite process.env configs 2 | 3 | document.ENV = document.ENV || Object.freeze({ 4 | /* ... */ 5 | }) 6 | -------------------------------------------------------------------------------- /src/constants/poll-states.js: -------------------------------------------------------------------------------- 1 | export const POLL_STATES = Object.freeze({ 2 | open: 1, 3 | passed: 2, 4 | failed: 3, 5 | canceled: 4, 6 | }) 7 | -------------------------------------------------------------------------------- /src/constants/review-states.js: -------------------------------------------------------------------------------- 1 | export const REVIEW_STATES = { 2 | approved: 'approved', 3 | skipped: 'skipped', 4 | rejected: 'rejected', 5 | } 6 | -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /src/constants/sale-definition-types.js: -------------------------------------------------------------------------------- 1 | export const SALE_DEFINITION_TYPES = Object.freeze({ 2 | none: 1, 3 | whitelist: 2, 4 | blacklist: 3, 5 | }) 6 | -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-Black.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-Light.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /src/constants/documents-policies.js: -------------------------------------------------------------------------------- 1 | export const DOCUMENTS_POLICIES = Object.freeze({ 2 | public: 'general_public', 3 | private: 'general_private', 4 | }) 5 | -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-SemiBold.ttf -------------------------------------------------------------------------------- /src/components/common/index.js: -------------------------------------------------------------------------------- 1 | export { default as CollectionLoader } from './CollectionLoader' 2 | export { default as FormConfirmation } from './FormConfirmation' 3 | -------------------------------------------------------------------------------- /src/constants/asset-request-types.js: -------------------------------------------------------------------------------- 1 | export const ASSET_REQUEST_TYPES = Object.freeze({ 2 | createAsset: 'create_asset', 3 | updateAsset: 'update_asset', 4 | }) 5 | -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-BlackItalic.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-BoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-ExtraLight.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-LightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokend/admin-panel/HEAD/static/fonts/SourceSansPro/SourceSansPro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/constants/stellar-types.js: -------------------------------------------------------------------------------- 1 | export const STELLAR_TYPES = { 2 | creditAlphanum4: 'credit_alphanum4', 3 | creditAlphanum12: 'credit_alphanum12', 4 | native: 'native', 5 | } 6 | -------------------------------------------------------------------------------- /src/router/helpers/isLoggedIn.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | export function isLoggedIn () { 4 | const { isLoggedIn } = store.state.auth 5 | return !!isLoggedIn 6 | } 7 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | export const vueRoutes = Object.freeze({ 2 | rolesAndRules: { name: 'roles-and-rules' }, 3 | rolesAndRulesIndex: { name: 'roles-and-rules.index' }, 4 | }) 5 | -------------------------------------------------------------------------------- /src/components/App/filters/formatMoney.js: -------------------------------------------------------------------------------- 1 | import { globalize } from './filters' 2 | 3 | export function formatMoney (value) { 4 | return globalize('formats.money', { value }) 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/limit-types.js: -------------------------------------------------------------------------------- 1 | export const LIMIT_TYPES = { 2 | dailyOut: 'dailyOut', 3 | weeklyOut: 'weeklyOut', 4 | monthlyOut: 'monthlyOut', 5 | annualOut: 'annualOut', 6 | } 7 | -------------------------------------------------------------------------------- /src/components/App/filters/formatNumber.js: -------------------------------------------------------------------------------- 1 | import { globalize } from './filters' 2 | 3 | export function formatNumber (value) { 4 | return globalize('formats.number', { value }) 5 | } 6 | -------------------------------------------------------------------------------- /src/components/App/scss/_modals.scss: -------------------------------------------------------------------------------- 1 | .modal__title { 2 | font-size: 22px; 3 | text-align: center; 4 | margin-bottom: 16px; 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/asset-policies.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | export const ASSET_POLICIES = Object.freeze( 3 | xdrEnumToConstant('AssetPolicy') 4 | ) 5 | -------------------------------------------------------------------------------- /src/constants/operation-type.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | 3 | export const OP_TYPES = Object.freeze( 4 | xdrEnumToConstant('OperationType') 5 | ) 6 | -------------------------------------------------------------------------------- /src/constants/stats-op-types.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | 3 | export const STATS_OPERATION_TYPES = Object.freeze(xdrEnumToConstant('StatsOpType')) 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/utils/wijmo-vue.js 4 | src/components/users/referrals/ 5 | src/components/financial/fee/specialOffer/SpecialOfferViews/specialOfferTable 6 | test 7 | -------------------------------------------------------------------------------- /src/constants/key-value.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | 3 | export const KEY_VALUE_ENTRY_TYPE = Object.freeze( 4 | xdrEnumToConstant('KeyValueEntryType') 5 | ) 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/constants/limits-request-states.js: -------------------------------------------------------------------------------- 1 | export const LIMITS_REQUEST_STATES_STR = Object.freeze({ 2 | updateLimits: 'update_limits', 3 | initial: 'initial', 4 | docsUploading: 'docsUploading', 5 | }) 6 | -------------------------------------------------------------------------------- /src/constants/request-types.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | 3 | export const REQUEST_TYPES = Object.freeze( 4 | xdrEnumToConstant('ReviewableRequestType') 5 | ) 6 | -------------------------------------------------------------------------------- /src/constants/issuance-request-states.js: -------------------------------------------------------------------------------- 1 | export const ISSUANCE_REQUEST_STATES = Object.freeze({ 2 | pending: 1, 3 | cancelled: 2, 4 | approved: 3, 5 | rejected: 4, 6 | permanentlyRejected: 5, 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/queue/constants/decision-actions.js: -------------------------------------------------------------------------------- 1 | export const DECISION_ACTIONS = Object.freeze({ 2 | approve: 'approve', 3 | reject: 'reject', 4 | skip: 'skip', 5 | none: 'none', 6 | }) 7 | -------------------------------------------------------------------------------- /src/js/records/rule.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class RuleRecord { 4 | constructor (record) { 5 | this._record = record 6 | this.ruleId = _get(record, 'id', '') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webpack/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const assetsPath = function (_path) { 4 | return path.posix.join('static', _path) 5 | } 6 | 7 | module.exports = { 8 | assetsPath: assetsPath, 9 | } 10 | -------------------------------------------------------------------------------- /src/constants/sorts-criteria.js: -------------------------------------------------------------------------------- 1 | export const SALES_SORT_CRITERIA = { 2 | sortTypeDefaultPage: '0', 3 | sortTypeMostFounded: '1', 4 | sortTypeByEndTime: '2', 5 | sortTypeByPopularity: '3', 6 | sortTypeStartTime: '4', 7 | } 8 | -------------------------------------------------------------------------------- /src/components/User/Assets/AssetPairs/AssetPairs.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/User/Assets/Withdrawals/Withdrawals.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/constants/fee-types.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | export const FEE_TYPES = Object.freeze(xdrEnumToConstant('FeeType')) 3 | export const PAYMENT_FEE_TYPES = Object.freeze(xdrEnumToConstant('PaymentFeeType')) 4 | -------------------------------------------------------------------------------- /src/components/User/Assets/SystemAssets/SystemAssets.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/User/Polls/PollRequests/PollRequests.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/User/Sales/SaleRequests/SaleRequests.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/User/Assets/AssetRequests/AssetRequests.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/common/formatters/index.js: -------------------------------------------------------------------------------- 1 | export { default as AssetPoliciesFormatter } from './AssetPoliciesFormatter' 2 | export { default as VerboseFormatter } from './VerboseFormatter' 3 | export { default as MarkdownFormatter } from './MarkdownFormatter' 4 | -------------------------------------------------------------------------------- /src/components/common/Tabs/index.js: -------------------------------------------------------------------------------- 1 | import { Tabs, Tab } from 'vue-tabs-component' 2 | import './tabs.scss' 3 | 4 | Tabs.props.cacheLifetime.default = 0 5 | Tabs.props.options.default = function () { return { useUrlFragment: false } } 6 | 7 | export { Tabs, Tab } 8 | -------------------------------------------------------------------------------- /src/js/errors/index.js: -------------------------------------------------------------------------------- 1 | import * as runtimeErrors from './runtime-errors' 2 | import { errors as sdkErrors } from '@tokend/js-sdk' 3 | 4 | const errors = { 5 | ...sdkErrors, 6 | ...runtimeErrors, 7 | } 8 | 9 | export { runtimeErrors, sdkErrors, errors } 10 | -------------------------------------------------------------------------------- /src/mixins/datalist.mixin.js: -------------------------------------------------------------------------------- 1 | import { DOCUMENT_TYPES_STR } from '@/constants' 2 | 3 | export default { 4 | methods: { 5 | getNormalizedDocsList () { 6 | return Object.values(DOCUMENT_TYPES_STR).map(item => item.replace(/_/g, ' ')) 7 | }, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | export const LOG_IN = ({ commit, dispatch }) => { 2 | commit('LOG_IN') 3 | dispatch('START_IDLE') 4 | } 5 | 6 | export const LOG_OUT = ({ commit }) => { 7 | commit('CLOSE_MODAL') 8 | commit('STOP_IDLE') 9 | commit('CLEAR_ALL_DATA') 10 | } 11 | -------------------------------------------------------------------------------- /src/components/User/OfflineOperations/PreIssuanceRequests/PreIssuanceRequests.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /envs/local.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"default"', 3 | HORIZON_SERVER: '"http://localhost:8000/_/api/"', 4 | FILE_STORAGE: '"http://localhost:8000/_/storage/api"', 5 | KEY_SERVER_ADMIN: '"http://localhost:8000/_/adks"', 6 | NETWORK_PASSPHRASE: '"TokenD Developer Network"', 7 | } 8 | -------------------------------------------------------------------------------- /envs/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"default"', 3 | HORIZON_SERVER: '"http://localhost:8000/_/api/"', 4 | FILE_STORAGE: '"http://localhost:8000/_/storage/api"', 5 | KEY_SERVER_ADMIN: '"http://localhost:8000/_/adks"', 6 | NETWORK_PASSPHRASE: '"TokenD Developer Network"', 7 | } 8 | -------------------------------------------------------------------------------- /.ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | version=$(test -z $CI_COMMIT_TAG && echo $CI_COMMIT_SHORT_SHA || echo $CI_COMMIT_TAG \($CI_COMMIT_SHORT_SHA\)) 6 | 7 | docker build --build-arg BUILD_VERSION="$version" --pull -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . 8 | docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -------------------------------------------------------------------------------- /src/apiHelper/index.js: -------------------------------------------------------------------------------- 1 | import assets from './assets' 2 | import tfa from './tfa' 3 | import users from './users' 4 | // new 5 | import { requests } from './requests' 6 | 7 | export default { 8 | assets: assets, 9 | tfa: tfa, 10 | users: users, 11 | 12 | // new: 13 | requests, 14 | } 15 | -------------------------------------------------------------------------------- /src/constants/user-types.js: -------------------------------------------------------------------------------- 1 | export const USER_TYPES = Object.freeze({ 2 | undefined: 0, 3 | notVerified: 1, 4 | corporate: 2, 5 | general: 4, 6 | }) 7 | 8 | export const USER_TYPES_STR = Object.freeze({ 9 | notVerified: 'not_verified', 10 | corporate: 'corporate', 11 | general: 'general', 12 | }) 13 | -------------------------------------------------------------------------------- /src/js/records/role.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class RoleRecord { 4 | constructor (record) { 5 | this._record = record 6 | this.roleId = _get(record, 'id', '') 7 | this.details = _get(record, 'details', '') 8 | this.ruleIDs = record.rules.map(item => item.id) || [] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/un-camel-case.js: -------------------------------------------------------------------------------- 1 | export function unCamelCase (s) { 2 | return s 3 | .split(/(?=[A-Z])/) 4 | .map(p => p.charAt(0).toUpperCase() + p.slice(1)) 5 | .join(' ') 6 | } 7 | 8 | export function snakeToCamelCase (s) { 9 | return s.replace(/(_\w)/g, function (m) { 10 | return m[1].toUpperCase() 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/router/helpers/navigationGuards.js: -------------------------------------------------------------------------------- 1 | import { isLoggedIn } from './isLoggedIn' 2 | 3 | export function authorizedGuard (to, from, next) { 4 | next(isLoggedIn() || { name: 'root', query: { redirect: to.fullPath } }) 5 | } 6 | 7 | export function unauthorizedGuard (to, from, next) { 8 | next(!isLoggedIn() || { name: 'root' }) 9 | } 10 | -------------------------------------------------------------------------------- /src/components/User/Users/Users.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/CreatePreIssuanceRequest.js: -------------------------------------------------------------------------------- 1 | import { ReviewableRequest } from './ReviewableRequest' 2 | 3 | export class CreatePreIssuanceRequest extends ReviewableRequest { 4 | amount () { 5 | return this.operationDetails.amount 6 | } 7 | 8 | asset () { 9 | return this.operationDetails.asset.id 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/queue/constants/decision-states.js: -------------------------------------------------------------------------------- 1 | export const DECISION_STATES = Object.freeze({ 2 | approve: 'approve', 3 | approving: 'approving', 4 | approved: 'approved', 5 | error: 'error', 6 | reject: 'reject', 7 | rejecting: 'rejecting', 8 | rejected: 'rejected', 9 | skip: 'skip', 10 | none: 'none', 11 | }) 12 | -------------------------------------------------------------------------------- /src/constants/user-states.js: -------------------------------------------------------------------------------- 1 | export const USER_STATES = Object.freeze({ 2 | nil: 1, 3 | waitingForApproval: 2, 4 | approved: 4, 5 | rejected: 8, 6 | }) 7 | 8 | export const USER_STATES_STR = Object.freeze({ 9 | nil: 'nil', 10 | waitingForApproval: 'waiting_for_approval', 11 | approved: 'approved', 12 | rejected: 'rejected', 13 | }) 14 | -------------------------------------------------------------------------------- /.ci/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | RELEASE=$CI_COMMIT_REF_NAME 6 | docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 7 | docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PWD 8 | docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA 9 | docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $DH_IMAGE:$RELEASE 10 | docker push $DH_IMAGE:$RELEASE 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case devs don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Declare files that will always have LF line endings on checkout. 5 | * text eol=lf 6 | 7 | # Denote all files that are truly binary and should not be modified. 8 | *.png binary 9 | *.jpg binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /src/components/User/Admins/Admins.New.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/components/User/Polls/Polls.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/components/User/Sales/Sales.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/KycRequests.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | .eslintcache 11 | package-lock.json 12 | 13 | test/bignumbers.js 14 | test/xdr.js 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | -------------------------------------------------------------------------------- /src/store/modules/loader.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | showLoader: false, 3 | } 4 | 5 | const getters = { 6 | showLoader: state => state.showLoader, 7 | } 8 | 9 | const mutations = { 10 | 'OPEN_LOADER' (state) { state.showLoader = true }, 11 | 'CLOSE_LOADER' (state) { state.showLoader = false }, 12 | } 13 | 14 | export default { 15 | state, 16 | getters, 17 | mutations, 18 | } 19 | -------------------------------------------------------------------------------- /src/components/User/KycRecoveryRequests/KycRecoveryRequests.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/components/common/getters/ImgGetter.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /src/constants/blob-types.js: -------------------------------------------------------------------------------- 1 | export const BLOB_TYPES = Object.freeze({ 2 | assetDescription: 1, 3 | saleOverview: 2, 4 | saleUpdate: 4, 5 | navUpdate: 8, 6 | saleDocument: 16, 7 | corporateKyc: 32, 8 | bravo: 64, 9 | charlie: 128, 10 | delta: 256, 11 | assetTerms: 512, 12 | assetMetrics: 1024, 13 | kycForm: 2048, 14 | kycIdDocument: 4096, 15 | kycProofOfAddress: 8192, 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/User/Sales/SaleRequests/SaleRequests.Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/components/common/getters/index.js: -------------------------------------------------------------------------------- 1 | export { default as EmailGetter } from './EmailGetter' 2 | export { default as ImgGetter } from './ImgGetter' 3 | export { default as DocLinkGetter } from './DocLinkGetter' 4 | export { default as UserDocLinkGetter } from './UserDocLinkGetter' 5 | export { default as UserDocGetter } from './UserDocGetter' 6 | export { default as OperationCounterparty } from './OperationCounterparty' 7 | -------------------------------------------------------------------------------- /src/components/User/Fees/Fees.Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /src/utils/xdrTypeFromValue.js: -------------------------------------------------------------------------------- 1 | import { base } from '@tokend/js-sdk' 2 | 3 | export function xdrTypeFromValue (xdrEnum, value) { 4 | xdrEnum = typeof xdrEnum === 'string' 5 | ? base.xdr[xdrEnum] 6 | : xdrEnum 7 | 8 | try { 9 | return xdrEnum.values().filter(item => item.value === value)[0] 10 | } catch (error) { 11 | throw new Error(`xdrEnumToConstant: Cannot get values from provided xdrEnum (${xdrEnum})`) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/User/KeyValue/KeyValue.Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /src/components/User/Polls/Polls.Show.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/components/User/Sales/Sales.Show.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/components/User/RolesAndRules/RolesAndRules.index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /src/store/wrappers/balance.js: -------------------------------------------------------------------------------- 1 | import safeGet from 'lodash/get' 2 | 3 | // Wraps list items of http://docs.tokend.io/horizon/#operation/getBalanceList 4 | export class Balance { 5 | constructor (record = {}) { 6 | this._record = record 7 | 8 | this.id = record.id 9 | this.assetCode = safeGet(record, 'asset.id') 10 | 11 | this.available = safeGet(record, 'state.available') 12 | this.locked = safeGet(record, 'state.locked') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/User/Admins/Admins.Show.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "target": "esnext", 5 | "allowSyntheticDefaultImports": false, 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ], 11 | "@comcom/*": [ 12 | "src/components/common/*" 13 | ], 14 | "@store/*": [ 15 | "src/store/*" 16 | ] 17 | } 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | "dist" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/common/formatters/VerboseFormatter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /src/components/User/Assets/Withdrawals/Withdrawals.Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /src/components/common/fields/index.js: -------------------------------------------------------------------------------- 1 | export { default as DataField } from './DataField' 2 | export { default as InputField } from './InputField' 3 | export { default as SelectField } from './SelectField' 4 | export { default as TextField } from './TextField' 5 | export { default as TickField } from './TickField' 6 | export { default as ImageField } from './ImageField' 7 | export { default as InputDateField } from './InputDateField' 8 | export { default as SwitchField } from './SwitchField' 9 | -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | const mutations = Object.freeze({ 2 | PUSH_EMAIL_TO_ADDRESS_BOOK: 'PUSH_TO_EMAIL_ADDRESS_BOOK', 3 | }) 4 | 5 | const actions = Object.freeze({ 6 | REQUEST_EMAIL_BY_ADDRESS: 'REQUEST_EMAIL_BY_ADDRESS', 7 | }) 8 | 9 | const getters = Object.freeze({ 10 | GET_USER: 'GET_USER', 11 | GET_USER_ADDRESS: 'GET_USER_ADDRESS', 12 | GET_EMAIL_ADDRESS_BOOK: 'GET_EMAIL_ADDRESS_BOOK', 13 | accountId: 'accountId', 14 | }) 15 | 16 | export { mutations, actions, getters } 17 | -------------------------------------------------------------------------------- /src/utils/clearObject.js: -------------------------------------------------------------------------------- 1 | export function clearObject (object) { 2 | const objectCopy = Object.assign({}, object) 3 | 4 | for (const key in objectCopy) { 5 | if (objectCopy.hasOwnProperty(key)) { 6 | const element = objectCopy[key] 7 | if (element === undefined || element === null || element === '' || 8 | element === "''" || element === '""' 9 | ) { 10 | delete objectCopy[key] 11 | } 12 | } 13 | } 14 | 15 | return objectCopy 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/xdrEnumToConstant.js: -------------------------------------------------------------------------------- 1 | import { base } from '@tokend/js-sdk' 2 | 3 | export function xdrEnumToConstant (xdrEnum) { 4 | xdrEnum = typeof xdrEnum === 'string' 5 | ? base.xdr[xdrEnum] 6 | : xdrEnum 7 | 8 | try { 9 | const res = {} 10 | xdrEnum.values().forEach(function (item) { res[item.name] = item.value }) 11 | return res 12 | } catch (error) { 13 | throw new Error(`xdrEnumToConstant: Cannot get values from provided xdrEnum (${xdrEnum})`) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/IssuanceCreateRequest.js: -------------------------------------------------------------------------------- 1 | import { ReviewableRequest } from './ReviewableRequest' 2 | 3 | export class IssuanceCreateRequest extends ReviewableRequest { 4 | get amount () { 5 | return this.operationDetails.amount 6 | } 7 | 8 | get asset () { 9 | return this.operationDetails.asset 10 | } 11 | 12 | get reference () { 13 | return this.record.reference 14 | } 15 | 16 | get requestor () { 17 | return this.record.requestor 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/User/Assets/AssetPairs/AssetPairs.New.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /src/js/errors/runtime-errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * General runtime error 3 | */ 4 | export class RuntimeError extends Error { } 5 | 6 | /** 7 | * The user already exists in the system 8 | */ 9 | export class UserExistsError extends RuntimeError { } 10 | 11 | /** 12 | * The user doesn't exist in the system 13 | */ 14 | export class UserDoesntExistError extends RuntimeError {} 15 | 16 | /** 17 | * The account doesn't have specified balance 18 | */ 19 | export class BalanceNotFoundError extends RuntimeError {} 20 | -------------------------------------------------------------------------------- /src/components/User/Sales/SaleRequests/SaleRequests.Show.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/components/User/Admins/Admins.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /src/components/User/Trades/Trades.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /src/components/User/Users/Users.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /src/components/User/Polls/PollRequests/PollRequests.Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 23 | -------------------------------------------------------------------------------- /src/components/User/KeyValue/KeyValue.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /src/constants/asset-pair-policies.js: -------------------------------------------------------------------------------- 1 | import { xdrEnumToConstant } from '@/utils/xdrEnumToConstant' 2 | import { base } from '@tokend/js-sdk' 3 | 4 | export const ASSET_PAIR_POLICIES = Object.freeze( 5 | xdrEnumToConstant('AssetPairPolicy') 6 | ) 7 | 8 | export const ASSET_PAIR_POLICIES_VERBOSE = { 9 | [base.xdr.AssetPairPolicy.tradeableSecondaryMarket().value]: 'asset-pair-policies.tradable-secondary-market', 10 | [base.xdr.AssetPairPolicy.physicalPriceRestriction().value]: '', 11 | [base.xdr.AssetPairPolicy.currentPriceRestriction().value]: '', 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/file_writer.js: -------------------------------------------------------------------------------- 1 | export function createTxtFile (value, type) { 2 | let textFile = null 3 | const makeTextFile = function (text) { 4 | const data = new Blob([text], { type }) 5 | 6 | // If we are replacing a previously generated file we need to 7 | // manually revoke the object URL to avoid memory leaks. 8 | if (textFile !== null) { 9 | window.URL.revokeObjectURL(textFile) 10 | } 11 | 12 | textFile = window.URL.createObjectURL(data) 13 | 14 | return textFile 15 | } 16 | 17 | return makeTextFile(value) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/User/Limits/Limits.Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /src/components/App/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | .app__form-row { 2 | display: flex; 3 | width: 100%; 4 | 5 | & + & { 6 | margin-top: 2.5rem; 7 | } 8 | 9 | & > .app__form-field { 10 | flex: 1; 11 | 12 | &--halved { 13 | flex: 0.5; 14 | margin-right: 2rem; 15 | } 16 | } 17 | 18 | & > .app__form-field + .app__form-field { 19 | margin-left: 2rem; 20 | } 21 | } 22 | 23 | .app__form-actions { 24 | display: flex; 25 | 26 | &:not(:first-child) { 27 | margin-top: 3.5rem; 28 | } 29 | 30 | & > button + button { 31 | margin-left: 1rem 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/User/Assets/AssetRequests/AssetRequests.Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/components/common/fields/DataField.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/constants/defaults.js: -------------------------------------------------------------------------------- 1 | import { base } from '@tokend/js-sdk' 2 | 3 | export const DEFAULT_BASE_ASSET = 'USD' 4 | export const DEFAULT_QUOTE_ASSET = 'BTC' 5 | export const DEFAULT_PRECISION = String(base.Operation.ONE || 1000000).match(/0/g).length 6 | export const DEFAULT_INPUT_STEP = `0.${'0'.repeat(DEFAULT_PRECISION - 1)}1` 7 | export const DEFAULT_INPUT_MIN = `0.${'0'.repeat(DEFAULT_PRECISION - 1)}1` 8 | export const DEFAULT_DATE_FORMAT = 'DD MMM YYYY' 9 | export const DEFAULT_DATE_TIME_FORMAT = 'DD MMM YYYY [at] HH:mm:ss' 10 | export const DEFAULT_MAX_AMOUNT = base.Operation.MAX_INT64_AMOUNT 11 | -------------------------------------------------------------------------------- /src/js/records/user.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class UserRecord { 4 | constructor (record = {}, identity = {}) { 5 | this._record = record 6 | this._identity = identity 7 | 8 | this.role = Number(_get(record, 'role.id') || 9 | _get(identity, 'role')) 10 | this.address = _get(identity, 'address') || 11 | _get(record, 'id') 12 | this.email = _get(identity, 'email') 13 | this.status = _get(identity, 'status') 14 | this.phone = _get(identity, 'phone_number') 15 | this.telegram = _get(identity, 'telegram_username') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/User/Sales/components/SaleManager/SaleManager.ParticipantsTab/SaleManager.ParticipantsTab.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 25 | -------------------------------------------------------------------------------- /webpack/test.conf.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const baseWebpackConfig = require('./base.conf') 4 | 5 | module.exports = merge(baseWebpackConfig, { 6 | mode: 'development', 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.css?$/, 11 | loader: 'style-loader!css-loader', 12 | }, 13 | ], 14 | }, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | // to prevent vue of printing hints about the dev tools to the console 18 | 'process.env': { NODE_ENV: '"production"' }, 19 | }), 20 | ], 21 | }) 22 | -------------------------------------------------------------------------------- /parseXdrEnvelope.js: -------------------------------------------------------------------------------- 1 | const base = require('@tokend/js-sdk').base 2 | 3 | function parseEnvelope (envelope) { 4 | const buffer = Buffer.from(envelope, 'base64') 5 | const transaction = base.xdr.TransactionEnvelope.fromXDR(buffer) 6 | const operations = transaction.tx().operations() 7 | 8 | if (operations.length === 1) { 9 | return base.Operation.operationToObject(operations[0]) 10 | } 11 | 12 | return operations 13 | .map(operation => base.Operation.operationToObject(operation)) 14 | } 15 | 16 | // eslint-disable-next-line no-console 17 | console.log(parseEnvelope(global.process.argv[2])) 18 | -------------------------------------------------------------------------------- /src/utils/parseXdrTxResponse.js: -------------------------------------------------------------------------------- 1 | import { base } from '@tokend/js-sdk' 2 | 3 | export function parseXdrTxResponse (txResponse) { 4 | const buffer = Buffer.from(txResponse.data.resultXdr, 'base64') 5 | const transaction = base.xdr.TransactionResult.fromXDR(buffer) 6 | return transaction.result().results() 7 | } 8 | 9 | export function deriveRequestIdFromKycRequestResult (txResponse, opIndex = 0) { 10 | const response = parseXdrTxResponse(txResponse) 11 | return response[opIndex] 12 | .tr() 13 | .createUpdateKycRequestResult() 14 | .value() 15 | .requestId() 16 | .toString() 17 | } 18 | -------------------------------------------------------------------------------- /src/components/User/Polls/PollRequests/PollRequests.Show.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /src/js/records/assetPair.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class AssetPairRecord { 4 | constructor (record = {}, details) { 5 | this._record = record 6 | this.base = _get(record, 'baseAsset.id') 7 | this.quote = _get(record, 'quoteAsset.id') 8 | 9 | this.maxPriceStep = _get(record, 'maxPriceStep') || 0 10 | this.physicalPrice = _get(record, 'price') || 0 11 | this.physicalPriceCorrection = _get(record, 'physicalPriceCorrection') || 0 12 | 13 | this.policy = _get(record, 'policies.value') || 0 14 | this.policies = _get(record, 'policies.flags') || [] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/apiHelper/tfa.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | import keyServer from '../utils/keyServer' 3 | 4 | export default { 5 | addGAuth () { 6 | return keyServer.post('/tfa', { username: store.state.user.name }) 7 | }, 8 | 9 | getTfaBackends () { 10 | return keyServer.get(`/tfa`, { wallet_id: store.state.user.wallet.id }, true) 11 | }, 12 | 13 | enableGAuth (id) { 14 | return keyServer.patch(`/tfa/${id}`, { wallet_id: store.state.user.wallet.id, priority: 10 }) 15 | }, 16 | 17 | verifyTfaCode (code, tfaToken) { 18 | return keyServer.get('/tfa/verify', { code: code, token: tfaToken }, false) 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | import { ApiCaller, DocumentsManager } from '@tokend/js-sdk' 2 | import config from '@/config' 3 | 4 | export const api = new ApiCaller() 5 | export const documentsManager = new DocumentsManager() 6 | 7 | export function useWallet (newWallet) { 8 | api.useWallet(newWallet) 9 | documentsManager.useApi(api) 10 | documentsManager.useStorageURL(config.FILE_STORAGE) 11 | } 12 | 13 | export async function loadingDataViaLoop (response) { 14 | let data = response.data 15 | while (response.data.length) { 16 | response = await response.fetchNext() 17 | data = [...data, ...response.data] 18 | } 19 | return data 20 | } 21 | -------------------------------------------------------------------------------- /src/components/common/getters/UserDocLinkGetter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 24 | -------------------------------------------------------------------------------- /src/js/records/keyValue.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class KeyValueRecord { 4 | constructor (record) { 5 | this._record = record 6 | this.key = _get(record, 'id', '') 7 | this.u32 = String(_get(record, 'value.u32', '')) 8 | this.str = _get(record, 'value.str', '') 9 | this.u64 = String(_get(record, 'value.u64', '')) 10 | this.entryTypeName = _get(record, 'value.type.name', '') 11 | this.entryType = _get(record, 'value.type.value', '') 12 | 13 | this.value = this.u32 || this.str || this.u64 || '0' 14 | } 15 | 16 | get isHaveKey () { 17 | return Boolean(this.key) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | 3 | ARG BUILD_VERSION 4 | WORKDIR /build 5 | COPY . $PWD 6 | RUN true \ 7 | && yarn install \ 8 | && yarn build --set-build-version "$BUILD_VERSION" \ 9 | && true 10 | 11 | FROM nginx:latest 12 | RUN echo '\n\ 13 | server {\n\ 14 | listen 80 default_server;\n\ 15 | root /usr/share/nginx/html;\n\ 16 | index index.html index.htm;\n\ 17 | server_name _;\n\ 18 | location / {\n\ 19 | try_files $uri /index.html;\n\ 20 | }\n\ 21 | }\n' > /etc/nginx/conf.d/default.conf 22 | COPY --from=0 /build/dist /usr/share/nginx/html 23 | CMD ["nginx", "-g", "daemon off;"] 24 | -------------------------------------------------------------------------------- /src/components/User/Assets/AssetPairs/AssetPairs.Show.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /src/js/records/movement.record.js: -------------------------------------------------------------------------------- 1 | import safeGet from 'lodash/get' 2 | 3 | export class Movement { 4 | constructor (record) { 5 | this.id = record.id 6 | 7 | if (record.operation) { 8 | this.appliedAt = safeGet(record, 'operation.appliedAt') || record.appliedAt 9 | this.operationDetails = record.operation 10 | this.operationType = safeGet(record, 'operation.details.type') 11 | this.sourceAccount = safeGet(record, 'operation.source.id') 12 | this.receiverAccount = safeGet(record, 'operation.details.receiverAccount.id') 13 | this.accountTo = safeGet(record, 'operation.details.accountTo.id') 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/User/Assets/SystemAssets/SystemAssets.New.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 28 | -------------------------------------------------------------------------------- /src/components/User/OfflineOperations/Preissuance/Preissuance.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /src/components/User/KycRecoveryRequests/KycRecoveryRequests.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /src/components/User/Fees/Fees.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /src/components/User/User.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /src/components/User/Assets/SystemAssets/SystemAssets.Show.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /src/components/User/RolesAndRules/RolesAndRules.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /src/components/User/Limits/Limits.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/UserDetails.GeneralKycViewer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/UserDetails.VerifiedKycViewer.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current", 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | }, 14 | "modules": "cjs" 15 | } 16 | ] 17 | ], 18 | "plugins": [ 19 | [ 20 | "@babel/plugin-transform-runtime", 21 | { 22 | "corejs": 2 23 | } 24 | ], 25 | [ 26 | "babel-plugin-transform-builtin-extend", 27 | { 28 | "globals": [ 29 | "Error", 30 | "Array" 31 | ] 32 | } 33 | ], 34 | ["@babel/plugin-syntax-dynamic-import"] 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/ErrorTracker.js: -------------------------------------------------------------------------------- 1 | import * as sentry from '@sentry/browser' 2 | import * as Integrations from '@sentry/integrations' 3 | import Vue from 'vue' 4 | 5 | export class ErrorTracker { 6 | static init (config) { 7 | sentry.init({ 8 | dsn: config.SENTRY_DSN, 9 | release: config.BUILD_VERSION, 10 | integrations: [ 11 | new Integrations.Vue({ 12 | Vue, 13 | attachProps: true, 14 | }), 15 | ], 16 | }) 17 | } 18 | 19 | static setLoggedInUser ({ accountId = '', name = '' }) { 20 | sentry.configureScope((scope) => { 21 | scope.setUser({ accountId, name }) 22 | }) 23 | } 24 | 25 | static trackMessage (msg) { 26 | sentry.captureMessage(msg) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 39 | -------------------------------------------------------------------------------- /src/components/User/Sales/Sales.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | import config from '../config' 2 | import { base } from '@tokend/js-sdk' 3 | import get from 'lodash/get' 4 | 5 | export const keypair = (state) => { 6 | if (!state.user.keys.seed) return {} 7 | return base.Keypair.fromSecret(state.user.keys.seed) 8 | } 9 | 10 | export const masterId = (state) => { 11 | return config.MASTER_ACCOUNT 12 | } 13 | 14 | export const pageLimit = (state) => { 15 | return config.PAGE_LIMIT 16 | } 17 | 18 | export const message = (state) => state.message 19 | 20 | export function GET_USER (state) { 21 | return state.user 22 | } 23 | 24 | export function GET_USER_ADDRESS (state) { 25 | return get(state, 'user.address', '') 26 | } 27 | 28 | export const accountId = (state) => { 29 | return state.user.keys.accountId 30 | } 31 | -------------------------------------------------------------------------------- /src/components/User/Polls/Polls.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserAccountDetails.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | 33 | 39 | -------------------------------------------------------------------------------- /src/components/User/OfflineOperations/ChangeAssetIssuer/ChangeAssetIssuer.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | 31 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/KycRequests.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /src/js/modals/flow-blocking-modal.mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data: _ => ({ 3 | form: { 4 | // form 5 | }, 6 | resolvers: { 7 | resolve: () => {}, 8 | reject: () => {}, 9 | }, 10 | isOpened: true, 11 | isResolved: false, 12 | }), 13 | 14 | methods: { 15 | setResolvers (resolve, reject) { 16 | this.resolvers.resolve = resolve 17 | this.resolvers.reject = reject 18 | }, 19 | 20 | resetResolvers () { 21 | this.isResolved = true 22 | }, 23 | 24 | close () { 25 | this.resetResolvers() 26 | this.isOpened = false 27 | this.removeElement() 28 | }, 29 | 30 | removeElement () { 31 | this.isOpened = false 32 | if (this.$el.parentNode) { 33 | this.$el.parentNode.removeChild(this.$el) 34 | } 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import { state } from './state' 5 | import * as getters from './getters' 6 | import * as actions from './actions' 7 | import * as mutations from './mutations' 8 | import plugins from './plugins' 9 | 10 | import idleHandler from './modules/idle-handler' 11 | import loader from './modules/loader' 12 | import tfa from './modules/tfa' 13 | import assets from './modules/assets' 14 | import keyValue from './modules/key-value' 15 | import { emailBooks } from './modules/email-books' 16 | 17 | Vue.use(Vuex) 18 | 19 | const store = new Vuex.Store({ 20 | state, 21 | getters, 22 | actions, 23 | mutations, 24 | plugins, 25 | modules: { 26 | idleHandler, 27 | loader, 28 | tfa, 29 | emailBooks, 30 | assets, 31 | keyValue, 32 | }, 33 | }) 34 | 35 | export default store 36 | -------------------------------------------------------------------------------- /src/constants/request-states.js: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | 3 | export const REQUEST_STATES = { 4 | pending: { 5 | state: 'pending', 6 | stateI: 1, 7 | }, 8 | cancelled: { 9 | state: 'cancelled', 10 | stateI: 2, 11 | }, 12 | approved: { 13 | state: 'approved', 14 | stateI: 3, 15 | }, 16 | rejected: { 17 | state: 'rejected', 18 | stateI: 4, 19 | }, 20 | permanentlyRejected: { 21 | state: 'permanentlyRejected', 22 | stateI: 5, 23 | }, 24 | } 25 | 26 | export const KYC_REQUEST_STATES = Object.freeze( 27 | Object.entries({ 28 | ...REQUEST_STATES, 29 | }) 30 | .filter(([key]) => { 31 | return Object.values(config.FEATURES.KYC_REQUEST_STATES) 32 | .includes(key) 33 | }) 34 | .reduce((res, [key, value]) => { 35 | res[key] = value 36 | return res 37 | }, {}) 38 | ) 39 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/ReviewableRequest.js: -------------------------------------------------------------------------------- 1 | import apiHelper from '@/apiHelper' 2 | 3 | export class ReviewableRequest { 4 | constructor (record) { 5 | this._rawRequest = { ...record } 6 | this.record = { ...record, ...record.requestDetails } 7 | this.operationDetails = this.record.requestDetails 8 | } 9 | 10 | get id () { 11 | return this.record.id 12 | } 13 | 14 | get state () { 15 | return this.record.state 16 | } 17 | 18 | get stateI () { 19 | return this.record.stateI 20 | } 21 | 22 | get rejectReason () { 23 | return this.record.rejectReason 24 | } 25 | 26 | // actions: 27 | 28 | fulfill () { 29 | return apiHelper.requests.approve(this._rawRequest) 30 | } 31 | 32 | reject (reason = '', isPermanent = false) { 33 | return apiHelper.requests.reject({ reason, isPermanent }, this._rawRequest) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/App/filters/globalizeLimitType.js: -------------------------------------------------------------------------------- 1 | import { LIMIT_TYPES } from '@/constants' 2 | import { globalize } from '@/components/App/filters/filters' 3 | 4 | export function globalizeLimitType (value) { 5 | let translationId = '' 6 | switch (value) { 7 | case LIMIT_TYPES.dailyOut: { 8 | translationId = 'filters.limit-types.daily' 9 | break 10 | } 11 | case LIMIT_TYPES.weeklyOut: { 12 | translationId = 'filters.limit-types.weekly' 13 | break 14 | } 15 | case LIMIT_TYPES.monthlyOut: { 16 | translationId = 'filters.limit-types.monthly' 17 | break 18 | } 19 | case LIMIT_TYPES.annualOut: { 20 | translationId = 'filters.limit-types.annual' 21 | break 22 | } 23 | default: { 24 | translationId = 'filters.limit-types.default' 25 | break 26 | } 27 | } 28 | return globalize(translationId) 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/mdi/AccountStarIcon.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default Vue.component('mdi-account-star-icon', { 4 | props: { 5 | className: { 6 | type: [Object, Array, String], 7 | default: 'mdi-account-star-icon', 8 | }, 9 | width: { 10 | type: Number, 11 | default: 24, 12 | }, 13 | height: { 14 | type: Number, 15 | default: 24, 16 | }, 17 | viewBox: { 18 | type: String, 19 | default: '0 0 24 24', 20 | }, 21 | }, 22 | 23 | template: ` 24 | 25 | 26 | `, 27 | }) 28 | -------------------------------------------------------------------------------- /src/components/App/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/scss/colors"; 2 | @import "defaults"; 3 | @import "typography"; 4 | @import "buttons"; 5 | @import "modals"; 6 | @import "forms"; 7 | @import "lists"; 8 | @import "data-representations"; 9 | 10 | $container-padding: 0 30px; 11 | 12 | .app__container { 13 | width: 100%; 14 | padding: $container-padding; 15 | margin: 0 auto; 16 | } 17 | 18 | .app__user-container { 19 | width: 100%; 20 | padding: $container-padding; 21 | margin: 4rem auto; 22 | max-width: 106rem; 23 | } 24 | 25 | .app__block { 26 | border-radius: .3rem; 27 | background-color: $color-content-bg; 28 | box-shadow: 0px 1px 5.6px 0.4px rgba(170, 170, 170, 0.72); 29 | padding: 4rem; 30 | } 31 | 32 | .app__more-btn-wrp { 33 | margin: 2rem auto 0; 34 | text-align: center; 35 | 36 | & > button { 37 | max-width: 6rem; 38 | margin: 0 auto; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/store/modules/assets.js: -------------------------------------------------------------------------------- 1 | import { Asset } from '../wrappers/asset' 2 | import { api, loadingDataViaLoop } from '@/api' 3 | 4 | const ASSETS_PAGE_LIMIT = 100 5 | 6 | const state = { 7 | assets: [], 8 | } 9 | 10 | const mutations = { 11 | SET_ASSETS (state, assets) { 12 | state.assets = assets 13 | }, 14 | } 15 | 16 | const actions = { 17 | async LOAD_ASSETS ({ commit }) { 18 | let pageResponse = await api.get('/v3/assets', { 19 | page: { limit: ASSETS_PAGE_LIMIT }, 20 | }) 21 | let assets = await loadingDataViaLoop(pageResponse) 22 | 23 | commit('SET_ASSETS', assets) 24 | }, 25 | } 26 | 27 | const getters = { 28 | assets: state => state.assets.map(a => new Asset(a)), 29 | assetByCode: (_, getters) => assetCode => 30 | getters.assets.find(item => item.code === assetCode), 31 | } 32 | 33 | export default { 34 | state, 35 | mutations, 36 | actions, 37 | getters, 38 | } 39 | -------------------------------------------------------------------------------- /src/store/modules/tfa.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | tfaToken: '', 3 | isRequired: false, 4 | initiator: '', 5 | } 6 | 7 | const getters = { 8 | tfaIsRequired: state => state.isRequired, 9 | tfaToken: state => state.tfaToken, 10 | tfaInitiator: state => state.initiator, 11 | } 12 | 13 | const actions = { 14 | 'CLOSE_TFA' ({ commit, state }) { 15 | state.tfaToken = '' 16 | state.isRequired = false 17 | commit('CLOSE_MODAL') 18 | }, 19 | } 20 | 21 | const mutations = { 22 | 'REQUIRE_TFA' (state, payload) { 23 | state.isRequired = true 24 | state.tfaToken = payload.tfaToken 25 | state.initiator = payload.initiator 26 | }, 27 | 28 | 'TFA_FORM_DONE' (state) {}, 29 | 30 | 'TFA_FORM_FALSE' (state) {}, 31 | 32 | 'TFA_FORM_RESEND' (state) {}, 33 | 34 | 'TFA_FORM_CLOSE' (state) {}, 35 | } 36 | 37 | export default { 38 | state, 39 | getters, 40 | actions, 41 | mutations, 42 | } 43 | -------------------------------------------------------------------------------- /src/store/plugins.js: -------------------------------------------------------------------------------- 1 | import { STORAGE_KEY } from './state' 2 | 3 | function isLocalStorageNameSupported () { 4 | const testKey = 'test' 5 | const storage = window.sessionStorage 6 | try { 7 | storage.setItem(testKey, '1') 8 | storage.removeItem(testKey) 9 | return true 10 | } catch (error) { 11 | return false 12 | } 13 | } 14 | 15 | const localStoragePlugin = store => { 16 | if (!isLocalStorageNameSupported()) return 17 | 18 | store.subscribe((mutation, state) => { 19 | state.timestamp = Date.now() 20 | sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state)) 21 | 22 | if (mutation.type === 'CLEAR_ALL_DATA') { 23 | sessionStorage.removeItem(STORAGE_KEY) 24 | } 25 | }) 26 | } 27 | 28 | // TODO: setup env 29 | // export default process.env.NODE_ENV !== 'production' 30 | // ? [localStoragePlugin] 31 | // : [localStoragePlugin] 32 | export default [localStoragePlugin] 33 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker:18-dind 3 | 4 | variables: 5 | GITHUB_REF: $CI_COMMIT_SHA 6 | DH_IMAGE: tokend/admin-client 7 | 8 | stages: 9 | - build 10 | - publish 11 | 12 | build: 13 | image: registry.gitlab.com/tokend/deployment/docker-build:latest 14 | stage: build 15 | before_script: 16 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 17 | tags: 18 | - tokend 19 | script: 20 | - ci-little-helper set-pending build 21 | - .ci/build.sh && STATE=0 || STATE=1 22 | - ci-little-helper set-state $STATE build && exit $STATE 23 | 24 | publish dockerhub: 25 | image: registry.gitlab.com/tokend/deployment/docker-build:latest 26 | stage: publish 27 | tags: 28 | - tokend 29 | only: 30 | - /^.+\..+\..+$/ 31 | script: 32 | - ci-little-helper set-pending publish 33 | - .ci/publish.sh && STATE=0 || STATE=1 34 | - ci-little-helper set-state $STATE publish && exit $STATE 35 | -------------------------------------------------------------------------------- /src/components/App/scss/_typography.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/scss/colors"; 2 | 3 | a { 4 | text-decoration: underline; 5 | color: $color-active; 6 | } 7 | 8 | h1 { 9 | font-size: 3.2rem; 10 | font-weight: 300; 11 | } 12 | 13 | h2 { 14 | font-size: 2.4rem; 15 | font-weight: 300; 16 | margin-bottom: 4.5rem; 17 | } 18 | 19 | h3 { 20 | font-size: 1.6rem; 21 | line-height: 1.5; 22 | font-weight: bold; 23 | } 24 | 25 | h4 { 26 | font-size: 1.4rem; 27 | font-weight: bold; 28 | margin-bottom: 1rem; 29 | } 30 | 31 | ul, ol, nav, li { 32 | font-size: 1.6rem; 33 | } 34 | 35 | p, span, br, label { 36 | font-size: 1.6rem; 37 | 38 | &.small { 39 | font-size: 1.2rem; 40 | } 41 | 42 | &.secondary { 43 | color: $color-text-secondary; 44 | } 45 | 46 | &.danger { 47 | color: $color-danger; 48 | } 49 | 50 | &.text { 51 | line-height: 1.5; 52 | } 53 | 54 | &.capitalized:first-letter { 55 | text-transform: uppercase; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import NProgress from 'nprogress' 5 | 6 | import { AuthRoutes } from './routes/AuthRoutes' 7 | import { UserRoutes } from './routes/UserRoutes' 8 | 9 | import { isLoggedIn } from './helpers/isLoggedIn' 10 | 11 | Vue.use(Router) 12 | NProgress.configure({ showSpinner: false }) 13 | 14 | const router = new Router({ 15 | mode: 'history', 16 | routes: [ 17 | { path: '*', redirect: '/' }, 18 | { 19 | path: '/', 20 | name: 'root', 21 | redirect () { 22 | return isLoggedIn() 23 | ? { name: UserRoutes.name } 24 | : { name: AuthRoutes.name } 25 | }, 26 | }, 27 | AuthRoutes, 28 | UserRoutes, 29 | ], 30 | }) 31 | 32 | router.beforeEach((to, from, next) => { 33 | if (to.name !== from.name) { 34 | NProgress.start() 35 | } 36 | next() 37 | }) 38 | 39 | router.afterEach((to, from) => { 40 | NProgress.done() 41 | }) 42 | 43 | export default router 44 | -------------------------------------------------------------------------------- /src/components/common/getters/link_getter.mixin.js: -------------------------------------------------------------------------------- 1 | import { api } from '@/api' 2 | 3 | export default { 4 | props: { 5 | fileKey: { type: String, default: '' }, 6 | }, 7 | 8 | data () { 9 | return { 10 | href: '', 11 | isLoaded: false, 12 | isFailed: false, 13 | isNoFile: false, 14 | } 15 | }, 16 | 17 | watch: { 18 | fileKey () { this.getHref() }, 19 | }, 20 | 21 | created () { 22 | this.getHref() 23 | }, 24 | 25 | methods: { 26 | async getHref () { 27 | this.isLoaded = false 28 | this.isFailed = false 29 | 30 | try { 31 | if (!this.fileKey) { 32 | this.isNoFile = true 33 | this.isFailed = true 34 | return 35 | } 36 | const { data } = await api 37 | .getWithSignature(`/documents/${this.fileKey}`) 38 | this.href = data.url 39 | this.isLoaded = true 40 | } catch (error) { 41 | this.isFailed = true 42 | } 43 | }, 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /src/js/records/limits.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | 3 | export class LimitsRecord { 4 | constructor (record = {}, details) { 5 | this._record = record 6 | this.assetCode = 7 | _get(record, 'asset.id') || 8 | details.assetCode || 9 | null 10 | 11 | this.annualOut = _get(record, 'annualOut') || '' 12 | this.dailyOut = _get(record, 'dailyOut') || '' 13 | this.weeklyOut = _get(record, 'weeklyOut') || '' 14 | this.monthlyOut = _get(record, 'monthlyOut') || '' 15 | 16 | this.statsOpType = 17 | _get(record, 'statsOpType') || 18 | details.statsOpType || 19 | null 20 | 21 | this.accountRole = 22 | _get(record, 'accountRole.id') || 23 | _get(details, 'accountRole') || 24 | null 25 | 26 | this.accountID = 27 | _get(record, 'account.id') || 28 | details.accountId || 29 | null 30 | 31 | this.isConvertNeeded = _get(record, 'isConvertNeeded') || false 32 | 33 | this.id = _get(record, 'id') || 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/file-reader.js: -------------------------------------------------------------------------------- 1 | export const fileReader = { 2 | deriveFileFromChangeEvent (event) { 3 | const files = event.target.files || event.dataTransfer.files 4 | if (!files.length) return 5 | return files[0] 6 | }, 7 | 8 | async readFileAsDataUrl (file) { 9 | // eslint-disable-next-line promise/avoid-new 10 | return new Promise((resolve, reject) => { 11 | const reader = new FileReader() 12 | reader.onload = () => { 13 | resolve(reader.result) 14 | } 15 | reader.onerror = (error) => { 16 | reject(error) 17 | } 18 | reader.readAsDataURL(file) 19 | }) 20 | }, 21 | 22 | async readFileAsArrayBuffer (file) { 23 | // eslint-disable-next-line promise/avoid-new 24 | return new Promise((resolve, reject) => { 25 | const reader = new FileReader() 26 | reader.onload = () => { 27 | resolve(reader.result) 28 | } 29 | reader.onerror = (error) => { 30 | reject(error) 31 | } 32 | reader.readAsArrayBuffer(file) 33 | }) 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /src/components/User/OfflineOperations/OfflineOperations.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /src/components/common/getters/OperationCounterparty.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 45 | -------------------------------------------------------------------------------- /src/components/common/getters/accountTypeLocalizer.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT_TYPES } from '@/constants' 2 | 3 | export const ACCOUNT_TYPES_VERBOSE = Object.freeze({ 4 | operational: 'Operational', 5 | [ACCOUNT_TYPES.operational]: 'Operational', 6 | 7 | general: 'General', 8 | [ACCOUNT_TYPES.general]: 'General', 9 | 10 | accreditedInvestor: 'Accredited Investor', 11 | [ACCOUNT_TYPES.accreditedInvestor]: 'Accredited Investor', 12 | 13 | commission: 'Comission', 14 | [ACCOUNT_TYPES.commission]: 'Comission', 15 | 16 | master: 'Master', 17 | [ACCOUNT_TYPES.master]: 'Master', 18 | 19 | notVerified: 'Not verified', 20 | [ACCOUNT_TYPES.notVerified]: 'Not verified', 21 | 22 | syndycate: 'Syndicate user', 23 | [ACCOUNT_TYPES.corporate]: 'Syndicate user', 24 | 25 | exchange: 'Exchange', 26 | [ACCOUNT_TYPES.exchange]: 'Exchange', 27 | 28 | institutionalInvestor: 'Institutional Investor', 29 | [ACCOUNT_TYPES.institutionalInvestor]: 'Institutional Investor', 30 | 31 | verified: 'Verified', 32 | [ACCOUNT_TYPES.verified]: 'Verified', 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/App/filters/globalizeStatsOpType.js: -------------------------------------------------------------------------------- 1 | import { STATS_OPERATION_TYPES } from '@/constants' 2 | import { globalize } from '@/components/App/filters/filters' 3 | 4 | export function globalizeStatsOpType (value) { 5 | let translationId = '' 6 | switch (value) { 7 | case STATS_OPERATION_TYPES.paymentOut: { 8 | translationId = 'filters.stats-op-type.payment-out' 9 | break 10 | } 11 | case STATS_OPERATION_TYPES.withdraw: { 12 | translationId = 'filters.stats-op-type.withdraw' 13 | break 14 | } 15 | case STATS_OPERATION_TYPES.spend: { 16 | translationId = 'filters.stats-op-type.spend' 17 | break 18 | } 19 | case STATS_OPERATION_TYPES.deposit: { 20 | translationId = 'filters.stats-op-type.deposit' 21 | break 22 | } 23 | case STATS_OPERATION_TYPES.payout: { 24 | translationId = 'filters.stats-op-type.payout' 25 | break 26 | } 27 | default: { 28 | translationId = 'filters.request-states.default' 29 | break 30 | } 31 | } 32 | return globalize(translationId) 33 | } 34 | -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | export const STORAGE_KEY = 'vuex' 2 | 3 | const initialState = () => { 4 | return { 5 | timestamp: null, 6 | auth: { 7 | isLoggedIn: false, 8 | }, 9 | user: { 10 | name: '', 11 | address: '', // user address (accountId the user logged in with) 12 | keys: { // signing keys 13 | seed: '', 14 | accountId: '', 15 | }, 16 | wallet: { 17 | id: '', 18 | }, 19 | }, 20 | message: { 21 | type: '', // info or error 22 | text: '', 23 | }, 24 | isModalOpen: false, 25 | } 26 | } 27 | 28 | const newState = initialState() 29 | 30 | // Check session storage for our key and retrieve the data, if it exists, 31 | // otherwise use defaults. 32 | if (sessionStorage.getItem(STORAGE_KEY)) { 33 | const oldState = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) 34 | const isExpired = Date.now() - oldState.timestamp > 13 * 1000 35 | if (!isExpired) { 36 | newState.auth = oldState.auth 37 | newState.user = oldState.user 38 | } 39 | } 40 | 41 | export const state = newState 42 | -------------------------------------------------------------------------------- /src/components/App/scss/_data-representations.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/scss/colors"; 2 | 3 | label.data-caption { 4 | &:not(:first-child) { 5 | margin-top: 2rem; 6 | } 7 | margin-bottom: 0.5rem; 8 | display: block; 9 | font-size: 1.6rem; 10 | 11 | &:not(.danger) { 12 | color: $color-text-secondary; 13 | } 14 | } 15 | 16 | ul.key-value-list { 17 | overflow: hidden; 18 | padding: 0 0.8rem; 19 | margin: 0 -0.8rem; 20 | 21 | & > li { 22 | padding: 0.3rem 0.8rem; 23 | margin: 0 -0.8rem; 24 | display: flex; 25 | justify-content: space-between; 26 | 27 | &:hover { 28 | background-color: rgba(0, 0, 0, 0.08); 29 | } 30 | } 31 | 32 | & > li > span { 33 | flex: 1; 34 | 35 | &:last-of-type { 36 | text-align: right; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | } 40 | 41 | &:last-of-type[title] { 42 | user-select: all; 43 | } 44 | } 45 | 46 | & > label.data-caption { 47 | margin-bottom: 0; 48 | 49 | &:not(:first-child) { 50 | margin-top: 1rem; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/App/filters/globalizeRequestStateI.js: -------------------------------------------------------------------------------- 1 | import { REQUEST_STATES } from '@/constants' 2 | import { globalize } from '@/components/App/filters/filters' 3 | 4 | export function globalizeRequestStateI (value) { 5 | let translationId = '' 6 | switch (+value) { 7 | case REQUEST_STATES.pending.stateI: { 8 | translationId = 'filters.request-states.pending' 9 | break 10 | } 11 | case REQUEST_STATES.cancelled.stateI: { 12 | translationId = 'filters.request-states.cancelled' 13 | break 14 | } 15 | case REQUEST_STATES.approved.stateI: { 16 | translationId = 'filters.request-states.approved' 17 | break 18 | } 19 | case REQUEST_STATES.rejected.stateI: { 20 | translationId = 'filters.request-states.rejected' 21 | break 22 | } 23 | case REQUEST_STATES.permanentlyRejected.stateI: { 24 | translationId = 'filters.request-states.permanently-rejected' 25 | break 26 | } 27 | default: { 28 | translationId = 'filters.request-states.default' 29 | break 30 | } 31 | } 32 | return globalize(translationId) 33 | } 34 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/KycRecoveryRequest.js: -------------------------------------------------------------------------------- 1 | import safeGet from 'lodash/get' 2 | import { REQUEST_STATES } from '@/constants' 3 | 4 | export class KycRecoveryRequest { 5 | constructor (record) { 6 | this._record = record 7 | this.id = record.id || '0' 8 | this.hash = record.hash 9 | this.type = safeGet(record, 'xdrType.value') 10 | 11 | this.pendingTasks = record.pendingTasks 12 | this.allTasks = record.allTasks 13 | this.requestor = safeGet(record, 'requestor.id') 14 | 15 | this.state = record.state 16 | this.stateI = record.stateI 17 | 18 | this.creatorDetails = safeGet(record, 'requestDetails.creatorDetails') 19 | 20 | this.externalDetails = safeGet(record, 'externalDetails.data') 21 | } 22 | 23 | get record () { 24 | return this._record 25 | } 26 | 27 | get isApproved () { 28 | return this.stateI === REQUEST_STATES.approved.stateI 29 | } 30 | 31 | get isPending () { 32 | return this.stateI === REQUEST_STATES.pending.stateI 33 | } 34 | 35 | get isRejected () { 36 | return this.stateI === REQUEST_STATES.rejected.stateI 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/js/records/fees.record.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get' 2 | import { FEE_TYPES } from '@/constants' 3 | 4 | export class FeesRecord { 5 | constructor (record = {}, details) { 6 | this._record = record 7 | this._details = details 8 | 9 | this.asset = 10 | _get(record, 'asset.id') || 11 | _get(record, 'appliedTo.asset') || 12 | _get(details, 'assetCode') 13 | 14 | this.feeType = 15 | _get(record, 'appliedTo.feeType') || 16 | _get(details, 'feeType') 17 | 18 | this.subtype = +this.feeType === +FEE_TYPES.paymentFee 19 | ? _get(record, 'appliedTo.subtype') || _get(details, 'paymentFeeSubtype') 20 | : 0 21 | 22 | this.lowerBound = _get(record, 'appliedTo.lowerBound') || 0 23 | this.upperBound = _get(record, 'appliedTo.upperBound') || 0 24 | 25 | this.fixed = _get(record, 'fixed') || 0 26 | this.percent = _get(record, 'percent') || 0 27 | this.exists = this._isFeeExists(record) 28 | } 29 | 30 | // In the fees that come from the server there is no field exists 31 | _isFeeExists (fee) { 32 | return typeof fee.exists === 'undefined' ? true : fee.exists 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/User/Assets/Assets.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /src/store/modules/email-books.js: -------------------------------------------------------------------------------- 1 | import { getters, actions, mutations } from '../types' 2 | import apiHelper from '@/apiHelper' 3 | 4 | const emailBooks = { 5 | state: { 6 | addressBook: {}, 7 | busyAddresses: new Set(), 8 | }, 9 | 10 | actions: { 11 | async [actions.REQUEST_EMAIL_BY_ADDRESS] (context, payload) { 12 | const address = payload 13 | const state = context.state 14 | 15 | if (state.busyAddresses.has(address)) { 16 | return 17 | } 18 | 19 | state.busyAddresses.add(address) 20 | try { 21 | const email = await apiHelper.users.getEmailByAccountId(address) 22 | context.commit(mutations.PUSH_EMAIL_TO_ADDRESS_BOOK, { address, email }) 23 | } catch (error) { 24 | throw error 25 | } 26 | state.busyAddresses.delete(address) 27 | }, 28 | }, 29 | 30 | mutations: { 31 | [mutations.PUSH_EMAIL_TO_ADDRESS_BOOK] (state, payload) { 32 | state.addressBook[payload.address] = payload.email 33 | }, 34 | }, 35 | 36 | getters: { 37 | [getters.GET_EMAIL_ADDRESS_BOOK] (state) { 38 | return state.addressBook 39 | }, 40 | }, 41 | } 42 | 43 | export { emailBooks } 44 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export const LOG_IN = (state) => { 2 | state.auth.isLoggedIn = true 3 | } 4 | export const UPDATE_AUTH = (state, auth) => { 5 | state.auth = auth 6 | } 7 | 8 | export const UPDATE_USER = (state, user) => { 9 | state.user = user 10 | } 11 | 12 | // MODALS 13 | export const OPEN_MODAL = (state) => { 14 | state.isModalOpen = true 15 | } 16 | 17 | export const CLOSE_MODAL = (state) => { 18 | state.isModalOpen = false 19 | } 20 | 21 | export const WANT_CLOSE_MODAL = (state) => {} 22 | 23 | /** 24 | * Clear each property, one by one, so reactivity still works. 25 | * 26 | * (ie. clear out state.auth.isLoggedIn so Navbar component automatically 27 | * reacts to logged out state, and the Navbar menu adjusts accordingly) 28 | * 29 | * TODO: use a common import of default state to reset these values with. 30 | */ 31 | export const CLEAR_ALL_DATA = (state) => { 32 | // Auth 33 | state.auth.isLoggedIn = false 34 | 35 | // User 36 | state.user.name = '' 37 | state.user.address = '' 38 | state.user.keys.seed = '' 39 | state.user.keys.accountId = '' 40 | state.user.wallet = {} 41 | 42 | // Status messages 43 | state.message.type = '' 44 | state.message.text = '' 45 | 46 | location.reload() 47 | } 48 | -------------------------------------------------------------------------------- /src/validators.js: -------------------------------------------------------------------------------- 1 | import * as validators from 'vuelidate/lib/validators' 2 | import { base } from '@tokend/js-sdk' 3 | import { ADMIN_CONST } from '@/constants' 4 | 5 | export const password = value => validators.minLength(8)(value) 6 | export const seed = value => base.Keypair.isValidSecretKey(value) 7 | export const accountId = value => base.Keypair.isValidPublicKey(value) 8 | export const balanceId = value => base.Keypair.isValidBalanceKey(value) 9 | 10 | export const emailOrAccountId = value => { 11 | return validators.email(value) || accountId(value) 12 | } 13 | 14 | export const emailOrAccountIdOrBalanceId = value => { 15 | return validators.email(value) || accountId(value) || balanceId(value) 16 | } 17 | 18 | export const isNotAdminRule = (value) => +value !== ADMIN_CONST.RULE_ID 19 | 20 | export const isRuleNotAdded = (rulesID) => 21 | (value) => rulesID.indexOf(value) === -1 22 | 23 | export const isRuleExists = (rulesID) => 24 | (value) => rulesID.indexOf(value) !== -1 25 | 26 | export const noMoreThanAvailableForIssuance = available => value => { 27 | return +available >= +value 28 | } 29 | 30 | export const hex = value => { 31 | return /^(0x|0X)?[a-fA-F0-9]+$/.test(value) 32 | } 33 | 34 | export * from 'vuelidate/lib/validators' 35 | -------------------------------------------------------------------------------- /src/constants/document-types.js: -------------------------------------------------------------------------------- 1 | export const DOCUMENT_TYPES = Object.freeze({ 2 | assetTerms: 'token_terms', 3 | assetLogo: 'asset_logo', 4 | }) 5 | export const ID_DOCUMENT_TYPES = { 6 | passport: 'passport', 7 | drivingLicense: 'driving_license', 8 | identityCard: 'identity_card', 9 | residencePermit: 'residence_permit', 10 | } 11 | 12 | export const DOCUMENT_TYPES_STR = Object.freeze({ 13 | kycIdDocument: 'kyc_id_document', 14 | kycProofOfAddress: 'kyc_poa', 15 | kycSelfie: 'kyc_selfie', 16 | kycTaxReturns: 'kyc_tax_returns', 17 | kycOriginationCertificate: 'company_origination_certificate', 18 | kycInvestmentPresentation: 'kyc_investment_presentation', 19 | kycProofOfInvestor: 'kyc_proof_investor', 20 | kycShareholdersCertificate: 'kyc_shaholders_certificate', 21 | kycAnnualReport: 'kyc_annual_report', 22 | kycMemorandium: 'kyc_memorandium', 23 | kycSignatoriesAuthorization: 'kyc_signatories_authorization', 24 | kycOrganizationChart: 'kyc_organization_chart', 25 | kycBusinessModel: 'kyc_business_model', 26 | passport: 'passport', 27 | driving_license: 'driving_license', 28 | identity_card: 'identity_card', 29 | residence_permit: 'residence_permit', 30 | assetTerms: 'token_terms', 31 | assetLogo: 'asset_logo', 32 | }) 33 | -------------------------------------------------------------------------------- /src/utils/kyc-tempater.js: -------------------------------------------------------------------------------- 1 | import { ID_DOCUMENT_TYPES } from '../constants' 2 | import get from 'lodash/get' 3 | 4 | export function fromKycTemplate (template) { 5 | return { 6 | idDocumentType: get(template, 'documents.kyc_id_document.type') || ID_DOCUMENT_TYPES.passport, 7 | documents: { 8 | kycIdDocument: { 9 | face: get(template, 'documents.kyc_id_document.face.key'), 10 | back: get(template, 'documents.kyc_id_document.back.key'), 11 | }, 12 | kycAvatar: get(template, 'documents.kyc_avatar.key'), 13 | kycSelfie: template.documents.kyc_selfie 14 | ? get(template, 'documents.kyc_selfie.key') 15 | : get(template, 'documents.bravo.key'), 16 | kycProofInvestor: get(template, 'documents.kyc_proof_investor.key'), 17 | }, 18 | first_name: template.first_name, 19 | last_name: template.last_name, 20 | date_of_birth: template.date_of_birth, 21 | address: { 22 | line_1: get(template, 'address.line_1'), 23 | line_2: get(template, 'address.line_2'), 24 | city: get(template, 'address.city'), 25 | country: get(template, 'address.country'), 26 | state: get(template, 'address.state'), 27 | postal_code: get(template, 'address.postal_code'), 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/User/Trades/Trades.Index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 48 | 49 | 58 | -------------------------------------------------------------------------------- /src/components/common/SyndicateMember/SyndicateMember.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | 56 | -------------------------------------------------------------------------------- /src/components/User/components/UserHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 60 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | extends: [ 6 | 'distributed-lab/vue', 7 | ], 8 | parserOptions: { 9 | parser: 'babel-eslint', 10 | ecmaVersion: 2017, 11 | sourceType: 'module', 12 | }, 13 | rules: { 14 | // 0 off, 1 warning, 2 error 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': 1, 21 | 'no-warning-comments': [1, { 22 | 'terms': ['hardcoded'], location: 'anywhere', 23 | }], 24 | 'no-console': [1, { 25 | allow: ['warn', 'error'], 26 | }], 27 | 'no-tabs': 2, 28 | 'max-len': [1, { 29 | 'code': 80, 30 | 'comments': 80, 31 | 'ignoreUrls': true, 32 | 'ignoreStrings': true, 33 | 'ignoreTemplateLiterals': true, 34 | 'ignoreRegExpLiterals': true, 35 | }], 36 | 'vue/max-attributes-per-line': [1, { 37 | 'singleline': 2, 38 | 'multiline': { 39 | 'max': 1, 40 | 'allowFirstLine': false, 41 | }, 42 | }], 43 | 'comma-dangle': [1, 'always-multiline'], 44 | 'linebreak-style': ['error', 'unix'], 45 | }, 46 | env: { 47 | mocha: true, 48 | }, 49 | globals: { 50 | sinon: true, 51 | expect: true, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /src/components/App/scss/_defaults.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/scss/colors"; 2 | 3 | * { 4 | box-sizing: border-box; 5 | 6 | &:before, 7 | &:after { 8 | box-sizing: border-box; 9 | } 10 | 11 | &:active, 12 | &:focus, 13 | &:hover { 14 | outline: none; 15 | } 16 | 17 | &::-moz-focus-inner {border: 0;} 18 | } 19 | 20 | 21 | body, 22 | html { 23 | padding: 0; 24 | margin: 0; 25 | width: 100%; 26 | height: 100%; 27 | font-size: 10px; 28 | font: 10px/1.25 "SourceSansPro", Helvetica, Arial, sans-serif; 29 | color: $color-text; 30 | fill: $color-text; 31 | } 32 | 33 | body { 34 | -ms-overflow-style: scrollbar; 35 | -webkit-font-smoothing: antialiased; 36 | -moz-osx-font-smoothing: grayscale; 37 | backface-visibility: hidden; 38 | overflow-y: scroll; 39 | background: white; 40 | } 41 | 42 | p, 43 | ul, ol, li, 44 | h1, h2, h3, h4, h5, h6, 45 | button, textarea, select, input { 46 | margin: 0; 47 | padding: 0; 48 | border-radius: 0; 49 | } 50 | 51 | ul, li { 52 | list-style: none; 53 | } 54 | 55 | textarea, 56 | select, 57 | button, 58 | input { 59 | font-family: inherit; 60 | color: inherit; 61 | line-height: inherit; 62 | font-size: inherit; 63 | } 64 | 65 | button { 66 | cursor: pointer; 67 | background: none; 68 | border: none; 69 | text-align: initial; 70 | } 71 | 72 | img { 73 | max-width: 100%; 74 | max-height: 100%; 75 | } 76 | -------------------------------------------------------------------------------- /src/components/common/Tabs/tabs.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/scss/colors'; 2 | 3 | .app__block > .tabs-component:first-of-type { 4 | & > .tabs-component-tabs { 5 | margin: -4rem -4rem 4rem; 6 | padding: 0 2rem; 7 | border-bottom: 1px solid lighten($color-unfocused, 25%); 8 | 9 | & > .tabs-component-tab { 10 | margin-top: .15rem; // WARN: magic number = 50% of .tabs-component-tab:after height + 50% of .tabs-component-tabs border width 11 | } 12 | } 13 | } 14 | 15 | .tabs-component-tabs { 16 | display: flex; 17 | height: 5rem; 18 | } 19 | 20 | .tabs-component-tab { 21 | height: 100%; 22 | text-decoration: none; 23 | color: $color-text; 24 | padding: 0 .75rem; 25 | font-size: 1.6rem; 26 | position: relative; 27 | 28 | &:after { 29 | content: ""; 30 | position: absolute; 31 | display: block; 32 | bottom: 0; 33 | left: 50%; 34 | transform: translate(-50%); 35 | background-color: $color-active; 36 | width: 0; 37 | height: .2rem; 38 | transition: width .2s, background-color .2s; 39 | pointer-events: none; 40 | } 41 | 42 | &.is-active { 43 | &:after { 44 | width: 100%; 45 | } 46 | } 47 | 48 | & + & { 49 | margin-left: 3rem; 50 | } 51 | } 52 | 53 | .tabs-component-tab-a { 54 | height: 100%; 55 | display: flex; 56 | align-items: center; 57 | color: inherit; 58 | text-decoration: none; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/common/details/Detail.Row.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 30 | 31 | 61 | -------------------------------------------------------------------------------- /src/components/User/Assets/Issuance/Issuance.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 45 | 46 | 55 | -------------------------------------------------------------------------------- /src/router/routes/AuthRoutes.js: -------------------------------------------------------------------------------- 1 | // TODO refactor, extract require 2 | import config from '@/config' 3 | 4 | import { unauthorizedGuard } from '../helpers/navigationGuards' 5 | 6 | // Each of these routes are loaded asynchronously, 7 | // when a user first navigates to each corresponding endpoint. 8 | // 9 | // The route will load once into memory, the first time it's called, 10 | // and no more on future calls. 11 | // 12 | // This behavior can be observed on the network tab of your browser dev tools. 13 | 14 | export const AuthRoutes = { 15 | path: '/auth', 16 | name: 'auth', 17 | redirect: { name: 'login' }, 18 | beforeEnter: unauthorizedGuard, 19 | component (resolve) { require(['../../components/Auth/Auth.vue'], resolve) }, 20 | children: [ 21 | { 22 | path: '/login', 23 | name: 'login', 24 | component: function (resolve) { 25 | require(['../../components/Auth/Login/Login.vue'], resolve) 26 | }, 27 | }, 28 | { 29 | path: '/sign-up', 30 | name: 'signup', 31 | component: function (resolve) { 32 | require(['../../components/Auth/Signup/Signup.vue'], resolve) 33 | }, 34 | }, 35 | ...(config.FEATURES.SEED_AUTH ? [{ 36 | path: '/seed-login', 37 | name: 'seed-login', 38 | component: function (resolve) { 39 | require(['../../components/Auth/Login/SeedLogin.vue'], resolve) 40 | }, 41 | }] : []), 42 | ], 43 | } 44 | -------------------------------------------------------------------------------- /src/components/User/Trades/models/AssetPair.js: -------------------------------------------------------------------------------- 1 | import { globalize } from '@/components/App/filters/filters' 2 | 3 | const UNKNOWN_SOURCE_TYPE_ERROR = globalize('asset-pair.unknown-source-format') 4 | 5 | export class AssetPair { 6 | constructor (source) { 7 | this._base = null 8 | this._quote = null 9 | 10 | if (typeof source === 'object' && source !== null) { 11 | this._parseObject(source) 12 | } else if (typeof source === 'string') { 13 | this._parseString(source) 14 | } else { 15 | throw new Error(UNKNOWN_SOURCE_TYPE_ERROR) 16 | } 17 | } 18 | 19 | _parseObject (obj) { 20 | const base = obj.base || obj.baseAsset.id 21 | const quote = obj.quote || obj.quoteAsset.id 22 | 23 | if (!base || !quote) throw new Error(UNKNOWN_SOURCE_TYPE_ERROR) 24 | 25 | this._base = base 26 | this._quote = quote 27 | } 28 | 29 | _parseString (str) { 30 | try { 31 | const [, base, quote] = str.match(/^([A-Za-z0-9]+)\/([A-Za-z0-9]+)$/) 32 | 33 | this._base = base 34 | this._quote = quote 35 | } catch (error) { 36 | throw new Error(UNKNOWN_SOURCE_TYPE_ERROR) 37 | } 38 | } 39 | 40 | get base () { 41 | return this._base 42 | } 43 | 44 | get quote () { 45 | return this._quote 46 | } 47 | 48 | toString () { 49 | return `${this._base}/${this._quote}` 50 | } 51 | 52 | toObject () { 53 | return { base: this._base, quote: this._quote } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/User/Assets/Issuance/components/issuance.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../../assets/scss/colors"; 2 | 3 | $paddind-in-tab: 4rem; 4 | $width-without-indentation:calc(100% + 2 * #{$paddind-in-tab}); 5 | 6 | .issuance-rl__filters{ 7 | background-color: $color-content-bg; 8 | border-radius: 0.3rem; 9 | padding-bottom: 2.5rem; 10 | display: flex; 11 | justify-content: space-between; 12 | } 13 | 14 | .issuance-rl__filter { 15 | flex: 0.33; 16 | } 17 | 18 | .issuance-rl__li { 19 | width: $width-without-indentation; 20 | display: flex; 21 | justify-content: space-between; 22 | text-decoration: none; 23 | color: inherit; 24 | padding-left: 1.5rem; 25 | padding-right: 1.5rem; 26 | margin-right: -$paddind-in-tab; 27 | margin-left: -$paddind-in-tab; 28 | 29 | &:nth-child(odd) { 30 | background: #f9f9f9; 31 | } 32 | 33 | &:hover { 34 | background-color: rgba(123, 110, 255, 0.06); 35 | } 36 | } 37 | 38 | .issuance-rl__li-header { 39 | width: $width-without-indentation; 40 | margin-right: -$paddind-in-tab; 41 | margin-left: -$paddind-in-tab; 42 | padding-left: 1.5rem; 43 | padding-right: 1.5rem; 44 | } 45 | 46 | .issuance-rl__li-a { 47 | width: 100%; 48 | text-decoration: none; 49 | color: inherit; 50 | display: flex; 51 | justify-content: space-between; 52 | } 53 | 54 | .issuance-rl__list-wrp { 55 | margin-top: 1rem; 56 | } 57 | 58 | .issuance-rl__request-counter-wrapper { 59 | margin-bottom: 3rem; 60 | } 61 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import packageJson from '../package.json' 2 | 3 | const defaultFeatures = { 4 | /* Enable authentication using seed. Such auth has no 2FA. 5 | * WARN: for demo purposes only, could be very dangerous on prod 6 | */ 7 | SEED_AUTH: true, 8 | 9 | PHOTO_VERIFICATION: true, 10 | KYC_REQUEST_STATES: ['approved', 'rejected', 'pending'], 11 | } 12 | 13 | export default Object.assign( 14 | { 15 | install (Vue) { 16 | Vue.params = this 17 | }, 18 | PAGE_LIMIT: 10, 19 | KEY_SERVER_ADMIN: process.env.KEY_SERVER_ADMIN, 20 | HORIZON_SERVER: process.env.HORIZON_SERVER, 21 | FILE_STORAGE: process.env.FILE_STORAGE, 22 | FEATURES: defaultFeatures, 23 | MASTER_ACCOUNT: '', 24 | NETWORK_PASSPHRASE: '', 25 | 26 | /** 27 | * Sets the logging level, for more options visit 28 | * https://www.npmjs.com/package/loglevel#documentation 29 | */ 30 | LOG_LEVEL: 'trace', 31 | 32 | /** 33 | * Should be populated by DevOps team during the deployment 34 | * The field being displayed on login screen. 35 | */ 36 | BUILD_VERSION: packageJson.version, 37 | 38 | /** 39 | * URL of the Sentry DSN. It’s a representation of the configuration 40 | * required by the Sentry SDKs. 41 | */ 42 | SENTRY_DSN: '', 43 | 44 | /** 45 | * URL of the web client 46 | */ 47 | WEB_CLIENT_URL: 'http://localhost:8060', 48 | }, 49 | process.env, 50 | document.ENV 51 | ) 52 | -------------------------------------------------------------------------------- /webpack/local.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const merge = require('webpack-merge') 4 | const baseWebpackConfig = require('./base.conf') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | module.exports = merge(baseWebpackConfig, { 13 | mode: 'development', 14 | devtool: '#cheap-module-eval-source-map', 15 | devServer: { 16 | port: 8091, 17 | hot: true, 18 | host: 'localhost', 19 | overlay: true, 20 | open: true, 21 | watchOptions: { 22 | poll: true, 23 | }, 24 | stats: 'errors-only', 25 | historyApiFallback: true, 26 | progress: true, 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.css?$/, 32 | loader: 'style-loader!css-loader', 33 | }, 34 | { 35 | test: /\.js$/, 36 | loader: 'babel-loader?cacheDirectory', 37 | include: [resolve('src')], 38 | }, 39 | ], 40 | }, 41 | plugins: [ 42 | new webpack.DefinePlugin({ 43 | 'process.env': require('../envs/local'), 44 | }), 45 | new webpack.HotModuleReplacementPlugin(), 46 | new HtmlWebpackPlugin({ 47 | filename: 'index.html', 48 | template: 'index.html', 49 | inject: true, 50 | }), 51 | new FriendlyErrorsPlugin(), 52 | ], 53 | }) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The TokenD admin-panel is written on [Vue v2](https://vuejs.org/v2/guide/) 4 | 5 | # How to 6 | ## Run the project 7 | 8 | 1. Install node.js (Go to [official website](https://nodejs.org/en/) for the help) 9 | 2. Install git (Go to [git install instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) for the help) 10 | 3. Clone the repository: `git clone https://github.com/tokend/admin-panel.git` 11 | 4. In project folder, execute: `npm install` 12 | 5. To start project on local server in development mode, execute: `npm run dev` 13 | 6. Open http://localhost:8091 in browser. 14 | 15 | To stop local server, press `Ctrl + C` in terminal. 16 | 17 | If the remote repository was updated, you need to execute `git pull` command on your local machine to get the updates. To restart the project, repeat step 4 and 5 of this instruction. 18 | 19 | ## Configure your instance 20 | All the environment files are located in `config` directory. You have to edit one of them 21 | to change the hostnames of `horizon`, `api` and `storage` servers and `network_passphrase` 22 | - `dev-local.env.js` is used for the local built instances 23 | - `prod.env.js` is used for the instances built for production 24 | - `default.env.js` contains default configuration of the application and may be included 25 | and merged into other `.env` files 26 | 27 | To run the application in local development mode run `npm run dev` 28 | To build the application for production run `npm run build.prod` 29 | -------------------------------------------------------------------------------- /src/components/User/Limits/Limits.Requests.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 57 | -------------------------------------------------------------------------------- /src/apiHelper/assets.js: -------------------------------------------------------------------------------- 1 | import { api } from '@/api' 2 | import { base } from '@tokend/js-sdk' 3 | 4 | export default { 5 | async createPair (params) { 6 | const operation = base.Operation.manageAssetPair({ 7 | base: params.base, 8 | quote: params.quote, 9 | action: base.xdr.ManageAssetPairAction.create(), 10 | policies: +params.policies, 11 | physicalPrice: '' + params.physicalPrice, 12 | physicalPriceCorrection: '' + params.physicalPriceCorrection, 13 | maxPriceStep: '' + params.maxPriceStep, 14 | }) 15 | const response = await api.postOperations(operation) 16 | return response.data 17 | }, 18 | 19 | async updatePair (params) { 20 | let action 21 | if (params.create) { 22 | action = base.xdr.ManageAssetPairAction.create() 23 | } else if (params.updatePrice) { 24 | action = base.xdr.ManageAssetPairAction.updatePrice() 25 | } else if (params.updatePolicy) { 26 | action = base.xdr.ManageAssetPairAction.updatePolicy() 27 | } else { 28 | throw new TypeError('manageAssetPair: Action is required') 29 | } 30 | 31 | const operation = base.Operation.manageAssetPair({ 32 | base: params.base, 33 | quote: params.quote, 34 | action: action, 35 | policies: +params.policies, 36 | physicalPrice: '' + params.physicalPrice, 37 | physicalPriceCorrection: '' + params.physicalPriceCorrection, 38 | maxPriceStep: '' + params.maxPriceStep, 39 | }) 40 | const response = await api.postOperations(operation) 41 | return response.data 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /src/components/User/Admins/Admins.Index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | 32 | 70 | -------------------------------------------------------------------------------- /src/components/App/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 64 | -------------------------------------------------------------------------------- /src/assets/style/_info-blocks.scss: -------------------------------------------------------------------------------- 1 | $brand-red: #cc6666; 2 | $brand-eminence: #602a60; 3 | $brand-violet-lighten: #9e8291; 4 | 5 | .info-block { 6 | margin-bottom: 25px; 7 | &:nth-last-child { 8 | margin-bottom: 0; 9 | } 10 | } 11 | 12 | .info-block__row, .info-block__image-row { 13 | margin: 10px 15px; 14 | } 15 | 16 | .info-block__row-item { 17 | margin-right: 35px; 18 | display: flex; 19 | flex-direction: row; 20 | align-items: center; 21 | justify-content: space-between; 22 | width: 100%; 23 | } 24 | 25 | .info-block__data { 26 | font-size: 20px; 27 | width: 55%; 28 | &--smaller { 29 | font-size: 16px; 30 | } 31 | } 32 | 33 | .info-block__input { 34 | min-width: 360px; 35 | height: 32px; 36 | font-size: 21px; 37 | border: grey 1px inset; 38 | border-radius: 8px; 39 | background: #fffaf5; 40 | color: #420d42; 41 | padding-left: 10px; 42 | padding-bottom: 2px; 43 | &--short { 44 | min-width: 85px !important; 45 | } 46 | 47 | &--number { 48 | width: 155px; 49 | min-width: 120px !important; 50 | } 51 | &:disabled { 52 | background: #fff0eb; 53 | } 54 | } 55 | 56 | .reject-reason { 57 | visibility: visible; 58 | text-align: center; 59 | border-radius: 6px; 60 | padding: 2px 0; 61 | position: absolute; 62 | top: 125%; 63 | left: 0%; 64 | font-size: 13px; 65 | &--parent { 66 | position: relative; 67 | margin-bottom: 40px; 68 | margin-top: 3px; 69 | } 70 | 71 | .info-block__input { 72 | font-size: 16px; 73 | height: 24px; 74 | border-radius: 5px; 75 | border-width: 1px; 76 | width: 100%; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/queue/components/ReviewDecisionViewer.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 48 | 49 | 62 | -------------------------------------------------------------------------------- /src/components/User/Assets/SystemAssets/SystemAssets.Index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | 31 | 69 | -------------------------------------------------------------------------------- /src/assets/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | // CAUTION: do not use these colors externally 2 | $_black: #3f4244; 3 | $_gray: #e2e3e3; 4 | $_white: #fff; 5 | $_orange: #ffaf03; 6 | $_blue: #578ebd; 7 | $_red: #c2243b; 8 | $_green: #3fa595; 9 | $_deep-blue: #0066cc; 10 | 11 | $_east-bay: rgb(58, 65, 128); 12 | $_east-bay-inactive: rgba($_east-bay, .7); 13 | $_cornflower-blue: #7b6eff; 14 | $_athens-gray: #f2f2f4; 15 | $_burnt-sienna: #ef5350; 16 | $_abbey: rgba(0, 0, 0, .54); 17 | $_scampi: #5F5894; 18 | $_emerald: #51CA90; 19 | $_koromiko: #FFB95F; 20 | $_medium-gray: #999999; 21 | 22 | // Use these ones instead 23 | $color-text: $_east-bay; 24 | $color-text-secondary: rgba($color: $_black, $alpha: 0.5); 25 | $color-text-inverse: $_white; 26 | $color-sidebar-bg: $_white; 27 | $color-bg: $_gray; 28 | $color-header-bg: $_east-bay; 29 | $color-header-hover: lighten($_black, 8%); 30 | $color-content-bg: $_white; 31 | $color-sub-nav-bg: $_white; 32 | $color-sub-nav-hover: rgba($color: $_black, $alpha: 0.1); 33 | $color-active: $_cornflower-blue; 34 | $color-inactive: lighten($color-active, 35%); 35 | $color-info: $_cornflower-blue; 36 | $color-success: $_emerald; 37 | $color-warning: $_koromiko; 38 | $color-danger: $_burnt-sienna; 39 | $color-danger-inactive: lighten($color-danger, 20%); 40 | $color-focused: $_east-bay; 41 | $color-unfocused: lighten($color-focused, 35%); 42 | $color-btn-secondary-hover: $_gray; 43 | $color-table-row-hover: $_gray; 44 | $color-loader-fill: $_deep-blue; 45 | $color-shadow-secondary-text: $_white; 46 | $color-text-init-loading-failed: rgb(200, 40, 40); 47 | $color-shadow-init-loading-failed: white; 48 | $color-banner-bg: $_koromiko; 49 | $color-disabled: $_medium-gray; 50 | -------------------------------------------------------------------------------- /static/noscript/style.css: -------------------------------------------------------------------------------- 1 | .noscript { 2 | max-width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | box-sizing: content-box; 9 | color: #202124; 10 | padding: 0 1rem; 11 | text-align: center; 12 | font: 1rem "SourceSansPro", Helvetica, Arial, sans-serif; 13 | } 14 | 15 | .noscript__title { 16 | font-weight: normal; 17 | font-size: 3rem; 18 | margin-top: 3rem; 19 | } 20 | 21 | .noscript__message { 22 | margin-top: 0; 23 | } 24 | 25 | .noscript__enable-support { 26 | color: #7b6eff; 27 | } 28 | 29 | .noscript__list { 30 | display: flex; 31 | justify-content: center; 32 | flex-wrap: wrap; 33 | list-style: none; 34 | padding: 0; 35 | } 36 | 37 | .noscript__list-item { 38 | display: flex; 39 | flex-direction: column; 40 | justify-content: center; 41 | align-items: center; 42 | margin-bottom: 2rem; 43 | width: 100%; 44 | } 45 | 46 | .noscript__link { 47 | border: 1px solid #dfe1e5; 48 | padding: 0.5rem; 49 | border-radius: 5%; 50 | } 51 | 52 | .noscript__link:hover { 53 | box-shadow: 0px 4px 8px 3px rgba(60,64,67,.15); 54 | } 55 | 56 | .noscript__browser-name { 57 | margin-top: 1rem; 58 | } 59 | 60 | @media all and (min-width: 576px) { 61 | .noscript__message { 62 | max-width: 700px; 63 | } 64 | 65 | .noscript__list-item { 66 | width: 34%; 67 | } 68 | } 69 | 70 | @media all and (min-width: 992px) { 71 | .noscript { 72 | margin-top: 7rem; 73 | padding: 0 5rem; 74 | } 75 | 76 | .noscript__list-item { 77 | width: unset; 78 | margin-right: 2rem; 79 | margin-bottom: 0; 80 | } 81 | 82 | .nosctipt__recomendations { 83 | margin-bottom: 0; 84 | } 85 | } -------------------------------------------------------------------------------- /src/components/User/Assets/AssetPairs/AssetPairs.Index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | 31 | 69 | -------------------------------------------------------------------------------- /src/components/App/components/Loader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | 84 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/ChangeRoleRequest.js: -------------------------------------------------------------------------------- 1 | import safeGet from 'lodash/get' 2 | import { REQUEST_STATES } from '@/constants' 3 | 4 | export class ChangeRoleRequest { 5 | constructor (record) { 6 | this._record = record 7 | this.id = record.id || '0' 8 | this.hash = record.hash 9 | this.type = safeGet(record, 'xdrType.value') 10 | 11 | this.rejectReason = record.rejectReason 12 | this.pendingTasks = record.pendingTasks 13 | this.allTasks = record.allTasks 14 | this.requestor = safeGet(record, 'requestor.id') 15 | 16 | this.resetReason = safeGet( 17 | record, 'requestDetails.creatorDetails.resetReason' 18 | ) 19 | this.blockReason = safeGet( 20 | record, 'requestDetails.creatorDetails.blockReason' 21 | ) 22 | 23 | this.relatedRequestId = safeGet( 24 | record, 'requestDetails.creatorDetails.latestApprovedRequestId' 25 | ) 26 | 27 | this.accountRoleToSet = safeGet(record, 'requestDetails.accountRoleToSet') 28 | this.state = record.state 29 | this.stateI = record.stateI 30 | 31 | this.creatorDetails = safeGet(record, 'requestDetails.creatorDetails') 32 | this.blobId = safeGet(record, 'requestDetails.creatorDetails.blobId') 33 | 34 | this.externalDetails = safeGet(record, 'externalDetails.data') 35 | } 36 | 37 | get record () { 38 | return this._record 39 | } 40 | 41 | get isApproved () { 42 | return this.stateI === REQUEST_STATES.approved.stateI 43 | } 44 | 45 | get isPending () { 46 | return this.stateI === REQUEST_STATES.pending.stateI 47 | } 48 | 49 | get isRejected () { 50 | return this.stateI === REQUEST_STATES.rejected.stateI 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/apiHelper/responseHandlers/requests/AssetRequest.js: -------------------------------------------------------------------------------- 1 | import { ReviewableRequest } from './ReviewableRequest' 2 | 3 | export class AssetRequest extends ReviewableRequest { 4 | get code () { 5 | return this.operationDetails.asset.id || this.operationDetails.asset 6 | } 7 | 8 | get policies () { 9 | return this.operationDetails.policies 10 | } 11 | 12 | get name () { 13 | return this.operationDetails.creatorDetails.name 14 | } 15 | 16 | get signer () { 17 | return this.operationDetails.preIssuanceAssetSigner 18 | } 19 | 20 | get maxAmount () { 21 | return this.operationDetails.maxIssuanceAmount 22 | } 23 | 24 | get requestor () { 25 | return this.record.requestor.id 26 | } 27 | 28 | get type () { 29 | return this.record.xdrType.name 30 | } 31 | 32 | get assetType () { 33 | return this.operationDetails.type 34 | } 35 | 36 | get rejectReason () { 37 | return this.record.rejectReason 38 | } 39 | 40 | get issuedAmount () { 41 | return this.operationDetails.initialPreissuedAmount 42 | } 43 | 44 | get creationDate () { 45 | return this.record.createdAt 46 | } 47 | 48 | get updateDate () { 49 | return this.record.updatedAt 50 | } 51 | 52 | get stellarAssetCode () { 53 | return this.operationDetails.creatorDetails.stellar.assetCode || '' 54 | } 55 | 56 | get stellarAssetType () { 57 | return this.operationDetails.creatorDetails.stellar.assetType || '' 58 | } 59 | 60 | get stellarWithdraw () { 61 | return this.operationDetails.creatorDetails.stellar.withdraw || false 62 | } 63 | 64 | get stellarDeposit () { 65 | return this.operationDetails.creatorDetails.stellar.deposit || false 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/keyServer.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { TokenD, KeyServerCaller, Wallet } from '@tokend/js-sdk' 3 | 4 | import store from '../store' 5 | import config from '../config' 6 | 7 | const sdkServer = () => { 8 | const sdkInstance = new TokenD(config.KEY_SERVER_ADMIN, { 9 | allowHttp: true, 10 | }) // true for use http, only localhost 11 | return new KeyServerCaller({ 12 | axios: axios.create({ 13 | baseURL: config.KEY_SERVER_ADMIN, 14 | }), 15 | sdk: sdkInstance, 16 | }) 17 | } 18 | 19 | const get = async (path, data = null, isSigned) => { 20 | if (isSigned) { 21 | const wallet = new Wallet( 22 | '', 23 | store.getters.GET_USER.keys.seed, 24 | store.getters.GET_USER.keys.accountId 25 | ) 26 | 27 | const response = await sdkServer().getWithSignature(path, data, wallet) 28 | return response.data 29 | } else { 30 | const response = await sdkServer().get(path, data) 31 | return response.data 32 | } 33 | } 34 | 35 | const patch = async (path, data = {}) => { 36 | const wallet = new Wallet( 37 | '', 38 | store.getters.GET_USER.keys.seed, 39 | store.getters.GET_USER.keys.accountId 40 | ) 41 | 42 | const response = await sdkServer().patchWithSignature(path, data, wallet) 43 | return response.data 44 | } 45 | 46 | const post = async (path, data = {}) => { 47 | const wallet = new Wallet( 48 | '', 49 | store.getters.GET_USER.keys.seed, 50 | store.getters.GET_USER.keys.accountId 51 | ) 52 | 53 | const response = await sdkServer().postWithSignature(path, data, wallet) 54 | return response.data 55 | } 56 | 57 | export default { 58 | sdkServer: sdkServer(), 59 | get, 60 | patch, 61 | post, 62 | } 63 | -------------------------------------------------------------------------------- /src/components/common/formatters/AssetPoliciesFormatter.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 54 | -------------------------------------------------------------------------------- /src/components/App/components/IERestrictionMessage.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 43 | 44 | 68 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export { ALL_ASSETS_ID } from './all-assets-id' 2 | export { ASSET_POLICIES } from './asset-policies' 3 | export { STELLAR_TYPES } from './stellar-types' 4 | export { ASSET_PAIR_POLICIES, ASSET_PAIR_POLICIES_VERBOSE } from './asset-pair-policies' 5 | export { USER_TYPES, USER_TYPES_STR } from './user-types' 6 | export { USER_STATES, USER_STATES_STR } from './user-states' 7 | export { ISSUANCE_REQUEST_STATES } from './issuance-request-states' 8 | export { REQUEST_TYPES } from './request-types' 9 | export { REQUEST_STATES, KYC_REQUEST_STATES } from './request-states' 10 | export { SALE_STATES } from './sale-states' 11 | export { BLOB_TYPES } from './blob-types' 12 | export { FEE_TYPES, PAYMENT_FEE_TYPES } from './fee-types' 13 | export { DOCUMENT_TYPES, DOCUMENT_TYPES_STR } from './document-types' 14 | export { REVIEW_STATES } from './review-states' 15 | export { ID_DOCUMENT_TYPES } from './document-types' 16 | export { OP_TYPES } from './operation-type' 17 | export { DOCUMENTS_POLICIES } from './documents-policies' 18 | export { SALES_SORT_CRITERIA } from './sorts-criteria' 19 | export { LIMITS_REQUEST_STATES_STR } from './limits-request-states' 20 | export { STATS_OPERATION_TYPES } from './stats-op-types' 21 | export { 22 | DEFAULT_BASE_ASSET, 23 | DEFAULT_QUOTE_ASSET, 24 | DEFAULT_PRECISION, 25 | DEFAULT_DATE_FORMAT, 26 | DEFAULT_DATE_TIME_FORMAT, 27 | DEFAULT_MAX_AMOUNT, 28 | DEFAULT_INPUT_STEP, 29 | DEFAULT_INPUT_MIN, 30 | } from './defaults' 31 | export { KEY_VALUE_ENTRY_TYPE } from './key-value' 32 | export { ASSET_REQUEST_TYPES } from './asset-request-types' 33 | export { SALE_DEFINITION_TYPES } from './sale-definition-types' 34 | export { OPERATION_DETAILS_TYPES } from './operation-details-types' 35 | export { LIMIT_TYPES } from './limit-types' 36 | export { ADMIN_CONST } from './admin-const' 37 | -------------------------------------------------------------------------------- /src/store/modules/idle-handler.js: -------------------------------------------------------------------------------- 1 | const forceLogoutDelay = 1.5 * 60 2 | const idleDelay = 15 * 60 * 1000 3 | 4 | const state = { 5 | showIdleForm: false, 6 | logoutTimer: null, 7 | idleDelay: idleDelay, 8 | timestamp: null, 9 | } 10 | 11 | const getters = { 12 | showIdleForm: state => state.showIdleForm, 13 | forceLogoutDelay: state => forceLogoutDelay, 14 | logoutTimer: state => state.logoutTimer, 15 | } 16 | 17 | const actions = { 18 | 'START_IDLE' ({ dispatch, commit, state }) { 19 | function resetTimer () { 20 | clearTimeout(state.logoutTimer) 21 | 22 | state.logoutTimer = setTimeout(() => { dispatch('SHOW_IDLE_FORM') }, idleDelay) 23 | } 24 | 25 | resetTimer() 26 | 27 | document.onload = resetTimer 28 | document.onmousemove = resetTimer 29 | document.onmousedown = resetTimer 30 | document.ontouchstart = resetTimer 31 | document.onclick = resetTimer 32 | document.onscroll = resetTimer 33 | document.onkeypress = resetTimer 34 | }, 35 | 36 | 'SHOW_IDLE_FORM': ({ commit, state }) => { 37 | commit('OPEN_MODAL') 38 | commit('SHOW_IDLE_FORM') 39 | }, 40 | 41 | 'CLOSE_IDLE_FORM': ({ commit, dispatch, state }) => { 42 | state.showIdleForm = false 43 | commit('CLOSE_MODAL') 44 | dispatch('START_IDLE') 45 | }, 46 | } 47 | 48 | const mutations = { 49 | 'SHOW_IDLE_FORM': (state) => { state.showIdleForm = true }, 50 | 51 | 'KEEP_SESSION': (state) => {}, 52 | 53 | 'STOP_IDLE': (state) => { 54 | clearTimeout(state.logoutTimer) 55 | state.showIdleForm = false 56 | state.logoutTimer = null 57 | 58 | document.onload = null 59 | document.onmousemove = null 60 | document.onmousedown = null 61 | document.ontouchstart = null 62 | document.onclick = null 63 | document.onscroll = null 64 | document.onkeypress = null 65 | }, 66 | } 67 | 68 | export default { 69 | state, 70 | getters, 71 | actions, 72 | mutations, 73 | } 74 | -------------------------------------------------------------------------------- /src/apiHelper/users.js: -------------------------------------------------------------------------------- 1 | import { api } from '@/api' 2 | import { base } from '@tokend/js-sdk' 3 | import { UserRecord } from '@/js/records/user.record' 4 | 5 | export default { 6 | async getAccountIdByEmail (email) { 7 | if (!email) return '' 8 | 9 | try { 10 | const { data } = await api.get('/identities', { 11 | filter: { email }, 12 | page: { limit: 1 }, 13 | }) 14 | return ((data || [])[0] || {}).address 15 | } catch (error) { 16 | if (error.httpStatus === 404) { 17 | return '' 18 | } else { 19 | throw error 20 | } 21 | } 22 | }, 23 | 24 | async getEmailByAccountId (accountId) { 25 | if (!accountId) return '' 26 | 27 | try { 28 | const { data } = await api.get('/identities', { 29 | filter: { address: accountId }, 30 | page: { limit: 1 }, 31 | }) 32 | return ((data || [])[0] || {}).email 33 | } catch (error) { 34 | if (error.httpStatus === 404) { 35 | return '' 36 | } else { 37 | throw error 38 | } 39 | } 40 | }, 41 | 42 | async getAccountIdBy (emailOrAccountId) { 43 | const value = emailOrAccountId 44 | 45 | if (base.Keypair.isValidPublicKey(value)) { 46 | return value 47 | } else { 48 | try { 49 | const address = await this.getAccountIdByEmail(value) 50 | return address || value 51 | } catch (error) { 52 | return value 53 | } 54 | } 55 | }, 56 | 57 | async getUserByAccountId (accountId) { 58 | let account = {} 59 | const { data: identities } = await api.getWithSignature('/identities', { 60 | filter: { address: accountId }, 61 | }) 62 | if (!identities.length) { 63 | const { data } = await api.getWithSignature(`/v3/accounts`, { 64 | filter: { account: accountId }, 65 | }) 66 | account = data[0] 67 | } 68 | return new UserRecord(account, identities[0]) 69 | }, 70 | } 71 | -------------------------------------------------------------------------------- /src/components/User/Polls/components/PollViewer.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 82 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/UserDetails.ExternalDetailsViewer.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | 76 | -------------------------------------------------------------------------------- /src/components/common/fields/scss/_fields-variables.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/scss/colors'; 2 | 3 | $field-color-text: $color-text; 4 | $field-color-focused: $color-text; 5 | $field-color-unfocused: $color-unfocused; 6 | $field-color-error: $color-danger; 7 | $field-label-font-size: 1.2rem; 8 | $field-label-line-height: 1; 9 | $field-text-font-size: 1.6rem; 10 | $field-text-line-height: 1.25; 11 | $field-error-font-size: 1.2rem; 12 | $field-error-line-height: 1.25; 13 | $field-transition-duration: .2s; 14 | $field-error-margin-top: .4rem; 15 | $field-input-padding-top: 1.7rem; 16 | $field-input-padding-bottom: .6rem; 17 | $field-input-padding: $field-input-padding-top 0 $field-input-padding-bottom 0; 18 | $field-placeholder-color: $field-color-unfocused; 19 | 20 | @mixin field-hints { 21 | line-height: 1.5; 22 | font-size: 1.4rem; 23 | font-weight: 600; 24 | margin-top: 0.4rem; 25 | 26 | &--unfocused { 27 | color: $field-color-unfocused; 28 | } 29 | } 30 | 31 | @mixin input-paddings { 32 | font-size: $field-text-font-size; 33 | line-height: $field-text-line-height; 34 | } 35 | 36 | @mixin text-font-sizes { 37 | font-size: $field-text-font-size; 38 | line-height: $field-text-line-height; 39 | } 40 | 41 | @mixin label-font-sizes { 42 | font-size: $field-label-font-size; 43 | line-height: $field-label-line-height; 44 | } 45 | 46 | @mixin material-border($color) { 47 | border-bottom: .1rem solid $field-color-unfocused; 48 | background: radial-gradient($color, $color) bottom -.1rem center no-repeat; 49 | background-size: 0px .2rem; 50 | -webkit-transition-property: background-size, border-color; 51 | -moz-transition-property: background-size, border-color; 52 | -ms-transition-property: background-size, border-color; 53 | -o-transition-property: background-size, border-color; 54 | transition-property: background-size, border-color; 55 | transition-duration: $field-transition-duration; 56 | 57 | &:focus { 58 | border-bottom-color: transparent; 59 | background-size: 100% .2rem; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/bus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import log from 'loglevel' 3 | import { globalize } from '@/components/App/filters/filters' 4 | 5 | export class EventBus extends Vue { 6 | constructor () { 7 | super() 8 | this._backlog = [] 9 | } 10 | 11 | on (eventName, handlerFn) { 12 | if (!this.eventExists(eventName)) { 13 | throw new Error(globalize('bus.error-no-event', { 14 | eventName: eventName, 15 | }) 16 | ) 17 | } 18 | 19 | const backloggedEvents = this._backlog.filter(e => e.name === eventName) 20 | 21 | for (const [index, event] of backloggedEvents.entries()) { 22 | handlerFn(event.payload) 23 | this._backlog.splice(index, 1) 24 | log.debug(`Event ${eventName} is backlogged. Handling...`) 25 | } 26 | 27 | this.$on(eventName, handlerFn) 28 | } 29 | 30 | emit (eventName, payload) { 31 | if (!this.eventExists(eventName)) { 32 | throw new Error(globalize('bus.error-no-event', { 33 | eventName: eventName, 34 | }) 35 | ) 36 | } 37 | 38 | if (!this._events[eventName]) { 39 | this._backlog.push({ 40 | name: eventName, 41 | payload, 42 | }) 43 | 44 | log.debug(`Backlogging event: ${eventName}`) 45 | return 46 | } 47 | 48 | this.$emit(eventName, payload) 49 | } 50 | 51 | reset () { 52 | this.$off() 53 | this._backlog = [] 54 | } 55 | 56 | success (payload) { this.emit(this.eventList.success, payload) } 57 | warning (payload) { this.emit(this.eventList.warning, payload) } 58 | error (payload) { this.emit(this.eventList.error, payload) } 59 | info (payload) { this.emit(this.eventList.info, payload) } 60 | 61 | get eventList () { 62 | return { 63 | success: 'success', 64 | warning: 'warning', 65 | error: 'error', 66 | info: 'info', 67 | } 68 | } 69 | 70 | eventExists (eventName) { 71 | return Object 72 | .values(this.eventList) 73 | .includes(eventName) 74 | } 75 | } 76 | 77 | export const Bus = new EventBus() 78 | -------------------------------------------------------------------------------- /src/components/App/components/LanguagePicker.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 57 | 58 | 95 | -------------------------------------------------------------------------------- /src/components/User/Limits/components/Limits.UserLimits.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 63 | 64 | 97 | -------------------------------------------------------------------------------- /src/components/common/formatters/MarkdownFormatter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | 33 | 109 | -------------------------------------------------------------------------------- /src/components/settings/Settings.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /src/components/common/getters/DocLinkGetter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 76 | 77 | 93 | -------------------------------------------------------------------------------- /src/components/User/Operations/OperationDetails.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 66 | -------------------------------------------------------------------------------- /static/fonts/SourceSansPro/stylesheet.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "SourceSansPro"; 3 | src: url("SourceSansPro-ExtraLight.ttf") format("truetype"); 4 | font-weight: 200; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: "SourceSansPro"; 10 | src: url("SourceSansPro-ExtraLightItalic.ttf") format("truetype"); 11 | font-weight: 200; 12 | font-style: italic; 13 | } 14 | 15 | @font-face { 16 | font-family: "SourceSansPro"; 17 | src: url("SourceSansPro-Light.ttf") format("truetype"); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: "SourceSansPro"; 24 | src: url("SourceSansPro-LightItalic.ttf") format("truetype"); 25 | font-weight: 300; 26 | font-style: italic; 27 | } 28 | 29 | @font-face { 30 | font-family: "SourceSansPro"; 31 | src: url("SourceSansPro-Regular.ttf") format("truetype"); 32 | font-weight: 400; 33 | font-style: normal; 34 | } 35 | 36 | @font-face { 37 | font-family: "SourceSansPro"; 38 | src: url("SourceSansPro-Italic.ttf") format("truetype"); 39 | font-weight: 400; 40 | font-style: italic; 41 | } 42 | 43 | @font-face { 44 | font-family: "SourceSansPro"; 45 | src: url("SourceSansPro-SemiBold.ttf") format("truetype"); 46 | font-weight: 600; 47 | font-style: normal; 48 | } 49 | 50 | @font-face { 51 | font-family: "SourceSansPro"; 52 | src: url("SourceSansPro-SemiBoldItalic.ttf") format("truetype"); 53 | font-weight: 600; 54 | font-style: italic; 55 | } 56 | 57 | @font-face { 58 | font-family: "SourceSansPro"; 59 | src: url("SourceSansPro-Bold.ttf") format("truetype"); 60 | font-weight: 700; 61 | font-style: normal; 62 | } 63 | 64 | @font-face { 65 | font-family: "SourceSansPro"; 66 | src: url("SourceSansPro-BoldItalic.ttf") format("truetype"); 67 | font-weight: 700; 68 | font-style: italic; 69 | } 70 | 71 | @font-face { 72 | font-family: "SourceSansPro"; 73 | src: url("SourceSansPro-Black.ttf") format("truetype"); 74 | font-weight: 900; 75 | font-style: normal; 76 | } 77 | 78 | @font-face { 79 | font-family: "SourceSansPro"; 80 | src: url("SourceSansPro-BlackItalic.ttf") format("truetype"); 81 | font-weight: 900; 82 | font-style: italic; 83 | } 84 | -------------------------------------------------------------------------------- /src/components/User/KycRequests/queue/wrappers/ReviewDecision.js: -------------------------------------------------------------------------------- 1 | import { DECISION_ACTIONS } from '../constants/decision-actions' 2 | import { DECISION_STATES } from '../constants/decision-states' 3 | 4 | export class ReviewDecision { 5 | constructor (request, action = DECISION_ACTIONS.none) { 6 | this.request = request 7 | 8 | this.action = action 9 | this.state = DECISION_STATES[this.action] 10 | 11 | this.reason = '' 12 | this.tasks = { 13 | toAdd: 0, 14 | toRemove: request.pendingTasks, 15 | } 16 | this.errorMessage = '' 17 | } 18 | 19 | get isEditable () { 20 | const editableStates = [ 21 | DECISION_STATES.approve, 22 | DECISION_STATES.reject, 23 | DECISION_STATES.skip, 24 | DECISION_STATES.none, 25 | DECISION_STATES.error, 26 | ] 27 | 28 | return editableStates.includes(this.state) 29 | } 30 | 31 | get isReadyForReview () { 32 | const readyForReviewStates = [ 33 | DECISION_STATES.approve, 34 | DECISION_STATES.reject, 35 | ] 36 | 37 | return readyForReviewStates.includes(this.state) 38 | } 39 | 40 | approve () { 41 | this._setReview({ action: DECISION_ACTIONS.approve }) 42 | } 43 | 44 | reject (reason) { 45 | this._setReview({ 46 | action: DECISION_ACTIONS.reject, 47 | reason, 48 | }) 49 | } 50 | 51 | skip () { 52 | this._setReview({ action: DECISION_ACTIONS.skip }) 53 | } 54 | 55 | setProcessing () { 56 | switch (this.action) { 57 | case DECISION_ACTIONS.approve: 58 | this.state = DECISION_STATES.approving 59 | break 60 | case DECISION_ACTIONS.reject: 61 | this.state = DECISION_STATES.rejecting 62 | break 63 | } 64 | } 65 | 66 | setReviewed () { 67 | switch (this.action) { 68 | case DECISION_ACTIONS.approve: 69 | this.state = DECISION_STATES.approved 70 | break 71 | case DECISION_ACTIONS.reject: 72 | this.state = DECISION_STATES.rejected 73 | break 74 | } 75 | } 76 | 77 | setError (errorMessage) { 78 | this.state = DECISION_STATES.error 79 | this.errorMessage = errorMessage 80 | } 81 | 82 | _setReview ({ action, reason = '' }) { 83 | this.action = action 84 | this.state = DECISION_STATES[this.action] 85 | 86 | this.reason = reason 87 | this.errorMessage = '' 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /webpack/base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const { VueLoaderPlugin } = require('vue-loader') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js', 13 | }, 14 | output: { 15 | path: path.resolve(__dirname, '../dist'), 16 | filename: '[name].js', 17 | publicPath: '/', 18 | }, 19 | optimization: { 20 | noEmitOnErrors: true, 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue', '.json', '.css', '.scss'], 24 | alias: { 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | '@': resolve('src'), 27 | '@comcom': resolve('src/components/common'), 28 | '@store': resolve('src/store'), 29 | }, 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.(js|vue)$/, 35 | loader: 'eslint-loader', 36 | enforce: 'pre', 37 | include: [resolve('src'), resolve('karma')], 38 | options: { 39 | formatter: require('eslint-friendly-formatter'), 40 | }, 41 | }, 42 | { 43 | test: /\.vue$/, 44 | loader: 'vue-loader', 45 | }, 46 | { 47 | test: /\.js$/, 48 | loader: 'babel-loader', 49 | include: [resolve('src'), resolve('karma')], 50 | }, 51 | { 52 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 53 | loader: 'url-loader', 54 | options: { 55 | limit: 10000, 56 | name: utils.assetsPath('img/[name].[hash:7].[ext]'), 57 | }, 58 | }, 59 | { 60 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 61 | loader: 'url-loader', 62 | options: { 63 | limit: 10000, 64 | name: utils.assetsPath('media/[name].[hash:7].[ext]'), 65 | }, 66 | }, 67 | { 68 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 69 | loader: 'url-loader', 70 | options: { 71 | limit: 10000, 72 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]'), 73 | }, 74 | }, 75 | { 76 | test: /\.s[a|c]ss$/, 77 | loader: 'style-loader!css-loader!sass-loader', 78 | }, 79 | ], 80 | }, 81 | plugins: [ 82 | new VueLoaderPlugin(), 83 | new webpack.IgnorePlugin(/ed25519/), 84 | ], 85 | } 86 | -------------------------------------------------------------------------------- /src/components/common/modals/Modal.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | 61 | 99 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/UserDetails.Account.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 92 | -------------------------------------------------------------------------------- /src/components/User/Users/components/UserDetails/UserDetails.AccreditedKycViewer.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 57 | 58 | 87 | -------------------------------------------------------------------------------- /src/components/common/CollectionLoader.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 93 | -------------------------------------------------------------------------------- /src/js/modals/confirmation_message.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '../../store' 3 | import FormBlockingModalMixin from './flow-blocking-modal.mixin' 4 | import Modal from '../../components/common/modals/Modal' 5 | import { globalize } from '@/components/App/filters/filters' 6 | const template = ` 7 | 8 | 9 |
10 | 13 | 16 |
17 |
18 | ` 19 | 20 | /** 21 | * @param {object} [opts] 22 | * @param opts.title 23 | * @param opts.message 24 | * @param opts.confirmText 25 | * @param opts.cancelText 26 | * @return {Promise} 27 | */ 28 | export function confirmAction (opts = {}) { 29 | const title = opts.title || globalize('confirmation-message.confirm-msg') 30 | const confirmText = opts.confirmText || globalize('confirmation-message.submit') 31 | const cancelText = opts.cancelText || globalize('confirmation-message.cancel') 32 | const container = document.createElement('div') 33 | document.querySelector('#app').appendChild(container) 34 | 35 | // eslint-disable-next-line promise/avoid-new 36 | return new Promise((resolve, reject) => { 37 | const confirmMessage = new Vue({ 38 | components: { Modal }, 39 | mixins: [FormBlockingModalMixin], 40 | 41 | data () { 42 | return { 43 | title, 44 | confirmText, 45 | cancelText, 46 | } 47 | }, 48 | 49 | watch: { 50 | isOpened (val) { 51 | if (!val) { 52 | if (!this.isResolved) { 53 | this.resolvers.resolve(false) 54 | } 55 | this.removeElement() 56 | } 57 | }, 58 | }, 59 | 60 | created () { 61 | this.setResolvers(resolve, reject) 62 | }, 63 | 64 | methods: { 65 | confirm () { 66 | this.resetResolvers() 67 | this.removeElement() 68 | return this.resolvers.resolve(true) 69 | }, 70 | 71 | cancel () { 72 | this.resetResolvers() 73 | this.removeElement() 74 | return this.resolvers.resolve(false) 75 | }, 76 | }, 77 | 78 | template, 79 | store, 80 | }) 81 | 82 | confirmMessage.$mount(container) 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /src/components/common/SocialLinks/SocialLinks.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 56 | 57 | 110 | -------------------------------------------------------------------------------- /src/components/App/scss/_lists.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/scss/colors"; 2 | 3 | .app-list-filters { 4 | background-color: $color-content-bg; 5 | border-radius: 0.3rem; 6 | box-shadow: 0.7px 0.7px 5.6px 0.4px rgba(170, 170, 170, 0.72); 7 | padding: 2rem 2.5rem 2.5rem; 8 | display: flex; 9 | justify-content: space-between; 10 | } 11 | 12 | .app-list-filters__field { 13 | flex: 1; 14 | align-self: center; 15 | & + & { 16 | margin-left: 2rem; 17 | } 18 | } 19 | 20 | .app-list { 21 | .app-list__cell { 22 | flex: 1; 23 | overflow: hidden; 24 | text-overflow: ellipsis; 25 | white-space: nowrap; 26 | display: inline-block; 27 | padding: 2rem 1.25rem; 28 | 29 | &--actions { 30 | display: flex; 31 | & > button + button { 32 | margin-left: 1rem; 33 | } 34 | } 35 | 36 | &--important { 37 | font-weight: 600; 38 | } 39 | 40 | &--wrap { 41 | white-space: normal; 42 | } 43 | 44 | &--center { 45 | text-align: center; 46 | justify-content: center; 47 | } 48 | 49 | &--left { 50 | text-align: left; 51 | justify-content: flex-start; 52 | } 53 | 54 | &--right { 55 | text-align: right; 56 | justify-content: flex-end; 57 | } 58 | 59 | &--thin { 60 | padding: 0 .75rem; 61 | } 62 | 63 | &:first-of-type { 64 | padding-left: 2.5rem; 65 | } 66 | 67 | &:last-of-type { 68 | padding-right: 2.5rem; 69 | } 70 | } 71 | 72 | .app-list__header { 73 | width: 100%; 74 | display: flex; 75 | justify-content: space-between; 76 | margin-bottom: 0.5rem; 77 | 78 | & > .app-list__cell { 79 | padding-top: 0; 80 | padding-bottom: 0; 81 | color: $color-text-secondary; 82 | } 83 | } 84 | 85 | .app-list__li-like, 86 | .app-list__li { 87 | background-color: $color-content-bg; 88 | border-radius: 0.3rem; 89 | box-shadow: 0.7px 0.7px 5.6px 0.4px rgba(170, 170, 170, 0.72); 90 | width: 100%; 91 | display: flex; 92 | justify-content: space-between; 93 | 94 | // a 95 | text-decoration: none; 96 | color: inherit; 97 | 98 | &--v-center { 99 | align-items: center; 100 | } 101 | 102 | & + .app-list__li { 103 | margin-top: 2rem; 104 | } 105 | 106 | &--no-shadow { 107 | box-shadow: none; 108 | } 109 | } 110 | 111 | .app-list__li-like { 112 | justify-content: center; 113 | padding: 2rem 2.5rem; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/User/Polls/components/PollViewerVoters.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 63 | 64 | 100 | -------------------------------------------------------------------------------- /src/components/common/getters/EmailGetter.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 105 | --------------------------------------------------------------------------------