From 9eb4c77d2b1f621b4715e3ee0a2583f5a4401a67 Mon Sep 17 00:00:00 2001 From: KodekaTeam Date: Wed, 15 Jul 2026 21:01:29 +0700 Subject: [PATCH] refactor(ui): standardize modal responsive layout --- src/app/apps/installed/page.tsx | 17 +- src/app/globals.css | 41 +- src/app/networks/page.tsx | 15 +- .../components/NotificationsSettingsPanel.tsx | 1570 +++++++++-------- .../components/S3StorageSettingsPanel.tsx | 12 +- .../ServerConfigWebServerPanel.tsx | 8 +- src/app/servers/page.tsx | 20 +- src/components/IssueDetailsSummary.tsx | 8 +- src/components/ProcessLogsModal.tsx | 38 +- 9 files changed, 872 insertions(+), 857 deletions(-) diff --git a/src/app/apps/installed/page.tsx b/src/app/apps/installed/page.tsx index 0bb5145..d74c24c 100644 --- a/src/app/apps/installed/page.tsx +++ b/src/app/apps/installed/page.tsx @@ -471,18 +471,7 @@ function InstallOperationsModal({ }, [activeTab, logContent, loading]); return ( -
+
@@ -1924,3 +1913,5 @@ export default function InstalledAppsPage() { ); } + + diff --git a/src/app/globals.css b/src/app/globals.css index 400cea6..eb144b1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -894,8 +894,10 @@ body { justify-content: center; z-index: 1000; animation: slide-in 0.2s ease-out; - padding: 16px; - overflow: hidden; + padding: clamp(8px, 2vw, 16px); + overflow-y: auto; + overflow-x: hidden; + box-sizing: border-box; isolation: isolate; contain: layout paint style; } @@ -904,10 +906,11 @@ body { background: var(--bg-card); border: 1px solid var(--border); border-radius: 14px; + box-sizing: border-box; padding: 24px; width: 100%; max-width: 480px; - max-height: calc(100vh - 32px); + max-height: calc(100dvh - 16px); overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; @@ -921,6 +924,8 @@ body { position: relative; width: 100%; max-width: 480px; + min-width: 0; + max-height: calc(100dvh - 16px); --modal-close-size: 28px; } @@ -936,7 +941,7 @@ body { .modal-wide { width: 100%; max-width: none; - max-height: calc(100dvh - 32px); + max-height: calc(100dvh - 16px); box-sizing: border-box; } @@ -995,8 +1000,29 @@ body { } @media (max-width: 640px) { + .modal-overlay { + align-items: flex-start; + padding: max(8px, env(safe-area-inset-top)) + max(8px, env(safe-area-inset-right)) + max(8px, env(safe-area-inset-bottom)) + max(8px, env(safe-area-inset-left)); + } + + .modal-shell { + max-width: none; + max-height: calc(100dvh - 16px); + } + + .modal { + max-height: calc(100dvh - 16px); + padding: 20px !important; + } + .modal-overlay-wide { - padding: 8px; + padding: max(8px, env(safe-area-inset-top)) + max(8px, env(safe-area-inset-right)) + max(8px, env(safe-area-inset-bottom)) + max(8px, env(safe-area-inset-left)); } .modal-shell-wide { @@ -1004,8 +1030,8 @@ body { } .modal-wide { - padding: 20px; - max-height: calc(100dvh - 32px); + padding: 20px !important; + max-height: calc(100dvh - 16px); } .modal-close { @@ -1106,4 +1132,3 @@ body { opacity: 1; transform: translateX(-50%) translateY(0); } - diff --git a/src/app/networks/page.tsx b/src/app/networks/page.tsx index 955562c..e58aca1 100644 --- a/src/app/networks/page.tsx +++ b/src/app/networks/page.tsx @@ -426,18 +426,7 @@ function NetworkDetailsModal({ ]; return ( -
+
{draft ? ( -
-
+
+
-
-
-

- {editingId ? "Update Notification" : "Add Notification"} -

-

- {editingId - ? "Update your notification providers for multiple channels." - : "Set up a new notification provider and configure which events will trigger notifications."} -

+
+
+

+ {editingId ? "Update Notification" : "Add Notification"} +

+

+ {editingId + ? "Update your notification providers for multiple channels." + : "Set up a new notification provider and configure which events will trigger notifications."} +

+
-
-
-
-

- Provider Type -

-
- {PROVIDER_OPTIONS.map((option) => { - const Icon = option.icon; - const selected = draft.type === option.type; +
+
+

+ Provider Type +

+
+ {PROVIDER_OPTIONS.map((option) => { + const Icon = option.icon; + const selected = draft.type === option.type; - return ( - + ); + })} +
+
+ +
+

+ Basic Information +

+
+
+ Provider name + setDraft((current) => current - ? { - ...current, - type: option.type, - name: - editingId || - current.name !== - getProviderMeta(current.type).label - ? current.name - : option.label, - } + ? { ...current, name: event.target.value } : current, ) } - style={{ - textAlign: "left", - padding: 14, - borderRadius: 14, - border: selected - ? `1px solid ${option.color}` - : "1px solid var(--border)", - background: selected - ? `${option.color}12` - : "var(--bg-card)", - display: "flex", - gap: 12, - alignItems: "flex-start", - cursor: "pointer", - }} - > -
- + placeholder="Contoh: Discord Infra, Telegram Ops, SMTP Finance" + /> +
+ + {draft.type === "email" ? ( + <> +
+ SMTP host + + setDraft((current) => + current + ? { ...current, smtpHost: event.target.value } + : current, + ) + } + placeholder="smtp.mailgun.org" + />
-

SMTP port + { + const nextPort = Number.parseInt( + event.target.value, + 10, + ); + setDraft((current) => + current + ? { + ...current, + smtpPort: Number.isNaN(nextPort) + ? 587 + : nextPort, + } + : current, + ); }} - > - {option.label} -

-

- {option.description} -

+ />
- - ); - })} -
-
+
+
+

+ Secure SMTP connection +

+

+ Activate if your SMTP server requires a secure + connection (TLS/SSL). +

+
+ + setDraft((current) => + current + ? { + ...current, + smtpSecure: !current.smtpSecure, + } + : current, + ) + } + /> +
+
+ SMTP username + + setDraft((current) => + current + ? { + ...current, + smtpUsername: event.target.value, + } + : current, + ) + } + placeholder="apikey or mailbox username" + /> +
+
+ SMTP password + + setDraft((current) => + current + ? { + ...current, + smtpPassword: event.target.value, + smtpPasswordConfigured: + current.smtpPasswordConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + /> + +
+
+ From email + + setDraft((current) => + current + ? { + ...current, + smtpFromEmail: event.target.value, + } + : current, + ) + } + placeholder="alerts@example.com" + /> +
+
+ From name + + setDraft((current) => + current + ? { + ...current, + smtpFromName: event.target.value, + } + : current, + ) + } + placeholder="Doktainer Alerts" + /> +
+ + ) : null} -
-

- Basic Information -

-
-
- Provider name - + {draft.type === "telegram" ? ( + <> +
+ Bot token + + setDraft((current) => + current + ? { + ...current, + telegramBotToken: event.target.value, + telegramBotTokenConfigured: + current.telegramBotTokenConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + /> + +
+
+ Chat ID + + setDraft((current) => + current + ? { + ...current, + telegramChatId: event.target.value, + } + : current, + ) + } + placeholder="-100xxxxxxxxxx" + /> +
+ + ) : null} + + {[ + "discord", + "slack", + "lark", + "teams", + "mattermost", + "custom", + ].includes(draft.type) ? ( + <> +
+ Webhook URL + + setDraft((current) => + current + ? { + ...current, + webhookUrl: event.target.value, + webhookConfigured: + current.webhookConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + placeholder="https://hooks.example.com/..." + /> + +
+
+ Channel / label + + setDraft((current) => + current + ? { ...current, channel: event.target.value } + : current, + ) + } + placeholder="#infra-alerts or Incident Room" + /> +
+ + ) : null} + + {draft.type === "resend" ? ( + <> +
+ API key + + setDraft((current) => + current + ? { + ...current, + apiKey: event.target.value, + apiKeyConfigured: + current.apiKeyConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + /> + +
+
+ From email + + setDraft((current) => + current + ? { + ...current, + smtpFromEmail: event.target.value, + } + : current, + ) + } + /> +
+
+ From name + + setDraft((current) => + current + ? { + ...current, + smtpFromName: event.target.value, + } + : current, + ) + } + /> +
+ + ) : null} + + {draft.type === "gotify" ? ( + <> +
+ Server URL + + setDraft((current) => + current + ? { + ...current, + serverUrl: event.target.value, + } + : current, + ) + } + placeholder="https://gotify.example.com" + /> +
+
+ API key + + setDraft((current) => + current + ? { + ...current, + apiKey: event.target.value, + apiKeyConfigured: + current.apiKeyConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + /> + +
+ + ) : null} + + {draft.type === "ntfy" ? ( + <> +
+ Server URL + + setDraft((current) => + current + ? { + ...current, + serverUrl: event.target.value, + } + : current, + ) + } + placeholder="https://ntfy.sh or internal endpoint" + /> +
+
+ Topic + + setDraft((current) => + current + ? { ...current, topic: event.target.value } + : current, + ) + } + placeholder="dokta infra alerts" + /> +
+ + ) : null} + + {draft.type === "pushover" ? ( + <> +
+ User key + + setDraft((current) => + current + ? { ...current, userKey: event.target.value } + : current, + ) + } + /> +
+
+ API token + + setDraft((current) => + current + ? { + ...current, + apiKey: event.target.value, + apiKeyConfigured: + current.apiKeyConfigured || + Boolean(event.target.value.trim()), + } + : current, + ) + } + /> + +
+ + ) : null} +
+
+ +
+
+
+

+ Alert Actions +

+

+ Old actions remain available, and new actions can be + enabled per provider. +

+
+ setDraft((current) => current - ? { ...current, name: event.target.value } + ? { ...current, enabled: !current.enabled } : current, ) } - placeholder="Contoh: Discord Infra, Telegram Ops, SMTP Finance" />
- {draft.type === "email" ? ( - <> -
- SMTP host - - setDraft((current) => - current - ? { ...current, smtpHost: event.target.value } - : current, - ) - } - placeholder="smtp.mailgun.org" - /> -
-
- SMTP port - { - const nextPort = Number.parseInt( - event.target.value, - 10, - ); - setDraft((current) => - current - ? { - ...current, - smtpPort: Number.isNaN(nextPort) - ? 587 - : nextPort, - } - : current, - ); +
+ {NOTIFICATION_ACTION_OPTIONS.map((action) => { + const checked = draft.actions.includes(action.value); + + return ( +
-
-
-
-

- Secure SMTP connection -

-

- Activate if your SMTP server requires a secure - connection (TLS/SSL). -

+ > +
+

+ {action.label} +

+

+ {action.description} +

+
+ + setDraft((current) => { + if (!current) return current; + + return { + ...current, + actions: checked + ? current.actions.filter( + (item) => item !== action.value, + ) + : [...current.actions, action.value], + }; + }) + } + />
- - setDraft((current) => - current - ? { - ...current, - smtpSecure: !current.smtpSecure, - } - : current, - ) - } - /> -
-
- SMTP username - - setDraft((current) => - current - ? { - ...current, - smtpUsername: event.target.value, - } - : current, - ) - } - placeholder="apikey or mailbox username" - /> -
-
- SMTP password - - setDraft((current) => - current - ? { - ...current, - smtpPassword: event.target.value, - smtpPasswordConfigured: - current.smtpPasswordConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - /> - -
-
- From email - - setDraft((current) => - current - ? { - ...current, - smtpFromEmail: event.target.value, - } - : current, - ) - } - placeholder="alerts@example.com" - /> -
-
- From name - - setDraft((current) => - current - ? { - ...current, - smtpFromName: event.target.value, - } - : current, - ) - } - placeholder="Doktainer Alerts" - /> -
- - ) : null} - - {draft.type === "telegram" ? ( - <> -
- Bot token - - setDraft((current) => - current - ? { - ...current, - telegramBotToken: event.target.value, - telegramBotTokenConfigured: - current.telegramBotTokenConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - /> - -
-
- Chat ID - - setDraft((current) => - current - ? { - ...current, - telegramChatId: event.target.value, - } - : current, - ) - } - placeholder="-100xxxxxxxxxx" - /> -
- - ) : null} - - {[ - "discord", - "slack", - "lark", - "teams", - "mattermost", - "custom", - ].includes(draft.type) ? ( - <> -
- Webhook URL - - setDraft((current) => - current - ? { - ...current, - webhookUrl: event.target.value, - webhookConfigured: - current.webhookConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - placeholder="https://hooks.example.com/..." - /> - -
-
- Channel / label - - setDraft((current) => - current - ? { ...current, channel: event.target.value } - : current, - ) - } - placeholder="#infra-alerts or Incident Room" - /> -
- - ) : null} - - {draft.type === "resend" ? ( - <> -
- API key - - setDraft((current) => - current - ? { - ...current, - apiKey: event.target.value, - apiKeyConfigured: - current.apiKeyConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - /> - -
-
- From email - - setDraft((current) => - current - ? { - ...current, - smtpFromEmail: event.target.value, - } - : current, - ) - } - /> -
-
- From name - - setDraft((current) => - current - ? { - ...current, - smtpFromName: event.target.value, - } - : current, - ) - } - /> -
- - ) : null} - - {draft.type === "gotify" ? ( - <> -
- Server URL - - setDraft((current) => - current - ? { ...current, serverUrl: event.target.value } - : current, - ) - } - placeholder="https://gotify.example.com" - /> -
-
- API key - - setDraft((current) => - current - ? { - ...current, - apiKey: event.target.value, - apiKeyConfigured: - current.apiKeyConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - /> - -
- - ) : null} - - {draft.type === "ntfy" ? ( - <> -
- Server URL - - setDraft((current) => - current - ? { ...current, serverUrl: event.target.value } - : current, - ) - } - placeholder="https://ntfy.sh or internal endpoint" - /> -
-
- Topic - - setDraft((current) => - current - ? { ...current, topic: event.target.value } - : current, - ) - } - placeholder="dokta infra alerts" - /> -
- - ) : null} - - {draft.type === "pushover" ? ( - <> -
- User key - - setDraft((current) => - current - ? { ...current, userKey: event.target.value } - : current, - ) - } - /> -
-
- API token - - setDraft((current) => - current - ? { - ...current, - apiKey: event.target.value, - apiKeyConfigured: - current.apiKeyConfigured || - Boolean(event.target.value.trim()), - } - : current, - ) - } - /> - -
- - ) : null} -
-
- -
-
-
-

- Alert Actions -

-

- Old actions remain available, and new actions can be - enabled per provider. -

+ ); + })}
- - setDraft((current) => - current - ? { ...current, enabled: !current.enabled } - : current, - ) - } - /> +
+
+ +
+
+ {editingId && TESTABLE_PROVIDER_TYPES.includes(draft.type) ? ( + + ) : null}
- -
- {NOTIFICATION_ACTION_OPTIONS.map((action) => { - const checked = draft.actions.includes(action.value); - - return ( -
-
-

- {action.label} -

-

- {action.description} -

-
- - setDraft((current) => { - if (!current) return current; - - return { - ...current, - actions: checked - ? current.actions.filter( - (item) => item !== action.value, - ) - : [...current.actions, action.value], - }; - }) - } - /> -
- ); - })} -
-
-
- -
-
- {editingId && TESTABLE_PROVIDER_TYPES.includes(draft.type) ? ( +
- ) : null} + +
-
- - -
-
diff --git a/src/app/s3-storage/components/S3StorageSettingsPanel.tsx b/src/app/s3-storage/components/S3StorageSettingsPanel.tsx index 004d305..a5aefff 100644 --- a/src/app/s3-storage/components/S3StorageSettingsPanel.tsx +++ b/src/app/s3-storage/components/S3StorageSettingsPanel.tsx @@ -160,8 +160,8 @@ function getDestinationSummary(destination: DestinationDraft) { } return parts.length > 0 - ? parts.join(" • ") - : "Bucket dan region belum lengkap"; + ? parts.join(" • ") + : "Bucket and region are incomplete"; } function StatusButton({ @@ -604,11 +604,8 @@ export default function S3StorageSettingsPanel({
{draft ? ( -
-
+
+