├── src ├── jest.init.js ├── favicon.ico ├── assets │ ├── img │ │ ├── dapp-bg.png │ │ └── icon-account.svg │ └── near-logo.svg ├── __mocks__ │ └── fileMock.js ├── scss │ ├── utilities │ │ ├── _shapes.scss │ │ ├── _cursors.scss │ │ ├── _display.scss │ │ ├── _loading.scss │ │ ├── _position.scss │ │ ├── _colors.scss │ │ ├── _text.scss │ │ └── _divider.scss │ ├── mixins │ │ ├── _avatar.scss │ │ ├── _text.scss │ │ ├── _clearfix.scss │ │ ├── _toast.scss │ │ ├── _shadow.scss │ │ ├── _label.scss │ │ ├── _color.scss │ │ ├── _button.scss │ │ └── _position.scss │ ├── _icons.scss │ ├── _utilities.scss │ ├── _mixins.scss │ ├── _animations.scss │ ├── _hero.scss │ ├── _empty.scss │ ├── _panels.scss │ ├── _navbar.scss │ ├── _breadcrumbs.scss │ ├── _codes.scss │ ├── _navs.scss │ ├── _chips.scss │ ├── _tiles.scss │ ├── _accordions.scss │ ├── _asian.scss │ ├── _tabs.scss │ ├── _dropdowns.scss │ ├── _labels.scss │ ├── _filters.scss │ ├── _base.scss │ ├── _cards.scss │ ├── _toasts.scss │ ├── _autocomplete.scss │ ├── _tables.scss │ ├── spectre.scss │ ├── _progress.scss │ ├── _pagination.scss │ ├── _timelines.scss │ ├── _badges.scss │ ├── _popovers.scss │ ├── _menus.scss │ ├── _media.scss │ ├── _steps.scss │ ├── _bars.scss │ ├── _avatars.scss │ ├── _modals.scss │ ├── _tooltips.scss │ ├── _typography.scss │ ├── _sliders.scss │ ├── _parallax.scss │ ├── _markdown.scss │ ├── _variables.scss │ ├── _buttons.scss │ ├── _calendars.scss │ ├── _layout.scss │ ├── _normalize.scss │ ├── near.scss │ └── _forms.scss ├── util │ ├── util.js │ └── near-util.js ├── main.test.js ├── App.test.js ├── wallet │ └── login │ │ └── index.html ├── App.css ├── config.js ├── 404.html ├── index.js ├── index.html ├── __snapshots__ │ └── App.test.js.snap ├── App.js ├── Claim.js └── Drops.js ├── wasm └── linkdrop.wasm ├── babel.config.js ├── .travis.yml ├── .gitignore ├── .csscomb.json ├── gulpfile.js ├── LICENSE ├── package.json ├── README.md ├── .github └── ISSUE_TEMPLATE │ └── BOUNTY.yml └── LICENSE-APACHE /src/jest.init.js: -------------------------------------------------------------------------------- 1 | import "regenerator-runtime/runtime"; 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-redpacket/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /wasm/linkdrop.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-redpacket/HEAD/wasm/linkdrop.wasm -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react'], 3 | }; 4 | -------------------------------------------------------------------------------- /src/assets/img/dapp-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-redpacket/HEAD/src/assets/img/dapp-bg.png -------------------------------------------------------------------------------- /src/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | // NOTE: This is used to mock resource imports in JSX for tests 2 | module.exports = ''; 3 | 4 | -------------------------------------------------------------------------------- /src/scss/utilities/_shapes.scss: -------------------------------------------------------------------------------- 1 | // Shapes 2 | .s-rounded { 3 | border-radius: $border-radius; 4 | } 5 | 6 | .s-circle { 7 | border-radius: 50%; 8 | } -------------------------------------------------------------------------------- /src/scss/mixins/_avatar.scss: -------------------------------------------------------------------------------- 1 | // Avatar mixin 2 | @mixin avatar-base($size: $unit-8) { 3 | font-size: $size / 2; 4 | height: $size; 5 | width: $size; 6 | } 7 | -------------------------------------------------------------------------------- /src/scss/mixins/_text.scss: -------------------------------------------------------------------------------- 1 | // Text Ellipsis 2 | @mixin text-ellipsis() { 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | -------------------------------------------------------------------------------- /src/scss/_icons.scss: -------------------------------------------------------------------------------- 1 | // CSS Icons 2 | @import "icons/icons-core"; 3 | @import "icons/icons-navigation"; 4 | @import "icons/icons-action"; 5 | @import "icons/icons-object"; -------------------------------------------------------------------------------- /src/scss/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // Clearfix mixin 2 | @mixin clearfix() { 3 | &::after { 4 | clear: both; 5 | content: ""; 6 | display: table; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/scss/mixins/_toast.scss: -------------------------------------------------------------------------------- 1 | // Toast variant mixin 2 | @mixin toast-variant($color: $dark-color) { 3 | background: rgba($color, .95); 4 | border-color: $color; 5 | } 6 | -------------------------------------------------------------------------------- /src/scss/_utilities.scss: -------------------------------------------------------------------------------- 1 | @import "utilities/colors"; 2 | @import "utilities/cursors"; 3 | @import "utilities/display"; 4 | @import "utilities/divider"; 5 | @import "utilities/loading"; 6 | @import "utilities/position"; 7 | @import "utilities/shapes"; 8 | @import "utilities/text"; 9 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | @import "mixins/avatar"; 3 | @import "mixins/button"; 4 | @import "mixins/clearfix"; 5 | @import "mixins/color"; 6 | @import "mixins/label"; 7 | @import "mixins/position"; 8 | @import "mixins/shadow"; 9 | @import "mixins/text"; 10 | @import "mixins/toast"; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | 5 | before_script: 6 | - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" 7 | 8 | script: 9 | - fossa init 10 | - fossa analyze --server-scan 11 | - fossa test 12 | -------------------------------------------------------------------------------- /src/scss/mixins/_shadow.scss: -------------------------------------------------------------------------------- 1 | // Component focus shadow 2 | @mixin control-shadow($color: $light-color) { 3 | box-shadow: 0 0 0 .1rem rgba($color, .1); 4 | } 5 | 6 | // Shadow mixin 7 | @mixin shadow-variant($offset) { 8 | box-shadow: 0 $offset ($offset + .05rem) * 2 rgba($dark-color, .3); 9 | } 10 | -------------------------------------------------------------------------------- /src/scss/mixins/_label.scss: -------------------------------------------------------------------------------- 1 | // Label base style 2 | @mixin label-base() { 3 | border-radius: $border-radius; 4 | line-height: 1.25; 5 | padding: .1rem .2rem; 6 | } 7 | 8 | @mixin label-variant($color: $light-color, $bg-color: $primary-color) { 9 | background: $bg-color; 10 | color: $color; 11 | } 12 | -------------------------------------------------------------------------------- /src/scss/utilities/_cursors.scss: -------------------------------------------------------------------------------- 1 | // Cursors 2 | .c-hand { 3 | cursor: pointer; 4 | } 5 | 6 | .c-move { 7 | cursor: move; 8 | } 9 | 10 | .c-zoom-in { 11 | cursor: zoom-in; 12 | } 13 | 14 | .c-zoom-out { 15 | cursor: zoom-out; 16 | } 17 | 18 | .c-not-allowed { 19 | cursor: not-allowed; 20 | } 21 | 22 | .c-auto { 23 | cursor: auto; 24 | } -------------------------------------------------------------------------------- /src/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | // Animations 2 | @keyframes loading { 3 | 0% { 4 | transform: rotate(0deg); 5 | } 6 | 100% { 7 | transform: rotate(360deg); 8 | } 9 | } 10 | 11 | @keyframes slide-down { 12 | 0% { 13 | opacity: 0; 14 | transform: translateY(-$unit-8); 15 | } 16 | 100% { 17 | opacity: 1; 18 | transform: translateY(0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/scss/_hero.scss: -------------------------------------------------------------------------------- 1 | // Hero 2 | .hero { 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | padding-bottom: 4rem; 7 | padding-top: 4rem; 8 | 9 | &.hero-sm { 10 | padding-bottom: 2rem; 11 | padding-top: 2rem; 12 | } 13 | 14 | &.hero-lg { 15 | padding-bottom: 8rem; 16 | padding-top: 8rem; 17 | } 18 | 19 | .hero-body { 20 | padding: $layout-spacing; 21 | } 22 | } -------------------------------------------------------------------------------- /src/scss/_empty.scss: -------------------------------------------------------------------------------- 1 | // Empty states (or Blank slates) 2 | .empty { 3 | border-radius: $border-radius; 4 | color: lighten($dark-color, 3%); 5 | text-align: center; 6 | padding: $unit-8 $unit-8; 7 | 8 | .empty-icon { 9 | font-size: 3rem; 10 | margin-bottom: $layout-spacing-lg; 11 | } 12 | 13 | .empty-title, 14 | .empty-subtitle { 15 | margin: $layout-spacing auto; 16 | } 17 | 18 | .empty-action { 19 | margin-top: $layout-spacing-lg; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/scss/_panels.scss: -------------------------------------------------------------------------------- 1 | // Panels 2 | .panel { 3 | border: $border-width solid $border-color; 4 | border-radius: $border-radius; 5 | display: flex; 6 | flex-direction: column; 7 | 8 | .panel-header, 9 | .panel-footer { 10 | flex: 0 0 auto; 11 | padding: $layout-spacing-lg; 12 | } 13 | 14 | .panel-nav { 15 | flex: 0 0 auto; 16 | } 17 | 18 | .panel-body { 19 | flex: 1 1 auto; 20 | overflow-y: auto; 21 | padding: 0 $layout-spacing-lg; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/scss/_navbar.scss: -------------------------------------------------------------------------------- 1 | // Navbar 2 | .navbar { 3 | align-items: stretch; 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-between; 7 | 8 | .navbar-section { 9 | align-items: center; 10 | display: flex; 11 | flex: 1 0 0; 12 | 13 | &:not(:first-child):last-child { 14 | justify-content: flex-end; 15 | } 16 | } 17 | 18 | .navbar-center { 19 | align-items: center; 20 | display: flex; 21 | flex: 0 0 auto; 22 | } 23 | 24 | .navbar-brand { 25 | font-size: $font-size-lg; 26 | text-decoration: none; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Developer note: near.gitignore will be renamed to .gitignore upon project creation 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /out 8 | 9 | #keys 10 | /neardev 11 | 12 | #parcel 13 | .cache 14 | 15 | #rust 16 | target 17 | 18 | # testing 19 | /coverage 20 | 21 | # production 22 | /dist 23 | 24 | # misc 25 | .DS_Store 26 | .env.local 27 | .env.development.local 28 | .env.test.local 29 | .env.production.local 30 | 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | -------------------------------------------------------------------------------- /src/scss/_breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | // Breadcrumbs 2 | .breadcrumb { 3 | list-style: none; 4 | margin: $unit-1 0; 5 | padding: $unit-1 0; 6 | 7 | .breadcrumb-item { 8 | color: $gray-color-dark; 9 | display: inline-block; 10 | margin: 0; 11 | padding: $unit-1 0; 12 | 13 | &:not(:last-child) { 14 | margin-right: $unit-1; 15 | 16 | a { 17 | color: $gray-color-dark; 18 | } 19 | } 20 | 21 | &:not(:first-child) { 22 | &::before { 23 | color: $gray-color-dark; 24 | content: "/"; 25 | padding-right: $unit-2; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/util/util.js: -------------------------------------------------------------------------------- 1 | export const howLongAgo = (ts) => { 2 | const howLong = (Date.now() - ts) / 1000 3 | if (howLong > 86400 * 2) return Math.floor(howLong / 86400) + ' days ago' 4 | else if (howLong > 86400) return Math.floor(howLong / 86400) + ' day ago' 5 | else if (howLong > 3600 * 2) return Math.floor(howLong / 3600) + ' hours ago' 6 | else if (howLong > 3600) return Math.floor(howLong / 3600) + ' hour ago' 7 | else if (howLong > 60 * 2) return Math.floor(howLong / 60) + ' minutes ago' 8 | else if (howLong > 60) return Math.floor(howLong / 60) + ' minute ago' 9 | else return Math.floor(howLong) + ' seconds ago' 10 | } -------------------------------------------------------------------------------- /src/scss/mixins/_color.scss: -------------------------------------------------------------------------------- 1 | // Background color utility mixin 2 | @mixin bg-color-variant($name: ".bg-primary", $color: $primary-color) { 3 | #{$name} { 4 | background: $color !important; 5 | 6 | @if (lightness($color) < 60) { 7 | color: $light-color; 8 | } 9 | } 10 | } 11 | 12 | // Text color utility mixin 13 | @mixin text-color-variant($name: ".text-primary", $color: $primary-color) { 14 | #{$name} { 15 | color: $color !important; 16 | } 17 | 18 | a#{$name} { 19 | &:focus, 20 | &:hover { 21 | color: darken($color, 5%); 22 | } 23 | &:visited { 24 | color: lighten($color, 5%); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main.test.js: -------------------------------------------------------------------------------- 1 | let near; 2 | let contract; 3 | let accountId; 4 | 5 | beforeAll(async function () { 6 | // NOTE: nearlib and nearConfig are made available by near-shell/test_environment 7 | console.log('nearConfig', nearConfig); 8 | near = await nearlib.connect(nearConfig); 9 | accountId = nearConfig.contractName; 10 | contract = await near.loadContract(nearConfig.contractName, { 11 | viewMethods: ['welcome'], 12 | changeMethods: [], 13 | sender: accountId 14 | }); 15 | }); 16 | 17 | it('welcome test', async () => { 18 | const message = await contract.welcome({account_id:"test"}); 19 | expect(message).toEqual({"text": "Hello test"}); 20 | }) -------------------------------------------------------------------------------- /src/scss/_codes.scss: -------------------------------------------------------------------------------- 1 | // Codes 2 | code { 3 | @include label-base(); 4 | @include label-variant($code-color, lighten($code-color, 42.5%)); 5 | font-size: 85%; 6 | } 7 | 8 | .code { 9 | border-radius: $border-radius; 10 | color: $body-font-color; 11 | position: relative; 12 | 13 | &::before { 14 | color: $gray-color; 15 | content: attr(data-lang); 16 | font-size: $font-size-sm; 17 | position: absolute; 18 | right: $layout-spacing; 19 | top: $unit-h; 20 | } 21 | 22 | code { 23 | background: $bg-color; 24 | color: inherit; 25 | display: block; 26 | line-height: 1.5; 27 | overflow-x: auto; 28 | padding: 1rem; 29 | width: 100%; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/scss/_navs.scss: -------------------------------------------------------------------------------- 1 | // Navs 2 | .nav { 3 | display: flex; 4 | flex-direction: column; 5 | list-style: none; 6 | margin: $unit-1 0; 7 | 8 | .nav-item { 9 | a { 10 | color: $gray-color-dark; 11 | padding: $unit-1 $unit-2; 12 | text-decoration: none; 13 | &:focus, 14 | &:hover { 15 | color: $primary-color; 16 | } 17 | } 18 | &.active { 19 | & > a { 20 | color: darken($gray-color-dark, 10%); 21 | font-weight: bold; 22 | &:focus, 23 | &:hover { 24 | color: $primary-color; 25 | } 26 | } 27 | } 28 | } 29 | 30 | & .nav { 31 | margin-bottom: $unit-2; 32 | margin-left: $unit-4; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/img/icon-account.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scss/_chips.scss: -------------------------------------------------------------------------------- 1 | // Chips 2 | .chip { 3 | align-items: center; 4 | background: $bg-color-dark; 5 | border-radius: 5rem; 6 | display: inline-flex; 7 | font-size: 90%; 8 | height: $unit-6; 9 | line-height: $unit-4; 10 | margin: $unit-h; 11 | max-width: $control-width-sm; 12 | overflow: hidden; 13 | padding: $unit-1 $unit-2; 14 | text-decoration: none; 15 | text-overflow: ellipsis; 16 | vertical-align: middle; 17 | white-space: nowrap; 18 | 19 | &.active { 20 | background: $primary-color; 21 | color: $light-color; 22 | } 23 | 24 | .avatar { 25 | margin-left: -$unit-2; 26 | margin-right: $unit-1; 27 | } 28 | 29 | .btn-clear { 30 | border-radius: 50%; 31 | transform: scale(.75); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/scss/_tiles.scss: -------------------------------------------------------------------------------- 1 | // Tiles 2 | .tile { 3 | align-content: space-between; 4 | align-items: flex-start; 5 | display: flex; 6 | 7 | .tile-icon, 8 | .tile-action { 9 | flex: 0 0 auto; 10 | } 11 | .tile-content { 12 | flex: 1 1 auto; 13 | &:not(:first-child) { 14 | padding-left: $unit-2; 15 | } 16 | &:not(:last-child) { 17 | padding-right: $unit-2; 18 | } 19 | } 20 | .tile-title, 21 | .tile-subtitle { 22 | line-height: $line-height; 23 | } 24 | 25 | &.tile-centered { 26 | align-items: center; 27 | 28 | .tile-content { 29 | overflow: hidden; 30 | } 31 | 32 | .tile-title, 33 | .tile-subtitle { 34 | @include text-ellipsis(); 35 | margin-bottom: 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/scss/_accordions.scss: -------------------------------------------------------------------------------- 1 | // Accordions 2 | .accordion { 3 | input:checked ~, 4 | &[open] { 5 | & .accordion-header { 6 | .icon { 7 | transform: rotate(90deg); 8 | } 9 | } 10 | 11 | & .accordion-body { 12 | max-height: 50rem; 13 | } 14 | } 15 | 16 | .accordion-header { 17 | display: block; 18 | padding: $unit-1 $unit-2; 19 | 20 | .icon { 21 | transition: transform .25s; 22 | } 23 | } 24 | 25 | .accordion-body { 26 | margin-bottom: $layout-spacing; 27 | max-height: 0; 28 | overflow: hidden; 29 | transition: max-height .25s; 30 | } 31 | } 32 | 33 | // Remove default details marker in Webkit 34 | summary.accordion-header { 35 | &::-webkit-details-marker { 36 | display: none; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/scss/_asian.scss: -------------------------------------------------------------------------------- 1 | // Optimized for East Asian CJK 2 | html:lang(zh), 3 | html:lang(zh-Hans), 4 | .lang-zh, 5 | .lang-zh-hans { 6 | font-family: $cjk-zh-hans-font-family; 7 | } 8 | 9 | html:lang(zh-Hant), 10 | .lang-zh-hant { 11 | font-family: $cjk-zh-hant-font-family; 12 | } 13 | 14 | html:lang(ja), 15 | .lang-ja { 16 | font-family: $cjk-jp-font-family; 17 | } 18 | 19 | html:lang(ko), 20 | .lang-ko { 21 | font-family: $cjk-ko-font-family; 22 | } 23 | 24 | :lang(zh), 25 | :lang(ja), 26 | .lang-cjk { 27 | ins, 28 | u { 29 | border-bottom: $border-width solid; 30 | text-decoration: none; 31 | } 32 | 33 | del + del, 34 | del + s, 35 | ins + ins, 36 | ins + u, 37 | s + del, 38 | s + s, 39 | u + ins, 40 | u + u { 41 | margin-left: .125em; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/scss/utilities/_display.scss: -------------------------------------------------------------------------------- 1 | // Display 2 | .d-block { 3 | display: block; 4 | } 5 | .d-inline { 6 | display: inline; 7 | } 8 | .d-inline-block { 9 | display: inline-block; 10 | } 11 | .d-flex { 12 | display: flex; 13 | } 14 | .d-inline-flex { 15 | display: inline-flex; 16 | } 17 | .d-none, 18 | .d-hide { 19 | display: none !important; 20 | } 21 | .d-visible { 22 | visibility: visible; 23 | } 24 | .d-invisible { 25 | visibility: hidden; 26 | } 27 | .text-hide { 28 | background: transparent; 29 | border: 0; 30 | color: transparent; 31 | font-size: 0; 32 | line-height: 0; 33 | text-shadow: none; 34 | } 35 | .text-assistive { 36 | border: 0; 37 | clip: rect(0,0,0,0); 38 | height: 1px; 39 | margin: -1px; 40 | overflow: hidden; 41 | padding: 0; 42 | position: absolute; 43 | width: 1px; 44 | } 45 | -------------------------------------------------------------------------------- /src/scss/_tabs.scss: -------------------------------------------------------------------------------- 1 | // Tabs 2 | .tab { 3 | align-items: center; 4 | border-bottom: $border-width solid transparent; 5 | display: flex; 6 | flex-wrap: wrap; 7 | list-style: none; 8 | margin: $unit-1 0 ($unit-1 - $border-width) 0; 9 | 10 | .tab-item { 11 | border-bottom: $border-width-lg solid transparent; 12 | color: $gray-color; 13 | cursor: pointer; 14 | display: block; 15 | font-size: .8rem; 16 | margin: 0 $unit-2; 17 | padding: $unit-2 $unit-1 $unit-2 - $border-width-lg $unit-1; 18 | text-align: center; 19 | text-decoration: none; 20 | width: 4rem; 21 | &:focus, 22 | &:hover { 23 | color: $gray-color-dark; 24 | } 25 | 26 | &.active { 27 | border-bottom-color: $dark-color; 28 | color: $dark-color; 29 | font-weight: 700; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/scss/_dropdowns.scss: -------------------------------------------------------------------------------- 1 | // Dropdown 2 | .dropdown { 3 | display: inline-block; 4 | position: relative; 5 | 6 | .menu { 7 | animation: slide-down .15s ease 1; 8 | display: none; 9 | left: 0; 10 | max-height: 50vh; 11 | overflow-y: auto; 12 | position: absolute; 13 | top: 90%; 14 | } 15 | 16 | &.dropdown-right { 17 | .menu { 18 | left: auto; 19 | right: 0; 20 | } 21 | } 22 | 23 | &:focus .menu, 24 | &:hover .menu, 25 | &.active .menu, 26 | .dropdown-toggle:focus + .menu, 27 | .menu:hover { 28 | display: block; 29 | } 30 | 31 | // Fix dropdown-toggle border radius in button groups 32 | .btn-group { 33 | .dropdown-toggle:nth-last-child(2) { 34 | border-bottom-right-radius: $border-radius; 35 | border-top-right-radius: $border-radius; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/scss/_labels.scss: -------------------------------------------------------------------------------- 1 | // Labels 2 | .label { 3 | @include label-base(); 4 | @include label-variant(lighten($body-font-color, 5%), $bg-color-dark); 5 | display: inline-block; 6 | 7 | // Label rounded 8 | &.label-rounded { 9 | border-radius: 5rem; 10 | padding-left: .4rem; 11 | padding-right: .4rem; 12 | } 13 | 14 | // Label colors 15 | &.label-primary { 16 | @include label-variant($light-color, $primary-color); 17 | } 18 | 19 | &.label-secondary { 20 | @include label-variant($primary-color, $secondary-color); 21 | } 22 | 23 | &.label-success { 24 | @include label-variant($light-color, $success-color); 25 | } 26 | 27 | &.label-warning { 28 | @include label-variant($light-color, $warning-color); 29 | } 30 | 31 | &.label-error { 32 | @include label-variant($light-color, $error-color); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/scss/_filters.scss: -------------------------------------------------------------------------------- 1 | // Filters 2 | // The number of filter options 3 | $filter-number: 8 !default; 4 | 5 | %filter-checked-nav { 6 | background: $primary-color; 7 | color: $light-color; 8 | } 9 | 10 | %filter-checked-body { 11 | display: none; 12 | } 13 | 14 | .filter { 15 | .filter-nav { 16 | margin: $layout-spacing 0; 17 | } 18 | 19 | .filter-body { 20 | display: flex; 21 | flex-wrap: wrap; 22 | } 23 | 24 | .filter-tag { 25 | @for $i from 0 through ($filter-number) { 26 | &#tag-#{$i}:checked ~ .filter-nav .chip[for="tag-#{$i}"] { 27 | @extend %filter-checked-nav; 28 | } 29 | } 30 | 31 | @for $i from 1 through ($filter-number) { 32 | &#tag-#{$i}:checked ~ .filter-body .filter-item:not([data-tag~="tag-#{$i}"]) { 33 | @extend %filter-checked-body; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/scss/utilities/_loading.scss: -------------------------------------------------------------------------------- 1 | // Loading 2 | .loading { 3 | color: currentColor !important; 4 | min-height: $unit-4; 5 | pointer-events: none; 6 | position: relative; 7 | &::after { 8 | animation: loading .5s infinite linear; 9 | border: $border-width-lg solid currentColor; 10 | border-radius: 50%; 11 | border-right-color: transparent; 12 | border-top-color: transparent; 13 | content: ""; 14 | display: block; 15 | height: $unit-4; 16 | left: 50%; 17 | margin-left: -$unit-2; 18 | margin-top: -$unit-2; 19 | position: absolute; 20 | top: 50%; 21 | width: $unit-4; 22 | z-index: $zindex-0; 23 | } 24 | 25 | &.loading-lg { 26 | min-height: $unit-10; 27 | &::after { 28 | height: $unit-8; 29 | margin-left: -$unit-4; 30 | margin-top: -$unit-4; 31 | width: $unit-8; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "remove-empty-rulesets": true, 3 | "always-semicolon": true, 4 | "color-case": "lower", 5 | "block-indent": " ", 6 | "color-shorthand": true, 7 | "element-case": "lower", 8 | "eof-newline": false, 9 | "leading-zero": false, 10 | "quotes": "double", 11 | "sort-order-fallback": "abc", 12 | "space-before-colon": "", 13 | "space-after-colon": " ", 14 | "space-before-combinator": " ", 15 | "space-after-combinator": " ", 16 | "space-between-declarations": "\n", 17 | "space-before-opening-brace": " ", 18 | "space-after-opening-brace": "\n", 19 | "space-after-selector-delimiter": "\n", 20 | "space-before-selector-delimiter": "", 21 | "space-before-closing-brace": "\n", 22 | "strip-spaces": true, 23 | "tab-size": true, 24 | "unitless-zero": true, 25 | "sort-order": [[ 26 | "$variable", 27 | "$include", 28 | "$import" 29 | ]] 30 | } 31 | -------------------------------------------------------------------------------- /src/util/near-util.js: -------------------------------------------------------------------------------- 1 | 2 | import Big from 'big.js' 3 | 4 | export const NETWORK_ID = process.env.REACT_APP_NETWORK_ID || 'default' 5 | 6 | export const ACCESS_KEY_ALLOWANCE = Big(1000000000).times(10 ** 24).toFixed() 7 | export const MAX_UINT8 = '340282366920938463463374607431768211455' 8 | export const BOATLOAD_OF_GAS = Big(2).times(10 ** 14).toFixed() 9 | export const DROP_GAS = Big(3).times(10 ** 13).toFixed() 10 | const APPROX_ZERO_MIN = 10 11 | 12 | export const toNear = (value = '0') => Big(value).times(10 ** 24).toFixed() 13 | export const nearTo = (value = '0', to = 2) => Big(value).div(10 ** 24).toFixed(to === 0 ? undefined : to) 14 | export const nearToInt = (value = '0') => parseInt(nearTo(value), 10) 15 | export const big = (value = '0') => Big(value) 16 | export const gtZero = (value = '0') => big(value).gt(big()) 17 | export const gtZeroApprox = (value = '0') => big(value).gt(big(APPROX_ZERO_MIN)) -------------------------------------------------------------------------------- /src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | // Base 2 | *, 3 | *::before, 4 | *::after { 5 | box-sizing: inherit; 6 | } 7 | 8 | html { 9 | box-sizing: border-box; 10 | font-size: $html-font-size; 11 | line-height: $html-line-height; 12 | -webkit-tap-highlight-color: transparent; 13 | } 14 | 15 | body { 16 | background: $body-bg; 17 | color: $body-font-color; 18 | font-family: $body-font-family; 19 | font-size: $font-size; 20 | overflow-x: hidden; 21 | text-rendering: optimizeLegibility; 22 | } 23 | 24 | a { 25 | color: $link-color; 26 | outline: none; 27 | text-decoration: none; 28 | 29 | &:focus { 30 | @include control-shadow(); 31 | } 32 | 33 | &:focus, 34 | &:hover, 35 | &:active, 36 | &.active { 37 | color: $link-color-dark; 38 | text-decoration: underline; 39 | } 40 | 41 | &:visited { 42 | color: $link-color-light; 43 | } 44 | } 45 | 46 | :focus { 47 | outline: none; 48 | } 49 | -------------------------------------------------------------------------------- /src/scss/_cards.scss: -------------------------------------------------------------------------------- 1 | // Cards 2 | .card { 3 | background: $bg-color-light; 4 | border: $border-width solid $border-color; 5 | border-radius: $border-radius; 6 | display: flex; 7 | flex-direction: column; 8 | 9 | .card-header, 10 | .card-body, 11 | .card-footer { 12 | padding: $layout-spacing-lg; 13 | padding-bottom: 0; 14 | 15 | &:last-child { 16 | padding-bottom: $layout-spacing-lg; 17 | } 18 | } 19 | 20 | .card-body { 21 | flex: 1 1 auto; 22 | } 23 | 24 | .card-image { 25 | padding-top: $layout-spacing-lg; 26 | 27 | &:first-child { 28 | padding-top: 0; 29 | 30 | img { 31 | border-top-left-radius: $border-radius; 32 | border-top-right-radius: $border-radius; 33 | } 34 | } 35 | 36 | &:last-child { 37 | img { 38 | border-bottom-left-radius: $border-radius; 39 | border-bottom-right-radius: $border-radius; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/scss/_toasts.scss: -------------------------------------------------------------------------------- 1 | // Toasts 2 | .toast { 3 | @include toast-variant($dark-color); 4 | border: $border-width solid $dark-color; 5 | border-radius: $border-radius; 6 | color: $light-color; 7 | display: block; 8 | padding: $layout-spacing; 9 | width: 100%; 10 | 11 | &.toast-primary { 12 | @include toast-variant($primary-color); 13 | } 14 | 15 | &.toast-success { 16 | @include toast-variant($success-color); 17 | } 18 | 19 | &.toast-warning { 20 | @include toast-variant($warning-color); 21 | } 22 | 23 | &.toast-error { 24 | @include toast-variant($error-color); 25 | } 26 | 27 | a { 28 | color: $light-color; 29 | text-decoration: underline; 30 | 31 | &:focus, 32 | &:hover, 33 | &:active, 34 | &.active { 35 | opacity: .75; 36 | } 37 | } 38 | 39 | .btn-clear { 40 | margin: $unit-h; 41 | } 42 | 43 | p { 44 | &:last-child { 45 | margin-bottom: 0; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/scss/utilities/_position.scss: -------------------------------------------------------------------------------- 1 | // Position 2 | .clearfix { 3 | @include clearfix(); 4 | } 5 | 6 | .float-left { 7 | float: left !important; 8 | } 9 | 10 | .float-right { 11 | float: right !important; 12 | } 13 | 14 | .p-relative { 15 | position: relative !important; 16 | } 17 | 18 | .p-absolute { 19 | position: absolute !important; 20 | } 21 | 22 | .p-fixed { 23 | position: fixed !important; 24 | } 25 | 26 | .p-sticky { 27 | position: sticky !important; 28 | } 29 | 30 | .p-centered { 31 | display: block; 32 | float: none; 33 | margin-left: auto; 34 | margin-right: auto; 35 | } 36 | 37 | .flex-centered { 38 | align-items: center; 39 | display: flex; 40 | justify-content: center; 41 | } 42 | 43 | // Spacing 44 | @include margin-variant(0, 0); 45 | 46 | @include margin-variant(1, $unit-1); 47 | 48 | @include margin-variant(2, $unit-2); 49 | 50 | @include padding-variant(0, 0); 51 | 52 | @include padding-variant(1, $unit-1); 53 | 54 | @include padding-variant(2, $unit-2); 55 | -------------------------------------------------------------------------------- /src/scss/_autocomplete.scss: -------------------------------------------------------------------------------- 1 | // Autocomplete 2 | .form-autocomplete { 3 | position: relative; 4 | 5 | .form-autocomplete-input { 6 | align-content: flex-start; 7 | display: flex; 8 | flex-wrap: wrap; 9 | height: auto; 10 | min-height: $unit-8; 11 | padding: $unit-h; 12 | 13 | &.is-focused { 14 | @include control-shadow(); 15 | border-color: $primary-color; 16 | } 17 | 18 | .form-input { 19 | border-color: transparent; 20 | box-shadow: none; 21 | display: inline-block; 22 | flex: 1 0 auto; 23 | height: $unit-6; 24 | line-height: $unit-4; 25 | margin: $unit-h; 26 | width: auto; 27 | } 28 | } 29 | 30 | .menu { 31 | left: 0; 32 | position: absolute; 33 | top: 100%; 34 | width: 100%; 35 | } 36 | 37 | &.autocomplete-oneline { 38 | .form-autocomplete-input { 39 | flex-wrap: nowrap; 40 | overflow-x: auto; 41 | } 42 | 43 | .chip { 44 | flex: 1 0 auto; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/scss/utilities/_colors.scss: -------------------------------------------------------------------------------- 1 | // Text colors 2 | @include text-color-variant(".text-primary", $primary-color); 3 | 4 | @include text-color-variant(".text-secondary", $secondary-color-dark); 5 | 6 | @include text-color-variant(".text-gray", $gray-color); 7 | 8 | @include text-color-variant(".text-light", $light-color); 9 | 10 | @include text-color-variant(".text-dark", $body-font-color); 11 | 12 | @include text-color-variant(".text-success", $success-color); 13 | 14 | @include text-color-variant(".text-warning", $warning-color); 15 | 16 | @include text-color-variant(".text-error", $error-color); 17 | 18 | // Background colors 19 | @include bg-color-variant(".bg-primary", $primary-color); 20 | 21 | @include bg-color-variant(".bg-secondary", $secondary-color); 22 | 23 | @include bg-color-variant(".bg-dark", $dark-color); 24 | 25 | @include bg-color-variant(".bg-gray", $bg-color); 26 | 27 | @include bg-color-variant(".bg-success", $success-color); 28 | 29 | @include bg-color-variant(".bg-warning", $warning-color); 30 | 31 | @include bg-color-variant(".bg-error", $error-color); 32 | -------------------------------------------------------------------------------- /src/scss/_tables.scss: -------------------------------------------------------------------------------- 1 | // Tables 2 | .table { 3 | border-collapse: collapse; 4 | border-spacing: 0; 5 | width: 100%; 6 | @if $rtl == true { 7 | text-align: right; 8 | } @else { 9 | text-align: left; 10 | } 11 | 12 | &.table-striped { 13 | tbody { 14 | tr:nth-of-type(odd) { 15 | background: $bg-color; 16 | } 17 | } 18 | } 19 | 20 | &, 21 | &.table-striped { 22 | tbody { 23 | tr { 24 | &.active { 25 | background: $bg-color-dark; 26 | } 27 | } 28 | } 29 | } 30 | 31 | &.table-hover { 32 | tbody { 33 | tr { 34 | &:hover { 35 | background: $bg-color-dark; 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Scollable tables 42 | &.table-scroll { 43 | display: block; 44 | overflow-x: auto; 45 | padding-bottom: .75rem; 46 | white-space: nowrap; 47 | } 48 | 49 | td, 50 | th { 51 | border-bottom: $border-width solid $border-color; 52 | padding: $unit-3 $unit-2; 53 | } 54 | th { 55 | border-bottom-width: $border-width-lg; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/scss/utilities/_text.scss: -------------------------------------------------------------------------------- 1 | // Text 2 | // Text alignment utilities 3 | .text-left { 4 | text-align: left; 5 | } 6 | 7 | .text-right { 8 | text-align: right; 9 | } 10 | 11 | .text-center { 12 | text-align: center; 13 | } 14 | 15 | .text-justify { 16 | text-align: justify; 17 | } 18 | 19 | // Text transform utilities 20 | .text-lowercase { 21 | text-transform: lowercase; 22 | } 23 | 24 | .text-uppercase { 25 | text-transform: uppercase; 26 | } 27 | 28 | .text-capitalize { 29 | text-transform: capitalize; 30 | } 31 | 32 | // Text style utilities 33 | .text-normal { 34 | font-weight: normal; 35 | } 36 | 37 | .text-bold { 38 | font-weight: bold; 39 | } 40 | 41 | .text-italic { 42 | font-style: italic; 43 | } 44 | 45 | .text-large { 46 | font-size: 1.2em; 47 | } 48 | 49 | // Text overflow utilities 50 | .text-ellipsis { 51 | @include text-ellipsis(); 52 | } 53 | 54 | .text-clip { 55 | overflow: hidden; 56 | text-overflow: clip; 57 | white-space: nowrap; 58 | } 59 | 60 | .text-break { 61 | hyphens: auto; 62 | word-break: break-word; 63 | word-wrap: break-word; 64 | } 65 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | const sass = require('gulp-sass')(require('node-sass')); 3 | const cleancss = require('gulp-clean-css'); 4 | const csscomb = require('gulp-csscomb'); 5 | const rename = require('gulp-rename'); 6 | const autoprefixer = require('gulp-autoprefixer'); 7 | // const nearUtils = require("near-bindgen-as/compiler"); 8 | 9 | // function build_wasm(done){ 10 | // nearUtils.compile("./assembly/main.ts", "./out/main.wasm", done); 11 | // }; 12 | 13 | function css() { 14 | return gulp 15 | .src('./src/scss/*.scss') 16 | .pipe(sass({outputStyle: 'compact', precision: 2}) 17 | .on('error', sass.logError) 18 | ) 19 | .pipe(autoprefixer()) 20 | .pipe(csscomb()) 21 | .pipe(gulp.dest('./src/assets/css')) 22 | .pipe(cleancss()) 23 | .pipe(rename({ 24 | suffix: '.min' 25 | })) 26 | .pipe(gulp.dest('./src/assets/css')); 27 | } 28 | 29 | function watch() { 30 | gulp.watch('./**/*.scss', css); 31 | } 32 | 33 | const build = gulp.series(css); 34 | 35 | exports.default = build; 36 | exports.watch = watch; 37 | exports.css = css; 38 | 39 | -------------------------------------------------------------------------------- /src/scss/spectre.scss: -------------------------------------------------------------------------------- 1 | // Variables and mixins 2 | @import "variables"; 3 | @import "mixins"; 4 | 5 | /*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */ 6 | // Reset and dependencies 7 | @import "normalize"; 8 | @import "base"; 9 | 10 | // Elements 11 | @import "typography"; 12 | @import "asian"; 13 | @import "tables"; 14 | @import "buttons"; 15 | @import "forms"; 16 | @import "labels"; 17 | @import "codes"; 18 | @import "media"; 19 | 20 | // Layout 21 | @import "layout"; 22 | @import "hero"; 23 | @import "navbar"; 24 | 25 | // Components 26 | @import "accordions"; 27 | @import "avatars"; 28 | @import "badges"; 29 | @import "breadcrumbs"; 30 | @import "bars"; 31 | @import "cards"; 32 | @import "chips"; 33 | @import "dropdowns"; 34 | @import "empty"; 35 | @import "markdown"; 36 | @import "menus"; 37 | @import "modals"; 38 | @import "navs"; 39 | @import "pagination"; 40 | @import "panels"; 41 | @import "popovers"; 42 | @import "steps"; 43 | @import "tabs"; 44 | @import "tiles"; 45 | @import "toasts"; 46 | @import "tooltips"; 47 | 48 | // Utility classes 49 | @import "animations"; 50 | @import "utilities"; 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 NEAR Inc 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/scss/utilities/_divider.scss: -------------------------------------------------------------------------------- 1 | // Divider 2 | .divider, 3 | .divider-vert { 4 | display: block; 5 | position: relative; 6 | 7 | &[data-content]::after { 8 | background: $bg-color-light; 9 | color: $gray-color; 10 | content: attr(data-content); 11 | display: inline-block; 12 | font-size: $font-size-sm; 13 | padding: 0 $unit-2; 14 | transform: translateY(-$font-size-sm + $border-width); 15 | } 16 | } 17 | 18 | .divider { 19 | border-top: $border-width solid $border-color-light; 20 | height: $border-width; 21 | margin: $unit-2 0; 22 | 23 | &[data-content] { 24 | margin: $unit-4 0; 25 | } 26 | } 27 | 28 | .divider-vert { 29 | display: block; 30 | padding: $unit-4; 31 | 32 | &::before { 33 | border-left: $border-width solid $border-color; 34 | bottom: $unit-2; 35 | content: ""; 36 | display: block; 37 | left: 50%; 38 | position: absolute; 39 | top: $unit-2; 40 | transform: translateX(-50%); 41 | } 42 | 43 | &[data-content]::after { 44 | left: 50%; 45 | padding: $unit-1 0; 46 | position: absolute; 47 | top: 50%; 48 | transform: translate(-50%, -50%); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/scss/_progress.scss: -------------------------------------------------------------------------------- 1 | // Progress 2 | // Credit: https://css-tricks.com/html5-progress-element/ 3 | .progress { 4 | appearance: none; 5 | background: $bg-color-dark; 6 | border: 0; 7 | border-radius: $border-radius; 8 | color: $primary-color; 9 | height: $unit-1; 10 | position: relative; 11 | width: 100%; 12 | 13 | &::-webkit-progress-bar { 14 | background: transparent; 15 | border-radius: $border-radius; 16 | } 17 | 18 | &::-webkit-progress-value { 19 | background: $primary-color; 20 | border-radius: $border-radius; 21 | } 22 | 23 | &::-moz-progress-bar { 24 | background: $primary-color; 25 | border-radius: $border-radius; 26 | } 27 | 28 | &:indeterminate { 29 | animation: progress-indeterminate 1.5s linear infinite; 30 | background: $bg-color-dark linear-gradient(to right, $primary-color 30%, $bg-color-dark 30%) top left / 150% 150% no-repeat; 31 | 32 | &::-moz-progress-bar { 33 | background: transparent; 34 | } 35 | } 36 | } 37 | 38 | @keyframes progress-indeterminate { 39 | 0% { 40 | background-position: 200% 0; 41 | } 42 | 100% { 43 | background-position: -200% 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/scss/_pagination.scss: -------------------------------------------------------------------------------- 1 | // Pagination 2 | .pagination { 3 | display: flex; 4 | list-style: none; 5 | margin: $unit-1 0; 6 | padding: $unit-1 0; 7 | 8 | .page-item { 9 | margin: $unit-1 $unit-o; 10 | 11 | span { 12 | display: inline-block; 13 | padding: $unit-1 $unit-1; 14 | } 15 | 16 | a { 17 | border-radius: $border-radius; 18 | display: inline-block; 19 | padding: $unit-1 $unit-2; 20 | text-decoration: none; 21 | &:focus, 22 | &:hover { 23 | color: $primary-color; 24 | } 25 | } 26 | 27 | &.disabled { 28 | a { 29 | cursor: default; 30 | opacity: .5; 31 | pointer-events: none; 32 | } 33 | } 34 | 35 | &.active { 36 | a { 37 | background: $primary-color; 38 | color: $light-color; 39 | } 40 | } 41 | 42 | &.page-prev, 43 | &.page-next { 44 | flex: 1 0 50%; 45 | } 46 | 47 | &.page-next { 48 | text-align: right; 49 | } 50 | 51 | .page-item-title { 52 | margin: 0; 53 | } 54 | 55 | .page-item-subtitle { 56 | margin: 0; 57 | opacity: .5; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import "regenerator-runtime/runtime"; 2 | import React from 'react'; 3 | import renderer from 'react-test-renderer'; 4 | import App from './App'; 5 | 6 | let near; 7 | let contract; 8 | let accountId; 9 | let walletConnection 10 | beforeAll(async function () { 11 | // NOTE: nearlib and nearConfig are made available by near-shell/test_environment 12 | console.log('nearConfig', nearConfig); 13 | near = await nearlib.connect(nearConfig); 14 | accountId = nearConfig.contractName; 15 | contract = await near.loadContract(nearConfig.contractName, { 16 | viewMethods: ['welcome'], 17 | changeMethods: [], 18 | sender: accountId 19 | }); 20 | 21 | // Fake instance of WalletConnection 22 | // Feel free to modify for specific tests 23 | walletConnection = { 24 | requestSignIn() { 25 | }, 26 | signOut() { 27 | }, 28 | isSignedIn() { 29 | return true; 30 | }, 31 | getAccountId() { 32 | return accountId; 33 | } 34 | } 35 | }); 36 | 37 | it('renders without crashing', () => { 38 | const app = renderer.create(); 39 | let tree = app.toJSON(); 40 | expect(tree).toMatchSnapshot(); 41 | }); 42 | -------------------------------------------------------------------------------- /src/scss/mixins/_button.scss: -------------------------------------------------------------------------------- 1 | // Button variant mixin 2 | @mixin button-variant($color: $primary-color) { 3 | background: $color; 4 | border-color: darken($color, 3%); 5 | color: $light-color; 6 | &:focus { 7 | @include control-shadow($color); 8 | } 9 | &:focus, 10 | &:hover { 11 | background: darken($color, 2%); 12 | border-color: darken($color, 5%); 13 | color: $light-color; 14 | } 15 | &:active, 16 | &.active { 17 | background: darken($color, 7%); 18 | border-color: darken($color, 10%); 19 | color: $light-color; 20 | } 21 | &.loading { 22 | &::after { 23 | border-bottom-color: $light-color; 24 | border-left-color: $light-color; 25 | } 26 | } 27 | } 28 | 29 | @mixin button-outline-variant($color: $primary-color) { 30 | background: $light-color; 31 | border-color: $color; 32 | color: $color; 33 | &:focus { 34 | @include control-shadow($color); 35 | } 36 | &:focus, 37 | &:hover { 38 | background: lighten($color, 50%); 39 | border-color: darken($color, 2%); 40 | color: $color; 41 | } 42 | &:active, 43 | &.active { 44 | background: $color; 45 | border-color: darken($color, 5%); 46 | color: $light-color; 47 | } 48 | &.loading { 49 | &::after { 50 | border-bottom-color: $color; 51 | border-left-color: $color; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scss/_timelines.scss: -------------------------------------------------------------------------------- 1 | // Timelines 2 | .timeline { 3 | .timeline-item { 4 | display: flex; 5 | margin-bottom: $unit-6; 6 | position: relative; 7 | &::before { 8 | background: $border-color; 9 | content: ""; 10 | height: 100%; 11 | left: 11px; 12 | position: absolute; 13 | top: $unit-6; 14 | width: 2px; 15 | } 16 | 17 | .timeline-left { 18 | flex: 0 0 auto; 19 | } 20 | 21 | .timeline-content { 22 | flex: 1 1 auto; 23 | padding: 2px 0 2px $layout-spacing-lg; 24 | } 25 | 26 | .timeline-icon { 27 | align-items: center; 28 | border-radius: 50%; 29 | color: $light-color; 30 | display: flex; 31 | height: $unit-6; 32 | justify-content: center; 33 | text-align: center; 34 | width: $unit-6; 35 | &::before { 36 | border: $border-width-lg solid $primary-color; 37 | border-radius: 50%; 38 | content: ""; 39 | display: block; 40 | height: $unit-2; 41 | left: $unit-2; 42 | position: absolute; 43 | top: $unit-2; 44 | width: $unit-2; 45 | } 46 | 47 | &.icon-lg { 48 | background: $primary-color; 49 | line-height: $line-height; 50 | &::before { 51 | content: none; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/scss/mixins/_position.scss: -------------------------------------------------------------------------------- 1 | // Margin utility mixin 2 | @mixin margin-variant($id: 1, $size: $unit-1) { 3 | .m-#{$id} { 4 | margin: $size !important; 5 | } 6 | 7 | .mb-#{$id} { 8 | margin-bottom: $size !important; 9 | } 10 | 11 | .ml-#{$id} { 12 | margin-left: $size !important; 13 | } 14 | 15 | .mr-#{$id} { 16 | margin-right: $size !important; 17 | } 18 | 19 | .mt-#{$id} { 20 | margin-top: $size !important; 21 | } 22 | 23 | .mx-#{$id} { 24 | margin-left: $size !important; 25 | margin-right: $size !important; 26 | } 27 | 28 | .my-#{$id} { 29 | margin-bottom: $size !important; 30 | margin-top: $size !important; 31 | } 32 | } 33 | 34 | // Padding utility mixin 35 | @mixin padding-variant($id: 1, $size: $unit-1) { 36 | .p-#{$id} { 37 | padding: $size !important; 38 | } 39 | 40 | .pb-#{$id} { 41 | padding-bottom: $size !important; 42 | } 43 | 44 | .pl-#{$id} { 45 | padding-left: $size !important; 46 | } 47 | 48 | .pr-#{$id} { 49 | padding-right: $size !important; 50 | } 51 | 52 | .pt-#{$id} { 53 | padding-top: $size !important; 54 | } 55 | 56 | .px-#{$id} { 57 | padding-left: $size !important; 58 | padding-right: $size !important; 59 | } 60 | 61 | .py-#{$id} { 62 | padding-bottom: $size !important; 63 | padding-top: $size !important; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/scss/_badges.scss: -------------------------------------------------------------------------------- 1 | // Badges 2 | .badge { 3 | position: relative; 4 | white-space: nowrap; 5 | 6 | &[data-badge], 7 | &:not([data-badge]) { 8 | &::after { 9 | background: $primary-color; 10 | background-clip: padding-box; 11 | border-radius: .5rem; 12 | box-shadow: 0 0 0 .1rem $bg-color-light; 13 | color: $light-color; 14 | content: attr(data-badge); 15 | display: inline-block; 16 | transform: translate(-.05rem, -.5rem); 17 | } 18 | } 19 | &[data-badge] { 20 | &::after { 21 | font-size: $font-size-sm; 22 | height: .9rem; 23 | line-height: 1; 24 | min-width: .9rem; 25 | padding: .1rem .2rem; 26 | text-align: center; 27 | white-space: nowrap; 28 | } 29 | } 30 | &:not([data-badge]), 31 | &[data-badge=""] { 32 | &::after { 33 | height: 6px; 34 | min-width: 6px; 35 | padding: 0; 36 | width: 6px; 37 | } 38 | } 39 | 40 | // Badges for Buttons 41 | &.btn { 42 | &::after { 43 | position: absolute; 44 | top: 0; 45 | right: 0; 46 | transform: translate(50%, -50%); 47 | } 48 | } 49 | 50 | // Badges for Avatars 51 | &.avatar { 52 | &::after { 53 | position: absolute; 54 | top: 14.64%; 55 | right: 14.64%; 56 | transform: translate(50%, -50%); 57 | z-index: $zindex-1; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/wallet/login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
For local account login, Please run the following command in shell, then enter account id here. 9 |
10 |
11 | 12 |
13 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /src/scss/_popovers.scss: -------------------------------------------------------------------------------- 1 | // Popovers 2 | .popover { 3 | display: inline-block; 4 | position: relative; 5 | 6 | .popover-container { 7 | left: 50%; 8 | opacity: 0; 9 | padding: $layout-spacing; 10 | position: absolute; 11 | top: 0; 12 | transform: translate(-50%, -50%) scale(0); 13 | transition: transform .2s; 14 | width: $control-width-sm; 15 | z-index: $zindex-3; 16 | } 17 | 18 | *:focus + .popover-container, 19 | &:hover .popover-container { 20 | display: block; 21 | opacity: 1; 22 | transform: translate(-50%, -100%) scale(1); 23 | } 24 | 25 | &.popover-right { 26 | .popover-container { 27 | left: 100%; 28 | top: 50%; 29 | } 30 | 31 | *:focus + .popover-container, 32 | &:hover .popover-container { 33 | transform: translate(0, -50%) scale(1); 34 | } 35 | } 36 | 37 | &.popover-bottom { 38 | .popover-container { 39 | left: 50%; 40 | top: 100%; 41 | } 42 | 43 | *:focus + .popover-container, 44 | &:hover .popover-container { 45 | transform: translate(-50%, 0) scale(1); 46 | } 47 | } 48 | 49 | &.popover-left { 50 | .popover-container { 51 | left: 0; 52 | top: 50%; 53 | } 54 | 55 | *:focus + .popover-container, 56 | &:hover .popover-container { 57 | transform: translate(-100%, -50%) scale(1); 58 | } 59 | } 60 | 61 | .card { 62 | @include shadow-variant(.2rem); 63 | border: 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/scss/_menus.scss: -------------------------------------------------------------------------------- 1 | // Menus 2 | .menu { 3 | @include shadow-variant(.05rem); 4 | background: $bg-color-light; 5 | border-radius: $border-radius; 6 | list-style: none; 7 | margin: 0; 8 | min-width: $control-width-xs; 9 | padding: $unit-2; 10 | transform: translateY($layout-spacing-sm); 11 | z-index: $zindex-3; 12 | 13 | &.menu-nav { 14 | background: transparent; 15 | box-shadow: none; 16 | } 17 | 18 | .menu-item { 19 | margin-top: 0; 20 | padding: 0 $unit-2; 21 | position: relative; 22 | text-decoration: none; 23 | 24 | & > a { 25 | border-radius: $border-radius; 26 | color: inherit; 27 | display: block; 28 | margin: 0 (-$unit-2); 29 | padding: $unit-1 $unit-2; 30 | text-decoration: none; 31 | &:focus, 32 | &:hover { 33 | background: $secondary-color; 34 | color: $primary-color; 35 | } 36 | &:active, 37 | &.active { 38 | background: $secondary-color; 39 | color: $primary-color; 40 | } 41 | } 42 | 43 | .form-checkbox, 44 | .form-radio, 45 | .form-switch { 46 | margin: $unit-h 0; 47 | } 48 | 49 | & + .menu-item { 50 | margin-top: $unit-1; 51 | } 52 | } 53 | 54 | .menu-badge { 55 | align-items: center; 56 | display: flex; 57 | height: 100%; 58 | position: absolute; 59 | right: 0; 60 | top: 0; 61 | 62 | .label { 63 | margin-right: $unit-2; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: 'Inter', sans-serif; 4 | } 5 | body { 6 | margin: 0; 7 | } 8 | 9 | .App-header { 10 | background-image: url('https://nearprotocol.com/wp-content/uploads/2019/03/illo-developers.svg'); 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | color: white; 17 | text-align: center; 18 | } 19 | 20 | .App-link { 21 | color: #61dafb; 22 | } 23 | 24 | .logo-wrapper { 25 | display: flex; 26 | flex-direction: row-reverse; 27 | } 28 | 29 | .App-logo { 30 | animation: App-logo-spin infinite 20s linear; 31 | pointer-events: none; 32 | } 33 | 34 | .margin-logo { 35 | margin: 20px; 36 | } 37 | 38 | @keyframes App-logo-spin { 39 | from { 40 | transform: rotate(0deg); 41 | } 42 | to { 43 | transform: rotate(360deg); 44 | } 45 | } 46 | 47 | .image-wrapper { 48 | display: flex; 49 | align-items: center; 50 | flex-direction: column; 51 | justify-content: center; 52 | } 53 | 54 | .logo { 55 | width: 300px; 56 | } 57 | 58 | p { 59 | color: #25282A; 60 | } 61 | 62 | .login { 63 | margin-top: 32px; 64 | } 65 | 66 | button { 67 | padding: 8px; 68 | color: #0072CE; 69 | border-radius: 32px; 70 | border: 2px solid #0072CE; 71 | margin: 8px 8px 16px 8px; 72 | font-size: 1rem; 73 | outline: none; 74 | } 75 | 76 | button:hover { 77 | cursor: pointer; 78 | color: white; 79 | background: #0072CE; 80 | outline: none; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/scss/_media.scss: -------------------------------------------------------------------------------- 1 | // Media 2 | // Image responsive 3 | .img-responsive { 4 | display: block; 5 | height: auto; 6 | max-width: 100%; 7 | } 8 | 9 | // object-fit support is coming to Microsoft Edge 10 | // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/objectfitandobjectposition/ 11 | .img-fit-cover { 12 | object-fit: cover; 13 | } 14 | 15 | .img-fit-contain { 16 | object-fit: contain; 17 | } 18 | 19 | // Video responsive 20 | .video-responsive { 21 | display: block; 22 | overflow: hidden; 23 | padding: 0; 24 | position: relative; 25 | width: 100%; 26 | &::before { 27 | content: ""; 28 | display: block; 29 | padding-bottom: 56.25%; // Default ratio 16:9, you can calculate this value by dividing 9 by 16 30 | } 31 | 32 | iframe, 33 | object, 34 | embed { 35 | border: 0; 36 | bottom: 0; 37 | height: 100%; 38 | left: 0; 39 | position: absolute; 40 | right: 0; 41 | top: 0; 42 | width: 100%; 43 | } 44 | } 45 | 46 | video.video-responsive { 47 | height: auto; 48 | max-width: 100%; 49 | 50 | &::before { 51 | content: none; 52 | } 53 | } 54 | 55 | .video-responsive-4-3 { 56 | &::before { 57 | padding-bottom: 75%; // Ratio 4:3 58 | } 59 | } 60 | 61 | .video-responsive-1-1 { 62 | &::before { 63 | padding-bottom: 100%; // Ratio 1:1 64 | } 65 | } 66 | 67 | // Figure 68 | .figure { 69 | margin: 0 0 $layout-spacing 0; 70 | 71 | .figure-caption { 72 | color: $gray-color-dark; 73 | margin-top: $layout-spacing; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/scss/_steps.scss: -------------------------------------------------------------------------------- 1 | // Steps 2 | .step { 3 | display: flex; 4 | flex-wrap: nowrap; 5 | list-style: none; 6 | margin: $unit-1 0; 7 | width: 100%; 8 | 9 | .step-item { 10 | flex: 1 1 0; 11 | margin-top: 0; 12 | min-height: 1rem; 13 | text-align: center; 14 | position: relative; 15 | 16 | &:not(:first-child)::before { 17 | background: $primary-color; 18 | content: ""; 19 | height: 2px; 20 | left: -50%; 21 | position: absolute; 22 | top: 9px; 23 | width: 100%; 24 | } 25 | 26 | a { 27 | color: $primary-color; 28 | display: inline-block; 29 | padding: 20px 10px 0; 30 | text-decoration: none; 31 | 32 | &::before { 33 | background: $primary-color; 34 | border: $border-width-lg solid $light-color; 35 | border-radius: 50%; 36 | content: ""; 37 | display: block; 38 | height: $unit-3; 39 | left: 50%; 40 | position: absolute; 41 | top: $unit-1; 42 | transform: translateX(-50%); 43 | width: $unit-3; 44 | z-index: $zindex-0; 45 | } 46 | } 47 | 48 | &.active { 49 | a { 50 | &::before { 51 | background: $light-color; 52 | border: $border-width-lg solid $primary-color; 53 | } 54 | } 55 | 56 | & ~ .step-item { 57 | &::before { 58 | background: $border-color; 59 | } 60 | 61 | a { 62 | color: $gray-color; 63 | 64 | &::before { 65 | background: $border-color; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/scss/_bars.scss: -------------------------------------------------------------------------------- 1 | // Bars 2 | .bar { 3 | background: $bg-color-dark; 4 | border-radius: $border-radius; 5 | display: flex; 6 | flex-wrap: nowrap; 7 | height: $unit-4; 8 | width: 100%; 9 | 10 | &.bar-sm { 11 | height: $unit-1; 12 | } 13 | 14 | // TODO: attr() support 15 | .bar-item { 16 | background: $primary-color; 17 | color: $light-color; 18 | display: block; 19 | font-size: $font-size-sm; 20 | flex-shrink: 0; 21 | line-height: $unit-4; 22 | height: 100%; 23 | position: relative; 24 | text-align: center; 25 | width: 0; 26 | 27 | &:first-child { 28 | border-bottom-left-radius: $border-radius; 29 | border-top-left-radius: $border-radius; 30 | } 31 | &:last-child { 32 | border-bottom-right-radius: $border-radius; 33 | border-top-right-radius: $border-radius; 34 | flex-shrink: 1; 35 | } 36 | } 37 | } 38 | 39 | // Slider bar 40 | .bar-slider { 41 | height: $border-width-lg; 42 | margin: $layout-spacing 0; 43 | position: relative; 44 | 45 | .bar-item { 46 | left: 0; 47 | padding: 0; 48 | position: absolute; 49 | &:not(:last-child):first-child { 50 | background: $bg-color-dark; 51 | z-index: $zindex-0; 52 | } 53 | } 54 | 55 | .bar-slider-btn { 56 | background: $primary-color; 57 | border: 0; 58 | border-radius: 50%; 59 | height: $unit-3; 60 | padding: 0; 61 | position: absolute; 62 | right: 0; 63 | top: 50%; 64 | transform: translate(50%, -50%); 65 | width: $unit-3; 66 | 67 | &:active { 68 | box-shadow: 0 0 0 .1rem $primary-color; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/scss/_avatars.scss: -------------------------------------------------------------------------------- 1 | // Avatars 2 | .avatar { 3 | @include avatar-base(); 4 | background: $primary-color; 5 | border-radius: 50%; 6 | color: rgba($light-color, .85); 7 | display: inline-block; 8 | font-weight: 300; 9 | line-height: 1.25; 10 | margin: 0; 11 | position: relative; 12 | vertical-align: middle; 13 | 14 | &.avatar-xs { 15 | @include avatar-base($unit-4); 16 | } 17 | &.avatar-sm { 18 | @include avatar-base($unit-6); 19 | } 20 | &.avatar-lg { 21 | @include avatar-base($unit-12); 22 | } 23 | &.avatar-xl { 24 | @include avatar-base($unit-16); 25 | } 26 | 27 | img { 28 | border-radius: 50%; 29 | height: 100%; 30 | position: relative; 31 | width: 100%; 32 | z-index: $zindex-0; 33 | } 34 | 35 | .avatar-icon, 36 | .avatar-presence { 37 | background: $bg-color-light; 38 | bottom: 14.64%; 39 | height: 50%; 40 | padding: $border-width-lg; 41 | position: absolute; 42 | right: 14.64%; 43 | transform: translate(50%, 50%); 44 | width: 50%; 45 | z-index: $zindex-0 + 1; 46 | } 47 | 48 | .avatar-presence { 49 | background: $gray-color; 50 | box-shadow: 0 0 0 $border-width-lg $light-color; 51 | border-radius: 50%; 52 | height: .5em; 53 | width: .5em; 54 | 55 | &.online { 56 | background: $success-color; 57 | } 58 | 59 | &.busy { 60 | background: $error-color; 61 | } 62 | 63 | &.away { 64 | background: $warning-color; 65 | } 66 | } 67 | 68 | &[data-initial]::before { 69 | color: currentColor; 70 | content: attr(data-initial); 71 | left: 50%; 72 | position: absolute; 73 | top: 50%; 74 | transform: translate(-50%, -50%); 75 | z-index: $zindex-0; 76 | } 77 | } -------------------------------------------------------------------------------- /src/scss/_modals.scss: -------------------------------------------------------------------------------- 1 | // Modals 2 | .modal { 3 | align-items: center; 4 | bottom: 0; 5 | display: none; 6 | justify-content: center; 7 | left: 0; 8 | opacity: 0; 9 | overflow: hidden; 10 | padding: $layout-spacing; 11 | position: fixed; 12 | right: 0; 13 | top: 0; 14 | 15 | &:target, 16 | &.active { 17 | display: flex; 18 | opacity: 1; 19 | z-index: $zindex-4; 20 | 21 | .modal-overlay { 22 | background: rgba($bg-color, .75); 23 | bottom: 0; 24 | cursor: default; 25 | display: block; 26 | left: 0; 27 | position: absolute; 28 | right: 0; 29 | top: 0; 30 | } 31 | 32 | .modal-container { 33 | animation: slide-down .2s ease 1; 34 | z-index: $zindex-0; 35 | } 36 | } 37 | 38 | &.modal-sm { 39 | .modal-container { 40 | max-width: $control-width-sm; 41 | padding: 0 $unit-2; 42 | } 43 | } 44 | 45 | &.modal-lg { 46 | .modal-overlay { 47 | background: $bg-color-light; 48 | } 49 | 50 | .modal-container { 51 | box-shadow: none; 52 | max-width: $control-width-lg; 53 | } 54 | } 55 | } 56 | 57 | .modal-container { 58 | @include shadow-variant(.2rem); 59 | background: $bg-color-light; 60 | border-radius: $border-radius; 61 | display: flex; 62 | flex-direction: column; 63 | max-height: 75vh; 64 | max-width: $control-width-md; 65 | padding: 0 $unit-4; 66 | width: 100%; 67 | 68 | &.modal-fullheight { 69 | max-height: 100vh; 70 | } 71 | 72 | .modal-header { 73 | color: $dark-color; 74 | padding: $unit-4; 75 | } 76 | 77 | .modal-body { 78 | overflow-y: auto; 79 | padding: $unit-4; 80 | position: relative; 81 | } 82 | 83 | .modal-footer { 84 | padding: $unit-4; 85 | text-align: right; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/scss/_tooltips.scss: -------------------------------------------------------------------------------- 1 | // Tooltips 2 | .tooltip { 3 | position: relative; 4 | &::after { 5 | background: rgba($dark-color, .95); 6 | border-radius: $border-radius; 7 | bottom: 100%; 8 | color: $light-color; 9 | content: attr(data-tooltip); 10 | display: block; 11 | font-size: $font-size-sm; 12 | left: 50%; 13 | max-width: $control-width-sm; 14 | opacity: 0; 15 | overflow: hidden; 16 | padding: $unit-1 $unit-2; 17 | pointer-events: none; 18 | position: absolute; 19 | text-overflow: ellipsis; 20 | transform: translate(-50%, $unit-2); 21 | transition: opacity .2s, transform .2s; 22 | white-space: pre; 23 | z-index: $zindex-3; 24 | } 25 | &:focus, 26 | &:hover { 27 | &::after { 28 | opacity: 1; 29 | transform: translate(-50%, -$unit-1); 30 | } 31 | } 32 | &[disabled], 33 | &.disabled { 34 | pointer-events: auto; 35 | } 36 | 37 | &.tooltip-right { 38 | &::after { 39 | bottom: 50%; 40 | left: 100%; 41 | transform: translate(-$unit-1, 50%); 42 | } 43 | &:focus, 44 | &:hover { 45 | &::after { 46 | transform: translate($unit-1, 50%); 47 | } 48 | } 49 | } 50 | 51 | &.tooltip-bottom { 52 | &::after { 53 | bottom: auto; 54 | top: 100%; 55 | transform: translate(-50%, -$unit-2); 56 | } 57 | &:focus, 58 | &:hover { 59 | &::after { 60 | transform: translate(-50%, $unit-1); 61 | } 62 | } 63 | } 64 | 65 | &.tooltip-left { 66 | &::after { 67 | bottom: 50%; 68 | left: auto; 69 | right: 100%; 70 | transform: translate($unit-2, 50%); 71 | } 72 | &:focus, 73 | &:hover { 74 | &::after { 75 | transform: translate(-$unit-1, 50%); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const CONTRACT_NAME = 'linkdrop'; 2 | 3 | function getConfig(env) { 4 | switch (env) { 5 | 6 | case 'production': 7 | case 'mainnet': 8 | return { 9 | networkId: 'default', 10 | nodeUrl: 'https://rpc.mainnet.near.org', 11 | contractName: 'near', 12 | walletUrl: 'https://app.mynearwallet.com/', 13 | helperUrl: 'https://helper.mainnet.near.org', 14 | }; 15 | case 'development': 16 | case 'testnet': 17 | return { 18 | networkId: 'default', 19 | nodeUrl: 'https://rpc.testnet.near.org', 20 | contractName: 'testnet', 21 | walletUrl: 'https://testnet.mynearwallet.com/', 22 | helperUrl: 'https://helper.testnet.near.org', 23 | }; 24 | case 'betanet': 25 | return { 26 | networkId: 'betanet', 27 | nodeUrl: 'https://rpc.betanet.near.org', 28 | contractName: CONTRACT_NAME, 29 | walletUrl: 'https://wallet.betanet.near.org', 30 | helperUrl: 'https://helper.betanet.near.org', 31 | }; 32 | case 'local': 33 | return { 34 | networkId: 'local', 35 | nodeUrl: 'http://localhost:3030', 36 | keyPath: `${process.env.HOME}/.near/validator_key.json`, 37 | walletUrl: 'http://localhost:4000/wallet', 38 | contractName: CONTRACT_NAME, 39 | }; 40 | case 'test': 41 | case 'ci': 42 | return { 43 | networkId: 'shared-test', 44 | nodeUrl: 'https://rpc.ci-testnet.near.org', 45 | contractName: CONTRACT_NAME, 46 | masterAccount: 'test.near', 47 | }; 48 | default: 49 | throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); 50 | } 51 | } 52 | 53 | module.exports = getConfig; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "near-redpacket", 3 | "version": "0.1.0", 4 | "license": "(MIT AND Apache-2.0)", 5 | "scripts": { 6 | "prebuild": "rm -rf dist/ && gulp", 7 | "build": "parcel build src/*.html --public-url ./", 8 | "predeploy": "yarn build", 9 | "deploy": "echo 'redpacket.near.org' > dist/CNAME && gh-pages -d dist", 10 | "start": "parcel src/index.html", 11 | "dev": "nodemon --watch assembly -e ts --exec \"npm run start\"" 12 | }, 13 | "devDependencies": { 14 | "@babel/preset-env": "^7.9.5", 15 | "@babel/preset-react": "^7.9.4", 16 | "babel-jest": "^26.0.0", 17 | "env-cmd": "^10.1.0", 18 | "gh-pages": "^3.1.0", 19 | "gulp": "latest", 20 | "gulp-autoprefixer": "^6.1.0", 21 | "gulp-clean-css": "^4.2.0", 22 | "gulp-csscomb": "latest", 23 | "gulp-rename": "latest", 24 | "gulp-sass": "latest", 25 | "jest": "^26.0.0", 26 | "jest-environment-node": "^26.0.1", 27 | "near-shell": "^0.24.1", 28 | "nodemon": "^2.0.3", 29 | "parcel-bundler": "^1.12.4", 30 | "react-test-renderer": "^16.13.1", 31 | "shelljs": "^0.8.4" 32 | }, 33 | "dependencies": { 34 | "big.js": "^5.2.2", 35 | "clipboard-polyfill": "^3.0.1", 36 | "detect-browser-language": "^0.0.2", 37 | "near-api-js": "^0.29.1", 38 | "near-bindgen-as": "^1.2.3", 39 | "near-sdk-bindgen": "^2.0.0", 40 | "node-sass": "^7.0.1", 41 | "react": "^16.13.1", 42 | "react-dom": "^16.13.1", 43 | "react-router-dom": "^5.2.0", 44 | "regenerator-runtime": "^0.13.7" 45 | }, 46 | "eslintConfig": { 47 | "extends": "react-app" 48 | }, 49 | "jest": { 50 | "moduleNameMapper": { 51 | "\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/__mocks__/fileMock.js", 52 | "\\.(css|less)$": "/src/__mocks__/fileMock.js" 53 | }, 54 | "setupFiles": [ 55 | "/src/jest.init.js" 56 | ] 57 | }, 58 | "browserslist": { 59 | "production": [ 60 | ">0.2%", 61 | "not dead", 62 | "not op_mini all" 63 | ], 64 | "development": [ 65 | "last 1 chrome version", 66 | "last 1 firefox version", 67 | "last 1 safari version" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/scss/_typography.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | // Headings 3 | h1, 4 | h2, 5 | h3, 6 | h4, 7 | h5, 8 | h6 { 9 | color: inherit; 10 | font-weight: 500; 11 | line-height: 1.2; 12 | margin-bottom: .5em; 13 | margin-top: 0; 14 | } 15 | .h1, 16 | .h2, 17 | .h3, 18 | .h4, 19 | .h5, 20 | .h6 { 21 | font-weight: 700; 22 | } 23 | h1, 24 | .h1 { 25 | font-size: 2rem; 26 | } 27 | h2, 28 | .h2 { 29 | font-size: 1.6rem; 30 | } 31 | h3, 32 | .h3 { 33 | font-size: 1.4rem; 34 | } 35 | h4, 36 | .h4 { 37 | font-size: 1.2rem; 38 | } 39 | h5, 40 | .h5 { 41 | font-size: 1rem; 42 | } 43 | h6, 44 | .h6 { 45 | font-size: .8rem; 46 | } 47 | 48 | // Paragraphs 49 | p { 50 | margin: 0 0 $line-height; 51 | } 52 | 53 | // Semantic text elements 54 | a, 55 | ins, 56 | u { 57 | text-decoration-skip: ink edges; 58 | } 59 | 60 | abbr[title] { 61 | border-bottom: $border-width dotted; 62 | cursor: help; 63 | text-decoration: none; 64 | } 65 | 66 | kbd { 67 | @include label-base(); 68 | @include label-variant($light-color, $dark-color); 69 | font-size: $font-size-sm; 70 | } 71 | 72 | mark { 73 | @include label-variant($body-font-color, $highlight-color); 74 | border-bottom: $unit-o solid darken($highlight-color, 15%); 75 | border-radius: $border-radius; 76 | padding: $unit-o $unit-h 0; 77 | } 78 | 79 | // Blockquote 80 | blockquote { 81 | border-left: $border-width-lg solid $border-color; 82 | margin-left: 0; 83 | padding: $unit-2 $unit-4; 84 | 85 | p:last-child { 86 | margin-bottom: 0; 87 | } 88 | } 89 | 90 | // Lists 91 | ul, 92 | ol { 93 | margin: $unit-4 0 $unit-4 $unit-4; 94 | padding: 0; 95 | 96 | ul, 97 | ol { 98 | margin: $unit-4 0 $unit-4 $unit-4; 99 | } 100 | 101 | li { 102 | margin-top: $unit-2; 103 | } 104 | } 105 | 106 | ul { 107 | list-style: disc inside; 108 | 109 | ul { 110 | list-style-type: circle; 111 | } 112 | } 113 | 114 | ol { 115 | list-style: decimal inside; 116 | 117 | ol { 118 | list-style-type: lower-alpha; 119 | } 120 | } 121 | 122 | dl { 123 | dt { 124 | font-weight: bold; 125 | } 126 | dd { 127 | margin: $unit-2 0 $unit-4 0; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/scss/_sliders.scss: -------------------------------------------------------------------------------- 1 | // Sliders 2 | // Credit: https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ 3 | .slider { 4 | appearance: none; 5 | background: transparent; 6 | display: block; 7 | width: 100%; 8 | height: $unit-6; 9 | 10 | &:focus { 11 | @include control-shadow(); 12 | outline: none; 13 | } 14 | 15 | &.tooltip:not([data-tooltip]) { 16 | &::after { 17 | content: attr(value); 18 | } 19 | } 20 | 21 | // Slider Thumb 22 | &::-webkit-slider-thumb { 23 | -webkit-appearance: none; 24 | background: $primary-color; 25 | border: 0; 26 | border-radius: 50%; 27 | height: $unit-3; 28 | margin-top: -($unit-3 - $unit-h) / 2; 29 | transition: transform .2s; 30 | width: $unit-3; 31 | } 32 | &::-moz-range-thumb { 33 | background: $primary-color; 34 | border: 0; 35 | border-radius: 50%; 36 | height: $unit-3; 37 | transition: transform .2s; 38 | width: $unit-3; 39 | } 40 | &::-ms-thumb { 41 | background: $primary-color; 42 | border: 0; 43 | border-radius: 50%; 44 | height: $unit-3; 45 | transition: transform .2s; 46 | width: $unit-3; 47 | } 48 | 49 | &:active { 50 | &::-webkit-slider-thumb { 51 | transform: scale(1.25); 52 | } 53 | &::-moz-range-thumb { 54 | transform: scale(1.25); 55 | } 56 | &::-ms-thumb { 57 | transform: scale(1.25); 58 | } 59 | } 60 | 61 | &:disabled, 62 | &.disabled { 63 | &::-webkit-slider-thumb { 64 | background: $gray-color-light; 65 | transform: scale(1); 66 | } 67 | &::-moz-range-thumb { 68 | background: $gray-color-light; 69 | transform: scale(1); 70 | } 71 | &::-ms-thumb { 72 | background: $gray-color-light; 73 | transform: scale(1); 74 | } 75 | } 76 | 77 | // Slider Track 78 | &::-webkit-slider-runnable-track { 79 | background: $bg-color-dark; 80 | border-radius: $border-radius; 81 | height: $unit-h; 82 | width: 100%; 83 | } 84 | &::-moz-range-track { 85 | background: $bg-color-dark; 86 | border-radius: $border-radius; 87 | height: $unit-h; 88 | width: 100%; 89 | } 90 | &::-ms-track { 91 | background: $bg-color-dark; 92 | border-radius: $border-radius; 93 | height: $unit-h; 94 | width: 100%; 95 | } 96 | &::-ms-fill-lower { 97 | background: $primary-color; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NEAR Redpacket 6 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Linkdrop example with contract account deployment 6 | 7 | ## About the app 8 | The app allows you to give people NEAR accounts that are prefunded with NEAR tokens. This is better than just a vanilla Linkdrop because it allows you to queue up multiple of them and it has a nice UI for the recipient. 9 | 10 | The app does this by sending funds to the Linkdrop contract which will create "Drops". You will have a list of these in local storage and you can remove them at any time. This claims the funds back to your current account. 11 | 12 | **NOTE:** If you follow the wallet link of a drop, be warned it will not create accounts because your contract is not eligible to create the `.testnet` domain accounts. 13 | 14 | Instead, click "Share Drop Link" and visit your own drop. 15 | 16 | You will now see a *URL Drop* heading with some information about the drop. This is what another user would see if they used your URL. 17 | 18 | You can either: 19 | 1. claim the funds 20 | 2. create an account 21 | 3. create a contract account (deploys a locked multisig account) 22 | 23 | ## Contract 24 | For more details on the linkdrop contract: 25 | https://github.com/near/near-linkdrop 26 | 27 | ## Quickstart 28 | ``` 29 | yarn && yarn dev 30 | ``` 31 | 32 | ## Deploying your own contract 33 | It's recommended you create a sub account to handle your contract deployments: 34 | ``` 35 | near login 36 | near create_account [account_id] --masterAccount [your_account_id] --initialBalance [1-5 N] 37 | ``` 38 | Now update config.js and set: 39 | ``` 40 | const CONTRACT_NAME = [account_id] 41 | ``` 42 | 43 | ## The Linkdrop contract and calling it from JS 44 | 45 | All calls to the contract can be found in `src/Drops.js`. 46 | 47 | The original linkdrop contract is here: 48 | https://github.com/nearprotocol/near-linkdrop 49 | 50 | An additional function is added to the regular linkdrop contract: 51 | ``` 52 | pub fn create_limited_contract_account 53 | ``` 54 | This takes 3 additional arguments over the existing `pub fn create_account_and_claim` function. 55 | In order to successfully invoke from JS you must pass in the following: 56 | ``` 57 | new_account_id: string, 58 | new_public_key: string, 59 | allowance: string, 60 | contract_bytes: [...new Uint8Array(contract_bytes)], 61 | method_names: [...new Uint8Array(new TextEncoder().encode(` 62 | methods,account,is_limited_too_call 63 | `))] 64 | ``` 65 | 66 | ##### IMPORTANT: Make sure you have the latest version of NEAR Shell and Node Version > 10.x 67 | 68 | 1. [Node.js](https://nodejs.org/en/download/package-manager/) 69 | 2. near-shell 70 | ``` 71 | npm i -g near-shell 72 | ``` 73 | ### To run on NEAR testnet 74 | 75 | ```bash 76 | yarn && yarn dev 77 | ``` 78 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Router, Switch, Route } from 'react-router-dom'; 4 | import { createBrowserHistory } from 'history'; 5 | import App from './App'; 6 | import Claim from './Claim'; 7 | import getConfig from './config.js'; 8 | import { getCurrentUser } from './util/near-util'; 9 | import * as nearApi from 'near-api-js'; 10 | 11 | const history = createBrowserHistory() 12 | 13 | // Initializing contract 14 | async function initContract() { 15 | window.nearConfig = getConfig(process.env.NODE_ENV || 'development') 16 | 17 | window.keyStore = new nearApi.keyStores.BrowserLocalStorageKeyStore(window.localStorage, 'nearlib:keystore:') 18 | // console.log(window.keyStore) 19 | window.near = await nearApi.connect(Object.assign({ deps: { keyStore: window.keyStore } }, window.nearConfig)); 20 | 21 | window.contractAccount = new nearApi.Account(window.near.connection, window.nearConfig.contractName) 22 | 23 | window.getCurrentUser = async () => { 24 | // Needed to access wallet 25 | window.walletConnection = new nearApi.WalletConnection(window.near) 26 | window.walletAccount = new nearApi.WalletAccount(window.near) 27 | if (walletConnection.getAccountId()) { 28 | const accountId = walletConnection.getAccountId() 29 | window.currentUser = { 30 | accountId, account_id: accountId, 31 | balance: (await walletConnection.account().state()).amount 32 | } 33 | } 34 | } 35 | await window.getCurrentUser() 36 | 37 | if (window.currentUser) { 38 | const account = window.account = window.walletConnection.account() 39 | window.contract = await new nearApi.Contract(account, window.nearConfig.contractName, { 40 | viewMethods: ['get_key_balance'], 41 | // Change methods can modify the state. But you don't receive the returned value when called. 42 | changeMethods: ['send', 'send_limited'], 43 | // Sender is the account ID to initialize transactions. 44 | sender: window.currentUser.accountId 45 | }); 46 | // console.log(contract) 47 | } 48 | } 49 | 50 | window.nearInitPromise = initContract().then(() => { 51 | ReactDOM.render( 52 | 53 | 54 | 55 | ( 56 | 57 | )} /> 58 | ( 59 | 60 | )} /> 61 | 62 | 63 | , 64 | document.getElementById('root') 65 | ); 66 | }).catch(console.error) -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | NEAR Redpacket 10 | 11 | 40 | 41 | 42 | 43 | 44 |
45 | 55 | 56 | 57 | 58 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/__snapshots__/App.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders without crashing 1`] = ` 4 |
7 |
10 | NEAR logo 15 |

16 | 20 | 🐟 21 | 22 | NEAR protocol is a new blockchain focused on developer productivity and useability! 23 | 27 | 🐟 28 | 29 |

30 |

31 | 35 | ⛓ 36 | 37 | This little react app is connected to blockchain right now. 38 | 42 | ⛓ 43 | 44 |

45 |

54 |

55 |
56 |
57 | 62 | 67 |
68 |
69 |
70 |
73 | logo 78 | logo 83 |
84 |

85 | Edit 86 | 87 | src/App.js 88 | 89 | and save to reload. 90 |

91 | 97 | Learn React 98 | 99 |

100 | 104 | 🕸 105 | 106 | 107 | 111 | NEAR Website 112 | 113 | 114 | 118 | 🕸 119 | 120 |

121 |

122 | 126 | 📚 127 | 128 | 132 | Learn from NEAR Documentation 133 | 134 | 135 | 139 | 📚 140 | 141 |

142 |
143 |
144 | `; 145 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BOUNTY.yml: -------------------------------------------------------------------------------- 1 | name: "Simple Bounty" 2 | description: "Use this template to create a HEROES Simple Bounty via Github bot" 3 | title: "Bounty: " 4 | labels: ["bounty"] 5 | assignees: heroes-bot-test 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Hi! Let's set up your bounty! Please don't change the template - @heroes-bot-test won't be able to help you. 11 | 12 | - type: dropdown 13 | id: type 14 | attributes: 15 | label: What talent are you looking for? 16 | options: 17 | - Marketing 18 | - Development 19 | - Design 20 | - Other 21 | - Content 22 | - Research 23 | - Audit 24 | 25 | - type: textarea 26 | id: description 27 | attributes: 28 | label: What you need to be done? 29 | 30 | - type: dropdown 31 | id: tags 32 | attributes: 33 | label: Tags 34 | description: Add tags that match the topic of the work 35 | multiple: true 36 | options: 37 | - API 38 | - Blockchain 39 | - Community 40 | - CSS 41 | - DAO 42 | - dApp 43 | - DeFi 44 | - Design 45 | - Documentation 46 | - HTML 47 | - Javascript 48 | - NFT 49 | - React 50 | - Rust 51 | - Smart contract 52 | - Typescript 53 | - UI/UX 54 | - web3 55 | - Translation 56 | - Illustration 57 | - Branding 58 | - Copywriting 59 | - Blogging 60 | - Editing 61 | - Video Creation 62 | - Social Media 63 | - Graphic Design 64 | - Transcription 65 | - Product Design 66 | - Artificial Intelligence 67 | - Quality Assurance 68 | - Risk Assessment 69 | - Security Audit 70 | - Bug Bounty 71 | - Code Review 72 | - Blockchain Security 73 | - Smart Contract Testing 74 | - Penetration Testing 75 | - Vulnerability Assessment 76 | - BOS 77 | - News 78 | - Hackathon 79 | - NEARCON2023 80 | - NEARWEEK 81 | 82 | - type: input 83 | id: deadline 84 | attributes: 85 | label: Deadline 86 | description: "Set a deadline for your bounty. Please enter the date in format: DD.MM.YYYY" 87 | placeholder: "19.05.2027" 88 | 89 | - type: dropdown 90 | id: currencyType 91 | attributes: 92 | label: Currency 93 | description: What is the currency you want to pay? 94 | options: 95 | - USDC.e 96 | - USDT.e 97 | - DAI 98 | - wNEAR 99 | - USDt 100 | - XP 101 | - marmaj 102 | - NEKO 103 | - JUMP 104 | - USDC 105 | - NEARVIDIA 106 | default: 0 107 | validations: 108 | required: true 109 | 110 | - type: input 111 | id: currencyAmount 112 | attributes: 113 | label: Amount 114 | description: How much it will be cost? 115 | 116 | - type: markdown 117 | attributes: 118 | value: "## Advanced settings" 119 | 120 | - type: checkboxes 121 | id: kyc 122 | attributes: 123 | label: KYC 124 | description: "Use HEROES' KYC Verification, only applicants who passed HEROES' KYC can apply and work on this bounty!" 125 | options: 126 | - label: Use KYC Verification 127 | 128 | - type: markdown 129 | attributes: 130 | value: | 131 | ### This cannot be changed once the bounty is live! 132 | -------------------------------------------------------------------------------- /src/scss/_parallax.scss: -------------------------------------------------------------------------------- 1 | // Parallax 2 | $parallax-deg: 3deg !default; 3 | $parallax-offset: 4.5px !default; 4 | $parallax-offset-z: 50px !default; 5 | $parallax-perspective: 1000px !default; 6 | $parallax-scale: .95 !default; 7 | $parallax-fade-color: rgba(255, 255, 255, .35) !default; 8 | 9 | // Mixin: Parallax direction 10 | @mixin parallax-dir() { 11 | height: 50%; 12 | outline: none; 13 | position: absolute; 14 | width: 50%; 15 | z-index: $zindex-1; 16 | } 17 | 18 | .parallax { 19 | display: block; 20 | height: auto; 21 | position: relative; 22 | width: auto; 23 | 24 | .parallax-content { 25 | @include shadow-variant(1rem); 26 | height: auto; 27 | transform: perspective($parallax-perspective); 28 | transform-style: preserve-3d; 29 | transition: all .4s ease; 30 | width: 100%; 31 | 32 | &::before { 33 | content: ""; 34 | display: block; 35 | height: 100%; 36 | left: 0; 37 | position: absolute; 38 | top: 0; 39 | width: 100%; 40 | } 41 | } 42 | 43 | .parallax-front { 44 | align-items: center; 45 | color: $light-color; 46 | display: flex; 47 | height: 100%; 48 | justify-content: center; 49 | left: 0; 50 | position: absolute; 51 | text-align: center; 52 | text-shadow: 0 0 20px rgba($dark-color, .75); 53 | top: 0; 54 | transform: translateZ($parallax-offset-z) scale($parallax-scale); 55 | transition: transform .4s; 56 | width: 100%; 57 | z-index: $zindex-0; 58 | } 59 | 60 | .parallax-top-left { 61 | @include parallax-dir(); 62 | left: 0; 63 | top: 0; 64 | 65 | &:focus ~ .parallax-content, 66 | &:hover ~ .parallax-content { 67 | transform: perspective($parallax-perspective) rotateX($parallax-deg) rotateY(-$parallax-deg); 68 | 69 | &::before { 70 | background: linear-gradient(135deg, $parallax-fade-color 0%, transparent 50%); 71 | } 72 | 73 | .parallax-front { 74 | transform: translate3d($parallax-offset, $parallax-offset, $parallax-offset-z) scale($parallax-scale); 75 | } 76 | } 77 | } 78 | 79 | .parallax-top-right { 80 | @include parallax-dir(); 81 | right: 0; 82 | top: 0; 83 | 84 | &:focus ~ .parallax-content, 85 | &:hover ~ .parallax-content { 86 | transform: perspective($parallax-perspective) rotateX($parallax-deg) rotateY($parallax-deg); 87 | 88 | &::before { 89 | background: linear-gradient(-135deg, $parallax-fade-color 0%, transparent 50%); 90 | } 91 | 92 | .parallax-front { 93 | transform: translate3d(-$parallax-offset, $parallax-offset, $parallax-offset-z) scale($parallax-scale); 94 | } 95 | } 96 | } 97 | 98 | .parallax-bottom-left { 99 | @include parallax-dir(); 100 | bottom: 0; 101 | left: 0; 102 | 103 | &:focus ~ .parallax-content, 104 | &:hover ~ .parallax-content { 105 | transform: perspective($parallax-perspective) rotateX(-$parallax-deg) rotateY(-$parallax-deg); 106 | 107 | &::before { 108 | background: linear-gradient(45deg, $parallax-fade-color 0%, transparent 50%); 109 | } 110 | 111 | .parallax-front { 112 | transform: translate3d($parallax-offset, -$parallax-offset, $parallax-offset-z) scale($parallax-scale); 113 | } 114 | } 115 | } 116 | 117 | .parallax-bottom-right { 118 | @include parallax-dir(); 119 | bottom: 0; 120 | right: 0; 121 | 122 | &:focus ~ .parallax-content, 123 | &:hover ~ .parallax-content { 124 | transform: perspective($parallax-perspective) rotateX(-$parallax-deg) rotateY($parallax-deg); 125 | 126 | &::before { 127 | background: linear-gradient(-45deg, $parallax-fade-color 0%, transparent 50%); 128 | } 129 | 130 | .parallax-front { 131 | transform: translate3d(-$parallax-offset, -$parallax-offset, $parallax-offset-z) scale($parallax-scale); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/scss/_markdown.scss: -------------------------------------------------------------------------------- 1 | .markdown { 2 | font-size: .8rem; 3 | font-weight: 400; 4 | line-height: 1.4rem; 5 | text-align: justify; 6 | word-break: break-word; 7 | 8 | // Reset 9 | & > :first-child { 10 | margin-top: 0; 11 | } 12 | 13 | // Headings 14 | h1, 15 | h2, 16 | h3, 17 | h4, 18 | h5, 19 | h6 { 20 | font-weight: 700; 21 | line-height: 1.5; 22 | margin-bottom: $unit-6; 23 | margin-top: $unit-2; 24 | 25 | & + hr { 26 | margin-top: -$unit-2; 27 | } 28 | } 29 | h1 { 30 | font-size: 28px; 31 | } 32 | h2 { 33 | font-size: 24px; 34 | } 35 | h3 { 36 | font-size: 22px; 37 | } 38 | h4 { 39 | font-size: 20px; 40 | } 41 | h5 { 42 | font-size: 18px; 43 | } 44 | h6 { 45 | font-size: 16px; 46 | } 47 | 48 | // Paragraphs 49 | p { 50 | hyphens: auto; 51 | margin: 0 0 $unit-8; 52 | } 53 | 54 | // Semantic text elements 55 | a { 56 | border-bottom: .05rem solid currentColor; 57 | font-weight: 500; 58 | padding-bottom: .1rem; 59 | 60 | &:focus, 61 | &:hover, 62 | &:active { 63 | text-decoration: none; 64 | } 65 | } 66 | 67 | a, 68 | ins, 69 | u { 70 | text-decoration-skip: ink edges; 71 | } 72 | 73 | del + del, 74 | del + s, 75 | ins + ins, 76 | ins + u, 77 | s + del, 78 | s + s, 79 | u + ins, 80 | u + u { 81 | margin-left: .125em; 82 | } 83 | 84 | // Divider 85 | hr { 86 | background: $border-color; 87 | border: none; 88 | display: block; 89 | height: 1px; 90 | margin: $unit-2 0 $unit-6; 91 | overflow: hidden; 92 | 93 | @include clearfix(); 94 | } 95 | 96 | // Lists 97 | ul, 98 | ol { 99 | margin: $unit-8 0 $unit-8 $unit-8; 100 | padding: 0; 101 | 102 | ul, 103 | ol { 104 | margin: $unit-4 0 $unit-4 $unit-8; 105 | } 106 | 107 | li { 108 | margin-top: $unit-2; 109 | 110 | & > p { 111 | display: inline; 112 | } 113 | } 114 | } 115 | 116 | ul { 117 | list-style: disc inside; 118 | 119 | ul { 120 | list-style-type: circle; 121 | } 122 | 123 | //- Fix content wider than screen 124 | & > li > ul > li > ul { 125 | display: none; 126 | } 127 | } 128 | 129 | ol { 130 | list-style: decimal inside; 131 | 132 | ol { 133 | list-style-type: lower-alpha; 134 | } 135 | } 136 | 137 | // Images 138 | img { 139 | border: 0; 140 | display: block; 141 | height: auto; 142 | margin: 1rem auto; 143 | max-width: 100%; 144 | 145 | & + em { 146 | display: inline-block; 147 | color: $gray-color-dark; 148 | font-size: .7rem; 149 | text-align: center; 150 | width: 100%; 151 | } 152 | 153 | &.emojione { 154 | display: inline; 155 | height: 1rem; 156 | margin: 0; 157 | vertical-align: -10%; 158 | width: 1rem; 159 | } 160 | } 161 | 162 | // Blockquote 163 | blockquote { 164 | border-left: .2rem solid $gray-color-dark; 165 | margin: $unit-8 0; 166 | padding: $unit-4 $unit-8; 167 | 168 | &:empty { 169 | display: none; 170 | } 171 | 172 | p:last-child { 173 | margin-bottom: 0; 174 | } 175 | } 176 | 177 | // Tables 178 | table { 179 | border-collapse: collapse; 180 | border-spacing: 0; 181 | display: block; 182 | font-size: .7rem; 183 | margin: $unit-8 0; 184 | overflow-x: auto; 185 | padding-bottom: .75rem; 186 | text-align: left; 187 | white-space: nowrap; 188 | width: 100%; 189 | 190 | tbody { 191 | tr:nth-of-type(odd) { 192 | background: $bg-color; 193 | } 194 | } 195 | 196 | td, 197 | th { 198 | border-bottom: 1px solid $dark-color; 199 | width: 20%; 200 | padding: $unit-4; 201 | } 202 | 203 | th { 204 | border-bottom-width: 2px; 205 | } 206 | } 207 | 208 | // Code 209 | code { 210 | background: $bg-color; 211 | color: $code-color; 212 | font-family: $mono-font-family; 213 | font-size: .9em; 214 | padding: .2em; 215 | } 216 | 217 | pre { 218 | background: $bg-color; 219 | margin: $unit-8 0; 220 | overflow-x: auto; 221 | padding: $unit-2 $unit-4; 222 | } 223 | 224 | .codehilite { 225 | font-size: .9em; 226 | line-height: 1.8; 227 | } 228 | } -------------------------------------------------------------------------------- /src/assets/near-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // Core variables 2 | $version: "0.5.8"; 3 | 4 | // Core features 5 | $rtl: false !default; 6 | 7 | // Core colors 8 | $primary-color: #E3514C !default; 9 | $primary-color-dark: darken($primary-color, 3%) !default; 10 | $primary-color-light: lighten($primary-color, 3%) !default; 11 | $secondary-color: lighten($primary-color, 37.5%) !default; 12 | $secondary-color-dark: darken($secondary-color, 3%) !default; 13 | $secondary-color-light: lighten($secondary-color, 3%) !default; 14 | $gold-color: #EEC88C !default; 15 | $gold-color-dark: darken($gold-color, 3%) !default; 16 | $gold-color-light: lighten($gold-color, 3%) !default; 17 | 18 | // Gray colors 19 | $dark-color: #242424 !default; 20 | $light-color: #fff !default; 21 | $gray-color: lighten($dark-color, 55%) !default; 22 | $gray-color-dark: darken($gray-color, 20%) !default; 23 | $gray-color-light: lighten($gray-color, 20%) !default; 24 | 25 | $border-color: #e5e5e5 !default; 26 | $border-color-dark: darken($border-color, 2%) !default; 27 | $border-color-light: lighten($border-color, 2%) !default; 28 | $bg-color: #f8f8f8 !default; 29 | $bg-color-dark: darken($bg-color, 3%) !default; 30 | $bg-color-light: lighten($bg-color, 3%) !default; 31 | 32 | // Control colors 33 | $success-color: #32b643 !default; 34 | $warning-color: #ffb700 !default; 35 | $error-color: #e85600 !default; 36 | 37 | // Other colors 38 | $code-color: #d73e48 !default; 39 | $highlight-color: #ffe9b3 !default; 40 | $body-bg: $bg-color-light !default; 41 | $body-font-color: lighten($dark-color, 5%) !default; 42 | $link-color: darken($gold-color, 55%) !default; 43 | $link-color-dark: darken($link-color, 10%) !default; 44 | $link-color-light: lighten($link-color, 10%) !default; 45 | 46 | // Fonts 47 | // Credit: https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ 48 | $base-font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto !default; 49 | $mono-font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Courier, monospace !default; 50 | $fallback-font-family: "Helvetica Neue", sans-serif !default; 51 | $cjk-zh-hans-font-family: $base-font-family, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", $fallback-font-family !default; 52 | $cjk-zh-hant-font-family: $base-font-family, "PingFang TC", "Hiragino Sans CNS", "Microsoft JhengHei", $fallback-font-family !default; 53 | $cjk-jp-font-family: $base-font-family, "Hiragino Sans", "Hiragino Kaku Gothic Pro", "Yu Gothic", YuGothic, Meiryo, $fallback-font-family !default; 54 | $cjk-ko-font-family: $base-font-family, "Malgun Gothic", $fallback-font-family !default; 55 | $body-font-family: $base-font-family, $fallback-font-family !default; 56 | 57 | // Unit sizes 58 | $unit-o: .05rem !default; 59 | $unit-h: .1rem !default; 60 | $unit-1: .2rem !default; 61 | $unit-2: .4rem !default; 62 | $unit-3: .6rem !default; 63 | $unit-4: .8rem !default; 64 | $unit-5: 1rem !default; 65 | $unit-6: 1.2rem !default; 66 | $unit-7: 1.4rem !default; 67 | $unit-8: 1.6rem !default; 68 | $unit-9: 1.8rem !default; 69 | $unit-10: 2rem !default; 70 | $unit-12: 2.4rem !default; 71 | $unit-16: 3.2rem !default; 72 | 73 | // Font sizes 74 | $html-font-size: 20px !default; 75 | $html-line-height: 1.5 !default; 76 | $font-size: .8rem !default; 77 | $font-size-sm: .7rem !default; 78 | $font-size-lg: .9rem !default; 79 | $line-height: 1.2rem !default; 80 | 81 | // Sizes 82 | $layout-spacing: $unit-2 !default; 83 | $layout-spacing-sm: $unit-1 !default; 84 | $layout-spacing-lg: $unit-4 !default; 85 | $border-radius: $unit-2 !default; 86 | $border-width: $unit-o !default; 87 | $border-width-lg: $unit-h !default; 88 | $control-size: $unit-9 !default; 89 | $control-size-sm: $unit-7 !default; 90 | $control-size-lg: $unit-10 !default; 91 | $control-padding-x: $unit-3 !default; 92 | $control-padding-x-sm: $unit-3 * .75 !default; 93 | $control-padding-x-lg: $unit-3 * 1.5 !default; 94 | $control-padding-y: ($control-size - $line-height) / 2 - $border-width !default; 95 | $control-padding-y-sm: ($control-size-sm - $line-height) / 2 - $border-width !default; 96 | $control-padding-y-lg: ($control-size-lg - $line-height) / 2 - $border-width !default; 97 | $control-icon-size: .8rem !default; 98 | 99 | $control-width-xs: 180px !default; 100 | $control-width-sm: 320px !default; 101 | $control-width-md: 640px !default; 102 | $control-width-lg: 960px !default; 103 | $control-width-xl: 1280px !default; 104 | 105 | // Responsive breakpoints 106 | $size-xs: 480px !default; 107 | $size-sm: 600px !default; 108 | $size-md: 840px !default; 109 | $size-lg: 960px !default; 110 | $size-xl: 1280px !default; 111 | $size-2x: 1440px !default; 112 | 113 | $responsive-breakpoint: $size-xs !default; 114 | 115 | // Z-index 116 | $zindex-0: 1 !default; 117 | $zindex-1: 100 !default; 118 | $zindex-2: 200 !default; 119 | $zindex-3: 300 !default; 120 | $zindex-4: 400 !default; 121 | -------------------------------------------------------------------------------- /src/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Buttons 2 | .btn { 3 | align-items: center; 4 | appearance: none; 5 | background: $bg-color-dark; 6 | border: $border-width solid $border-color-light; 7 | border-radius: 10rem; 8 | box-shadow: 0 .1rem 1rem rgba($dark-color, .1); 9 | color: $gray-color-dark; 10 | cursor: pointer; 11 | display: inline-flex; 12 | font-size: $font-size; 13 | height: $control-size; 14 | line-height: $line-height; 15 | outline: none; 16 | padding: $control-padding-y $control-padding-x; 17 | text-align: center; 18 | text-decoration: none; 19 | transition: background .2s, border .2s, box-shadow .2s, color .2s; 20 | user-select: none; 21 | vertical-align: middle; 22 | white-space: nowrap; 23 | &:visited { 24 | color: $gray-color-dark; 25 | } 26 | &:focus { 27 | @include control-shadow(); 28 | } 29 | &:focus, 30 | &:hover { 31 | background: $primary-color; 32 | border-color: $primary-color-dark; 33 | color: $light-color; 34 | text-decoration: none; 35 | } 36 | &:active, 37 | &.active { 38 | background: $primary-color-dark; 39 | border-color: darken($primary-color-dark, 5%); 40 | color: $light-color; 41 | text-decoration: none; 42 | &.loading { 43 | &::after { 44 | border-bottom-color: $light-color; 45 | border-left-color: $light-color; 46 | } 47 | } 48 | } 49 | &[disabled], 50 | &:disabled, 51 | &.disabled { 52 | cursor: default; 53 | opacity: .5; 54 | pointer-events: none; 55 | } 56 | 57 | // Button Primary 58 | &.btn-primary { 59 | background: $primary-color; 60 | border-color: $primary-color-dark; 61 | box-shadow: 0 .1rem 1rem rgba($primary-color, .25); 62 | color: $light-color; 63 | &:visited { 64 | color: $light-color; 65 | } 66 | &:focus, 67 | &:hover { 68 | background: darken($primary-color-dark, 2%); 69 | border-color: darken($primary-color-dark, 5%); 70 | color: $light-color; 71 | } 72 | &:active, 73 | &.active { 74 | background: darken($primary-color-dark, 4%); 75 | border-color: darken($primary-color-dark, 7%); 76 | color: $light-color; 77 | } 78 | &.loading { 79 | &::after { 80 | border-bottom-color: $light-color; 81 | border-left-color: $light-color; 82 | } 83 | } 84 | } 85 | 86 | // Button Gold 87 | &.btn-gold { 88 | background: darken($gold-color, 55%); 89 | border-color: darken($gold-color, 58%); 90 | box-shadow: 0 .2rem 1rem rgba(darken($gold-color, 55%), .5); 91 | color: lighten($gold-color, 15%); 92 | &:visited { 93 | color: lighten($gold-color, 15%); 94 | } 95 | &:focus, 96 | &:hover, 97 | &:active, 98 | &.active { 99 | background: darken($gold-color, 58%); 100 | border-color: darken($gold-color, 61%); 101 | color: lighten($gold-color, 15%); 102 | } 103 | } 104 | 105 | // Button Link 106 | &.btn-link { 107 | background: transparent; 108 | border-color: transparent; 109 | box-shadow: none; 110 | color: $link-color; 111 | &:visited, 112 | &:focus, 113 | &:hover, 114 | &:active, 115 | &.active { 116 | color: $link-color-dark; 117 | } 118 | } 119 | 120 | // Button Sizes 121 | &.btn-sm { 122 | font-size: $font-size-sm; 123 | height: $control-size-sm; 124 | padding: $control-padding-y-sm $control-padding-x-sm; 125 | } 126 | 127 | &.btn-lg { 128 | height: $control-size-lg; 129 | padding: $control-padding-y-lg $control-padding-x-lg; 130 | } 131 | 132 | // Button Block 133 | &.btn-block { 134 | display: block; 135 | width: 100%; 136 | } 137 | 138 | // Button Block 139 | &.btn-rounded { 140 | border-radius: 10rem; 141 | } 142 | 143 | // Button Action 144 | &.btn-action { 145 | width: $control-size; 146 | padding-left: 0; 147 | padding-right: 0; 148 | 149 | &.btn-sm { 150 | width: $control-size-sm; 151 | } 152 | 153 | &.btn-lg { 154 | width: $control-size-lg; 155 | } 156 | } 157 | 158 | // Button Clear 159 | &.btn-clear { 160 | background: transparent; 161 | border: 0; 162 | color: currentColor; 163 | height: $unit-5; 164 | line-height: $unit-4; 165 | margin-left: $unit-1; 166 | margin-right: -2px; 167 | opacity: 1; 168 | padding: $unit-h; 169 | text-decoration: none; 170 | width: $unit-5; 171 | 172 | &:focus, 173 | &:hover { 174 | background: rgba($bg-color, .5); 175 | opacity: .95; 176 | } 177 | 178 | &::before { 179 | content: "\2715"; 180 | } 181 | } 182 | } 183 | 184 | // Button groups 185 | .btn-group { 186 | display: inline-flex; 187 | flex-wrap: wrap; 188 | 189 | .btn { 190 | flex: 1 0 auto; 191 | &:first-child:not(:last-child) { 192 | border-bottom-right-radius: 0; 193 | border-top-right-radius: 0; 194 | } 195 | &:not(:first-child):not(:last-child) { 196 | border-radius: 0; 197 | margin-left: -$border-width; 198 | } 199 | &:last-child:not(:first-child) { 200 | border-bottom-left-radius: 0; 201 | border-top-left-radius: 0; 202 | margin-left: -$border-width; 203 | } 204 | &:focus, 205 | &:hover, 206 | &:active, 207 | &.active { 208 | z-index: $zindex-0; 209 | } 210 | } 211 | 212 | &.btn-group-block { 213 | display: flex; 214 | 215 | .btn { 216 | flex: 1 0 0; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/scss/_calendars.scss: -------------------------------------------------------------------------------- 1 | // Calendars 2 | .calendar { 3 | border: $border-width solid $border-color; 4 | border-radius: $border-radius; 5 | display: block; 6 | min-width: 280px; 7 | 8 | .calendar-nav { 9 | align-items: center; 10 | background: $bg-color; 11 | border-top-left-radius: $border-radius; 12 | border-top-right-radius: $border-radius; 13 | display: flex; 14 | font-size: $font-size-lg; 15 | padding: $layout-spacing; 16 | } 17 | 18 | .calendar-header, 19 | .calendar-body { 20 | display: flex; 21 | flex-wrap: wrap; 22 | justify-content: center; 23 | padding: $layout-spacing 0; 24 | 25 | .calendar-date { 26 | flex: 0 0 14.28%; // 7 calendar-items each row 27 | max-width: 14.28%; 28 | } 29 | } 30 | 31 | .calendar-header { 32 | background: $bg-color; 33 | border-bottom: $border-width solid $border-color; 34 | color: $gray-color; 35 | font-size: $font-size-sm; 36 | text-align: center; 37 | } 38 | 39 | .calendar-body { 40 | color: $gray-color-dark; 41 | } 42 | 43 | .calendar-date { 44 | border: 0; 45 | padding: $unit-1; 46 | 47 | .date-item { 48 | appearance: none; 49 | background: transparent; 50 | border: $border-width solid transparent; 51 | border-radius: 50%; 52 | color: $gray-color-dark; 53 | cursor: pointer; 54 | font-size: $font-size-sm; 55 | height: $unit-7; 56 | line-height: $unit-5; 57 | outline: none; 58 | padding: $unit-h; 59 | position: relative; 60 | text-align: center; 61 | text-decoration: none; 62 | transition: background .2s, border .2s, box-shadow .2s, color .2s; 63 | vertical-align: middle; 64 | white-space: nowrap; 65 | width: $unit-7; 66 | 67 | &.date-today { 68 | border-color: $secondary-color-dark; 69 | color: $primary-color; 70 | } 71 | 72 | &:focus { 73 | @include control-shadow(); 74 | } 75 | 76 | &:focus, 77 | &:hover { 78 | background: $secondary-color-light; 79 | border-color: $secondary-color-dark; 80 | color: $primary-color; 81 | text-decoration: none; 82 | } 83 | &:active, 84 | &.active { 85 | background: $primary-color-dark; 86 | border-color: darken($primary-color-dark, 5%); 87 | color: $light-color; 88 | } 89 | 90 | // Calendar badge support 91 | &.badge { 92 | &::after { 93 | position: absolute; 94 | top: 3px; 95 | right: 3px; 96 | transform: translate(50%, -50%); 97 | } 98 | } 99 | } 100 | 101 | .date-item, 102 | .calendar-event { 103 | &:disabled, 104 | &.disabled { 105 | cursor: default; 106 | opacity: .25; 107 | pointer-events: none; 108 | } 109 | } 110 | 111 | &.prev-month, 112 | &.next-month { 113 | .date-item, 114 | .calendar-event { 115 | opacity: .25; 116 | } 117 | } 118 | } 119 | 120 | .calendar-range { 121 | position: relative; 122 | 123 | &::before { 124 | background: $secondary-color; 125 | content: ""; 126 | height: $unit-7; 127 | left: 0; 128 | position: absolute; 129 | right: 0; 130 | top: 50%; 131 | transform: translateY(-50%); 132 | } 133 | &.range-start { 134 | &::before { 135 | left: 50%; 136 | } 137 | } 138 | &.range-end { 139 | &::before { 140 | right: 50%; 141 | } 142 | } 143 | 144 | &.range-start, 145 | &.range-end { 146 | .date-item { 147 | background: $primary-color-dark; 148 | border-color: darken($primary-color-dark, 5%); 149 | color: $light-color; 150 | } 151 | } 152 | 153 | .date-item { 154 | color: $primary-color; 155 | } 156 | } 157 | 158 | // Calendars size 159 | &.calendar-lg { 160 | .calendar-body { 161 | padding: 0; 162 | 163 | .calendar-date { 164 | border-bottom: $border-width solid $border-color; 165 | border-right: $border-width solid $border-color; 166 | display: flex; 167 | flex-direction: column; 168 | height: 5.5rem; 169 | padding: 0; 170 | 171 | &:nth-child(7n) { 172 | border-right: 0; 173 | } 174 | &:nth-last-child(-n+7) { 175 | border-bottom: 0; 176 | } 177 | } 178 | } 179 | 180 | .date-item { 181 | align-self: flex-end; 182 | height: $unit-7; 183 | margin-right: $layout-spacing-sm; 184 | margin-top: $layout-spacing-sm; 185 | } 186 | 187 | .calendar-range { 188 | &::before { 189 | top: 19px; 190 | } 191 | &.range-start { 192 | &::before { 193 | left: auto; 194 | width: 19px; 195 | } 196 | } 197 | &.range-end { 198 | &::before { 199 | right: 19px; 200 | } 201 | } 202 | } 203 | 204 | .calendar-events { 205 | flex-grow: 1; 206 | line-height: 1; 207 | overflow-y: auto; 208 | padding: $layout-spacing-sm; 209 | } 210 | 211 | .calendar-event { 212 | border-radius: $border-radius; 213 | font-size: $font-size-sm; 214 | display: block; 215 | margin: $unit-h auto; 216 | overflow: hidden; 217 | padding: 3px 4px; 218 | text-overflow: ellipsis; 219 | white-space: nowrap; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import React, { Component } from 'react'; 3 | import Drops from './Drops'; 4 | import nearlogo from './assets/near-logo.svg'; 5 | import iconUser from './assets/img/icon-account.svg'; 6 | 7 | class App extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | login: false, 12 | currentUser: window.currentUser, 13 | } 14 | this.signedInFlow = this.signedInFlow.bind(this); 15 | this.requestSignIn = this.requestSignIn.bind(this); 16 | this.requestSignOut = this.requestSignOut.bind(this); 17 | this.signedOutFlow = this.signedOutFlow.bind(this); 18 | this.updateUser = this.updateUser.bind(this); 19 | } 20 | 21 | async componentDidMount() { 22 | let loggedIn = this.props.wallet.isSignedIn(); 23 | if (loggedIn) { 24 | this.signedInFlow(); 25 | } else { 26 | this.signedOutFlow(); 27 | } 28 | this.setState({ currentUser: window.currentUser }) 29 | } 30 | 31 | async signedInFlow() { 32 | this.setState({ 33 | login: true, 34 | }) 35 | const accountId = await this.props.wallet.getAccountId() 36 | if (window.location.search.includes("account_id")) { 37 | window.location.replace(window.location.origin + window.location.pathname) 38 | } 39 | } 40 | 41 | async requestSignIn() { 42 | const appTitle = 'NEAR Redpacket'; 43 | await this.props.wallet.requestSignIn( 44 | window.nearConfig.contractName, 45 | appTitle 46 | ) 47 | } 48 | 49 | async updateUser() { 50 | await window.getCurrentUser() 51 | this.setState({ currentUser: window.currentUser }) 52 | } 53 | 54 | requestSignOut() { 55 | this.props.wallet.signOut(); 56 | setTimeout(this.signedOutFlow, 500); 57 | console.log("after sign out", this.props.wallet.isSignedIn()) 58 | } 59 | 60 | signedOutFlow() { 61 | if (window.location.search.includes("account_id")) { 62 | window.location.replace(window.location.origin + window.location.pathname) 63 | } 64 | this.setState({ 65 | login: false, 66 | currentUser: null, 67 | }) 68 | } 69 | 70 | /******************************** 71 | Download keypair 72 | ********************************/ 73 | downloadFile(fileName, data, type='text/plain') { 74 | const a = document.createElement('a') 75 | a.style.display = 'none' 76 | document.body.appendChild(a) 77 | a.href = window.URL.createObjectURL(new Blob([data], { type })) 78 | a.setAttribute("download", fileName) 79 | a.click() 80 | window.URL.revokeObjectURL(a.href) 81 | document.body.removeChild(a) 82 | } 83 | 84 | downloadJson() { 85 | try { 86 | const accountId = currentUser.account_id 87 | const dropStorageKey = '__drops_' + accountId 88 | const data = localStorage.getItem(dropStorageKey) || '[]' 89 | this.downloadFile(accountId + '.json', data) 90 | } catch(e) { 91 | console.warn(e) 92 | } 93 | } 94 | 95 | render() { 96 | const { 97 | state, 98 | updateUser 99 | } = this 100 | const { 101 | currentUser 102 | } = state 103 | 104 | console.log(state) 105 | 106 | return ( 107 |
108 |
109 |
110 |
111 | NEAR logo 112 |
113 |
114 | {this.state.login ? 115 |
116 |
117 | NEAR user 118 | {currentUser.account_id} 119 |
120 | 143 |
144 | : <> 145 | } 146 |
147 |
148 |
149 |
150 | { currentUser ? 151 | 152 | : 153 |
154 |
🧧
155 |

NEAR Redpackets

156 |

Login and Send NEAR Redpackets.

157 |
158 | 164 |
165 |
166 | } 167 |
168 |
169 |
170 |
171 | ) 172 | } 173 | 174 | } 175 | 176 | export default App; 177 | -------------------------------------------------------------------------------- /src/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | // Layout 2 | .container { 3 | margin-left: auto; 4 | margin-right: auto; 5 | padding-left: $layout-spacing; 6 | padding-right: $layout-spacing; 7 | width: 100%; 8 | 9 | $grid-spacing: ($layout-spacing / ($layout-spacing * 0 + 1)) * $html-font-size; 10 | 11 | &.grid-xl { 12 | max-width: $grid-spacing * 2 + $size-xl; 13 | } 14 | 15 | &.grid-lg { 16 | max-width: $grid-spacing * 2 + $size-lg; 17 | } 18 | 19 | &.grid-md { 20 | max-width: $grid-spacing * 2 + $size-md; 21 | } 22 | 23 | &.grid-sm { 24 | max-width: $grid-spacing * 2 + $size-sm; 25 | } 26 | 27 | &.grid-xs { 28 | max-width: $grid-spacing * 2 + $size-xs; 29 | } 30 | } 31 | 32 | // Responsive breakpoint system 33 | .show-xs, 34 | .show-sm, 35 | .show-md, 36 | .show-lg, 37 | .show-xl { 38 | display: none !important; 39 | } 40 | 41 | // Responsive grid system 42 | .columns { 43 | display: flex; 44 | flex-wrap: wrap; 45 | margin-left: -$layout-spacing; 46 | margin-right: -$layout-spacing; 47 | 48 | &.col-gapless { 49 | margin-left: 0; 50 | margin-right: 0; 51 | 52 | & > .column { 53 | padding-left: 0; 54 | padding-right: 0; 55 | } 56 | } 57 | &.col-oneline { 58 | flex-wrap: nowrap; 59 | overflow-x: auto; 60 | } 61 | } 62 | .column { 63 | flex: 1; 64 | max-width: 100%; 65 | padding-left: $layout-spacing; 66 | padding-right: $layout-spacing; 67 | 68 | &.col-12, 69 | &.col-11, 70 | &.col-10, 71 | &.col-9, 72 | &.col-8, 73 | &.col-7, 74 | &.col-6, 75 | &.col-5, 76 | &.col-4, 77 | &.col-3, 78 | &.col-2, 79 | &.col-1, 80 | &.col-auto { 81 | flex: none; 82 | } 83 | } 84 | .col-12 { 85 | width: 100%; 86 | } 87 | .col-11 { 88 | width: 91.66666667%; 89 | } 90 | .col-10 { 91 | width: 83.33333333%; 92 | } 93 | .col-9 { 94 | width: 75%; 95 | } 96 | .col-8 { 97 | width: 66.66666667%; 98 | } 99 | .col-7 { 100 | width: 58.33333333%; 101 | } 102 | .col-6 { 103 | width: 50%; 104 | } 105 | .col-5 { 106 | width: 41.66666667%; 107 | } 108 | .col-4 { 109 | width: 33.33333333%; 110 | } 111 | .col-3 { 112 | width: 25%; 113 | } 114 | .col-2 { 115 | width: 16.66666667%; 116 | } 117 | .col-1 { 118 | width: 8.33333333%; 119 | } 120 | .col-auto { 121 | flex: 0 0 auto; 122 | max-width: none; 123 | width: auto; 124 | } 125 | .col-mx-auto { 126 | margin-left: auto; 127 | margin-right: auto; 128 | } 129 | .col-ml-auto { 130 | margin-left: auto; 131 | } 132 | .col-mr-auto { 133 | margin-right: auto; 134 | } 135 | @media (max-width: $size-xl) { 136 | .col-xl-12, 137 | .col-xl-11, 138 | .col-xl-10, 139 | .col-xl-9, 140 | .col-xl-8, 141 | .col-xl-7, 142 | .col-xl-6, 143 | .col-xl-5, 144 | .col-xl-4, 145 | .col-xl-3, 146 | .col-xl-2, 147 | .col-xl-1, 148 | .col-xl-auto { 149 | flex: none; 150 | } 151 | .col-xl-12 { 152 | width: 100%; 153 | } 154 | .col-xl-11 { 155 | width: 91.66666667%; 156 | } 157 | .col-xl-10 { 158 | width: 83.33333333%; 159 | } 160 | .col-xl-9 { 161 | width: 75%; 162 | } 163 | .col-xl-8 { 164 | width: 66.66666667%; 165 | } 166 | .col-xl-7 { 167 | width: 58.33333333%; 168 | } 169 | .col-xl-6 { 170 | width: 50%; 171 | } 172 | .col-xl-5 { 173 | width: 41.66666667%; 174 | } 175 | .col-xl-4 { 176 | width: 33.33333333%; 177 | } 178 | .col-xl-3 { 179 | width: 25%; 180 | } 181 | .col-xl-2 { 182 | width: 16.66666667%; 183 | } 184 | .col-xl-1 { 185 | width: 8.33333333%; 186 | } 187 | .col-xl-auto { 188 | width: auto; 189 | } 190 | .hide-xl { 191 | display: none !important; 192 | } 193 | .show-xl { 194 | display: block !important; 195 | } 196 | } 197 | @media (max-width: $size-lg) { 198 | .col-lg-12, 199 | .col-lg-11, 200 | .col-lg-10, 201 | .col-lg-9, 202 | .col-lg-8, 203 | .col-lg-7, 204 | .col-lg-6, 205 | .col-lg-5, 206 | .col-lg-4, 207 | .col-lg-3, 208 | .col-lg-2, 209 | .col-lg-1, 210 | .col-lg-auto { 211 | flex: none; 212 | } 213 | .col-lg-12 { 214 | width: 100%; 215 | } 216 | .col-lg-11 { 217 | width: 91.66666667%; 218 | } 219 | .col-lg-10 { 220 | width: 83.33333333%; 221 | } 222 | .col-lg-9 { 223 | width: 75%; 224 | } 225 | .col-lg-8 { 226 | width: 66.66666667%; 227 | } 228 | .col-lg-7 { 229 | width: 58.33333333%; 230 | } 231 | .col-lg-6 { 232 | width: 50%; 233 | } 234 | .col-lg-5 { 235 | width: 41.66666667%; 236 | } 237 | .col-lg-4 { 238 | width: 33.33333333%; 239 | } 240 | .col-lg-3 { 241 | width: 25%; 242 | } 243 | .col-lg-2 { 244 | width: 16.66666667%; 245 | } 246 | .col-lg-1 { 247 | width: 8.33333333%; 248 | } 249 | .col-lg-auto { 250 | width: auto; 251 | } 252 | .hide-lg { 253 | display: none !important; 254 | } 255 | .show-lg { 256 | display: block !important; 257 | } 258 | } 259 | @media (max-width: $size-md) { 260 | .col-md-12, 261 | .col-md-11, 262 | .col-md-10, 263 | .col-md-9, 264 | .col-md-8, 265 | .col-md-7, 266 | .col-md-6, 267 | .col-md-5, 268 | .col-md-4, 269 | .col-md-3, 270 | .col-md-2, 271 | .col-md-1, 272 | .col-md-auto { 273 | flex: none; 274 | } 275 | .col-md-12 { 276 | width: 100%; 277 | } 278 | .col-md-11 { 279 | width: 91.66666667%; 280 | } 281 | .col-md-10 { 282 | width: 83.33333333%; 283 | } 284 | .col-md-9 { 285 | width: 75%; 286 | } 287 | .col-md-8 { 288 | width: 66.66666667%; 289 | } 290 | .col-md-7 { 291 | width: 58.33333333%; 292 | } 293 | .col-md-6 { 294 | width: 50%; 295 | } 296 | .col-md-5 { 297 | width: 41.66666667%; 298 | } 299 | .col-md-4 { 300 | width: 33.33333333%; 301 | } 302 | .col-md-3 { 303 | width: 25%; 304 | } 305 | .col-md-2 { 306 | width: 16.66666667%; 307 | } 308 | .col-md-1 { 309 | width: 8.33333333%; 310 | } 311 | .col-md-auto { 312 | width: auto; 313 | } 314 | .hide-md { 315 | display: none !important; 316 | } 317 | .show-md { 318 | display: block !important; 319 | } 320 | } 321 | @media (max-width: $size-sm) { 322 | .col-sm-12, 323 | .col-sm-11, 324 | .col-sm-10, 325 | .col-sm-9, 326 | .col-sm-8, 327 | .col-sm-7, 328 | .col-sm-6, 329 | .col-sm-5, 330 | .col-sm-4, 331 | .col-sm-3, 332 | .col-sm-2, 333 | .col-sm-1, 334 | .col-sm-auto { 335 | flex: none; 336 | } 337 | .col-sm-12 { 338 | width: 100%; 339 | } 340 | .col-sm-11 { 341 | width: 91.66666667%; 342 | } 343 | .col-sm-10 { 344 | width: 83.33333333%; 345 | } 346 | .col-sm-9 { 347 | width: 75%; 348 | } 349 | .col-sm-8 { 350 | width: 66.66666667%; 351 | } 352 | .col-sm-7 { 353 | width: 58.33333333%; 354 | } 355 | .col-sm-6 { 356 | width: 50%; 357 | } 358 | .col-sm-5 { 359 | width: 41.66666667%; 360 | } 361 | .col-sm-4 { 362 | width: 33.33333333%; 363 | } 364 | .col-sm-3 { 365 | width: 25%; 366 | } 367 | .col-sm-2 { 368 | width: 16.66666667%; 369 | } 370 | .col-sm-1 { 371 | width: 8.33333333%; 372 | } 373 | .col-sm-auto { 374 | width: auto; 375 | } 376 | .hide-sm { 377 | display: none !important; 378 | } 379 | .show-sm { 380 | display: block !important; 381 | } 382 | } 383 | @media (max-width: $size-xs) { 384 | .col-xs-12, 385 | .col-xs-11, 386 | .col-xs-10, 387 | .col-xs-9, 388 | .col-xs-8, 389 | .col-xs-7, 390 | .col-xs-6, 391 | .col-xs-5, 392 | .col-xs-4, 393 | .col-xs-3, 394 | .col-xs-2, 395 | .col-xs-1, 396 | .col-xs-auto { 397 | flex: none; 398 | } 399 | .col-xs-12 { 400 | width: 100%; 401 | } 402 | .col-xs-11 { 403 | width: 91.66666667%; 404 | } 405 | .col-xs-10 { 406 | width: 83.33333333%; 407 | } 408 | .col-xs-9 { 409 | width: 75%; 410 | } 411 | .col-xs-8 { 412 | width: 66.66666667%; 413 | } 414 | .col-xs-7 { 415 | width: 58.33333333%; 416 | } 417 | .col-xs-6 { 418 | width: 50%; 419 | } 420 | .col-xs-5 { 421 | width: 41.66666667%; 422 | } 423 | .col-xs-4 { 424 | width: 33.33333333%; 425 | } 426 | .col-xs-3 { 427 | width: 25%; 428 | } 429 | .col-xs-2 { 430 | width: 16.66666667%; 431 | } 432 | .col-xs-1 { 433 | width: 8.33333333%; 434 | } 435 | .col-xs-auto { 436 | width: auto; 437 | } 438 | .hide-xs { 439 | display: none !important; 440 | } 441 | .show-xs { 442 | display: block !important; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/Claim.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import React, { Component } from 'react'; 3 | import * as nearApi from 'near-api-js'; 4 | import { KeyPair } from 'near-api-js'; 5 | import detectBrowserLanguage from 'detect-browser-language'; 6 | import { 7 | nearTo, nearToInt, toNear, BOATLOAD_OF_GAS, DROP_GAS, NETWORK_ID, ACCESS_KEY_ALLOWANCE 8 | } from './util/near-util'; 9 | import nearcover from './assets/img/redpacket-cover.svg'; 10 | 11 | class Claim extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | status: true, 16 | secretkey: null, 17 | walletClaimUrl: '', 18 | login: false, 19 | currentUser: window.currentUser, 20 | lang: '', 21 | amount: 0, 22 | } 23 | this.signedInFlow = this.signedInFlow.bind(this); 24 | this.requestSignIn = this.requestSignIn.bind(this); 25 | this.requestSignOut = this.requestSignOut.bind(this); 26 | this.signedOutFlow = this.signedOutFlow.bind(this); 27 | this.updateUser = this.updateUser.bind(this); 28 | this.claimDrop = this.claimDrop.bind(this); 29 | } 30 | 31 | async componentDidMount() { 32 | let loggedIn = this.props.wallet.isSignedIn(); 33 | if (loggedIn) { 34 | this.signedInFlow(); 35 | } else { 36 | this.signedOutFlow(); 37 | } 38 | this.setState({ currentUser: window.currentUser }) 39 | 40 | // Detect browser lang 41 | this.setState({ lang: detectBrowserLanguage() }) 42 | 43 | const { 44 | contractName, 45 | walletUrl 46 | } = window.nearConfig 47 | 48 | if (this.props.match.params) { 49 | const secretkey = this.props.match.params.key 50 | let status = true, 51 | amount = 0, 52 | walletClaimUrl = '' 53 | try { 54 | walletClaimUrl = await this.getWalletLink(secretkey) 55 | amount = await this.checkNearDropBalance(contractName, secretkey) 56 | this.setState({ status, secretkey, walletClaimUrl, amount }) 57 | console.log(status, contractName, secretkey, walletClaimUrl, amount) 58 | } catch (err) { 59 | status = false 60 | this.setState({ status }) 61 | console.log(err) 62 | } 63 | } 64 | 65 | // Show toast for WeChat users 66 | const userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase(); 67 | if (/micromessenger|qq/i.test(userAgent)) { 68 | let ele = document.getElementsByClassName("toast-wechat") 69 | console.log(ele) 70 | ele[0].style.display="block" 71 | } 72 | } 73 | 74 | async signedInFlow() { 75 | this.setState({ 76 | login: true, 77 | }) 78 | const accountId = await this.props.wallet.getAccountId() 79 | if (window.location.search.includes("account_id")) { 80 | window.location.replace(window.location.origin + window.location.pathname) 81 | } 82 | } 83 | 84 | async requestSignIn() { 85 | const appTitle = 'NEAR Redpacket'; 86 | await this.props.wallet.requestSignIn( 87 | window.nearConfig.contractName, 88 | appTitle 89 | ) 90 | } 91 | 92 | async updateUser() { 93 | await window.getCurrentUser() 94 | this.setState({ currentUser: window.currentUser }) 95 | } 96 | 97 | requestSignOut() { 98 | this.props.wallet.signOut(); 99 | setTimeout(this.signedOutFlow, 500); 100 | console.log("after sign out", this.props.wallet.isSignedIn()) 101 | } 102 | 103 | signedOutFlow() { 104 | if (window.location.search.includes("account_id")) { 105 | window.location.replace(window.location.origin + window.location.pathname) 106 | } 107 | this.setState({ 108 | login: false, 109 | currentUser: null, 110 | }) 111 | } 112 | 113 | async checkNearDropBalance(fundingContract, fundingKey) { 114 | const account = this.getAccount(fundingContract) 115 | 116 | const contract = new nearApi.Contract(account, fundingContract, { 117 | viewMethods: ['get_key_balance'], 118 | sender: fundingContract 119 | }); 120 | 121 | const key = KeyPair.fromString(fundingKey).publicKey.toString() 122 | return await contract.get_key_balance({ key }) 123 | } 124 | 125 | async getWalletLink(secretkey) { 126 | return `${window.nearConfig.walletUrl}/linkdrop/${window.nearConfig.contractName}/${secretkey}` 127 | } 128 | 129 | async getContract(viewMethods = [], changeMethods = [], secretKey) { 130 | if (secretKey) { 131 | await window.keyStore.setKey( 132 | NETWORK_ID, window.nearConfig.contractName, 133 | nearApi.KeyPair.fromString(secretKey) 134 | ) 135 | } 136 | const contract = new nearApi.Contract(window.contractAccount, window.nearConfig.contractName, { 137 | viewMethods, 138 | changeMethods, 139 | sender: window.nearConfig.contractName 140 | }) 141 | return contract 142 | } 143 | 144 | async claimDrop(secretkey) { 145 | const account_id = currentUser.account_id 146 | 147 | if (!window.confirm(`Claim NEAR to ${account_id}.\n\nDo you want to continue?`)) { 148 | return 149 | } 150 | const contract = await this.getContract([], ['claim'], secretkey) 151 | // return funds to current user 152 | await contract.claim({ account_id }, BOATLOAD_OF_GAS) 153 | .then(() => { 154 | window.alert('Drop claimed') 155 | }) 156 | .catch((e) => { 157 | console.log(e) 158 | alert('Unable to claim drop. The drop may have already been claimed.') 159 | }) 160 | this.updateUser() 161 | window.location.reload() 162 | } 163 | 164 | getAccount(accountId) { 165 | return new nearApi.Account(window.near.connection, accountId) 166 | } 167 | 168 | render() { 169 | const { status, secretkey, walletClaimUrl, login, amount, lang } = this.state; 170 | 171 | return ( 172 |
173 |
174 |
175 | 176 | 177 |
178 |
179 |
180 |
{lang == "zh-CN" ? "感谢您的关注" : "Thank You" }
181 |
{lang == "zh-CN" ? "来自 NEAR 团队的祝福" : "FROM NEAR Protocol" }
182 |
183 |
184 | 185 |
186 |
{lang == "zh-CN" ? "感谢您的关注" : "Thank You" }
187 |
188 |
{lang == "zh-CN" ? "金额" : "AMOUNT" }
189 | { status ? 190 |
{nearTo(amount, 2)}
191 | : 192 |
{lang == "zh-CN" ? "已领取" : "ALREADY CLAIMED" }
193 | } 194 |
195 |
196 | { status && walletClaimUrl ? 197 | <> 198 | {login ? 199 | <> 200 | 203 | 207 | 208 | : 209 | <> 210 | {lang == "zh-CN" ? "注册并领取 NEAR" : "CLAIM NEAR" } 211 | 214 | 215 | } 216 | 217 | : 218 | {lang == "zh-CN" ? "无法领取 NEAR" : "Invalid Redpacket" } 219 | } 220 |
221 |
222 |
223 |
224 | 225 |
226 | 推荐在浏览器中打开领取 ↗ 227 |
228 |
229 | ) 230 | } 231 | 232 | } 233 | 234 | export default Claim; 235 | -------------------------------------------------------------------------------- /src/scss/_normalize.scss: -------------------------------------------------------------------------------- 1 | /* Manually forked from Normalize.css */ 2 | /* normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 3 | 4 | /** 5 | * 1. Change the default font family in all browsers (opinionated). 6 | * 2. Correct the line height in all browsers. 7 | * 3. Prevent adjustments of font size after orientation changes in 8 | * IE on Windows Phone and in iOS. 9 | */ 10 | 11 | /* Document 12 | ========================================================================== */ 13 | 14 | html { 15 | font-family: sans-serif; /* 1 */ 16 | -ms-text-size-adjust: 100%; /* 3 */ 17 | -webkit-text-size-adjust: 100%; /* 3 */ 18 | } 19 | 20 | /* Sections 21 | ========================================================================== */ 22 | 23 | /** 24 | * Remove the margin in all browsers (opinionated). 25 | */ 26 | 27 | body { 28 | margin: 0; 29 | } 30 | 31 | /** 32 | * Add the correct display in IE 9-. 33 | */ 34 | 35 | article, 36 | aside, 37 | footer, 38 | header, 39 | nav, 40 | section { 41 | display: block; 42 | } 43 | 44 | /** 45 | * Correct the font size and margin on `h1` elements within `section` and 46 | * `article` contexts in Chrome, Firefox, and Safari. 47 | */ 48 | 49 | h1 { 50 | font-size: 2em; 51 | margin: 0.67em 0; 52 | } 53 | 54 | /* Grouping content 55 | ========================================================================== */ 56 | 57 | /** 58 | * Add the correct display in IE 9-. 59 | * 1. Add the correct display in IE. 60 | */ 61 | 62 | figcaption, 63 | figure, 64 | main { /* 1 */ 65 | display: block; 66 | } 67 | 68 | /** 69 | * Add the correct margin in IE 8 (removed). 70 | */ 71 | 72 | /** 73 | * 1. Add the correct box sizing in Firefox. 74 | * 2. Show the overflow in Edge and IE. 75 | */ 76 | 77 | hr { 78 | box-sizing: content-box; /* 1 */ 79 | height: 0; /* 1 */ 80 | overflow: visible; /* 2 */ 81 | } 82 | 83 | /** 84 | * 1. Correct the inheritance and scaling of font size in all browsers. (removed) 85 | * 2. Correct the odd `em` font sizing in all browsers. 86 | */ 87 | 88 | /* Text-level semantics 89 | ========================================================================== */ 90 | 91 | /** 92 | * 1. Remove the gray background on active links in IE 10. 93 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 94 | */ 95 | 96 | a { 97 | background-color: transparent; /* 1 */ 98 | -webkit-text-decoration-skip: objects; /* 2 */ 99 | } 100 | 101 | /** 102 | * Remove the outline on focused links when they are also active or hovered 103 | * in all browsers (opinionated). 104 | */ 105 | 106 | a:active, 107 | a:hover { 108 | outline-width: 0; 109 | } 110 | 111 | /** 112 | * Modify default styling of address. 113 | */ 114 | 115 | address { 116 | font-style: normal; 117 | } 118 | 119 | /** 120 | * 1. Remove the bottom border in Firefox 39-. 121 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. (removed) 122 | */ 123 | 124 | /** 125 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 126 | */ 127 | 128 | b, 129 | strong { 130 | font-weight: inherit; 131 | } 132 | 133 | /** 134 | * Add the correct font weight in Chrome, Edge, and Safari. 135 | */ 136 | 137 | b, 138 | strong { 139 | font-weight: bolder; 140 | } 141 | 142 | /** 143 | * 1. Correct the inheritance and scaling of font size in all browsers. 144 | * 2. Correct the odd `em` font sizing in all browsers. 145 | */ 146 | 147 | code, 148 | kbd, 149 | pre, 150 | samp { 151 | font-family: $mono-font-family; /* 1 (changed) */ 152 | font-size: 1em; /* 2 */ 153 | } 154 | 155 | /** 156 | * Add the correct font style in Android 4.3-. 157 | */ 158 | 159 | dfn { 160 | font-style: italic; 161 | } 162 | 163 | /** 164 | * Add the correct background and color in IE 9-. (Removed) 165 | */ 166 | 167 | /** 168 | * Add the correct font size in all browsers. 169 | */ 170 | 171 | small { 172 | font-size: 80%; 173 | font-weight: 400; /* (added) */ 174 | } 175 | 176 | /** 177 | * Prevent `sub` and `sup` elements from affecting the line height in 178 | * all browsers. 179 | */ 180 | 181 | sub, 182 | sup { 183 | font-size: 75%; 184 | line-height: 0; 185 | position: relative; 186 | vertical-align: baseline; 187 | } 188 | 189 | sub { 190 | bottom: -0.25em; 191 | } 192 | 193 | sup { 194 | top: -0.5em; 195 | } 196 | 197 | /* Embedded content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Add the correct display in IE 9-. 202 | */ 203 | 204 | audio, 205 | video { 206 | display: inline-block; 207 | } 208 | 209 | /** 210 | * Add the correct display in iOS 4-7. 211 | */ 212 | 213 | audio:not([controls]) { 214 | display: none; 215 | height: 0; 216 | } 217 | 218 | /** 219 | * Remove the border on images inside links in IE 10-. 220 | */ 221 | 222 | img { 223 | border-style: none; 224 | } 225 | 226 | /** 227 | * Hide the overflow in IE. 228 | */ 229 | 230 | svg:not(:root) { 231 | overflow: hidden; 232 | } 233 | 234 | /* Forms 235 | ========================================================================== */ 236 | 237 | /** 238 | * 1. Change the font styles in all browsers (opinionated). 239 | * 2. Remove the margin in Firefox and Safari. 240 | */ 241 | 242 | button, 243 | input, 244 | optgroup, 245 | select, 246 | textarea { 247 | font-family: inherit; /* 1 (changed) */ 248 | font-size: inherit; /* 1 (changed) */ 249 | line-height: inherit; /* 1 (changed) */ 250 | margin: 0; /* 2 */ 251 | } 252 | 253 | /** 254 | * Show the overflow in IE. 255 | * 1. Show the overflow in Edge. 256 | */ 257 | 258 | button, 259 | input { /* 1 */ 260 | overflow: visible; 261 | } 262 | 263 | /** 264 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 265 | * 1. Remove the inheritance of text transform in Firefox. 266 | */ 267 | 268 | button, 269 | select { /* 1 */ 270 | text-transform: none; 271 | } 272 | 273 | /** 274 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 275 | * controls in Android 4. 276 | * 2. Correct the inability to style clickable types in iOS and Safari. 277 | */ 278 | 279 | button, 280 | html [type="button"], /* 1 */ 281 | [type="reset"], 282 | [type="submit"] { 283 | -webkit-appearance: button; /* 2 */ 284 | } 285 | 286 | /** 287 | * Remove the inner border and padding in Firefox. 288 | */ 289 | 290 | button::-moz-focus-inner, 291 | [type="button"]::-moz-focus-inner, 292 | [type="reset"]::-moz-focus-inner, 293 | [type="submit"]::-moz-focus-inner { 294 | border-style: none; 295 | padding: 0; 296 | } 297 | 298 | /** 299 | * Restore the focus styles unset by the previous rule (removed). 300 | */ 301 | 302 | 303 | /** 304 | * Change the border, margin, and padding in all browsers (opinionated) (changed). 305 | */ 306 | 307 | fieldset { 308 | border: 0; 309 | margin: 0; 310 | padding: 0; 311 | } 312 | 313 | /** 314 | * 1. Correct the text wrapping in Edge and IE. 315 | * 2. Correct the color inheritance from `fieldset` elements in IE. 316 | * 3. Remove the padding so developers are not caught out when they zero out 317 | * `fieldset` elements in all browsers. 318 | */ 319 | 320 | legend { 321 | box-sizing: border-box; /* 1 */ 322 | color: inherit; /* 2 */ 323 | display: table; /* 1 */ 324 | max-width: 100%; /* 1 */ 325 | padding: 0; /* 3 */ 326 | white-space: normal; /* 1 */ 327 | } 328 | 329 | /** 330 | * 1. Add the correct display in IE 9-. 331 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 332 | */ 333 | 334 | progress { 335 | display: inline-block; /* 1 */ 336 | vertical-align: baseline; /* 2 */ 337 | } 338 | 339 | /** 340 | * Remove the default vertical scrollbar in IE. 341 | */ 342 | 343 | textarea { 344 | overflow: auto; 345 | } 346 | 347 | /** 348 | * 1. Add the correct box sizing in IE 10-. 349 | * 2. Remove the padding in IE 10-. 350 | */ 351 | 352 | [type="checkbox"], 353 | [type="radio"] { 354 | box-sizing: border-box; /* 1 */ 355 | padding: 0; /* 2 */ 356 | } 357 | 358 | /** 359 | * Correct the cursor style of increment and decrement buttons in Chrome. 360 | */ 361 | 362 | [type="number"]::-webkit-inner-spin-button, 363 | [type="number"]::-webkit-outer-spin-button { 364 | height: auto; 365 | } 366 | 367 | /** 368 | * 1. Correct the odd appearance in Chrome and Safari. 369 | * 2. Correct the outline style in Safari. 370 | */ 371 | 372 | [type="search"] { 373 | -webkit-appearance: textfield; /* 1 */ 374 | outline-offset: -2px; /* 2 */ 375 | } 376 | 377 | /** 378 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 379 | */ 380 | 381 | [type="search"]::-webkit-search-cancel-button, 382 | [type="search"]::-webkit-search-decoration { 383 | -webkit-appearance: none; 384 | } 385 | 386 | /** 387 | * 1. Correct the inability to style clickable types in iOS and Safari. 388 | * 2. Change font properties to `inherit` in Safari. 389 | */ 390 | 391 | ::-webkit-file-upload-button { 392 | -webkit-appearance: button; /* 1 */ 393 | font: inherit; /* 2 */ 394 | } 395 | 396 | /* Interactive 397 | ========================================================================== */ 398 | 399 | /* 400 | * Add the correct display in IE 9-. 401 | * 1. Add the correct display in Edge, IE, and Firefox. 402 | */ 403 | 404 | details, /* 1 */ 405 | menu { 406 | display: block; 407 | } 408 | 409 | /* 410 | * Add the correct display in all browsers. 411 | */ 412 | 413 | summary { 414 | display: list-item; 415 | outline: none; 416 | } 417 | 418 | /* Scripting 419 | ========================================================================== */ 420 | 421 | /** 422 | * Add the correct display in IE 9-. 423 | */ 424 | 425 | canvas { 426 | display: inline-block; 427 | } 428 | 429 | /** 430 | * Add the correct display in IE. 431 | */ 432 | 433 | template { 434 | display: none; 435 | } 436 | 437 | /* Hidden 438 | ========================================================================== */ 439 | 440 | /** 441 | * Add the correct display in IE 10-. 442 | */ 443 | 444 | [hidden] { 445 | display: none; 446 | } 447 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/scss/near.scss: -------------------------------------------------------------------------------- 1 | // Variables and mixins 2 | @import "variables"; 3 | @import "mixins"; 4 | 5 | /*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */ 6 | // Reset and dependencies 7 | @import "normalize"; 8 | @import "base"; 9 | 10 | // Elements 11 | @import "typography"; 12 | @import "asian"; 13 | @import "tables"; 14 | @import "buttons"; 15 | @import "forms"; 16 | @import "labels"; 17 | @import "codes"; 18 | @import "media"; 19 | 20 | // Layout 21 | @import "layout"; 22 | @import "hero"; 23 | @import "navbar"; 24 | 25 | // Components 26 | @import "accordions"; 27 | @import "avatars"; 28 | @import "badges"; 29 | @import "breadcrumbs"; 30 | @import "bars"; 31 | @import "cards"; 32 | @import "chips"; 33 | @import "dropdowns"; 34 | @import "empty"; 35 | @import "markdown"; 36 | @import "menus"; 37 | @import "modals"; 38 | @import "navs"; 39 | @import "pagination"; 40 | @import "panels"; 41 | @import "popovers"; 42 | @import "steps"; 43 | @import "tabs"; 44 | @import "tiles"; 45 | @import "toasts"; 46 | @import "tooltips"; 47 | 48 | // Utility classes 49 | @import "animations"; 50 | @import "utilities"; 51 | 52 | /*! NEAR.css | MIT License */ 53 | body { 54 | background: $bg-color url('../img/dapp-bg.png'); 55 | overflow: hidden; 56 | } 57 | 58 | .near-container { 59 | align-items: center; 60 | display: flex; 61 | height: 100vh; 62 | overflow: hidden; 63 | padding: .5rem; 64 | width: 100vw; 65 | 66 | @media screen and (max-width: $size-md) { 67 | padding: 2rem 1.5rem; 68 | } 69 | 70 | .near-dapp { 71 | background: $light-color; 72 | border: .1rem solid rgba($dark-color, .1); 73 | border-radius: $border-radius; 74 | box-shadow: 0 .2rem 1rem rgba($dark-color, .05); 75 | display: flex; 76 | flex-direction: column; 77 | height: 100%; 78 | margin-left: auto; 79 | margin-right: auto; 80 | max-width: $size-xs; 81 | padding: .5rem 0; 82 | width: 100%; 83 | } 84 | 85 | .near-dapp-header { 86 | display: flex; 87 | height: 2.6rem; 88 | justify-content: space-between; 89 | padding: .5rem 1rem; 90 | 91 | .near-logo { 92 | height: 1.6rem; 93 | } 94 | } 95 | 96 | .near-user { 97 | .dropdown { 98 | height: 2rem; 99 | } 100 | 101 | .btn { 102 | max-width: 40vw; 103 | padding: 0 .5rem 0 0; 104 | 105 | &:focus, 106 | &:hover { 107 | background: $bg-color-dark; 108 | border-color: $border-color-light; 109 | color: $gray-color-dark; 110 | } 111 | } 112 | 113 | .btn-icon { 114 | background: $bg-color-light; 115 | border-radius: 50%; 116 | height: 1.6rem; 117 | margin: 0 .25rem 0 .05rem; 118 | padding: .3rem; 119 | width: 1.6rem; 120 | } 121 | } 122 | 123 | .near-dapp-body { 124 | height: 100%; 125 | overflow-y: auto; 126 | padding: .5rem 1rem; 127 | 128 | .near-balance { 129 | padding: .5rem; 130 | text-align: center; 131 | 132 | .near-balance-title { 133 | color: $gray-color-dark; 134 | font-size: .7rem; 135 | } 136 | 137 | .near-balance-funds { 138 | font-size: 2rem; 139 | font-weight: 900; 140 | line-height: 2rem; 141 | } 142 | 143 | .near-balance-actions { 144 | margin-top: 1rem; 145 | } 146 | } 147 | 148 | .near-tabs { 149 | display: flex; 150 | justify-content: center; 151 | padding: .5rem 0 1rem 0; 152 | } 153 | } 154 | 155 | .near-drop-item { 156 | border: .05 solid $bg-color; 157 | border-radius: .4rem; 158 | box-shadow: 0 .1rem 1rem rgba($dark-color, .05); 159 | margin-bottom: 1rem; 160 | overflow: hidden; 161 | padding: 1rem; 162 | position: relative; 163 | transition: box-shadow .2s; 164 | 165 | &:hover { 166 | box-shadow: 0 .4rem 1rem rgba($dark-color, .1); 167 | } 168 | 169 | .drop-item-funds { 170 | font-size: 1.6rem; 171 | font-weight: 900; 172 | line-height: 1.6rem; 173 | margin-bottom: .5rem; 174 | } 175 | 176 | .drop-item-pubkey { 177 | font-size: .6rem; 178 | margin-bottom: .5rem; 179 | } 180 | 181 | .drop-item-status { 182 | background: $secondary-color; 183 | color: $primary-color-light; 184 | font-size: .6rem; 185 | line-height: .8rem; 186 | padding: .2rem; 187 | position: absolute; 188 | right: -2.5rem; 189 | top: 1rem; 190 | text-align: center; 191 | text-transform: uppercase; 192 | transform: rotate(45deg); 193 | width: 8rem; 194 | } 195 | } 196 | 197 | .near-dapp-redpacket { 198 | background: darken($primary-color, 5%); 199 | border: .1rem solid darken($primary-color, 20%); 200 | box-shadow: 0 .1rem 1rem rgba($primary-color, .75); 201 | border-radius: .8rem; 202 | max-height: 35rem; 203 | max-width: 18.75rem; 204 | overflow: hidden; 205 | padding: 0; 206 | 207 | .near-redpacket-header { 208 | animation: re-slide-up 3s ease-in-out 1; 209 | background: linear-gradient(to bottom, darken($primary-color, 5%) 5%, darken($primary-color, 10%)); 210 | border-radius: .8rem .8rem 0 0; 211 | flex: 1 1 65vh; 212 | height: 65vh; 213 | position: relative; 214 | transform: translateY(-45vh); 215 | z-index: 999; 216 | 217 | &::after, 218 | &::before { 219 | border-color: darken($primary-color, 10%) transparent transparent transparent; 220 | border-radius: 20px; 221 | border-style: solid; 222 | border-width: 2rem 20rem 0 20rem; 223 | content: ''; 224 | display: block; 225 | height: 2rem; 226 | left: 50%; 227 | position: absolute; 228 | top: 99%; 229 | transform: translate(-50%, 0); 230 | width: 100%; 231 | z-index: 99; 232 | } 233 | 234 | &::before { 235 | border-top-color: darken($primary-color, 20%); 236 | transform: translate(-50%, .1rem); 237 | z-index: 99; 238 | } 239 | 240 | .redpacket-cover { 241 | display: block; 242 | height: 50vh; 243 | margin: 1rem auto; 244 | width: auto; 245 | } 246 | 247 | .redpacket-btn { 248 | animation: re-flip-btn 3s ease-in-out 1; 249 | align-items: center; 250 | background: linear-gradient(45deg, lighten($gold-color, 3%) 0, darken($gold-color-dark, 5%) 100%); 251 | border: .1rem solid rgba($dark-color, .1); 252 | border-radius: 50%; 253 | bottom: -4.5rem; 254 | box-shadow: 0 .2rem 1rem rgba($dark-color, .25); 255 | color: rgba($dark-color, .75); 256 | display: flex; 257 | font-size: 1.2rem; 258 | font-weight: 700; 259 | height: 5rem; 260 | justify-content: center; 261 | left: calc(50% - 2.5rem); 262 | opacity: 0; 263 | position: absolute; 264 | user-select: none; 265 | width: 5rem; 266 | z-index: 999; 267 | 268 | &::after { 269 | border: .1rem solid rgba($dark-color, .05); 270 | border-radius: 50%; 271 | content: ''; 272 | display: block; 273 | flex: 0 0 4rem; 274 | height: 4.4rem; 275 | left: .2rem; 276 | position: absolute; 277 | top: .2rem; 278 | width: 4.4rem; 279 | } 280 | } 281 | } 282 | 283 | .near-redpacket-body { 284 | background: darken($primary-color, 5%); 285 | height: 35vh; 286 | position: relative; 287 | 288 | .redpacket-content { 289 | bottom: 1.5rem; 290 | color: $gold-color-dark; 291 | display: flex; 292 | height: 100%; 293 | flex-direction: column; 294 | justify-content: flex-end; 295 | position: absolute; 296 | text-align: center; 297 | text-shadow: 0 .2rem .5rem rgba($dark-color, .15); 298 | width: 100%; 299 | 300 | .redpacket-content-title { 301 | font-size: 1.6rem; 302 | font-weight: 700; 303 | line-height: 1.8rem; 304 | margin-bottom: .5rem; 305 | } 306 | } 307 | 308 | .redpacket-card { 309 | animation: re-slide-down 3s ease-in-out 1; 310 | background: linear-gradient(to bottom, darken($gold-color-dark, 3%) 0, darken($gold-color-dark, 8%) 100%); 311 | border: .1rem solid rgba($dark-color, .15); 312 | border-radius: .6rem; 313 | bottom: 4rem; 314 | color: darken($gold-color, 55%); 315 | display: flex; 316 | flex-direction: column; 317 | height: 78vh; 318 | left: 50%; 319 | max-height: 30rem; 320 | padding: 6rem 1.5rem 2rem 1.5rem; 321 | position: absolute; 322 | text-align: center; 323 | text-shadow: 0 .2rem .5rem rgba($dark-color, .15); 324 | transform: translate(-50%, 1rem); 325 | width: 70%; 326 | z-index: 99; 327 | 328 | &::after { 329 | border: .1rem solid rgba($dark-color, .05); 330 | border-radius: .6rem; 331 | bottom: .3rem; 332 | content: ''; 333 | display: block; 334 | flex: 0 0 4rem; 335 | left: .3rem; 336 | position: absolute; 337 | right: .3rem; 338 | top: .3rem; 339 | } 340 | 341 | .redpacket-cover { 342 | display: block; 343 | height: 50vh; 344 | left: 50%; 345 | margin: 1rem auto; 346 | opacity: .5; 347 | position: absolute; 348 | top: 40%; 349 | transform: translate(-50%, -50%); 350 | width: auto; 351 | z-index: -1; 352 | } 353 | 354 | .redpacket-card-header { 355 | flex: 0 0 1rem; 356 | 357 | .h2 { 358 | font-size: 1.4rem; 359 | } 360 | } 361 | 362 | .redpacket-card-body { 363 | align-items: center; 364 | display: flex; 365 | flex: 1 1 auto; 366 | flex-direction: column; 367 | justify-content: center; 368 | 369 | .h1 { 370 | font-weight: 900; 371 | } 372 | } 373 | 374 | .redpacket-card-footer { 375 | align-items: center; 376 | display: flex; 377 | flex: 0 0 1rem; 378 | flex-direction: column; 379 | justify-content: center; 380 | margin-top: 2rem; 381 | 382 | .btn { 383 | position: relative; 384 | z-index: 999; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | } 391 | //- Toast 392 | .toast-wechat { 393 | background: darken($gold-color, 55%); 394 | border-radius: .2rem; 395 | box-shadow: 0 .2rem 2rem rgba(darken($gold-color, 55%), .75); 396 | color: lighten($gold-color, 15%); 397 | display: none; 398 | padding: .5rem 1rem; 399 | position: fixed; 400 | right: 1rem; 401 | top: .75rem; 402 | z-index: 9999; 403 | 404 | &::after { 405 | border-color: transparent transparent darken($gold-color, 55%) transparent; 406 | border-radius: $border-radius; 407 | border-style: solid; 408 | border-width: 0 .5rem .5rem .5rem; 409 | content: ''; 410 | position: absolute; 411 | right: 1rem; 412 | top: -.4rem; 413 | } 414 | } 415 | 416 | //- Animations 417 | @keyframes re-flip-btn { 418 | 0% { 419 | opacity: 1; 420 | transform: rotateY(0); 421 | } 422 | 75% { 423 | opacity: 1; 424 | } 425 | 100% { 426 | opacity: 0; 427 | transform: rotateY(1080deg); 428 | } 429 | } 430 | 431 | @keyframes re-slide-up { 432 | 0% { 433 | transform: translateY(0); 434 | } 435 | 75% { 436 | transform: translateY(0); 437 | } 438 | 100% { 439 | transform: translateY(-45vh); 440 | } 441 | } 442 | 443 | @keyframes re-slide-down { 444 | 0% { 445 | transform: translate(-50%, -100%); 446 | } 447 | 50% { 448 | transform: translate(-50%, -100%); 449 | } 450 | 100% { 451 | transform: translate(-50%, 1rem); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/Drops.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import React, { useState, useEffect } from 'react'; 3 | import * as nearApi from 'near-api-js'; 4 | import * as clipboard from "clipboard-polyfill/text"; 5 | import { 6 | nearTo, nearToInt, toNear, BOATLOAD_OF_GAS, DROP_GAS, NETWORK_ID, ACCESS_KEY_ALLOWANCE 7 | } from './util/near-util'; 8 | // import { howLongAgo } from './util/util'; 9 | 10 | const get = (k) => JSON.parse(localStorage.getItem(k) || '[]') 11 | const set = (k, v) => localStorage.setItem(k, JSON.stringify(v)) 12 | 13 | const Drops = (props) => { 14 | 15 | const { 16 | contractName, 17 | walletUrl 18 | } = window.nearConfig 19 | 20 | const { 21 | currentUser, currentUser: { account_id }, updateUser 22 | } = props 23 | 24 | // in case account_id is null or undefined 25 | let accountId = account_id 26 | if (!accountId || accountId.length === 0) { 27 | accountId = window.prompt('Your AccountId?') 28 | } 29 | 30 | const dropStorageKey = '__drops_' + accountId 31 | 32 | const [drops, setDrops] = useState([]) 33 | const [showUsed, setShowUsed] = useState(false) 34 | const [urlDrop, setUrlDrop] = useState() 35 | 36 | useEffect(() => { 37 | updateDrops(true) 38 | const url = new URL(window.location.href) 39 | const key = url.searchParams.get('key') 40 | const amount = url.searchParams.get('amount') 41 | const from = url.searchParams.get('from') 42 | const limited = url.searchParams.get('limited') === 'true' 43 | if (key && amount && from) { 44 | setUrlDrop({ key, amount, from, limited }) 45 | } 46 | }, []) 47 | /******************************** 48 | Update drops (idb + state), add drop, remove drop 49 | ********************************/ 50 | async function getDrop(public_key) { 51 | const drops = await get(dropStorageKey) || [] 52 | return drops.find((d) => d.public_key === public_key) 53 | } 54 | async function updateDrops(check = false) { 55 | const drops = (await get(dropStorageKey) || []) 56 | for (let drop of drops) { 57 | const { public_key: key } = drop 58 | drop.walletLink = await getWalletLink(key) 59 | if (!check) { 60 | continue 61 | } 62 | // check drop is valid 63 | const { contract } = window 64 | let res 65 | try { 66 | res = await contract.get_key_balance({ key }) 67 | } catch (e) { 68 | console.warn(e) 69 | if (e.message.indexOf('Key is missing') > -1) { 70 | await useDrop(key) 71 | } 72 | } 73 | } 74 | setDrops(drops) 75 | } 76 | async function useDrop(public_key) { 77 | const drops = await get(dropStorageKey) || [] 78 | const drop = drops.find((d) => d.public_key === public_key) 79 | drop.used = true 80 | await set(dropStorageKey, drops) 81 | updateDrops() 82 | } 83 | async function removeDrop(public_key) { 84 | const drops = await get(dropStorageKey) || [] 85 | drops.splice(drops.findIndex((d) => d.public_key === public_key), 1) 86 | await set(dropStorageKey, drops) 87 | updateDrops() 88 | } 89 | async function addDrop(newKeyPair) { 90 | const drops = await get(dropStorageKey) || [] 91 | drops.push(newKeyPair) 92 | await set(dropStorageKey, drops) 93 | updateDrops() 94 | } 95 | /******************************** 96 | Drop links 97 | ********************************/ 98 | async function getWalletLink(public_key) { 99 | const { secretKey } = await getDrop(public_key) 100 | return `${window.location.origin}/${secretKey}` 101 | } 102 | 103 | /******************************** 104 | Get Contract Helper 105 | ********************************/ 106 | async function getContract(viewMethods = [], changeMethods = [], secretKey) { 107 | if (secretKey) { 108 | await window.keyStore.setKey( 109 | NETWORK_ID, contractName, 110 | nearApi.KeyPair.fromString(secretKey) 111 | ) 112 | } 113 | const contract = new nearApi.Contract(window.contractAccount, contractName, { 114 | viewMethods, 115 | changeMethods, 116 | sender: contractName 117 | }) 118 | return contract 119 | } 120 | /******************************** 121 | Funding an open drop (claim, create account, create contract) with your currently logged in account 122 | ********************************/ 123 | async function fundDrop() { 124 | // get a drop amount from the user 125 | const amount = toNear(window.prompt('Amount to fund with in Near Ⓝ') || 0) 126 | // TODO: What is minimum allowance? Seems to not match what is in contract source? 127 | if (nearToInt(amount) <= 1) { 128 | window.alert('Amount too small for drop') 129 | return 130 | } 131 | // create a new drop keypair, add the amount to the object, store it 132 | const newKeyPair = nearApi.KeyPair.fromRandom('ed25519') 133 | const public_key = newKeyPair.public_key = newKeyPair.publicKey.toString().replace('ed25519:', '') 134 | 135 | // TODO: Make localstorage exportable 136 | // download keypair if user wants 137 | // const downloadKey = window.confirm('Download keypair before funding?') 138 | // if (downloadKey) { 139 | // const { secretKey, public_key: publicKey } = JSON.parse(JSON.stringify(newKeyPair)) 140 | // downloadFile(accountId + '.txt', JSON.stringify({ publicKey, secretKey })) 141 | // } 142 | 143 | newKeyPair.amount = amount 144 | newKeyPair.ts = Date.now() 145 | await addDrop(newKeyPair) 146 | // register the drop public key and send the amount to contract 147 | const { contract } = window 148 | try { 149 | await contract.send({ public_key }, DROP_GAS, amount) 150 | } catch(e) { 151 | console.warn(e) 152 | } 153 | } 154 | /******************************** 155 | Reclaim a drop / cancels the drop and claims to the current user 156 | ********************************/ 157 | async function reclaimDrop(public_key) { 158 | // get the drops from idb and find the one matching this public key 159 | const drops = await get(dropStorageKey) || [] 160 | const drop = drops.find((d) => d.public_key === public_key) 161 | if (!window.confirm(`Remove drop of ${nearTo(drop.amount, 2)} Ⓝ and transfer funds to\n${accountId}\nDo you want to continue?`)) { 162 | return 163 | } 164 | const contract = await getContract([], ['claim'], drop.secretKey) 165 | // return funds to current user 166 | await contract.claim({ account_id }, BOATLOAD_OF_GAS) 167 | .then(() => { 168 | window.alert('Drop claimed') 169 | }) 170 | .catch((e) => { 171 | console.log(e) 172 | alert('Unable to claim drop. The drop may have already been claimed.') 173 | }) 174 | useDrop(public_key) 175 | updateUser() 176 | } 177 | 178 | const activeDrops = drops.filter((d) => !d.used).sort((a, b) => b.ts - a.ts) 179 | const usedDrops = drops.filter((d) => d.used).sort((a, b) => b.ts - a.ts) 180 | 181 | console.log('ACTIVE DROPS', activeDrops) 182 | console.log('USED DROPS', usedDrops) 183 | 184 | return ( 185 |
186 |
187 |
Balance
188 |
{nearTo(currentUser.balance, 2)}
189 |
190 | 191 |
192 |
193 |
194 |
    195 |
  • setShowUsed(false)}>Active
  • 196 |
  • setShowUsed(true)}>Claimed
  • 197 |
198 |
199 | 200 | { 201 | urlDrop &&
202 |

URL Drop

203 |

{nearTo(urlDrop.amount, 2)} Ⓝ

204 |

From: {urlDrop.from}

205 | { urlDrop.limited ? 206 | 207 | : 208 | <> 209 | 210 | 211 | 212 | 213 | } 214 |
215 | } 216 | { !showUsed && 217 |
218 | { 219 | activeDrops.length > 0 ? 220 |
221 | { 222 | activeDrops.map(({ public_key, amount, ts, walletLink }) => 223 |
224 |
{nearTo(amount, 2)}
225 |
Active
226 |
Public Key: {public_key}
227 | 231 | 232 |
) 233 | } 234 |
235 | : 236 |
237 |
🧧
238 |

No Available Redpackets

239 |

Click the button to create a new NEAR redpacket.

240 |
241 | } 242 |
243 | } 244 | { showUsed && 245 | <> 246 | { 247 | usedDrops.length > 0 ? 248 |
249 | { 250 | usedDrops.map(({ public_key, amount, ts, walletLink }) => 251 |
252 |
{nearTo(amount, 2)}
253 |
Claimed
254 |
Public Key: {public_key}
255 | 259 | 260 |
) 261 | } 262 |
263 | : 264 |
265 |
🧧
266 |

No Claimed Redpackets

267 |

Click the button to create a new NEAR redpacket.

268 |
269 | } 270 | 271 | } 272 |
273 | ) 274 | } 275 | 276 | export default Drops; 277 | -------------------------------------------------------------------------------- /src/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | // Forms 2 | .form-group { 3 | &:not(:last-child) { 4 | margin-bottom: $layout-spacing; 5 | } 6 | } 7 | 8 | fieldset { 9 | margin-bottom: $layout-spacing-lg; 10 | } 11 | 12 | legend { 13 | font-size: $font-size-lg; 14 | font-weight: 500; 15 | margin-bottom: $layout-spacing-lg; 16 | } 17 | 18 | // Form element: Label 19 | .form-label { 20 | display: block; 21 | line-height: $line-height; 22 | padding: $control-padding-y + $border-width 0; 23 | 24 | &.label-sm { 25 | font-size: $font-size-sm; 26 | padding: $control-padding-y-sm + $border-width 0; 27 | } 28 | 29 | &.label-lg { 30 | font-size: $font-size-lg; 31 | padding: $control-padding-y-lg + $border-width 0; 32 | } 33 | } 34 | 35 | // Form element: Input 36 | .form-input { 37 | appearance: none; 38 | background: $bg-color-light; 39 | background-image: none; 40 | border: $border-width solid $border-color-dark; 41 | border-radius: $border-radius; 42 | color: $body-font-color; 43 | display: block; 44 | font-size: $font-size; 45 | height: $control-size; 46 | line-height: $line-height; 47 | max-width: 100%; 48 | outline: none; 49 | padding: $control-padding-y $control-padding-x; 50 | position: relative; 51 | transition: background .2s, border .2s, box-shadow .2s, color .2s; 52 | width: 100%; 53 | &:focus { 54 | @include control-shadow(); 55 | border-color: $primary-color; 56 | } 57 | &::placeholder { 58 | color: $gray-color; 59 | } 60 | 61 | // Input sizes 62 | &.input-sm { 63 | font-size: $font-size-sm; 64 | height: $control-size-sm; 65 | padding: $control-padding-y-sm $control-padding-x-sm; 66 | } 67 | 68 | &.input-lg { 69 | font-size: $font-size-lg; 70 | height: $control-size-lg; 71 | padding: $control-padding-y-lg $control-padding-x-lg; 72 | } 73 | 74 | &.input-inline { 75 | display: inline-block; 76 | vertical-align: middle; 77 | width: auto; 78 | } 79 | 80 | // Input types 81 | &[type="file"] { 82 | height: auto; 83 | } 84 | } 85 | 86 | // Form element: Textarea 87 | textarea.form-input { 88 | &, 89 | &.input-lg, 90 | &.input-sm { 91 | height: auto; 92 | } 93 | } 94 | 95 | // Form element: Input hint 96 | .form-input-hint { 97 | color: $gray-color; 98 | font-size: $font-size-sm; 99 | margin-top: $unit-1; 100 | 101 | .has-success &, 102 | .is-success + & { 103 | color: $success-color; 104 | } 105 | 106 | .has-error &, 107 | .is-error + & { 108 | color: $error-color; 109 | } 110 | } 111 | 112 | // Form element: Select 113 | .form-select { 114 | appearance: none; 115 | border: $border-width solid $border-color-dark; 116 | border-radius: $border-radius; 117 | color: inherit; 118 | font-size: $font-size; 119 | height: $control-size; 120 | line-height: $line-height; 121 | outline: none; 122 | padding: $control-padding-y $control-padding-x; 123 | vertical-align: middle; 124 | width: 100%; 125 | background: $bg-color-light; 126 | &:focus { 127 | @include control-shadow(); 128 | border-color: $primary-color; 129 | } 130 | &::-ms-expand { 131 | display: none; 132 | } 133 | 134 | // Select sizes 135 | &.select-sm { 136 | font-size: $font-size-sm; 137 | height: $control-size-sm; 138 | padding: $control-padding-y-sm ($control-icon-size + $control-padding-x-sm) $control-padding-y-sm $control-padding-x-sm; 139 | } 140 | 141 | &.select-lg { 142 | font-size: $font-size-lg; 143 | height: $control-size-lg; 144 | padding: $control-padding-y-lg ($control-icon-size + $control-padding-x-lg) $control-padding-y-lg $control-padding-x-lg; 145 | } 146 | 147 | // Multiple select 148 | &[size], 149 | &[multiple] { 150 | height: auto; 151 | padding: $control-padding-y $control-padding-x; 152 | 153 | option { 154 | padding: $unit-h $unit-1; 155 | } 156 | } 157 | &:not([multiple]):not([size]) { 158 | background: $bg-color-light url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center / .4rem .5rem; 159 | padding-right: $control-icon-size + $control-padding-x; 160 | } 161 | } 162 | 163 | // Form Icons 164 | .has-icon-left, 165 | .has-icon-right { 166 | position: relative; 167 | 168 | .form-icon { 169 | height: $control-icon-size; 170 | margin: 0 $control-padding-y; 171 | position: absolute; 172 | top: 50%; 173 | transform: translateY(-50%); 174 | width: $control-icon-size; 175 | z-index: $zindex-0 + 1; 176 | } 177 | } 178 | 179 | .has-icon-left { 180 | .form-icon { 181 | left: $border-width; 182 | } 183 | 184 | .form-input { 185 | padding-left: $control-icon-size + $control-padding-y * 2; 186 | } 187 | } 188 | 189 | .has-icon-right { 190 | .form-icon { 191 | right: $border-width; 192 | } 193 | 194 | .form-input { 195 | padding-right: $control-icon-size + $control-padding-y * 2; 196 | } 197 | } 198 | 199 | // Form element: Checkbox and Radio 200 | .form-checkbox, 201 | .form-radio, 202 | .form-switch { 203 | display: block; 204 | line-height: $line-height; 205 | margin: ($control-size - $control-size-sm) / 2 0; 206 | min-height: $control-size-sm; 207 | padding: (($control-size-sm - $line-height) / 2) $control-padding-x (($control-size-sm - $line-height) / 2) ($control-icon-size + $control-padding-x); 208 | position: relative; 209 | 210 | input { 211 | clip: rect(0, 0, 0, 0); 212 | height: 1px; 213 | margin: -1px; 214 | overflow: hidden; 215 | position: absolute; 216 | width: 1px; 217 | &:focus + .form-icon { 218 | @include control-shadow(); 219 | border-color: $primary-color; 220 | } 221 | &:checked + .form-icon { 222 | background: $primary-color; 223 | border-color: $primary-color; 224 | } 225 | } 226 | 227 | .form-icon { 228 | border: $border-width solid $border-color-dark; 229 | cursor: pointer; 230 | display: inline-block; 231 | position: absolute; 232 | transition: background .2s, border .2s, box-shadow .2s, color .2s; 233 | } 234 | 235 | // Input checkbox, radio and switch sizes 236 | &.input-sm { 237 | font-size: $font-size-sm; 238 | margin: 0; 239 | } 240 | 241 | &.input-lg { 242 | font-size: $font-size-lg; 243 | margin: ($control-size-lg - $control-size-sm) / 2 0; 244 | } 245 | } 246 | 247 | .form-checkbox, 248 | .form-radio { 249 | .form-icon { 250 | background: $bg-color-light; 251 | height: $control-icon-size; 252 | left: 0; 253 | top: ($control-size-sm - $control-icon-size) / 2; 254 | width: $control-icon-size; 255 | } 256 | 257 | input { 258 | &:active + .form-icon { 259 | background: $bg-color-dark; 260 | } 261 | } 262 | } 263 | .form-checkbox { 264 | .form-icon { 265 | border-radius: $border-radius; 266 | } 267 | 268 | input { 269 | &:checked + .form-icon { 270 | &::before { 271 | background-clip: padding-box; 272 | border: $border-width-lg solid $light-color; 273 | border-left-width: 0; 274 | border-top-width: 0; 275 | content: ""; 276 | height: 9px; 277 | left: 50%; 278 | margin-left: -3px; 279 | margin-top: -6px; 280 | position: absolute; 281 | top: 50%; 282 | transform: rotate(45deg); 283 | width: 6px; 284 | } 285 | } 286 | &:indeterminate + .form-icon { 287 | background: $primary-color; 288 | border-color: $primary-color; 289 | &::before { 290 | background: $bg-color-light; 291 | content: ""; 292 | height: 2px; 293 | left: 50%; 294 | margin-left: -5px; 295 | margin-top: -1px; 296 | position: absolute; 297 | top: 50%; 298 | width: 10px; 299 | } 300 | } 301 | } 302 | } 303 | .form-radio { 304 | .form-icon { 305 | border-radius: 50%; 306 | } 307 | 308 | input { 309 | &:checked + .form-icon { 310 | &::before { 311 | background: $bg-color-light; 312 | border-radius: 50%; 313 | content: ""; 314 | height: 6px; 315 | left: 50%; 316 | position: absolute; 317 | top: 50%; 318 | transform: translate(-50%, -50%); 319 | width: 6px; 320 | } 321 | } 322 | } 323 | } 324 | 325 | // Form element: Switch 326 | .form-switch { 327 | padding-left: ($unit-8 + $control-padding-x); 328 | 329 | .form-icon { 330 | background: $gray-color; 331 | background-clip: padding-box; 332 | border-radius: $unit-2 + $border-width; 333 | height: $unit-4 + $border-width * 2; 334 | left: 0; 335 | top: ($control-size-sm - $unit-4) / 2 - $border-width; 336 | width: $unit-8; 337 | &::before { 338 | background: $bg-color-light; 339 | border-radius: 50%; 340 | content: ""; 341 | display: block; 342 | height: $unit-4; 343 | left: 0; 344 | position: absolute; 345 | top: 0; 346 | transition: background .2s, border .2s, box-shadow .2s, color .2s, left .2s; 347 | width: $unit-4; 348 | } 349 | } 350 | 351 | input { 352 | &:checked + .form-icon { 353 | &::before { 354 | left: 14px; 355 | } 356 | } 357 | &:active + .form-icon { 358 | &::before { 359 | background: $bg-color; 360 | } 361 | } 362 | } 363 | } 364 | 365 | // Form element: Input groups 366 | .input-group { 367 | display: flex; 368 | 369 | .input-group-addon { 370 | background: $bg-color; 371 | border: $border-width solid $border-color-dark; 372 | border-radius: $border-radius; 373 | line-height: $line-height; 374 | padding: $control-padding-y $control-padding-x; 375 | white-space: nowrap; 376 | 377 | &.addon-sm { 378 | font-size: $font-size-sm; 379 | padding: $control-padding-y-sm $control-padding-x-sm; 380 | } 381 | 382 | &.addon-lg { 383 | font-size: $font-size-lg; 384 | padding: $control-padding-y-lg $control-padding-x-lg; 385 | } 386 | } 387 | 388 | .form-input, 389 | .form-select { 390 | flex: 1 1 auto; 391 | width: 1%; 392 | } 393 | 394 | .input-group-btn { 395 | z-index: $zindex-0; 396 | } 397 | 398 | .form-input, 399 | .form-select, 400 | .input-group-addon, 401 | .input-group-btn { 402 | &:first-child:not(:last-child) { 403 | border-bottom-right-radius: 0; 404 | border-top-right-radius: 0; 405 | } 406 | &:not(:first-child):not(:last-child) { 407 | border-radius: 0; 408 | margin-left: -$border-width; 409 | } 410 | &:last-child:not(:first-child) { 411 | border-bottom-left-radius: 0; 412 | border-top-left-radius: 0; 413 | margin-left: -$border-width; 414 | } 415 | &:focus { 416 | z-index: $zindex-0 + 1; 417 | } 418 | } 419 | 420 | .form-select { 421 | width: auto; 422 | } 423 | 424 | &.input-inline { 425 | display: inline-flex; 426 | } 427 | } 428 | 429 | // Form validation states 430 | .form-input, 431 | .form-select { 432 | .has-success &, 433 | &.is-success { 434 | background: lighten($success-color, 53%); 435 | border-color: $success-color; 436 | &:focus { 437 | @include control-shadow($success-color); 438 | } 439 | } 440 | 441 | .has-error &, 442 | &.is-error { 443 | background: lighten($error-color, 53%); 444 | border-color: $error-color; 445 | &:focus { 446 | @include control-shadow($error-color); 447 | } 448 | } 449 | } 450 | 451 | .form-checkbox, 452 | .form-radio, 453 | .form-switch { 454 | .has-error &, 455 | &.is-error { 456 | .form-icon { 457 | border-color: $error-color; 458 | } 459 | 460 | input { 461 | &:checked + .form-icon { 462 | background: $error-color; 463 | border-color: $error-color; 464 | } 465 | 466 | &:focus + .form-icon { 467 | @include control-shadow($error-color); 468 | border-color: $error-color; 469 | } 470 | } 471 | } 472 | } 473 | 474 | .form-checkbox { 475 | .has-error &, 476 | &.is-error { 477 | input { 478 | &:indeterminate + .form-icon { 479 | background: $error-color; 480 | border-color: $error-color; 481 | } 482 | } 483 | } 484 | } 485 | 486 | // validation based on :placeholder-shown (Edge doesn't support it yet) 487 | .form-input { 488 | &:not(:placeholder-shown) { 489 | &:invalid { 490 | border-color: $error-color; 491 | &:focus { 492 | @include control-shadow($error-color); 493 | background: lighten($error-color, 53%); 494 | } 495 | 496 | & + .form-input-hint { 497 | color: $error-color; 498 | } 499 | } 500 | } 501 | } 502 | 503 | // Form disabled and readonly 504 | .form-input, 505 | .form-select { 506 | &:disabled, 507 | &.disabled { 508 | background-color: $bg-color-dark; 509 | cursor: not-allowed; 510 | opacity: .5; 511 | } 512 | } 513 | 514 | .form-input { 515 | &[readonly] { 516 | background-color: $bg-color; 517 | } 518 | } 519 | 520 | input { 521 | &:disabled, 522 | &.disabled { 523 | & + .form-icon { 524 | background: $bg-color-dark; 525 | cursor: not-allowed; 526 | opacity: .5; 527 | } 528 | } 529 | } 530 | 531 | .form-switch { 532 | input { 533 | &:disabled, 534 | &.disabled { 535 | & + .form-icon::before { 536 | background: $bg-color-light; 537 | } 538 | } 539 | } 540 | } 541 | 542 | // Form horizontal 543 | .form-horizontal { 544 | padding: $layout-spacing 0; 545 | 546 | .form-group { 547 | display: flex; 548 | flex-wrap: wrap; 549 | } 550 | } 551 | 552 | // Form inline 553 | .form-inline { 554 | display: inline-block; 555 | } 556 | --------------------------------------------------------------------------------