Add transaction history view to wallet #66
@ -29,9 +29,6 @@ steps:
|
||||
- yarn install
|
||||
- rake css:build
|
||||
- rake spec
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- name: rebuild-cache
|
||||
image: drillster/drone-volume-cache
|
||||
volumes:
|
||||
|
@ -7,3 +7,4 @@
|
||||
@import "components/forms";
|
||||
@import "components/links";
|
||||
@import "components/notifications";
|
||||
@import "components/tables";
|
||||
|
@ -1,11 +1,12 @@
|
||||
@layer base {
|
||||
body {
|
||||
@apply leading-none
|
||||
@apply leading-none bg-cover bg-fixed;
|
||||
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
|
||||
}
|
||||
|
||||
/* h1, h2, h3 { */
|
||||
/* @apply font-light; */
|
||||
/* } */
|
||||
body#admin {
|
||||
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%), url('/img/bg-1.jpg');
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl uppercase;
|
||||
@ -26,4 +27,16 @@
|
||||
main section:first-of-type {
|
||||
@apply pt-0;
|
||||
}
|
||||
|
||||
main p {
|
||||
@apply mb-4 leading-6;
|
||||
}
|
||||
|
||||
main ul {
|
||||
@apply mb-6;
|
||||
}
|
||||
|
||||
main ul li {
|
||||
@apply leading-6;
|
||||
}
|
||||
}
|
||||
|
22
app/assets/stylesheets/components/tables.css
Normal file
@ -0,0 +1,22 @@
|
||||
@layer components {
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
table thead tr {
|
||||
@apply text-left;
|
||||
}
|
||||
|
||||
table th {
|
||||
@apply pb-3.5 text-sm font-normal uppercase text-gray-500;
|
||||
}
|
||||
|
||||
table th:not(:last-of-type),
|
||||
table td:not(:last-of-type) {
|
||||
@apply pr-2;
|
||||
}
|
||||
|
||||
table td {
|
||||
@apply py-2;
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
@import "legacy/layout";
|
||||
@import "legacy/main_nav";
|
@ -1,118 +0,0 @@
|
||||
@import "variables";
|
||||
@import "mediaqueries";
|
||||
|
||||
body {
|
||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
|
||||
url('/img/bg-1.jpg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body#admin {
|
||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
|
||||
url('/img/bg-1.jpg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.ks-site-icon {
|
||||
svg {
|
||||
display: inline-block;
|
||||
height: 1.875rem;
|
||||
vertical-align: top;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
> header {
|
||||
margin: 0 auto;
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
|
||||
url('/img/bg-1.jpg');
|
||||
background-size: cover;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #fff;
|
||||
|
||||
span.project-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
p.current-user {
|
||||
color: rgba(255,255,255,0.6);
|
||||
|
||||
@include media-max(small) {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(255,255,255,0.6);
|
||||
transition: color 0.1s linear;
|
||||
|
||||
&:hover, &:active {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&.notice {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
li {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
th {
|
||||
color: $text-color-discreet;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
padding-bottom: 0.825rem;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
|
||||
&.services {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-row-gap: 1rem;
|
||||
grid-column-gap: 2rem;
|
||||
|
||||
@include media-max(small) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
@import "variables";
|
||||
@import "mediaqueries";
|
||||
|
||||
#main-nav {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: #efefef;
|
||||
|
||||
.wrapper {
|
||||
width: $content-width;
|
||||
max-width: $content-max-width;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include media-max(large) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
li {
|
||||
@include media-min(large) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@include media-max(large) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 1.5rem 2rem;
|
||||
text-decoration: none;
|
||||
color: $text-color-discreet;
|
||||
|
||||
@include media-max(large) {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $text-color-body;
|
||||
border-bottom: 2px solid #4ea2df;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
$breakpoints-max: (
|
||||
small: 600px,
|
||||
medium: 960px,
|
||||
large: 1280px
|
||||
);
|
||||
|
||||
$breakpoints-min: (
|
||||
small: 601px,
|
||||
medium: 961px,
|
||||
large: 1281px
|
||||
);
|
||||
|
||||
@mixin media-max($screen-size) {
|
||||
@if map-has-key($breakpoints-max, $screen-size) {
|
||||
@media (max-width: map-get($breakpoints-max, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
||||
|
||||
@mixin media-min($screen-size) {
|
||||
@if map-has-key($breakpoints-min, $screen-size) {
|
||||
@media (min-width: map-get($breakpoints-min, $screen-size)) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
// Debugging
|
||||
@warn "'#{$screen-size}' has not been declared as a breakpoint."
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
$content-width: 800px;
|
||||
$content-max-width: 100%;
|
||||
|
||||
$text-color-body: #222;
|
||||
$text-color-discreet: #888;
|
||||
|
||||
$background-color-notice: #efffc4;
|
||||
$background-color-alert: #fff4c2;
|
||||
|
||||
$color-blue: #0d4f99;
|
||||
$color-purple: #8955a0;
|
||||
$color-red-bright: #c00;
|
||||
$color-red-dark: #990c0e;
|
18
app/components/wallet_summary_component.html.erb
Normal file
@ -0,0 +1,18 @@
|
||||
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
|
||||
<div class="md:col-span-8">
|
||||
<p>
|
||||
Send and receive sats via the Bitcoin Lightning Network.
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:col-span-4 mt-4 md:mt-0">
|
||||
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
||||
<% if @balance %>
|
||||
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
|
||||
<span class="text-sm text-gray-500">Available balance</span>
|
||||
<% else %>
|
||||
<span class="text-xl">n/a sats</span><br>
|
||||
<span class="text-sm text-gray-500">Balance unavailable</span>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
8
app/components/wallet_summary_component.rb
Normal file
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WalletSummaryComponent < ViewComponent::Base
|
||||
def initialize(balance:)
|
||||
@balance = balance
|
||||
end
|
||||
|
||||
end
|
@ -3,10 +3,10 @@ require "rqrcode"
|
||||
class WalletController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_with_lndhub
|
||||
before_action :set_current_section
|
||||
before_action :fetch_balance
|
||||
|
||||
def index
|
||||
@current_section = :wallet
|
||||
|
||||
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
|
||||
qrcode = RQRCode::QRCode.new(@wallet_url)
|
||||
@ -20,8 +20,10 @@ class WalletController < ApplicationController
|
||||
class: 'inline-block'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@balance = fetch_balance rescue nil
|
||||
def transactions
|
||||
@transactions = fetch_transactions
|
||||
end
|
||||
|
||||
private
|
||||
@ -39,9 +41,41 @@ class WalletController < ApplicationController
|
||||
# TODO add exception tracking
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@current_section = :wallet
|
||||
end
|
||||
|
||||
def fetch_balance
|
||||
lndhub = Lndhub.new
|
||||
data = lndhub.balance @ln_auth_token
|
||||
data["BTC"]["AvailableBalance"]
|
||||
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||
end
|
||||
|
||||
def fetch_transactions
|
||||
lndhub = Lndhub.new
|
||||
txs = lndhub.gettxs @ln_auth_token
|
||||
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||
|
||||
process_transactions(txs + invoices)
|
||||
end
|
||||
|
||||
def process_transactions(txs)
|
||||
txs.collect do |tx|
|
||||
if tx["type"] == "bitcoind_tx"
|
||||
tx["amount_sats"] = (tx["amount"] * 100000000).to_i
|
||||
tx["datetime"] = Time.at(tx["time"].to_i)
|
||||
tx["title"] = "Received"
|
||||
tx["description"] = "On-chain topup"
|
||||
tx["received"] = true
|
||||
else
|
||||
tx["amount_sats"] = tx["value"] || tx["amt"]
|
||||
tx["datetime"] = Time.at(tx["timestamp"].to_i)
|
||||
tx["title"] = tx["type"] == "paid_invoice" ? "Sent" : "Received"
|
||||
tx["description"] = tx["memo"] || tx["description"]
|
||||
tx["received"] = tx["type"] == "user_invoice"
|
||||
end
|
||||
end
|
||||
|
||||
txs.sort{ |a,b| b["datetime"] <=> a["datetime"] }
|
||||
end
|
||||
end
|
||||
|
@ -46,6 +46,14 @@ class Lndhub
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def gettxs(user_token)
|
||||
get "gettxs", user_token || auth_token
|
||||
end
|
||||
|
||||
def getuserinvoices(user_token)
|
||||
get "getuserinvoices", user_token || auth_token
|
||||
end
|
||||
|
||||
def addinvoice(payload)
|
||||
invoice = post "addinvoice", {
|
||||
amt: payload[:amount],
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<% if @donations.any? %>
|
||||
<table class="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="text-left">
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Amount BTC</th>
|
||||
<th>in EUR</th>
|
||||
<th>in USD</th>
|
||||
<th>Public name</th>
|
||||
<th class="text-right">Amount BTC</th>
|
||||
<th class="text-right">in EUR</th>
|
||||
<th class="text-right">in USD</th>
|
||||
<th class="pl-2">Public name</th>
|
||||
<th>Date</th>
|
||||
<th colspan="3"></th>
|
||||
</tr>
|
||||
@ -19,10 +19,10 @@
|
||||
<% @donations.each do |donation| %>
|
||||
<tr>
|
||||
<td><%= donation.user.address %></td>
|
||||
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
|
||||
<td><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
||||
<td><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
||||
<td><%= donation.public_name %></td>
|
||||
<td class="text-right"><%= sats_to_btc donation.amount_sats %></td>
|
||||
<td class="text-right"><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
||||
<td class="text-right"><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
||||
<td class="pl-2"><%= donation.public_name %></td>
|
||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
|
||||
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="text-left">
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<th>Accepted</th>
|
||||
<th>Invited user</th>
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="text-left">
|
||||
<tr>
|
||||
<th>UID</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Admin</th>
|
||||
|
@ -6,7 +6,7 @@
|
||||
Your Kosmos account and password currently give you access to these
|
||||
services:
|
||||
</p>
|
||||
<div class="grid services mt-12">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 services mt-12">
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
||||
|
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2 <%= custom_class %>"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||||
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 376 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link <%= custom_class %>"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 392 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap <%= custom_class %>"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
||||
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 303 B |
@ -8,7 +8,7 @@
|
||||
</p>
|
||||
<table class="mt-12">
|
||||
<thead>
|
||||
<tr class="text-left">
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -33,7 +33,7 @@
|
||||
<h3>Accepted Invitations</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="text-left">
|
||||
<tr>
|
||||
<th class="hidden md:block">ID</th>
|
||||
<th>Accepted</th>
|
||||
<th>Invited user</th>
|
||||
|
@ -6,7 +6,6 @@
|
||||
<%= csp_meta_tag %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
|
||||
<%= stylesheet_link_tag 'legacy', "data-turbo-track": "reload" %>
|
||||
<%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
</head>
|
||||
@ -18,7 +17,7 @@
|
||||
<div class="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||
<div class="flex items-center">
|
||||
<div class="ks-site-icon flex-shrink-0">
|
||||
<%= render partial: "shared/icons/comet" %>
|
||||
<%= render partial: "shared/icons/comet", locals: { custom_class: "inline-block align-top w-auto h-7" } %>
|
||||
</div>
|
||||
<% if user_signed_in? && current_user.confirmed? %>
|
||||
<div class="hidden md:block">
|
||||
|
@ -1 +1 @@
|
||||
<svg id="icon-comet" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg>
|
||||
<svg id="icon-comet" class="<%= custom_class %>" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 655 B |
@ -1,22 +1,14 @@
|
||||
<%= render HeaderComponent.new(title: "Wallet") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
|
||||
<div class="md:col-span-8">
|
||||
<p>
|
||||
Send and receive sats via the Bitcoin Lightning Network.
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:col-span-4 mt-4 md:mt-0">
|
||||
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
||||
<% if @balance %>
|
||||
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
|
||||
<span class="text-sm text-gray-500">Available balance</span>
|
||||
<% else %>
|
||||
<span class="text-xl">n/a sats</span><br>
|
||||
<span class="text-sm text-gray-500">Balance unavailable</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= link_to "Info", wallet_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||
<%= link_to "Transactions", wallet_transactions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
57
app/views/wallet/transactions.html.erb
Normal file
@ -0,0 +1,57 @@
|
||||
<%= render HeaderComponent.new(title: "Wallet") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<%= render WalletSummaryComponent.new(balance: @balance) %>
|
||||
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= link_to "Info", wallet_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
|
||||
<%= link_to "Transactions", wallet_transactions_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="hidden">Transactions</h3>
|
||||
|
||||
<% if @transactions.any? %>
|
||||
<ul class="list-none">
|
||||
<% @transactions.each do |tx| %>
|
||||
<li class="py-4 md:py-4 grid gap-y-1 gap-x-2 grid-cols-4 border-b border-dotted border-gray-300">
|
||||
<h3 class="col-span-2 md:col-span-3 mb-0">
|
||||
<% if tx["type"] == "bitcoind_tx" %>
|
||||
<span class="inline-block">
|
||||
<%= render partial: "icons/link-2", locals: { custom_class: "text-emerald-500 h-4 w-4 mr-0.5" } %>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-block">
|
||||
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 mr-0.5" } %>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= tx["title"] %>
|
||||
</h3>
|
||||
<p class="col-span-2 md:col-span-1 mb-0 text-right">
|
||||
<span class="text-xl font-mono <%= tx["received"] ? "text-emerald-600" : "" %>">
|
||||
<%= tx["received"] ? "+" : "" %><%= number_with_delimiter tx["amount_sats"] %>
|
||||
<span class="hidden md:inline">sats</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="col-span-4 md:col-span-3 mb-0 text-gray-500">
|
||||
<%= tx["description"].present? ? tx["description"] : raw("<span class='text-gray-400'>No memo</span>") %>
|
||||
</p>
|
||||
<p class="col-span-4 md:col-span-1 md:text-right mb-0">
|
||||
<span class="col-span-2 md:col-span-1 text-sm text-gray-500">
|
||||
<%= tx["datetime"].strftime("%B %e, %H:%M") %>
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-gray-500">
|
||||
No transactions yet. As soon as you start receiving sats, you will find some entries here.
|
||||
</p>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
@ -20,6 +20,7 @@ Rails.application.routes.draw do
|
||||
resources :donations
|
||||
|
||||
get 'wallet', to: 'wallet#index'
|
||||
get 'wallet/transactions', to: 'wallet#transactions'
|
||||
|
||||
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
|
||||
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
|
||||
|
@ -14,8 +14,7 @@
|
||||
},
|
||||
"version": "0.3.0",
|
||||
"scripts": {
|
||||
"build:css:sass": "sass ./app/assets/stylesheets/legacy.sass.scss ./app/assets/builds/legacy.css --no-source-map --load-path=node_modules",
|
||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||
"build:css": "yarn run build:css:sass && yarn run build:css:tailwind"
|
||||
"build:css": "yarn run build:css:tailwind"
|
||||
}
|
||||
}
|
||||
|
11
spec/components/wallet_summary_component_spec.rb
Normal file
@ -0,0 +1,11 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe WalletSummaryComponent, type: :component do
|
||||
it "renders the balance as a human-readable number" do
|
||||
expect(
|
||||
render_inline(described_class.new(balance: 2301000)) {}.css("section").to_html
|
||||
).to include(
|
||||
"2,301,000 sats"
|
||||
)
|
||||
end
|
||||
end
|
@ -10,6 +10,8 @@ require 'capybara'
|
||||
require 'devise'
|
||||
require 'support/controller_macros'
|
||||
require 'support/database_cleaner'
|
||||
require "view_component/test_helpers"
|
||||
require "capybara/rspec"
|
||||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||
@ -70,5 +72,8 @@ RSpec.configure do |config|
|
||||
config.include Warden::Test::Helpers
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
config.include ActiveJob::TestHelper, type: :job
|
||||
config.include ViewComponent::TestHelpers, type: :component
|
||||
config.include Capybara::RSpecMatchers, type: :component
|
||||
|
||||
config.extend ControllerMacros, :type => :controller
|
||||
end
|
||||
|