cover/Elixir.DaProductAppWeb.SettingsLive.html

1
:-(
defmodule DaProductAppWeb.SettingsLive do
2 use DaProductAppWeb, :live_view
3
4 alias DaProductApp.Accounts
5 alias DaProductApp.Settings
6
7
:-(
def mount(_params, session, socket) do
8
:-(
case get_current_user(session) do
9
:-(
nil ->
10 {:ok, redirect(socket, to: ~p"/login")}
11
12 user ->
13
:-(
if has_access?(user) do
14 # Initialize state
15
:-(
socket =
16 socket
17 |> assign(:current_user, user)
18 |> assign(:page_title, "Settings")
19 |> assign(:current_page, :settings)
20 |> assign(
21 active_tab: "general",
22 settings: %{},
23 loading: true,
24 saving: false,
25 changes_made: false,
26 show_confirmation_modal: false,
27 pending_changes: %{}
28 )
29 |> load_all_settings()
30 |> assign(loading: false)
31
32 {:ok, socket}
33 else
34 {:ok,
35 socket
36 |> put_flash(:error, "Access denied")
37 |> redirect(to: ~p"/login")}
38 end
39 end
40 end
41
42
:-(
def handle_params(%{"tab" => tab}, _uri, socket) do
43
:-(
valid_tabs = ["general", "upi", "international", "security", "notifications", "integrations", "system"]
44
45
:-(
tab = if tab in valid_tabs, do: tab, else: "general"
46
47
:-(
socket = assign(socket, active_tab: tab)
48 {:noreply, socket}
49 end
50
51
:-(
def handle_params(_params, _uri, socket) do
52 {:noreply, assign(socket, active_tab: "general")}
53 end
54
55
:-(
def handle_event("change_tab", %{"tab" => tab}, socket) do
56 {:noreply, push_patch(socket, to: ~p"/settings/#{tab}")}
57 end
58
59
:-(
def handle_event("update_setting", %{"category" => category, "key" => key, "value" => value}, socket) do
60
:-(
current_settings = socket.assigns.settings
61
:-(
pending_changes = socket.assigns.pending_changes
62
63 # Update the setting in memory
64
:-(
updated_settings = put_in(current_settings, [category, key], parse_setting_value(value))
65
:-(
updated_pending = put_in(pending_changes, [category, key], parse_setting_value(value))
66
67
:-(
socket =
68 socket
69 |> assign(
70 settings: updated_settings,
71 pending_changes: updated_pending,
72 changes_made: true
73 )
74
75 {:noreply, socket}
76 end
77
78
:-(
def handle_event("toggle_setting", %{"category" => category, "key" => key}, socket) do
79
:-(
current_value = get_in(socket.assigns.settings, [category, key]) || false
80
:-(
new_value = not current_value
81
82
:-(
current_settings = socket.assigns.settings
83
:-(
pending_changes = socket.assigns.pending_changes
84
85
:-(
updated_settings = put_in(current_settings, [category, key], new_value)
86
:-(
updated_pending = put_in(pending_changes, [category, key], new_value)
87
88
:-(
socket =
89 socket
90 |> assign(
91 settings: updated_settings,
92 pending_changes: updated_pending,
93 changes_made: true
94 )
95
96 {:noreply, socket}
97 end
98
99
:-(
def handle_event("save_settings", _params, socket) do
100
:-(
socket = assign(socket, saving: true)
101
102 # Simulate saving (in real implementation, this would save to database)
103
:-(
Process.send_after(self(), :save_complete, 1000)
104
105 {:noreply, socket}
106 end
107
108
:-(
def handle_event("discard_changes", _params, socket) do
109
:-(
socket =
110 socket
111 |> assign(show_confirmation_modal: true)
112
113 {:noreply, socket}
114 end
115
116
:-(
def handle_event("confirm_discard", _params, socket) do
117
:-(
socket =
118 socket
119 |> load_all_settings()
120 |> assign(
121 changes_made: false,
122 pending_changes: %{},
123 show_confirmation_modal: false
124 )
125
126 {:noreply, socket}
127 end
128
129
:-(
def handle_event("cancel_discard", _params, socket) do
130
:-(
socket = assign(socket, show_confirmation_modal: false)
131 {:noreply, socket}
132 end
133
134
:-(
def handle_event("reset_to_default", %{"category" => category}, socket) do
135
:-(
default_settings = get_default_settings()
136
:-(
current_settings = socket.assigns.settings
137
:-(
pending_changes = socket.assigns.pending_changes
138
139 # Reset category to defaults
140
:-(
updated_settings = Map.put(current_settings, category, default_settings[category])
141
:-(
updated_pending = Map.put(pending_changes, category, default_settings[category])
142
143
:-(
socket =
144 socket
145 |> assign(
146 settings: updated_settings,
147 pending_changes: updated_pending,
148 changes_made: true
149 )
150
151 {:noreply, socket}
152 end
153
154
:-(
def handle_event("test_connection", %{"service" => service}, socket) do
155 # Simulate connection test
156
:-(
Process.send_after(self(), {:test_result, service, :success}, 2000)
157
158
:-(
socket = put_flash(socket, :info, "Testing #{service} connection...")
159 {:noreply, socket}
160 end
161
162
:-(
def handle_info(:save_complete, socket) do
163
:-(
socket =
164 socket
165 |> assign(
166 saving: false,
167 changes_made: false,
168 pending_changes: %{}
169 )
170 |> put_flash(:success, "Settings saved successfully")
171
172 {:noreply, socket}
173 end
174
175
:-(
def handle_info({:test_result, service, result}, socket) do
176
:-(
message = case result do
177
:-(
:success -> "#{service} connection test successful"
178
:-(
:error -> "#{service} connection test failed"
179 end
180
181
:-(
flash_type = if result == :success, do: :success, else: :error
182
183
:-(
socket = put_flash(socket, flash_type, message)
184 {:noreply, socket}
185 end
186
187 defp load_all_settings(socket) do
188 # In real implementation, this would load from database/config
189
:-(
settings = get_default_settings()
190
:-(
assign(socket, settings: settings)
191 end
192
193 defp get_default_settings do
194
:-(
%{
195 "general" => %{
196 "platform_name" => "Mercury UPI PSP",
197 "platform_description" => "Advanced UPI Payment Service Provider",
198 "default_timezone" => "Asia/Kolkata",
199 "default_currency" => "INR",
200 "maintenance_mode" => false,
201 "debug_mode" => false,
202 "max_transaction_amount" => "200000.00",
203 "min_transaction_amount" => "1.00"
204 },
205 "upi" => %{
206 "npci_endpoint" => "https://api.npci.org.in/v1",
207 "npci_timeout" => "30",
208 "retry_attempts" => "3",
209 "retry_delay" => "5",
210 "qr_expiry_minutes" => "15",
211 "max_qr_per_merchant" => "100",
212 "validate_vpa_real_time" => true,
213 "allow_zero_amount_qr" => true,
214 "mandate_beneficiary_validation" => true
215 },
216 "international" => %{
217 "enable_international_payments" => true,
218 "default_fx_provider" => "reuters",
219 "fx_rate_refresh_interval" => "300",
220 "fx_rate_tolerance" => "0.05",
221 "supported_corridors" => ["SGD-INR", "USD-INR", "AED-INR"],
222 "max_international_amount" => "500000.00",
223 "compliance_check_required" => true,
224 "auto_settlement_enabled" => false
225 },
226 "security" => %{
227 "encryption_enabled" => true,
228 "encryption_algorithm" => "AES-256-GCM",
229 "session_timeout" => "30",
230 "max_login_attempts" => "5",
231 "lockout_duration" => "15",
232 "require_2fa" => false,
233 "ip_whitelist_enabled" => false,
234 "audit_logging_enabled" => true,
235 "sensitive_data_masking" => true
236 },
237 "notifications" => %{
238 "email_enabled" => true,
239 "sms_enabled" => true,
240 "webhook_enabled" => true,
241 "push_notifications_enabled" => false,
242 "transaction_alerts" => true,
243 "failure_alerts" => true,
244 "system_alerts" => true,
245 "daily_reports" => true,
246 "alert_threshold_amount" => "50000.00"
247 },
248 "integrations" => %{
249 "razorpay_enabled" => false,
250 "razorpay_api_key" => "",
251 "razorpay_webhook_secret" => "",
252 "paytm_enabled" => true,
253 "paytm_merchant_id" => "PAYTM_MERCHANT_001",
254 "paytm_api_key" => "",
255 "phonepe_enabled" => true,
256 "phonepe_merchant_id" => "PHONEPE_MERCHANT_001",
257 "phonepe_api_key" => ""
258 },
259 "system" => %{
260 "max_concurrent_transactions" => "1000",
261 "queue_size_limit" => "10000",
262 "worker_pool_size" => "50",
263 "database_pool_size" => "20",
264 "cache_ttl_seconds" => "3600",
265 "log_level" => "info",
266 "metrics_enabled" => true,
267 "health_check_interval" => "60",
268 "backup_enabled" => true,
269 "backup_frequency" => "daily"
270 }
271 }
272 end
273
274 defp parse_setting_value(value) when is_binary(value) do
275
:-(
cond do
276
:-(
value == "true" -> true
277
:-(
value == "false" -> false
278
:-(
String.match?(value, ~r/^\d+$/) -> String.to_integer(value)
279
:-(
String.match?(value, ~r/^\d+\.\d+$/) -> String.to_float(value)
280
:-(
true -> value
281 end
282 end
283
284
:-(
defp parse_setting_value(value), do: value
285
286 # Helper functions for the UI
287
:-(
def get_tab_config do
288 [
289 %{
290 id: "general",
291 name: "General",
292 icon: "⚙️",
293 description: "Basic platform configuration"
294 },
295 %{
296 id: "upi",
297 name: "UPI Settings",
298 icon: "💳",
299 description: "UPI payment processing settings"
300 },
301 %{
302 id: "international",
303 name: "International",
304 icon: "🌍",
305 description: "Cross-border payment settings"
306 },
307 %{
308 id: "security",
309 name: "Security",
310 icon: "🔒",
311 description: "Security and authentication settings"
312 },
313 %{
314 id: "notifications",
315 name: "Notifications",
316 icon: "🔔",
317 description: "Alert and notification settings"
318 },
319 %{
320 id: "integrations",
321 name: "Integrations",
322 icon: "🔗",
323 description: "Third-party service integrations"
324 },
325 %{
326 id: "system",
327 name: "System",
328 icon: "🖥️",
329 description: "System performance and monitoring"
330 }
331 ]
332 end
333
334 def get_setting_type(value) do
335
:-(
cond do
336
:-(
is_boolean(value) -> :boolean
337
:-(
is_integer(value) -> :integer
338
:-(
is_float(value) -> :float
339
:-(
is_list(value) -> :list
340
:-(
true -> :string
341 end
342 end
343
344 def get_setting_description(category, key) do
345
:-(
descriptions = %{
346 "general" => %{
347 "platform_name" => "Display name for the platform",
348 "platform_description" => "Brief description of the platform",
349 "default_timezone" => "Default timezone for the platform",
350 "default_currency" => "Default currency code",
351 "maintenance_mode" => "Enable maintenance mode",
352 "debug_mode" => "Enable debug logging",
353 "max_transaction_amount" => "Maximum transaction amount allowed",
354 "min_transaction_amount" => "Minimum transaction amount allowed"
355 },
356 "upi" => %{
357 "npci_endpoint" => "NPCI API endpoint URL",
358 "npci_timeout" => "API request timeout in seconds",
359 "retry_attempts" => "Number of retry attempts for failed requests",
360 "retry_delay" => "Delay between retry attempts in seconds",
361 "qr_expiry_minutes" => "QR code expiry time in minutes",
362 "max_qr_per_merchant" => "Maximum QR codes per merchant",
363 "validate_vpa_real_time" => "Real-time VPA validation",
364 "allow_zero_amount_qr" => "Allow QR codes with zero amount",
365 "mandate_beneficiary_validation" => "Require beneficiary validation"
366 },
367 "international" => %{
368 "enable_international_payments" => "Enable international payment processing",
369 "default_fx_provider" => "Default foreign exchange rate provider",
370 "fx_rate_refresh_interval" => "FX rate refresh interval in seconds",
371 "fx_rate_tolerance" => "FX rate tolerance percentage",
372 "supported_corridors" => "List of supported payment corridors",
373 "max_international_amount" => "Maximum international payment amount",
374 "compliance_check_required" => "Require compliance checks",
375 "auto_settlement_enabled" => "Enable automatic settlement"
376 },
377 "security" => %{
378 "encryption_enabled" => "Enable data encryption",
379 "encryption_algorithm" => "Encryption algorithm to use",
380 "session_timeout" => "User session timeout in minutes",
381 "max_login_attempts" => "Maximum login attempts before lockout",
382 "lockout_duration" => "Account lockout duration in minutes",
383 "require_2fa" => "Require two-factor authentication",
384 "ip_whitelist_enabled" => "Enable IP address whitelisting",
385 "audit_logging_enabled" => "Enable audit logging",
386 "sensitive_data_masking" => "Mask sensitive data in logs"
387 },
388 "notifications" => %{
389 "email_enabled" => "Enable email notifications",
390 "sms_enabled" => "Enable SMS notifications",
391 "webhook_enabled" => "Enable webhook notifications",
392 "push_notifications_enabled" => "Enable push notifications",
393 "transaction_alerts" => "Send transaction alerts",
394 "failure_alerts" => "Send failure alerts",
395 "system_alerts" => "Send system alerts",
396 "daily_reports" => "Send daily reports",
397 "alert_threshold_amount" => "Amount threshold for alerts"
398 },
399 "integrations" => %{
400 "razorpay_enabled" => "Enable Razorpay integration",
401 "razorpay_api_key" => "Razorpay API key",
402 "razorpay_webhook_secret" => "Razorpay webhook secret",
403 "paytm_enabled" => "Enable Paytm integration",
404 "paytm_merchant_id" => "Paytm merchant ID",
405 "paytm_api_key" => "Paytm API key",
406 "phonepe_enabled" => "Enable PhonePe integration",
407 "phonepe_merchant_id" => "PhonePe merchant ID",
408 "phonepe_api_key" => "PhonePe API key"
409 },
410 "system" => %{
411 "max_concurrent_transactions" => "Maximum concurrent transactions",
412 "queue_size_limit" => "Maximum queue size",
413 "worker_pool_size" => "Worker pool size",
414 "database_pool_size" => "Database connection pool size",
415 "cache_ttl_seconds" => "Cache TTL in seconds",
416 "log_level" => "Logging level",
417 "metrics_enabled" => "Enable metrics collection",
418 "health_check_interval" => "Health check interval in seconds",
419 "backup_enabled" => "Enable automatic backups",
420 "backup_frequency" => "Backup frequency"
421 }
422 }
423
424
:-(
get_in(descriptions, [category, key]) || "Configuration setting"
425 end
426
427 def is_sensitive_setting?(category, key) do
428
:-(
sensitive_keys = [
429 "api_key", "secret", "password", "token", "private_key",
430 "webhook_secret", "encryption_key"
431 ]
432
433
:-(
Enum.any?(sensitive_keys, &String.contains?(key, &1))
434 end
435
436 def format_setting_value(value) when is_list(value) do
437
:-(
Enum.join(value, ", ")
438 end
439
440
:-(
def format_setting_value(value), do: to_string(value)
441
442 def get_setting_options(category, key) do
443
:-(
case {category, key} do
444
:-(
{"general", "default_timezone"} -> [
445 "Asia/Kolkata", "UTC", "Asia/Singapore", "America/New_York", "Europe/London"
446 ]
447
:-(
{"general", "default_currency"} -> [
448 "INR", "USD", "SGD", "AED", "EUR", "GBP"
449 ]
450
:-(
{"international", "default_fx_provider"} -> [
451 "reuters", "bloomberg", "xe", "fixer", "currencylayer"
452 ]
453
:-(
{"security", "encryption_algorithm"} -> [
454 "AES-256-GCM", "AES-256-CBC", "ChaCha20-Poly1305"
455 ]
456
:-(
{"system", "log_level"} -> [
457 "debug", "info", "warn", "error"
458 ]
459
:-(
{"system", "backup_frequency"} -> [
460 "hourly", "daily", "weekly", "monthly"
461 ]
462
:-(
_ -> nil
463 end
464 end
465
466 # Helper functions for authentication and access control
467
:-(
defp get_current_user(session) do
468
:-(
case session do
469 %{"user_id" => user_id} when is_binary(user_id) or is_integer(user_id) ->
470
:-(
Accounts.get_user!(user_id)
471
:-(
_ ->
472 nil
473 end
474 rescue
475
:-(
_ -> nil
476 end
477
478
:-(
defp has_access?(_user), do: true
479 end
Line Hits Source