diff --git a/.drone.yml b/.drone.yml
index 4dc1976..f63ece6 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -29,9 +29,6 @@ steps:
- yarn install
- rake css:build
- rake spec
- when:
- branch:
- - master
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css
index 7cdbcd5..7a9da2e 100644
--- a/app/assets/stylesheets/application.tailwind.css
+++ b/app/assets/stylesheets/application.tailwind.css
@@ -7,3 +7,4 @@
@import "components/forms";
@import "components/links";
@import "components/notifications";
+@import "components/tables";
diff --git a/app/assets/stylesheets/components/base.css b/app/assets/stylesheets/components/base.css
index 5bb472c..45e6263 100644
--- a/app/assets/stylesheets/components/base.css
+++ b/app/assets/stylesheets/components/base.css
@@ -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;
+ }
}
diff --git a/app/assets/stylesheets/components/tables.css b/app/assets/stylesheets/components/tables.css
new file mode 100644
index 0000000..9b5e1fc
--- /dev/null
+++ b/app/assets/stylesheets/components/tables.css
@@ -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;
+ }
+}
diff --git a/app/assets/stylesheets/legacy.sass.scss b/app/assets/stylesheets/legacy.sass.scss
deleted file mode 100644
index 04739eb..0000000
--- a/app/assets/stylesheets/legacy.sass.scss
+++ /dev/null
@@ -1,2 +0,0 @@
-@import "legacy/layout";
-@import "legacy/main_nav";
diff --git a/app/assets/stylesheets/legacy/_layout.scss b/app/assets/stylesheets/legacy/_layout.scss
deleted file mode 100644
index 222b34e..0000000
--- a/app/assets/stylesheets/legacy/_layout.scss
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/app/assets/stylesheets/legacy/_main_nav.scss b/app/assets/stylesheets/legacy/_main_nav.scss
deleted file mode 100644
index 68361ab..0000000
--- a/app/assets/stylesheets/legacy/_main_nav.scss
+++ /dev/null
@@ -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;
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/legacy/_mediaqueries.scss b/app/assets/stylesheets/legacy/_mediaqueries.scss
deleted file mode 100644
index 44095a9..0000000
--- a/app/assets/stylesheets/legacy/_mediaqueries.scss
+++ /dev/null
@@ -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."
- }
-}
diff --git a/app/assets/stylesheets/legacy/_variables.scss b/app/assets/stylesheets/legacy/_variables.scss
deleted file mode 100644
index 04f158f..0000000
--- a/app/assets/stylesheets/legacy/_variables.scss
+++ /dev/null
@@ -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;
diff --git a/app/components/wallet_summary_component.html.erb b/app/components/wallet_summary_component.html.erb
new file mode 100644
index 0000000..b02e0d8
--- /dev/null
+++ b/app/components/wallet_summary_component.html.erb
@@ -0,0 +1,18 @@
+
+
+
+ Send and receive sats via the Bitcoin Lightning Network.
+
+
+
+
+ <% if @balance %>
+ <%= number_with_delimiter @balance %> sats
+ Available balance
+ <% else %>
+ n/a sats
+ Balance unavailable
+ <% end %>
+
+
+
diff --git a/app/components/wallet_summary_component.rb b/app/components/wallet_summary_component.rb
new file mode 100644
index 0000000..7ca89ef
--- /dev/null
+++ b/app/components/wallet_summary_component.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class WalletSummaryComponent < ViewComponent::Base
+ def initialize(balance:)
+ @balance = balance
+ end
+
+end
diff --git a/app/controllers/wallet_controller.rb b/app/controllers/wallet_controller.rb
index 59ef4d7..4990894 100644
--- a/app/controllers/wallet_controller.rb
+++ b/app/controllers/wallet_controller.rb
@@ -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
diff --git a/app/services/lndhub.rb b/app/services/lndhub.rb
index 01f7abf..bd3670a 100644
--- a/app/services/lndhub.rb
+++ b/app/services/lndhub.rb
@@ -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],
diff --git a/app/views/admin/donations/index.html.erb b/app/views/admin/donations/index.html.erb
index 2285a6a..baf51cb 100644
--- a/app/views/admin/donations/index.html.erb
+++ b/app/views/admin/donations/index.html.erb
@@ -2,14 +2,14 @@
<%= render MainSimpleComponent.new do %>
<% if @donations.any? %>
-
+
-
+
User
- Amount BTC
- in EUR
- in USD
- Public name
+ Amount BTC
+ in EUR
+ in USD
+ Public name
Date
@@ -19,10 +19,10 @@
<% @donations.each do |donation| %>
<%= donation.user.address %>
- <%= sats_to_btc donation.amount_sats %> BTC
- <% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %>
- <% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %>
- <%= donation.public_name %>
+ <%= sats_to_btc donation.amount_sats %>
+ <% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %>
+ <% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %>
+ <%= donation.public_name %>
<%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %>
<%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
<%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
diff --git a/app/views/admin/invitations/index.html.erb b/app/views/admin/invitations/index.html.erb
index e0cea28..584188e 100644
--- a/app/views/admin/invitations/index.html.erb
+++ b/app/views/admin/invitations/index.html.erb
@@ -14,7 +14,7 @@
Accepted (<%= @invitations_used.length %>)
-
+
Token
Accepted
Invited user
diff --git a/app/views/admin/ldap_users/index.html.erb b/app/views/admin/ldap_users/index.html.erb
index fa69197..85637b0 100644
--- a/app/views/admin/ldap_users/index.html.erb
+++ b/app/views/admin/ldap_users/index.html.erb
@@ -13,7 +13,7 @@
-
+
UID
E-Mail
Admin
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb
index 3d98b91..bb8619e 100644
--- a/app/views/dashboard/index.html.erb
+++ b/app/views/dashboard/index.html.erb
@@ -6,7 +6,7 @@
Your Kosmos account and password currently give you access to these
services:
-
+
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
diff --git a/app/views/icons/_link-2.html.erb b/app/views/icons/_link-2.html.erb
index 8cc7f6d..4bfc68c 100644
--- a/app/views/icons/_link-2.html.erb
+++ b/app/views/icons/_link-2.html.erb
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/views/icons/_link.html.erb b/app/views/icons/_link.html.erb
index c89dd41..965e681 100644
--- a/app/views/icons/_link.html.erb
+++ b/app/views/icons/_link.html.erb
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/views/icons/_zap.html.erb b/app/views/icons/_zap.html.erb
index 8fdafa9..f809708 100644
--- a/app/views/icons/_zap.html.erb
+++ b/app/views/icons/_zap.html.erb
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/app/views/invitations/index.html.erb b/app/views/invitations/index.html.erb
index e91d9ca..a65f3ff 100644
--- a/app/views/invitations/index.html.erb
+++ b/app/views/invitations/index.html.erb
@@ -8,7 +8,7 @@
-
+
URL
@@ -33,7 +33,7 @@
Accepted Invitations
-
+
ID
Accepted
Invited user
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 13b7f8c..ef75312 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -6,7 +6,6 @@
<%= csp_meta_tag %>
- <%= stylesheet_link_tag 'legacy', "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
@@ -18,7 +17,7 @@
- <%= render partial: "shared/icons/comet" %>
+ <%= render partial: "shared/icons/comet", locals: { custom_class: "inline-block align-top w-auto h-7" } %>
<% if user_signed_in? && current_user.confirmed? %>
diff --git a/app/views/shared/icons/_comet.html.erb b/app/views/shared/icons/_comet.html.erb
index 78071f8..60f5a46 100644
--- a/app/views/shared/icons/_comet.html.erb
+++ b/app/views/shared/icons/_comet.html.erb
@@ -1 +1 @@
-
+
diff --git a/app/views/wallet/index.html.erb b/app/views/wallet/index.html.erb
index afabb25..406bd60 100644
--- a/app/views/wallet/index.html.erb
+++ b/app/views/wallet/index.html.erb
@@ -1,22 +1,14 @@
<%= render HeaderComponent.new(title: "Wallet") %>
<%= render MainSimpleComponent.new do %>
-
-
-
- Send and receive sats via the Bitcoin Lightning Network.
-
-
-
-
- <% if @balance %>
- <%= number_with_delimiter @balance %> sats
- Available balance
- <% else %>
- n/a sats
- Balance unavailable
- <% end %>
-
+ <%= render WalletSummaryComponent.new(balance: @balance) %>
+
+
+
+
+ <%= 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" %>
+
diff --git a/app/views/wallet/transactions.html.erb b/app/views/wallet/transactions.html.erb
new file mode 100644
index 0000000..c7c495e
--- /dev/null
+++ b/app/views/wallet/transactions.html.erb
@@ -0,0 +1,57 @@
+<%= render HeaderComponent.new(title: "Wallet") %>
+
+<%= render MainSimpleComponent.new do %>
+ <%= render WalletSummaryComponent.new(balance: @balance) %>
+
+
+
+
+ <%= 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" %>
+
+
+
+
+
+ Transactions
+
+ <% if @transactions.any? %>
+
+ <% @transactions.each do |tx| %>
+
+
+ <% if tx["type"] == "bitcoind_tx" %>
+
+ <%= render partial: "icons/link-2", locals: { custom_class: "text-emerald-500 h-4 w-4 mr-0.5" } %>
+
+ <% else %>
+
+ <%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 mr-0.5" } %>
+
+ <% end %>
+ <%= tx["title"] %>
+
+
+ ">
+ <%= tx["received"] ? "+" : "" %><%= number_with_delimiter tx["amount_sats"] %>
+ sats
+
+
+
+ <%= tx["description"].present? ? tx["description"] : raw("No memo ") %>
+
+
+
+ <%= tx["datetime"].strftime("%B %e, %H:%M") %>
+
+
+
+ <% end %>
+
+ <% else %>
+
+ No transactions yet. As soon as you start receiving sats, you will find some entries here.
+
+ <% end %>
+
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 8843058..8ecb38b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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: /[^\/]+/}
diff --git a/package.json b/package.json
index 96235ba..251dafe 100644
--- a/package.json
+++ b/package.json
@@ -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"
}
}
diff --git a/spec/components/wallet_summary_component_spec.rb b/spec/components/wallet_summary_component_spec.rb
new file mode 100644
index 0000000..7acdb04
--- /dev/null
+++ b/spec/components/wallet_summary_component_spec.rb
@@ -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
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index c4a74a4..eab4dee 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -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