Skip to content
Next
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
Minds Frontend
Project
Project
Details
Activity
Releases
Dependency List
Cycle Analytics
Insights
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
820
Issues
820
List
Boards
Labels
Service Desk
Milestones
Merge Requests
47
Merge Requests
47
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
List
Container Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Minds
Minds Frontend
Commits
3ccd8be8
Commit
3ccd8be8
authored
27 minutes ago
by
Mark Harding
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
(feat): various changes to introduce USD option to wire'
parent
6f4eaf65
epic/37-wire
No related merge requests found
Pipeline
#72503119
failed with stages
in 6 minutes and 36 seconds
Changes
14
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
423 additions
and
129 deletions
+423
-129
components.scss
src/app/common/components/components.scss
+40
-1
new-card.component.html
src/app/modules/payments/new-card/new-card.component.html
+8
-0
new-card.component.ts
src/app/modules/payments/new-card/new-card.component.ts
+80
-0
payments.module.ts
src/app/modules/payments/payments.module.ts
+12
-1
select-card.component.html
...p/modules/payments/select-card/select-card.component.html
+18
-0
select-card.component.scss
...p/modules/payments/select-card/select-card.component.scss
+11
-0
select-card.component.ts
...app/modules/payments/select-card/select-card.component.ts
+55
-0
saved-cards.component.html
...s/settings/billing/saved-cards/saved-cards.component.html
+16
-4
saved-cards.component.ts
...les/settings/billing/saved-cards/saved-cards.component.ts
+21
-63
creator.component.html
src/app/modules/wire/creator/creator.component.html
+101
-43
creator.component.scss
src/app/modules/wire/creator/creator.component.scss
+13
-7
creator.component.ts
src/app/modules/wire/creator/creator.component.ts
+23
-6
wire.module.ts
src/app/modules/wire/wire.module.ts
+2
-0
wire.service.ts
src/app/modules/wire/wire.service.ts
+23
-4
No files found.
src/app/common/components/components.scss
View file @
3ccd8be8
...
...
@@ -65,4 +65,43 @@ minds-button-thumbs-down {
color
:
rgba
(
themed
(
$m-blue-dark
)
,
0
.9
)
!
important
;
}
}
}
\ No newline at end of file
}
.m-selector
{
position
:
relative
;
select
{
padding
:
8px
16px
;
max-width
:
100%
;
appearance
:
none
;
display
:
block
;
width
:
100%
;
font-family
:
'Roboto'
,
Helvetica
,
sans-serif
;
font-size
:
13px
;
cursor
:
pointer
;
font-weight
:
600
;
@include
m-theme
(){
border
:
1px
solid
themed
(
$m-grey-100
);
}
}
&
:
:
before
{
content
:
'\25bc'
;
position
:
absolute
;
pointer-events
:
none
;
top
:
0
;
bottom
:
1px
;
padding-top
:
0
.7em
;
line-height
:
1
;
right
:
0
;
width
:
2em
;
text-align
:
center
;
transform
:
scale
(
0
.84
,
0
.42
);
filter
:
progid
:
DXImageTransform
.
Microsoft
.
Matrix
(
M11
=.
84
,
M12
=
0
,
M21
=
0
,
M22
=.
42
,
SizingMethod
=
'auto expand'
);
@include
m-theme
(){
color
:
themed
(
$m-grey-500
);
}
}
}
This diff is collapsed.
Click to expand it.
src/app/modules/payments/new-card/new-card.component.html
0 → 100644
View file @
3ccd8be8
<div
class=
"mdl-spinner mdl-js-spinner is-active"
[
mdl
]
[
hidden
]="
intentKey
"
></div>
<iframe
[
src
]="
url
"
#
iframe
*
ngIf=
"intentKey"
>
</iframe>
This diff is collapsed.
Click to expand it.
src/app/modules/payments/new-card/new-card.component.ts
0 → 100644
View file @
3ccd8be8
import
{
Component
,
EventEmitter
,
Input
,
Output
,
ChangeDetectorRef
,
ChangeDetectionStrategy
,
ViewChild
,
ElementRef
,
}
from
'
@angular/core
'
;
import
{
DomSanitizer
}
from
'
@angular/platform-browser
'
;
import
{
Client
}
from
'
../../../services/api
'
;
import
{
WalletService
}
from
'
../../../services/wallet
'
;
import
{
Storage
}
from
'
../../../services/storage
'
;
import
{
Session
}
from
'
../../../services/session
'
;
@
Component
({
selector
:
'
m-payments__newCard
'
,
templateUrl
:
'
new-card.component.html
'
,
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
})
export
class
PaymentsNewCard
{
minds
=
(
<
any
>
window
).
Minds
;
intentKey
:
string
=
''
;
intentId
:
string
=
''
;
@
ViewChild
(
'
iframe
'
,
{
static
:
false
})
iframe
:
ElementRef
;
@
Output
()
completed
:
EventEmitter
<
void
>
=
new
EventEmitter
();
_opts
:
any
;
set
opts
(
opts
:
any
)
{
this
.
_opts
=
opts
;
}
constructor
(
public
session
:
Session
,
public
client
:
Client
,
public
cd
:
ChangeDetectorRef
,
private
sanitizer
:
DomSanitizer
,
)
{
}
ngOnInit
()
{
window
.
addEventListener
(
'
message
'
,
(
msg
)
=>
{
if
(
msg
.
data
===
'
completed-saved-card
'
)
{
this
.
saveCard
();
}
},
false
);
this
.
setupIntent
();
}
async
setupIntent
()
{
const
{
intent
}
=
<
any
>
await
this
.
client
.
put
(
'
api/v2/payments/stripe/intents/setup
'
);
this
.
intentKey
=
intent
.
client_secret
;
this
.
intentId
=
intent
.
id
;
this
.
detectChanges
();
}
get
url
()
{
const
url
=
'
https://checkout.minds.com/stripe?intent_key=
'
+
this
.
intentKey
;
return
this
.
sanitizer
.
bypassSecurityTrustResourceUrl
(
url
);
}
async
saveCard
()
{
const
{
success
}
=
<
any
>
await
this
.
client
.
post
(
'
api/v2/payments/stripe/paymentmethods/apply
'
,
{
intent_id
:
this
.
intentId
});
this
.
intentKey
=
''
;
this
.
completed
.
next
();
this
.
_opts
.
onCompleted
();
this
.
detectChanges
();
}
detectChanges
()
{
this
.
cd
.
markForCheck
();
this
.
cd
.
detectChanges
();
}
}
This diff is collapsed.
Click to expand it.
src/app/modules/payments/payments.module.ts
View file @
3ccd8be8
...
...
@@ -8,10 +8,14 @@ import { ModalsModule } from '../modals/modals.module';
import
{
PayWall
}
from
'
./paywall/paywall.component
'
;
import
{
PaywallCancelButton
}
from
'
./paywall/paywall-cancel.component
'
;
import
{
PaymentsNewCard
}
from
'
./new-card/new-card.component
'
;
import
{
PaymentsSelectCard
}
from
'
./select-card/select-card.component
'
;
@
NgModule
({
imports
:
[
NgCommonModule
,
FormsModule
,
ReactiveFormsModule
,
CommonModule
,
CheckoutModule
,
ModalsModule
...
...
@@ -19,11 +23,18 @@ import { PaywallCancelButton } from './paywall/paywall-cancel.component';
declarations
:
[
PayWall
,
PaywallCancelButton
,
PaymentsNewCard
,
PaymentsSelectCard
,
],
exports
:
[
PayWall
,
PaywallCancelButton
,
]
PaymentsNewCard
,
PaymentsSelectCard
,
],
entryComponents
:
[
PaymentsNewCard
,
],
})
export
class
PaymentsModule
{
}
This diff is collapsed.
Click to expand it.
src/app/modules/payments/select-card/select-card.component.html
0 → 100644
View file @
3ccd8be8
<div
class=
"m-selector"
>
<select
[
ngModel
]="
paymentMethodId
"
(
ngModelChange
)="
paymentMethodId =
$event;
selected
.
next
(
paymentMethodId
)"
>
<option
*
ngFor=
"let card of paymentMethods"
[
value
]="
card
.
id
"
>
{{ card.card_brand | uppercase }} *** {{ card.card_last4 }} {{ card.card_expires }}
</option>
<option
value=
"new"
>
Add a new card
</option>
</select>
</div>
<div
class=
"m-paymentsSelectCard__addNewCard"
*
ngIf=
"paymentMethodId === 'new'"
>
<m-payments
__newCard
(
completed
)="
loadCards
()"
>
</m-payments
__newCard
>
</div>
This diff is collapsed.
Click to expand it.
src/app/modules/payments/select-card/select-card.component.scss
0 → 100644
View file @
3ccd8be8
.m-paymentsSelectCard__addNewCard
{
margin-top
:
8px
;
@include
m-theme
(){
border-top
:
2px
solid
themed
(
$m-grey-100
);
}
iframe
{
max-height
:
112px
;
}
}
This diff is collapsed.
Click to expand it.
src/app/modules/payments/select-card/select-card.component.ts
0 → 100644
View file @
3ccd8be8
import
{
Component
,
EventEmitter
,
Input
,
Output
,
ChangeDetectorRef
,
ChangeDetectionStrategy
,
ViewChild
,
ElementRef
,
}
from
'
@angular/core
'
;
import
{
DomSanitizer
}
from
'
@angular/platform-browser
'
;
import
{
Client
}
from
'
../../../services/api
'
;
import
{
WalletService
}
from
'
../../../services/wallet
'
;
import
{
Storage
}
from
'
../../../services/storage
'
;
import
{
Session
}
from
'
../../../services/session
'
;
@
Component
({
selector
:
'
m-payments__selectCard
'
,
templateUrl
:
'
select-card.component.html
'
,
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
})
export
class
PaymentsSelectCard
{
minds
=
(
<
any
>
window
).
Minds
;
@
Output
()
selected
:
EventEmitter
<
void
>
=
new
EventEmitter
();
paymentMethodId
:
string
=
''
;
paymentMethods
=
[];
constructor
(
public
session
:
Session
,
public
client
:
Client
,
public
cd
:
ChangeDetectorRef
,
private
sanitizer
:
DomSanitizer
,
)
{
}
ngOnInit
()
{
this
.
loadCards
();
}
async
loadCards
()
{
const
{
paymentmethods
}
=
<
any
>
await
this
.
client
.
get
(
'
api/v2/payments/stripe/paymentmethods
'
);
this
.
paymentMethods
=
paymentmethods
;
this
.
paymentMethodId
=
paymentmethods
[
0
].
id
;
this
.
selected
.
next
(
this
.
paymentMethodId
);
this
.
detectChanges
();
}
detectChanges
()
{
this
.
cd
.
markForCheck
();
this
.
cd
.
detectChanges
();
}
}
This diff is collapsed.
Click to expand it.
src/app/modules/settings/billing/saved-cards/saved-cards.component.html
View file @
3ccd8be8
<div
class=
"m-settings--section m-border"
*
ngIf=
"cards.length"
>
<div
class=
"m-settings--section m-border"
>
<h4
i18n=
"@@SETTINGS__BILLING__SAVED_CARDS__TITLE"
>
Payment Methods
</h4>
...
...
@@ -11,12 +11,24 @@
<li
*
ngFor=
"let card of cards; let i = index"
class=
"m-settings--billing-saved-cards--cards-item"
>
<span
class=
"m-settings--billing-saved-cards--cards-item-type"
>
{{card.brand}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-number"
>
**** {{card.last4}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-expiry"
>
{{card.exp
_month}} / {{card.exp_year
}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-type"
>
{{card.
card_
brand}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-number"
>
**** {{card.
card_
last4}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-expiry"
>
{{card.exp
ires
}}
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-select"
(
click
)="
removeCard
(
i
)"
i18n=
"@@M__ACTION__REMOVE"
>
Remove
</span>
</li>
<li
class=
"m-settings--billing-saved-cards--cards-item m-settings--billing-saved-cards--cards-item-new"
>
<span
class=
"m-settings--billing-saved-cards--cards-item-type"
i18n=
"@@SETTINGS__BILLING__SAVED_CARDS__ADD_NEW_CARD_LABEL"
>
Add a new card
</span>
<span
class=
"m-settings--billing-saved-cards--cards-item-select"
(
click
)="
addNewCard
()"
i18n=
"@@SETTINGS__BILLING__SAVED_CARDS__ADD_ACTION"
>
ADD
</span>
</li>
</ul>
</div>
<m-payments
__newCard
(
completed
)="
loadSaveCards
()"
*
ngIf=
"addingNewCard"
>
</m-payments
__newCard
>
</div>
This diff is collapsed.
Click to expand it.
src/app/modules/settings/billing/saved-cards/saved-cards.component.ts
View file @
3ccd8be8
import
{
Component
,
ChangeDetectorRef
}
from
'
@angular/core
'
;
import
{
Client
}
from
'
../../../../common/api/client.service
'
;
import
{
OverlayModalService
}
from
'
../../../../services/ux/overlay-modal
'
;
import
{
PaymentsNewCard
}
from
'
../../../payments/new-card/new-card.component
'
;
@
Component
({
selector
:
'
m-settings--billing-saved-cards
'
,
...
...
@@ -12,37 +13,30 @@ export class SettingsBillingSavedCardsComponent {
minds
=
window
.
Minds
;
inProgress
:
boolean
=
false
;
addNewCard
:
boolean
=
false
;
add
ing
NewCard
:
boolean
=
false
;
cards
:
Array
<
any
>
=
[];
constructor
(
private
client
:
Client
,
private
cd
:
ChangeDetectorRef
)
{
constructor
(
private
client
:
Client
,
private
cd
:
ChangeDetectorRef
,
private
overlayModal
:
OverlayModalService
,
)
{
}
ngOnInit
()
{
this
.
loadSavedCards
();
this
.
setupStripe
();
setTimeout
(()
=>
{
this
.
setupStripe
();
},
1000
);
//sometimes stripe can take a while to download
}
setupStripe
()
{
if
((
<
any
>
window
).
Stripe
)
{
(
<
any
>
window
).
Stripe
.
setPublishableKey
(
this
.
minds
.
stripe_key
);
}
}
loadSavedCards
():
Promise
<
any
>
{
this
.
inProgress
=
true
;
this
.
cards
=
[];
return
this
.
client
.
get
(
`api/v
1/payments/stripe/car
ds`
)
.
then
(({
car
ds
})
=>
{
return
this
.
client
.
get
(
`api/v
2/payments/stripe/paymentmetho
ds`
)
.
then
(({
paymentmetho
ds
})
=>
{
this
.
inProgress
=
false
;
if
(
cards
&&
car
ds
.
length
)
{
this
.
cards
=
car
ds
;
if
(
paymentmethods
&&
paymentmetho
ds
.
length
)
{
this
.
cards
=
paymentmetho
ds
;
this
.
detectChanges
();
}
})
...
...
@@ -55,7 +49,7 @@ export class SettingsBillingSavedCardsComponent {
removeCard
(
index
:
number
)
{
this
.
inProgress
=
true
;
this
.
client
.
delete
(
'
api/v
1/payments/stripe/card
/
'
+
this
.
cards
[
index
].
id
)
this
.
client
.
delete
(
'
api/v
2/payments/stripe/paymentmethods
/
'
+
this
.
cards
[
index
].
id
)
.
then
(()
=>
{
this
.
cards
.
splice
(
index
,
1
);
...
...
@@ -68,50 +62,14 @@ export class SettingsBillingSavedCardsComponent {
});
}
setCard
(
card
)
{
this
.
inProgress
=
true
;
this
.
detectChanges
();
this
.
getCardNonce
(
card
)
.
then
((
token
)
=>
{
this
.
saveCard
(
token
)
.
then
(()
=>
{
this
.
inProgress
=
false
;
this
.
addNewCard
=
false
;
this
.
detectChanges
();
this
.
loadSavedCards
();
})
.
catch
(
e
=>
{
this
.
inProgress
=
false
;
this
.
detectChanges
();
alert
((
e
&&
e
.
message
)
||
'
There was an error saving your card.
'
);
});
})
.
catch
((
e
)
=>
{
this
.
inProgress
=
false
;
this
.
detectChanges
();
alert
((
e
&&
e
.
message
)
||
'
There was an error with your card information.
'
);
});
}
saveCard
(
token
:
string
):
Promise
<
any
>
{
return
this
.
client
.
put
(
'
api/v1/payments/stripe/card/
'
+
token
);
}
getCardNonce
(
card
):
Promise
<
string
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
(
<
any
>
window
).
Stripe
.
card
.
createToken
({
number
:
card
.
number
,
cvc
:
card
.
sec
,
exp_month
:
card
.
month
,
exp_year
:
card
.
year
},
(
status
,
response
)
=>
{
if
(
response
.
error
)
{
return
reject
(
response
.
error
.
message
);
}
return
resolve
(
response
.
id
);
});
});
addNewCard
()
{
this
.
overlayModal
.
create
(
PaymentsNewCard
,
{},
{
class
:
''
,
onCompleted
:
()
=>
{
this
.
loadSavedCards
();
//refresh list
this
.
overlayModal
.
dismiss
();
},
}).
present
();
}
detectChanges
():
void
{
...
...
This diff is collapsed.
Click to expand it.
src/app/modules/wire/creator/creator.component.html
View file @
3ccd8be8
This diff is collapsed.
Click to expand it.
src/app/modules/wire/creator/creator.component.scss
View file @
3ccd8be8
...
...
@@ -88,6 +88,7 @@
font-weight
:
400
;
margin-bottom
:
0
;
padding-left
:
$minds-margin
*
2
;
padding-right
:
$minds-padding
*
3
;
line-height
:
16px
;
}
}
...
...
@@ -208,7 +209,12 @@
display
:
inline-block
;
}
>
i
{
font-size
:
42px
;
font-size
:
52px
;
display
:
block
;
margin-left
:
-12px
;
}
>
img
{
height
:
52px
;
display
:
block
;
}
}
...
...
@@ -394,16 +400,16 @@
appearance
:
none
;
padding
:
8px
32px
;
background
:
none
;
border-radius
:
0
;
//
border-radius: 0;
font-family
:
inherit
;
font-size
:
inherit
;
font-weight
:
inherit
;
text-transform
:
uppercase
;
cursor
:
pointer
;
@include
m-theme
(){
border
:
1px
solid
themed
(
$m-grey-700
);
color
:
themed
(
$m-grey-700
);
}
//
@include m-theme(){
//
border: 1px solid themed($m-grey-700);
//
color: themed($m-grey-700);
//
}
&
[
disabled
]
{
cursor
:
default
;
...
...
@@ -654,7 +660,7 @@
// }
&
:not
(
.m-wire--creator-selector--highlight
)
.m-wire--creator-selector-type
{
>
i
,
h5
>
span
,
>
i
,
h5
>
span
,
img
,
.m-boost--creator-selector--hoverable
,
.material-icons
{
opacity
:
0
.5
;
...
...
This diff is collapsed.
Click to expand it.
src/app/modules/wire/creator/creator.component.ts
View file @
3ccd8be8
...
...
@@ -11,7 +11,7 @@ import { TokenContractService } from '../../blockchain/contracts/token-contract.
import
{
MindsUser
}
from
'
../../../interfaces/entities
'
;
import
{
Router
}
from
'
@angular/router
'
;
export
type
PayloadType
=
'
onchain
'
|
'
offchain
'
|
'
creditcard
'
;
export
type
PayloadType
=
'
onchain
'
|
'
offchain
'
|
'
usd
'
|
'
eth
'
;
export
class
VisibleWireError
extends
Error
{
visible
:
boolean
=
true
;
...
...
@@ -244,7 +244,7 @@ export class WireCreatorComponent {
this
.
wire
.
payload
=
null
;
if
(
payloadType
===
'
onchain
'
)
{
if
(
payloadType
===
'
onchain
'
||
payloadType
===
'
eth
'
)
{
this
.
setOnchainNoncePayload
(
''
);
}
...
...
@@ -416,10 +416,10 @@ export class WireCreatorComponent {
}
break
;
case
'
creditcar
d
'
:
if
(
!
this
.
wire
.
payload
)
{
throw
new
Error
(
'
Payment method not processed.
'
);
}
case
'
us
d
'
:
//
if (!this.wire.payload) {
//
throw new Error('Payment method not processed.');
//
}
break
;
}
...
...
@@ -514,4 +514,21 @@ export class WireCreatorComponent {
this
.
inProgress
=
false
;
}
}
get
canRecur
():
boolean
{
switch
(
this
.
wire
.
payloadType
)
{
case
'
onchain
'
:
case
'
offchain
'
:
case
'
usd
'
:
return
true
;
}
return
false
;
}
setUsdPaymentMethod
(
paymentMethodId
)
{
this
.
wire
.
payload
=
{
paymentMethodId
:
paymentMethodId
,
};
}
}
This diff is collapsed.
Click to expand it.
src/app/modules/wire/wire.module.ts
View file @
3ccd8be8
...
...
@@ -6,6 +6,7 @@ import { CommonModule } from '../../common/common.module';
import
{
FormsModule
,
ReactiveFormsModule
}
from
'
@angular/forms
'
;
import
{
CheckoutModule
}
from
'
../checkout/checkout.module
'
;
import
{
FaqModule
}
from
'
../faq/faq.module
'
;
import
{
PaymentsModule
}
from
'
../payments/payments.module
'
;
import
{
WireCreatorComponent
}
from
'
./creator/creator.component
'
;
import
{
WirePaymentsCreatorComponent
}
from
'
./payments-creator/creator.component
'
;
...
...
@@ -39,6 +40,7 @@ const wireRoutes : Routes = [
CommonModule
,
CheckoutModule
,
FaqModule
,
PaymentsModule
,
],
declarations
:
[
WireLockScreenComponent
,
...
...
This diff is collapsed.
Click to expand it.
src/app/modules/wire/wire.service.ts
View file @
3ccd8be8
...
...
@@ -55,8 +55,27 @@ export class WireService {
}
break
;
case
'
creditcard
'
:
payload
.
method
=
'
creditcard
'
;
case
'
eth
'
:
await
this
.
web3Wallet
.
ready
();
if
(
this
.
web3Wallet
.
isUnavailable
())
{
throw
new
Error
(
'
No Ethereum wallets available on your browser.
'
);
}
else
if
(
!
(
await
this
.
web3Wallet
.
unlock
()))
{
throw
new
Error
(
'
Your Ethereum wallet is locked or connected to another network.
'
);
}
await
this
.
web3Wallet
.
sendTransaction
({
from
:
await
this
.
web3Wallet
.
getCurrentWallet
(),
to
:
payload
.
receiver
,
gasPrice
:
this
.
web3Wallet
.
EthJS
.
toWei
(
2
,
'
Gwei
'
),
gas
:
21000
,
value
:
this
.
web3Wallet
.
EthJS
.
toWei
(
wire
.
amount
,
'
ether
'
).
toString
(),
data
:
'
0x
'
,
});
break
;
case
'
usd
'
:
payload
.
method
=
'
usd
'
;
break
;
case
'
offchain
'
:
...
...
@@ -65,9 +84,9 @@ export class WireService {
}
try
{
let
response
:
any
=
await
this
.
client
.
post
(
`api/v
1
/wire/
${
wire
.
guid
}
`
,
{
let
response
:
any
=
await
this
.
client
.
post
(
`api/v
2
/wire/
${
wire
.
guid
}
`
,
{
payload
,
method
:
'
tokens
'
,
method
:
payload
.
method
,
amount
:
wire
.
amount
,
recurring
:
wire
.
recurring
});
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment