fuik 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -5
  3. data/app/assets/images/fuik/icons/adyen.jpg +0 -0
  4. data/app/assets/images/fuik/icons/anthropic.jpg +0 -0
  5. data/app/assets/images/fuik/icons/apple.jpg +0 -0
  6. data/app/assets/images/fuik/icons/basecamp.jpg +0 -0
  7. data/app/assets/images/fuik/icons/chirpform.jpg +0 -0
  8. data/app/assets/images/fuik/icons/dropbox.jpg +0 -0
  9. data/app/assets/images/fuik/icons/facebook.jpg +0 -0
  10. data/app/assets/images/fuik/icons/fizzy.jpg +0 -0
  11. data/app/assets/images/fuik/icons/github.jpg +0 -0
  12. data/app/assets/images/fuik/icons/gitlab.jpg +0 -0
  13. data/app/assets/images/fuik/icons/google.jpg +0 -0
  14. data/app/assets/images/fuik/icons/gumroad.jpg +0 -0
  15. data/app/assets/images/fuik/icons/linkedin.jpg +0 -0
  16. data/app/assets/images/fuik/icons/loops.jpg +0 -0
  17. data/app/assets/images/fuik/icons/mailgun.jpg +0 -0
  18. data/app/assets/images/fuik/icons/mailpace.jpg +0 -0
  19. data/app/assets/images/fuik/icons/mollie.jpg +0 -0
  20. data/app/assets/images/fuik/icons/moneybird.jpg +0 -0
  21. data/app/assets/images/fuik/icons/openai.jpg +0 -0
  22. data/app/assets/images/fuik/icons/postmark.jpg +0 -0
  23. data/app/assets/images/fuik/icons/resend.jpg +0 -0
  24. data/app/assets/images/fuik/icons/shopify.jpg +0 -0
  25. data/app/assets/images/fuik/icons/slack.jpg +0 -0
  26. data/app/assets/images/fuik/icons/stripe.jpg +0 -0
  27. data/app/assets/images/fuik/icons/telegram.jpg +0 -0
  28. data/app/assets/images/fuik/icons/twitter.jpg +0 -0
  29. data/app/assets/images/fuik/icons/userlist.jpg +0 -0
  30. data/app/assets/images/fuik/icons/webhook.svg +1 -0
  31. data/app/assets/images/fuik/icons/zoom.jpg +0 -0
  32. data/app/assets/stylesheets/fuik/application.css +95 -25
  33. data/app/controllers/concerns/fuik/event_type.rb +2 -0
  34. data/app/controllers/fuik/downloads_controller.rb +9 -0
  35. data/app/controllers/fuik/events_controller.rb +5 -0
  36. data/app/helpers/fuik/highlight_helper.rb +61 -0
  37. data/app/helpers/fuik/icon.rb +28 -0
  38. data/app/models/fuik/event.rb +3 -1
  39. data/app/views/fuik/events/_copy_button.html.erb +7 -3
  40. data/app/views/fuik/events/index.html.erb +16 -10
  41. data/app/views/fuik/events/show.html.erb +18 -3
  42. data/app/views/layouts/fuik/application.html.erb +2 -2
  43. data/config/routes.rb +1 -0
  44. data/lib/fuik/dot_access.rb +48 -0
  45. data/lib/fuik/engine.rb +7 -0
  46. data/lib/fuik/version.rb +1 -1
  47. data/lib/fuik.rb +1 -0
  48. metadata +35 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 263dbeb940d32f359659c36cbed9181e9cd75a73adbfbc525ee9af89dee6f010
4
- data.tar.gz: 74f7e2c3abe2a7020869e4a5722550fcb3e9cb6a7439bb19b758d38773691ee1
3
+ metadata.gz: 27567e69538dc46d35faddb9c331ccdd3059afbb810f40a84e0c3a1ee0a573bf
4
+ data.tar.gz: 23ad7f6fa24a03835fb55f5afcd1157282f60aa600d7e5a8e8084c1c376b110f
5
5
  SHA512:
6
- metadata.gz: ae37e3e0ee6eadfe94091d1bc24fa6490c59e8bcb8eb77f75eccf41c6b14abb30cef6732ad9dbd0f0ed1065927a53f4f21282e36332f501654797ece1fe68214
7
- data.tar.gz: 8bc9a6bbb71631fd958530392283c3e616f253e2e4d0d2bc874e74456ac8d49a5697a30664e8b787ffe2bea21727e0376d867dde8ddd53e915bd85f1fc202413
6
+ metadata.gz: bf626291cd8b4cf4e069b5be95fa58ca45fec06628d0ffe353c9e01af6460537805a7d3b45ad403bf681b26beddfcbb495e3e03c71af880a311c68454064390e
7
+ data.tar.gz: 835c24fcb7798aa948d3a6ca545d35cae78a4e83489f6a798b10527d53a8ea836aa17d9495f3ed64d6ec3c16b468254d810249868a3e5101fb7437b980aed759
data/README.md CHANGED
@@ -54,7 +54,7 @@ The engine mounts at `/webhooks` automatically.
54
54
 
55
55
  ### View events
56
56
 
57
- Visit `/webhooks` to see all received webhooks. Click any event to see the full payload, headers and status.
57
+ Visit `/webhooks` to see all received webhooks. Click any event to view all the payload details and copy the payload or download as JSON.
58
58
 
59
59
  <img alt="Fuik event detail interface" src="https://raw.githubusercontent.com/Rails-Designer/fuik/HEAD/.github/docs/event-detail.jpg" style="max-width: 100%;">
60
60
 
@@ -63,22 +63,21 @@ Visit `/webhooks` to see all received webhooks. Click any event to see the full
63
63
 
64
64
  ### Add business logic
65
65
 
66
- Generate event handlers when you're ready to automate:
66
+ Generate classes for events you want to process:
67
67
  ```bash
68
- bin/rails generate fuik:provider stripe checkout_session_completed customer_subscription_updated
68
+ bin/rails generate fuik:provider stripe checkout_session_completed
69
69
  ```
70
70
 
71
71
  This creates:
72
72
  - `app/webhooks/stripe/base.rb`
73
73
  - `app/webhooks/stripe/checkout_session_completed.rb`
74
- - `app/webhooks/stripe/customer_subscription_updated.rb`
75
74
 
76
75
  Each class is a thin wrapper around your business logic:
77
76
  ```ruby
78
77
  module Stripe
79
78
  class CheckoutSessionCompleted < Base
80
79
  def process!
81
- User.find_by(id: payload.dig("client_reference_id")).tap do |user|
80
+ User.find_by(id: payload.client_reference_id).tap do |user|
82
81
  user.activate_subscription!
83
82
  user.send_welcome_email
84
83
 
@@ -91,6 +90,17 @@ module Stripe
91
90
  end
92
91
  ```
93
92
 
93
+ The `payload` method supports dot notation and standard hash methods:
94
+ ```ruby
95
+ # Dot notation
96
+ payload.client_reference_id
97
+ payload.customer.email
98
+
99
+ # Hash syntax (strings/symbols)
100
+ payload["client_reference_id"]
101
+ payload[:customer_id]
102
+ ```
103
+
94
104
  Implement `Base.verify!` to enable signature verification:
95
105
  ```ruby
96
106
  module Stripe
@@ -149,6 +159,12 @@ event_id:
149
159
  key: custom_id
150
160
  ```
151
161
 
162
+ The options for `event_type`'s source are:
163
+
164
+ - header
165
+ - payload
166
+ - static; for cases when no event type is present in header or payload
167
+
152
168
 
153
169
  ## Add your custom provider
154
170
 
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="#64748b" viewBox="0 0 256 256"><path d="M178.16 176h-66.84a48 48 0 1 1-85.72-36.81 8 8 0 0 1 12.8 9.61A31.69 31.69 0 0 0 32 168a32 32 0 0 0 64 0 8 8 0 0 1 8-8h74.16a16 16 0 1 1 0 16ZM64 184a16 16 0 0 0 14.08-23.61l35.77-58.14a8 8 0 0 0-2.62-11 32 32 0 1 1 46.1-40.06 8 8 0 1 0 14.67-6.4 48 48 0 1 0-75.62 55.33L64.44 152H64a16 16 0 0 0 0 32Zm128-64a48.18 48.18 0 0 0-18 3.49L142.08 71.6A16 16 0 1 0 128 80h.44l35.78 58.15a8 8 0 0 0 11 2.61A32 32 0 1 1 192 200a8 8 0 0 0 0 16 48 48 0 0 0 0-96Z"/></svg>
@@ -68,25 +68,77 @@
68
68
  position: relative;
69
69
  overflow-x: auto;
70
70
  }
71
+
72
+ .actions {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: .5rem;
76
+ position: absolute;
77
+ top: .5rem; right: .5rem;
78
+ }
79
+
80
+ svg {
81
+ width: 1rem;
82
+ aspect-ratio: 1 / 1;
83
+ color: oklch(from var(--color-text) l c h / 80%);
84
+ background: var(--color-bg);
85
+ }
86
+
87
+ dd .copy-button .success-icon {
88
+ display: none;
89
+ }
90
+
91
+ dd:has([data-copy-success="true"]) .copy-button .copy-icon {
92
+ display: none;
93
+ }
94
+
95
+ dd:has([data-copy-success="true"]) .copy-button .success-icon {
96
+ display: block;
97
+ }
71
98
  }
72
99
  }
73
100
 
74
101
  @layer components {
75
- article {
76
- margin-block-end: 1rem;
77
- padding-block: .5rem;
78
- padding-inline: .75rem;
79
- border: 1px solid var(--color-border);
80
- border-radius: .5rem;
81
-
82
- &:hover { background: var(--color-bg-hover); }
83
-
84
- a {
85
- display: grid;
86
- grid-template-columns: repeat(4, minmax(0, 1fr));
87
- gap: 1rem;
102
+ .events {
103
+ list-style: none;
104
+
105
+ li {
106
+ --icon-width: 1.125rem;
107
+
108
+ padding-block: .5rem;
109
+ padding-inline: .75rem;
110
+
111
+ &:hover { background: var(--color-bg-hover); }
112
+
113
+ a {
114
+ display: grid;
115
+ grid-template-columns: repeat(4, minmax(0, 1fr));
116
+ gap: 1rem;
117
+
118
+ &:hover { text-decoration: none; }
119
+ }
120
+
121
+ .name {
122
+ display: grid;
123
+ grid-template-columns: var(--icon-width) 1fr;
124
+ align-items: center;
125
+ gap: .5rem;
126
+ }
127
+
128
+ .icon {
129
+ width: var(--icon-width);
130
+ aspect-ratio: 1/1;
131
+ border-radius: .125rem;
132
+ }
133
+
134
+ code {
135
+ display: flex;
136
+ align-items: center;
137
+ }
138
+ }
88
139
 
89
- &:hover { text-decoration: none; }
140
+ li:not(:first-child) {
141
+ border-top: 1px solid var(--color-border);
90
142
  }
91
143
  }
92
144
 
@@ -95,18 +147,25 @@
95
147
  align-items: center;
96
148
  column-gap: .375rem;
97
149
 
98
- &::before { content: "●"; }
150
+ &::before {
151
+ content: "";
152
+ width: .5rem;
153
+ aspect-ratio: 1/1;
154
+ background-color: currentColor;
155
+ border: 2px solid oklch(from currentColor 1 c h / 85%);
156
+ border-radius: .5rem;
157
+ }
99
158
 
100
- &[data-status="pending"]::before { color: oklch(65% 0.15 75); }
159
+ &[data-status="pending"]::before { color: oklch(65% .15 80); }
101
160
 
102
- &[data-status="processed"]::before { color: oklch(65% 0.15 150); }
161
+ &[data-status="processed"]::before { color: oklch(65% .15 150); }
103
162
 
104
- &[data-status="failed"]::before { color: oklch(60% 0.2 25); }
163
+ &[data-status="failed"]::before { color: oklch(60% .2 25); }
105
164
  }
106
165
 
166
+ button,
107
167
  .copy-button {
108
168
  padding: .125rem;
109
- color: oklch(from var(--color-text) l c h / 80%);
110
169
  background: var(--color-bg);
111
170
  border: 0;
112
171
  border-radius: .25rem;
@@ -117,15 +176,26 @@
117
176
  transition: transform ease-in-out 300ms;
118
177
  transform: scale(1.10);
119
178
  }
179
+ }
120
180
 
121
- &[position="fixed"] {
122
- position: absolute;
123
- top: .5rem; right: .5rem;
181
+ .json-highlight {
182
+ .json-key {
183
+ color: oklch(35% .15 250);
184
+ cursor: pointer;
185
+
186
+ &:hover {
187
+ background: oklch(90% 0.02 250);
188
+ }
124
189
  }
125
190
 
126
- svg {
127
- width: 1rem;
128
- aspect-ratio: 1 / 1;
191
+ .json-key-selected {
192
+ background: oklch(85% 0.05 250);
129
193
  }
194
+
195
+ .json-string { color: oklch(30% .18 160); }
196
+ .json-number { color: oklch(40% .15 130); }
197
+ .json-boolean { color: oklch(45% .18 260); }
198
+ .json-null { color: oklch(50% .05 250); }
199
+ .json-punctuation { color: oklch(20% .03 250); }
130
200
  }
131
201
  }
@@ -32,6 +32,8 @@ module Fuik
32
32
  request.headers[config[key]["key"]]
33
33
  when "payload"
34
34
  payload[config[key]["key"]]
35
+ when "static"
36
+ config[key]["value"]
35
37
  end
36
38
  end
37
39
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fuik::DownloadsController < ApplicationController
4
+ def create
5
+ webhook_event = Fuik::WebhookEvent.find(params[:event_id])
6
+
7
+ send_data webhook_event.body, filename: "webhook_#{webhook_event.provider}_#{webhook_event.event_id}.json", type: "application/json", disposition: "attachment"
8
+ end
9
+ end
@@ -10,6 +10,11 @@ module Fuik
10
10
 
11
11
  def show
12
12
  @webhook_event = WebhookEvent.find(params[:id])
13
+
14
+ respond_to do |format|
15
+ format.html
16
+ format.json { render json: @webhook_event }
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ module HighlightHelper
5
+ def highlighted(json)
6
+ annotate(JSON.parse(json), [], 0).html_safe
7
+ end
8
+
9
+ private
10
+
11
+ def annotate(object, current_path, depth)
12
+ case object
13
+ when Hash
14
+ hashed(object, current_path:, depth:)
15
+ when Array
16
+ arrayed(object, current_path:, depth:)
17
+ when String
18
+ %(<span class="json-string">"#{object}"</span>)
19
+ when Numeric
20
+ %(<span class="json-number">#{object}</span>)
21
+ when TrueClass, FalseClass
22
+ %(<span class="json-boolean">#{object}</span>)
23
+ when NilClass
24
+ '<span class="json-null">null</span>'
25
+ end
26
+ end
27
+
28
+ def hashed(object, current_path:, depth:)
29
+ indent = " " * depth
30
+ next_indent = " " * (depth + 1)
31
+
32
+ object.each_with_index.map do |(key, value), index|
33
+ key_path = current_path + [key]
34
+ path_string = key_path.map { "[\"#{it}\"]" }.join
35
+
36
+ comma = (index == object.size - 1) ? "" : '<span class="json-punctuation">,</span>'
37
+
38
+ "#{next_indent}#{%(<span class="json-key" data-path='#{path_string}'>"#{key}"</span>)}<span class=\"json-punctuation\">:</span> #{annotate(value, key_path, depth + 1)}#{comma}"
39
+ end.tap do |lines|
40
+ lines.unshift('<span class="json-punctuation">{</span>')
41
+
42
+ lines.push("#{indent}<span class=\"json-punctuation\">}</span>")
43
+ end.join("\n")
44
+ end
45
+
46
+ def arrayed(object, current_path:, depth:)
47
+ indent = " " * depth
48
+ next_indent = " " * (depth + 1)
49
+
50
+ object.each_with_index.map do |value, index|
51
+ comma = (index == object.size - 1) ? "" : '<span class="json-punctuation">,</span>'
52
+
53
+ "#{next_indent}#{annotate(value, current_path + [index], depth + 1)}#{comma}"
54
+ end.tap do |lines|
55
+ lines.unshift('<span class="json-punctuation">[</span>')
56
+
57
+ lines.push("#{indent}<span class=\"json-punctuation\">]</span>")
58
+ end.join("\n")
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ module IconHelper
5
+ def fuik_icon(provider)
6
+ provider = provider.to_s.downcase
7
+ path = "fuik/icons/#{provider}.jpg"
8
+
9
+ if asset_exists?(path)
10
+ image_tag path, alt: provider.capitalize, class: "icon"
11
+ else
12
+ image_tag "fuik/icons/webhook.svg", alt: "Webhook", class: "icon"
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def asset_exists?(path)
19
+ if defined?(Propshaft)
20
+ Rails.application.assets.load_path.find(path).present?
21
+ else
22
+ Rails.application.assets&.find_asset(path).present?
23
+ end
24
+ rescue
25
+ false
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Fuik
4
4
  class Event
5
+ using DotAccess
6
+
5
7
  def initialize(webhook_event)
6
8
  @webhook_event = webhook_event
7
9
  end
@@ -11,7 +13,7 @@ module Fuik
11
13
  end
12
14
 
13
15
  def payload
14
- @webhook_event.payload
16
+ @webhook_event.payload.to_dot
15
17
  end
16
18
  end
17
19
  end
@@ -1,6 +1,10 @@
1
- <%# locals: (target:, delay: 5000, position: :relative) %>
2
- <button data-action="copy" data-target="<%= target %>" data-copy-delay="<%= delay %>" position="<%= position %>" class="copy-button">
3
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon" stroke-width="">
1
+ <%# locals: (target:, delay: 5000) %>
2
+ <button data-action="copy" data-target="<%= target %>" data-copy-delay="<%= delay %>" class="copy-button">
3
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon" class="copy-icon">
4
4
  <path fill-rule="evenodd" d="M13.887 3.182c.396.037.79.08 1.183.128C16.194 3.45 17 4.414 17 5.517V16.75A2.25 2.25 0 0 1 14.75 19h-9.5A2.25 2.25 0 0 1 3 16.75V5.517c0-1.103.806-2.068 1.93-2.207.393-.048.787-.09 1.183-.128A3.001 3.001 0 0 1 9 1h2c1.373 0 2.531.923 2.887 2.182ZM7.5 4A1.5 1.5 0 0 1 9 2.5h2A1.5 1.5 0 0 1 12.5 4v.5h-5V4Z" clip-rule="evenodd" ></path>
5
5
  </svg>
6
+
7
+ <svg class="success-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" data-slot="icon" stroke-width="">
8
+ <path d="M200,32H163.74a47.92,47.92,0,0,0-71.48,0H56A16,16,0,0,0,40,48V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Zm-72,0a32,32,0,0,1,32,32H96A32,32,0,0,1,128,32Zm32,128H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Z"></path>
9
+ </svg>
6
10
  </button>
@@ -1,15 +1,21 @@
1
1
  <h1>Webhooks</h1>
2
2
 
3
- <% @webhook_events.each do |event| %>
4
- <article>
5
- <%= link_to event_path(event) do %>
6
- <span><%= event.provider %></span>
3
+ <ul class="events">
4
+ <% @webhook_events.each do |event| %>
5
+ <li>
6
+ <%= link_to event_path(event) do %>
7
+ <div class="name">
8
+ <%= fuik_icon event.provider %>
7
9
 
8
- <code><%= event.event_type %></code>
10
+ <span><%= event.provider %></span>
11
+ </div>
9
12
 
10
- <span class="status" data-status="<%= event.status %>"><%= event.status %></span>
13
+ <code><%= event.event_type %></code>
11
14
 
12
- <time><%= event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></time>
13
- <% end %>
14
- </article>
15
- <% end %>
15
+ <span class="status" data-status="<%= event.status %>"><%= event.status %></span>
16
+
17
+ <time><%= event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></time>
18
+ <% end %>
19
+ </li>
20
+ <% end %>
21
+ </ul>
@@ -1,3 +1,5 @@
1
+ <% content_for :title, "#{@webhook_event.provider} / #{@webhook_event.event_type} | Fuik Admin" %>
2
+
1
3
  <h1>
2
4
  <%= link_to "Webhooks", root_path %> / <%= @webhook_event.event_id %>
3
5
  </h1>
@@ -7,7 +9,7 @@
7
9
  <dd><%= @webhook_event.provider %></dd>
8
10
 
9
11
  <dt>Event ID</dt>
10
- <dd><%= tag.code @webhook_event.event_id, id: "event_id" %> <%= render "copy_button", target: "#event_id" %></dd>
12
+ <dd><%= tag.code @webhook_event.event_id, id: "event_id" %> <%= render "copy_button", target: "event_id" %></dd>
11
13
 
12
14
  <dt>Type</dt>
13
15
  <dd><%= @webhook_event.event_type %></dd>
@@ -19,13 +21,26 @@
19
21
  <dd><%= @webhook_event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
20
22
 
21
23
  <dt>Payload</dt>
22
- <dd><pre id="payload"><%= JSON.pretty_generate(@webhook_event.payload) %></pre> <%= render "copy_button", target: "#payload", position: :fixed %></dd>
24
+ <dd><pre id="payload" data-root="payload" class="json-highlight"><%= highlighted(JSON.pretty_generate(@webhook_event.payload)) %></pre> <div class="actions"><%= button_to downloads_path, params: { event_id: @webhook_event.id }, method: :post, class: "" do %><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34Zm-56,83.32-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35a8,8,0,0,1,11.32,11.32ZM152,88V44l44,44Z"/></svg><% end %> <%= render "copy_button", target: "payload" %></div></dd>
23
25
 
24
26
  <dt>Headers</dt>
25
- <dd><pre id="headers"><%= JSON.pretty_generate(@webhook_event.headers) %></pre> <%= render "copy_button", target: "#headers", position: :fixed %></dd>
27
+ <dd><pre id="headers" class="json-highlight"><%= highlighted JSON.pretty_generate(@webhook_event.headers) %></pre> <div class="actions"><%= render "copy_button", target: "headers" %></div></dd>
26
28
 
27
29
  <% if @webhook_event.error.present? %>
28
30
  <dt>Error</dt>
29
31
  <dd><%= @webhook_event.error %></dd>
30
32
  <% end %>
31
33
  </dl>
34
+
35
+ <script>
36
+ document.addEventListener("click", (event) => {
37
+ if (event.target.classList.contains("json-key")) {
38
+ const path = event.target.dataset.path;
39
+ const root = event.target.closest("[data-root]").dataset.root;
40
+ navigator.clipboard.writeText(root + path);
41
+
42
+ event.target.classList.add("json-key-selected");
43
+ setTimeout(() => event.target.classList.remove("json-key-selected"), 200);
44
+ }
45
+ });
46
+ </script>
@@ -1,14 +1,14 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Fuik Admin</title>
4
+ <title><%= content_for?(:title) ? yield(:title) : "Fuik Admin" %></title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <%= yield :head %>
9
9
 
10
10
  <%= stylesheet_link_tag "fuik/application", media: "all" %>
11
- <script defer src="https://cdn.jsdelivr.net/npm/attractivejs@0.11.0"></script>
11
+ <script defer src="https://cdn.jsdelivr.net/npm/attractivejs@0.12.0"></script>
12
12
  </head>
13
13
  <body>
14
14
  <main>
data/config/routes.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  Fuik::Engine.routes.draw do
4
4
  root to: "events#index"
5
5
  resources :events, only: %w[show]
6
+ resources :downloads, only: %w[create]
6
7
 
7
8
  post ":provider", to: "webhooks#create"
8
9
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ module DotAccess
5
+ refine Hash do
6
+ def to_dot = AccessPayload.new(self)
7
+ end
8
+
9
+ class AccessPayload
10
+ def initialize(payload)
11
+ @payload = payload
12
+ end
13
+
14
+ def [](key)
15
+ value = @payload[key] || @payload[key.to_s] || @payload[key.to_sym]
16
+
17
+ value.is_a?(Hash) ? self.class.new(value) : value
18
+ end
19
+
20
+ def []=(key, value)
21
+ @payload[key] = value
22
+ end
23
+
24
+ def dig(*keys)
25
+ value = @payload.dig(*keys)
26
+
27
+ value.is_a?(Hash) ? self.class.new(value) : value
28
+ end
29
+
30
+ def key?(key)
31
+ @payload.key?(key) || @payload.key?(key.to_s) || @payload.key?(key.to_sym)
32
+ end
33
+
34
+ def method_missing(method_name, *arguments)
35
+ return super unless arguments.empty? && !block_given?
36
+
37
+ value = @payload[method_name] || @payload[method_name.to_s] || @payload[method_name.to_sym]
38
+ value.is_a?(Hash) ? self.class.new(value) : value
39
+ end
40
+
41
+ def respond_to_missing?(method_name, include_private = false)
42
+ key?(method_name) || super
43
+ end
44
+
45
+ def to_h = @payload
46
+ end
47
+ end
48
+ end
data/lib/fuik/engine.rb CHANGED
@@ -6,5 +6,12 @@ module Fuik
6
6
 
7
7
  config.webhooks_controller_parent = "ActionController::Base"
8
8
  config.events_controller_parent = "ActionController::Base"
9
+
10
+ config.to_prepare do
11
+ ActiveSupport.on_load(:action_view) do
12
+ include Fuik::IconHelper
13
+ include Fuik::HighlightHelper
14
+ end
15
+ end
9
16
  end
10
17
  end
data/lib/fuik/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fuik
2
- VERSION = "0.8.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/fuik.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "fuik/version"
2
+ require "fuik/dot_access"
2
3
  require "fuik/engine"
3
4
 
4
5
  module Fuik
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fuik
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer
@@ -35,11 +35,43 @@ files:
35
35
  - MIT-LICENSE
36
36
  - README.md
37
37
  - Rakefile
38
+ - app/assets/images/fuik/icons/adyen.jpg
39
+ - app/assets/images/fuik/icons/anthropic.jpg
40
+ - app/assets/images/fuik/icons/apple.jpg
41
+ - app/assets/images/fuik/icons/basecamp.jpg
42
+ - app/assets/images/fuik/icons/chirpform.jpg
43
+ - app/assets/images/fuik/icons/dropbox.jpg
44
+ - app/assets/images/fuik/icons/facebook.jpg
45
+ - app/assets/images/fuik/icons/fizzy.jpg
46
+ - app/assets/images/fuik/icons/github.jpg
47
+ - app/assets/images/fuik/icons/gitlab.jpg
48
+ - app/assets/images/fuik/icons/google.jpg
49
+ - app/assets/images/fuik/icons/gumroad.jpg
50
+ - app/assets/images/fuik/icons/linkedin.jpg
51
+ - app/assets/images/fuik/icons/loops.jpg
52
+ - app/assets/images/fuik/icons/mailgun.jpg
53
+ - app/assets/images/fuik/icons/mailpace.jpg
54
+ - app/assets/images/fuik/icons/mollie.jpg
55
+ - app/assets/images/fuik/icons/moneybird.jpg
56
+ - app/assets/images/fuik/icons/openai.jpg
57
+ - app/assets/images/fuik/icons/postmark.jpg
58
+ - app/assets/images/fuik/icons/resend.jpg
59
+ - app/assets/images/fuik/icons/shopify.jpg
60
+ - app/assets/images/fuik/icons/slack.jpg
61
+ - app/assets/images/fuik/icons/stripe.jpg
62
+ - app/assets/images/fuik/icons/telegram.jpg
63
+ - app/assets/images/fuik/icons/twitter.jpg
64
+ - app/assets/images/fuik/icons/userlist.jpg
65
+ - app/assets/images/fuik/icons/webhook.svg
66
+ - app/assets/images/fuik/icons/zoom.jpg
38
67
  - app/assets/stylesheets/fuik/application.css
39
68
  - app/controllers/concerns/fuik/event_type.rb
40
69
  - app/controllers/fuik/application_controller.rb
70
+ - app/controllers/fuik/downloads_controller.rb
41
71
  - app/controllers/fuik/events_controller.rb
42
72
  - app/controllers/fuik/webhooks_controller.rb
73
+ - app/helpers/fuik/highlight_helper.rb
74
+ - app/helpers/fuik/icon.rb
43
75
  - app/jobs/fuik/application_job.rb
44
76
  - app/models/fuik/application_record.rb
45
77
  - app/models/fuik/event.rb
@@ -51,6 +83,7 @@ files:
51
83
  - config/routes.rb
52
84
  - db/migrate/20250101000000_create_webhook_events.rb
53
85
  - lib/fuik.rb
86
+ - lib/fuik/dot_access.rb
54
87
  - lib/fuik/engine.rb
55
88
  - lib/fuik/version.rb
56
89
  - lib/generators/fuik/install/install_generator.rb
@@ -90,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
123
  - !ruby/object:Gem::Version
91
124
  version: '0'
92
125
  requirements: []
93
- rubygems_version: 4.0.4
126
+ rubygems_version: 4.0.8
94
127
  specification_version: 4
95
128
  summary: A fish trap for webhooks
96
129
  test_files: []