diff --git a/web/.prettierignore b/web/.prettierignore index d0097d3..d50a46c 100644 --- a/web/.prettierignore +++ b/web/.prettierignore @@ -1,2 +1,2 @@ build/ -public/static/langs/ \ No newline at end of file +public/static/langs/ diff --git a/web/package.json b/web/package.json index 1ca2da7..10c198d 100644 --- a/web/package.json +++ b/web/package.json @@ -43,5 +43,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "prettier": { + "printWidth": 160 } } diff --git a/web/public/config.js b/web/public/config.js index a748dd8..2f46d65 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -15,15 +15,5 @@ var config = { enable_emails: true, enable_calls: true, billing_contact: "", - disallowed_topics: [ - "docs", - "static", - "file", - "app", - "account", - "settings", - "signup", - "login", - "v1", - ], + disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"], }; diff --git a/web/public/index.html b/web/public/index.html index 31dd280..e59a62e 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -5,10 +5,7 @@ ntfy web - + @@ -18,11 +15,7 @@ - + @@ -40,23 +33,13 @@ - - + +
diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index 3f11611..9af220a 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -56,9 +56,7 @@ class AccountApi { async logout() { const url = accountTokenUrl(config.base_url); - console.log( - `[AccountApi] Logging out from ${url} using token ${session.token()}` - ); + console.log(`[AccountApi] Logging out from ${url} using token ${session.token()}`); await fetchOrThrow(url, { method: "DELETE", headers: withBearerAuth({}, session.token()), @@ -227,9 +225,7 @@ class AccountApi { async upsertReservation(topic, everyone) { const url = accountReservationUrl(config.base_url); - console.log( - `[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}` - ); + console.log(`[AccountApi] Upserting user access to topic ${topic}, everyone=${everyone}`); await fetchOrThrow(url, { method: "POST", headers: withBearerAuth({}, session.token()), @@ -264,16 +260,12 @@ class AccountApi { } async createBillingSubscription(tier, interval) { - console.log( - `[AccountApi] Creating billing subscription with ${tier} and interval ${interval}` - ); + console.log(`[AccountApi] Creating billing subscription with ${tier} and interval ${interval}`); return await this.upsertBillingSubscription("POST", tier, interval); } async updateBillingSubscription(tier, interval) { - console.log( - `[AccountApi] Updating billing subscription with ${tier} and interval ${interval}` - ); + console.log(`[AccountApi] Updating billing subscription with ${tier} and interval ${interval}`); return await this.upsertBillingSubscription("PUT", tier, interval); } @@ -324,9 +316,7 @@ class AccountApi { async addPhoneNumber(phoneNumber, code) { const url = accountPhoneUrl(config.base_url); - console.log( - `[AccountApi] Adding phone number with verification code ${url}` - ); + console.log(`[AccountApi] Adding phone number with verification code ${url}`); await fetchOrThrow(url, { method: "PUT", headers: withBearerAuth({}, session.token()), @@ -371,10 +361,7 @@ class AccountApi { } } if (account.subscriptions) { - await subscriptionManager.syncFromRemote( - account.subscriptions, - account.reservations - ); + await subscriptionManager.syncFromRemote(account.subscriptions, account.reservations); } return account; } catch (e) { diff --git a/web/src/app/Api.js b/web/src/app/Api.js index 345b0f2..4d7ce82 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -1,12 +1,4 @@ -import { - fetchLinesIterator, - maybeWithAuth, - topicShortUrl, - topicUrl, - topicUrlAuth, - topicUrlJsonPoll, - topicUrlJsonPollWithSince, -} from "./utils"; +import { fetchLinesIterator, maybeWithAuth, topicShortUrl, topicUrl, topicUrlAuth, topicUrlJsonPoll, topicUrlJsonPollWithSince } from "./utils"; import userManager from "./UserManager"; import { fetchOrThrow } from "./errors"; @@ -14,9 +6,7 @@ class Api { async poll(baseUrl, topic, since) { const user = await userManager.get(baseUrl); const shortUrl = topicShortUrl(baseUrl, topic); - const url = since - ? topicUrlJsonPollWithSince(baseUrl, topic, since) - : topicUrlJsonPoll(baseUrl, topic); + const url = since ? topicUrlJsonPollWithSince(baseUrl, topic, since) : topicUrlJsonPoll(baseUrl, topic); const messages = []; const headers = maybeWithAuth({}, user); console.log(`[Api] Polling ${url}`); @@ -73,17 +63,11 @@ class Api { xhr.upload.addEventListener("progress", onProgress); xhr.addEventListener("readystatechange", () => { if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status <= 299) { - console.log( - `[Api] Publish successful (HTTP ${xhr.status})`, - xhr.response - ); + console.log(`[Api] Publish successful (HTTP ${xhr.status})`, xhr.response); resolve(xhr.response); } else if (xhr.readyState === 4) { // Firefox bug; see description above! - console.log( - `[Api] Publish failed (HTTP ${xhr.status})`, - xhr.responseText - ); + console.log(`[Api] Publish failed (HTTP ${xhr.status})`, xhr.responseText); let errorText; try { const error = JSON.parse(xhr.responseText); diff --git a/web/src/app/Connection.js b/web/src/app/Connection.js index 5dfc41b..2341678 100644 --- a/web/src/app/Connection.js +++ b/web/src/app/Connection.js @@ -1,10 +1,4 @@ -import { - basicAuth, - bearerAuth, - encodeBase64Url, - topicShortUrl, - topicUrlWs, -} from "./utils"; +import { basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs } from "./utils"; const retryBackoffSeconds = [5, 10, 20, 30, 60, 120]; @@ -15,16 +9,7 @@ const retryBackoffSeconds = [5, 10, 20, 30, 60, 120]; * Incoming messages and state changes are forwarded via listeners. */ class Connection { - constructor( - connectionId, - subscriptionId, - baseUrl, - topic, - user, - since, - onNotification, - onStateChanged - ) { + constructor(connectionId, subscriptionId, baseUrl, topic, user, since, onNotification, onStateChanged) { this.connectionId = connectionId; this.subscriptionId = subscriptionId; this.baseUrl = baseUrl; @@ -44,78 +29,51 @@ class Connection { // we don't want to re-trigger the main view re-render potentially hundreds of times. const wsUrl = this.wsUrl(); - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Opening connection to ${wsUrl}`); this.ws = new WebSocket(wsUrl); this.ws.onopen = (event) => { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, - event - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection established`, event); this.retryCount = 0; this.onStateChanged(this.subscriptionId, ConnectionState.Connected); }; this.ws.onmessage = (event) => { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Message received from server: ${event.data}`); try { const data = JSON.parse(event.data); if (data.event === "open") { return; } - const relevantAndValid = - data.event === "message" && - "id" in data && - "time" in data && - "message" in data; + const relevantAndValid = data.event === "message" && "id" in data && "time" in data && "message" in data; if (!relevantAndValid) { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Unexpected message. Ignoring.`); return; } this.since = data.id; this.onNotification(this.subscriptionId, data); } catch (e) { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error handling message: ${e}`); } }; this.ws.onclose = (event) => { if (event.wasClean) { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection closed cleanly, code=${event.code} reason=${event.reason}`); this.ws = null; } else { - const retrySeconds = - retryBackoffSeconds[ - Math.min(this.retryCount, retryBackoffSeconds.length - 1) - ]; + const retrySeconds = retryBackoffSeconds[Math.min(this.retryCount, retryBackoffSeconds.length - 1)]; this.retryCount++; - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Connection died, retrying in ${retrySeconds} seconds`); this.retryTimeout = setTimeout(() => this.start(), retrySeconds * 1000); this.onStateChanged(this.subscriptionId, ConnectionState.Connecting); } }; this.ws.onerror = (event) => { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, - event - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Error occurred: ${event}`, event); }; } close() { - console.log( - `[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection` - ); + console.log(`[Connection, ${this.shortUrl}, ${this.connectionId}] Closing connection`); const socket = this.ws; const retryTimeout = this.retryTimeout; if (socket !== null) { diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js index ced32d5..15b94cd 100644 --- a/web/src/app/ConnectionManager.js +++ b/web/src/app/ConnectionManager.js @@ -49,12 +49,8 @@ class ConnectionManager { return { ...s, user, connectionId }; }) ); - const targetIds = subscriptionsWithUsersAndConnectionId.map( - (s) => s.connectionId - ); - const deletedIds = Array.from(this.connections.keys()).filter( - (id) => !targetIds.includes(id) - ); + const targetIds = subscriptionsWithUsersAndConnectionId.map((s) => s.connectionId); + const deletedIds = Array.from(this.connections.keys()).filter((id) => !targetIds.includes(id)); // Create and add new connections subscriptionsWithUsersAndConnectionId.forEach((subscription) => { @@ -73,15 +69,12 @@ class ConnectionManager { topic, user, since, - (subscriptionId, notification) => - this.notificationReceived(subscriptionId, notification), + (subscriptionId, notification) => this.notificationReceived(subscriptionId, notification), (subscriptionId, state) => this.stateChanged(subscriptionId, state) ); this.connections.set(connectionId, connection); console.log( - `[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${ - user ? user.username : "anonymous" - })` + `[ConnectionManager] Starting new connection ${connectionId} (subscription ${subscriptionId} with user ${user ? user.username : "anonymous"})` ); connection.start(); } @@ -101,10 +94,7 @@ class ConnectionManager { try { this.stateListener(subscriptionId, state); } catch (e) { - console.error( - `[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`, - e - ); + console.error(`[ConnectionManager] Error updating state of ${subscriptionId} to ${state}`, e); } } } @@ -114,23 +104,14 @@ class ConnectionManager { try { this.messageListener(subscriptionId, notification); } catch (e) { - console.error( - `[ConnectionManager] Error handling notification for ${subscriptionId}`, - e - ); + console.error(`[ConnectionManager] Error handling notification for ${subscriptionId}`, e); } } } } const makeConnectionId = async (subscription, user) => { - return user - ? hashCode( - `${subscription.id}|${user.username}|${user.password ?? ""}|${ - user.token ?? "" - }` - ) - : hashCode(`${subscription.id}`); + return user ? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`) : hashCode(`${subscription.id}`); }; const connectionManager = new ConnectionManager(); diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js index e4396d2..2d00dea 100644 --- a/web/src/app/Notifier.js +++ b/web/src/app/Notifier.js @@ -1,11 +1,4 @@ -import { - formatMessage, - formatTitleWithDefault, - openUrl, - playSound, - topicDisplayName, - topicShortUrl, -} from "./utils"; +import { formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl } from "./utils"; import prefs from "./Prefs"; import subscriptionManager from "./SubscriptionManager"; import logo from "../img/ntfy.png"; @@ -30,9 +23,7 @@ class Notifier { const title = formatTitleWithDefault(notification, displayName); // Show notification - console.log( - `[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}` - ); + console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`); const n = new Notification(title, { body: message, icon: logo, @@ -96,11 +87,7 @@ class Notifier { * is not supported, see https://developer.mozilla.org/en-US/docs/Web/API/notification */ contextSupported() { - return ( - location.protocol === "https:" || - location.hostname.match("^127.") || - location.hostname === "localhost" - ); + return location.protocol === "https:" || location.hostname.match("^127.") || location.hostname === "localhost"; } } diff --git a/web/src/app/Poller.js b/web/src/app/Poller.js index d2bf696..402e36b 100644 --- a/web/src/app/Poller.js +++ b/web/src/app/Poller.js @@ -34,18 +34,12 @@ class Poller { console.log(`[Poller] Polling ${subscription.id}`); const since = subscription.last; - const notifications = await api.poll( - subscription.baseUrl, - subscription.topic, - since - ); + const notifications = await api.poll(subscription.baseUrl, subscription.topic, since); if (!notifications || notifications.length === 0) { console.log(`[Poller] No new notifications found for ${subscription.id}`); return; } - console.log( - `[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}` - ); + console.log(`[Poller] Adding ${notifications.length} notification(s) for ${subscription.id}`); await subscriptionManager.addNotifications(subscription.id, notifications); } diff --git a/web/src/app/Pruner.js b/web/src/app/Pruner.js index 84853b6..498c156 100644 --- a/web/src/app/Pruner.js +++ b/web/src/app/Pruner.js @@ -20,15 +20,12 @@ class Pruner { async prune() { const deleteAfterSeconds = await prefs.deleteAfter(); - const pruneThresholdTimestamp = - Math.round(Date.now() / 1000) - deleteAfterSeconds; + const pruneThresholdTimestamp = Math.round(Date.now() / 1000) - deleteAfterSeconds; if (deleteAfterSeconds === 0) { console.log(`[Pruner] Pruning is disabled. Skipping.`); return; } - console.log( - `[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})` - ); + console.log(`[Pruner] Pruning notifications older than ${deleteAfterSeconds}s (timestamp ${pruneThresholdTimestamp})`); try { await subscriptionManager.pruneNotifications(pruneThresholdTimestamp); } catch (e) { diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index 25d0830..a539362 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -7,9 +7,7 @@ class SubscriptionManager { const subscriptions = await db.subscriptions.toArray(); await Promise.all( subscriptions.map(async (s) => { - s.new = await db.notifications - .where({ subscriptionId: s.id, new: 1 }) - .count(); + s.new = await db.notifications.where({ subscriptionId: s.id, new: 1 }).count(); }) ); return subscriptions; @@ -38,20 +36,14 @@ class SubscriptionManager { } async syncFromRemote(remoteSubscriptions, remoteReservations) { - console.log( - `[SubscriptionManager] Syncing subscriptions from remote`, - remoteSubscriptions - ); + console.log(`[SubscriptionManager] Syncing subscriptions from remote`, remoteSubscriptions); // Add remote subscriptions let remoteIds = []; // = topicUrl(baseUrl, topic) for (let i = 0; i < remoteSubscriptions.length; i++) { const remote = remoteSubscriptions[i]; const local = await this.add(remote.base_url, remote.topic, false); - const reservation = - remoteReservations?.find( - (r) => remote.base_url === config.base_url && remote.topic === r.topic - ) || null; + const reservation = remoteReservations?.find((r) => remote.base_url === config.base_url && remote.topic === r.topic) || null; await this.update(local.id, { displayName: remote.display_name, // May be undefined reservation: reservation, // May be null! @@ -122,9 +114,7 @@ class SubscriptionManager { /** Adds/replaces notifications, will not throw if they exist */ async addNotifications(subscriptionId, notifications) { - const notificationsWithSubscriptionId = notifications.map( - (notification) => ({ ...notification, subscriptionId }) - ); + const notificationsWithSubscriptionId = notifications.map((notification) => ({ ...notification, subscriptionId })); const lastNotificationId = notifications.at(-1).id; await db.notifications.bulkPut(notificationsWithSubscriptionId); await db.subscriptions.update(subscriptionId, { @@ -158,9 +148,7 @@ class SubscriptionManager { } async markNotificationsRead(subscriptionId) { - await db.notifications - .where({ subscriptionId: subscriptionId, new: 1 }) - .modify({ new: 0 }); + await db.notifications.where({ subscriptionId: subscriptionId, new: 1 }).modify({ new: 0 }); } async setMutedUntil(subscriptionId, mutedUntil) { diff --git a/web/src/app/errors.js b/web/src/app/errors.js index 96aaf86..e31949d 100644 --- a/web/src/app/errors.js +++ b/web/src/app/errors.js @@ -15,12 +15,7 @@ export const throwAppError = async (response) => { } const error = await maybeToJson(response); if (error?.code) { - console.log( - `[Error] HTTP ${response.status}, ntfy error ${error.code}: ${ - error.error || "" - }`, - response - ); + console.log(`[Error] HTTP ${response.status}, ntfy error ${error.code}: ${error.error || ""}`, response); if (error.code === UserExistsError.CODE) { throw new UserExistsError(); } else if (error.code === TopicReservedError.CODE) { diff --git a/web/src/app/utils.js b/web/src/app/utils.js index f67c2d4..d6bb02d 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -10,37 +10,23 @@ import config from "./config"; import { Base64 } from "js-base64"; export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`; -export const topicUrlWs = (baseUrl, topic) => - `${topicUrl(baseUrl, topic)}/ws` - .replaceAll("https://", "wss://") - .replaceAll("http://", "ws://"); -export const topicUrlJson = (baseUrl, topic) => - `${topicUrl(baseUrl, topic)}/json`; -export const topicUrlJsonPoll = (baseUrl, topic) => - `${topicUrlJson(baseUrl, topic)}?poll=1`; -export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => - `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`; -export const topicUrlAuth = (baseUrl, topic) => - `${topicUrl(baseUrl, topic)}/auth`; -export const topicShortUrl = (baseUrl, topic) => - shortUrl(topicUrl(baseUrl, topic)); +export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`.replaceAll("https://", "wss://").replaceAll("http://", "ws://"); +export const topicUrlJson = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/json`; +export const topicUrlJsonPoll = (baseUrl, topic) => `${topicUrlJson(baseUrl, topic)}?poll=1`; +export const topicUrlJsonPollWithSince = (baseUrl, topic, since) => `${topicUrlJson(baseUrl, topic)}?poll=1&since=${since}`; +export const topicUrlAuth = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/auth`; +export const topicShortUrl = (baseUrl, topic) => shortUrl(topicUrl(baseUrl, topic)); export const accountUrl = (baseUrl) => `${baseUrl}/v1/account`; export const accountPasswordUrl = (baseUrl) => `${baseUrl}/v1/account/password`; export const accountTokenUrl = (baseUrl) => `${baseUrl}/v1/account/token`; export const accountSettingsUrl = (baseUrl) => `${baseUrl}/v1/account/settings`; -export const accountSubscriptionUrl = (baseUrl) => - `${baseUrl}/v1/account/subscription`; -export const accountReservationUrl = (baseUrl) => - `${baseUrl}/v1/account/reservation`; -export const accountReservationSingleUrl = (baseUrl, topic) => - `${baseUrl}/v1/account/reservation/${topic}`; -export const accountBillingSubscriptionUrl = (baseUrl) => - `${baseUrl}/v1/account/billing/subscription`; -export const accountBillingPortalUrl = (baseUrl) => - `${baseUrl}/v1/account/billing/portal`; +export const accountSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/subscription`; +export const accountReservationUrl = (baseUrl) => `${baseUrl}/v1/account/reservation`; +export const accountReservationSingleUrl = (baseUrl, topic) => `${baseUrl}/v1/account/reservation/${topic}`; +export const accountBillingSubscriptionUrl = (baseUrl) => `${baseUrl}/v1/account/billing/subscription`; +export const accountBillingPortalUrl = (baseUrl) => `${baseUrl}/v1/account/billing/portal`; export const accountPhoneUrl = (baseUrl) => `${baseUrl}/v1/account/phone`; -export const accountPhoneVerifyUrl = (baseUrl) => - `${baseUrl}/v1/account/phone/verify`; +export const accountPhoneVerifyUrl = (baseUrl) => `${baseUrl}/v1/account/phone/verify`; export const tiersUrl = (baseUrl) => `${baseUrl}/v1/tiers`; export const shortUrl = (url) => url.replaceAll(/https?:\/\//g, ""); export const expandUrl = (url) => [`https://${url}`, `http://${url}`]; @@ -208,9 +194,7 @@ export const formatShortDateTime = (timestamp) => { }; export const formatShortDate = (timestamp) => { - return new Intl.DateTimeFormat("default", { dateStyle: "short" }).format( - new Date(timestamp * 1000) - ); + return new Intl.DateTimeFormat("default", { dateStyle: "short" }).format(new Date(timestamp * 1000)); }; export const formatBytes = (bytes, decimals = 2) => { @@ -312,8 +296,7 @@ export async function* fetchLinesIterator(fileURL, headers) { } export const randomAlphanumericString = (len) => { - const alphabet = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let id = ""; for (let i = 0; i < len; i++) { id += alphabet[(Math.random() * alphabet.length) | 0]; diff --git a/web/src/components/Account.js b/web/src/components/Account.js index bb8e7a7..b6710c6 100644 --- a/web/src/components/Account.js +++ b/web/src/components/Account.js @@ -38,18 +38,8 @@ import DialogContent from "@mui/material/DialogContent"; import TextField from "@mui/material/TextField"; import routes from "./routes"; import IconButton from "@mui/material/IconButton"; -import { - formatBytes, - formatShortDate, - formatShortDateTime, - openUrl, -} from "../app/utils"; -import accountApi, { - LimitBasis, - Role, - SubscriptionInterval, - SubscriptionStatus, -} from "../app/AccountApi"; +import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils"; +import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Pref, PrefGroup } from "./Pref"; import db from "../app/db"; @@ -108,11 +98,7 @@ const Username = () => { const labelId = "prefUsername"; return ( - +
{session.username()} {account?.role === Role.ADMIN ? ( @@ -146,30 +132,16 @@ const ChangePassword = () => { }; return ( - +
- + ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤ - +
- +
); }; @@ -190,9 +162,7 @@ const ChangePasswordDialog = (props) => { } catch (e) { console.log(`[Account] Error changing password`, e); if (e instanceof IncorrectPasswordError) { - setError( - t("account_basics_password_dialog_current_password_incorrect") - ); + setError(t("account_basics_password_dialog_current_password_incorrect")); } else if (e instanceof UnauthorizedError) { session.resetAndRedirect(routes.login); } else { @@ -209,9 +179,7 @@ const ChangePasswordDialog = (props) => { margin="dense" id="current-password" label={t("account_basics_password_dialog_current_password_label")} - aria-label={t( - "account_basics_password_dialog_current_password_label" - )} + aria-label={t("account_basics_password_dialog_current_password_label")} type="password" value={currentPassword} onChange={(ev) => setCurrentPassword(ev.target.value)} @@ -233,9 +201,7 @@ const ChangePasswordDialog = (props) => { margin="dense" id="confirm" label={t("account_basics_password_dialog_confirm_password_label")} - aria-label={t( - "account_basics_password_dialog_confirm_password_label" - )} + aria-label={t("account_basics_password_dialog_confirm_password_label")} type="password" value={confirmPassword} onChange={(ev) => setConfirmPassword(ev.target.value)} @@ -245,14 +211,7 @@ const ChangePasswordDialog = (props) => { - @@ -299,9 +258,7 @@ const AccountType = () => { : t("account_basics_tier_admin_suffix_no_tier"); accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`; } else if (!account.tier) { - accountType = config.enable_payments - ? t("account_basics_tier_free") - : t("account_basics_tier_basic"); + accountType = config.enable_payments ? t("account_basics_tier_free") : t("account_basics_tier_basic"); } else { accountType = account.tier.name; if (account.billing?.interval === SubscriptionInterval.MONTH) { @@ -313,10 +270,7 @@ const AccountType = () => { return ( 0 - } + alignTop={account.billing?.status === SubscriptionStatus.PAST_DUE || account.billing?.cancel_at > 0} title={t("account_basics_tier_title")} description={t("account_basics_tier_description")} > @@ -333,49 +287,23 @@ const AccountType = () => { )} - {config.enable_payments && - account.role === Role.USER && - !account.billing?.subscription && ( - - )} - {config.enable_payments && - account.role === Role.USER && - account.billing?.subscription && ( - - )} - {config.enable_payments && - account.role === Role.USER && - account.billing?.customer && ( - - )} + {config.enable_payments && account.role === Role.USER && !account.billing?.subscription && ( + + )} + {config.enable_payments && account.role === Role.USER && account.billing?.subscription && ( + + )} + {config.enable_payments && account.role === Role.USER && account.billing?.customer && ( + + )} {config.enable_payments && ( - setUpgradeDialogOpen(false)} - /> + setUpgradeDialogOpen(false)} /> )}
{account.billing?.status === SubscriptionStatus.PAST_DUE && ( @@ -456,11 +384,7 @@ const PhoneNumbers = () => { } return ( - +
{account?.phone_numbers?.map((phoneNumber) => ( { onDelete={() => handleDelete(phoneNumber)} /> ))} - {!account?.phone_numbers && ( - {t("account_basics_phone_numbers_no_phone_numbers_yet")} - )} + {!account?.phone_numbers && {t("account_basics_phone_numbers_no_phone_numbers_yet")}}
- + { return ( - - {t("account_basics_phone_numbers_dialog_title")} - + {t("account_basics_phone_numbers_dialog_title")} - - {t("account_basics_phone_numbers_dialog_description")} - + {t("account_basics_phone_numbers_dialog_description")} {!verificationCodeSent && (
setPhoneNumber(ev.target.value)} @@ -585,28 +497,15 @@ const AddPhoneNumberDialog = (props) => { sx={{ flexGrow: 1 }} /> - + setChannel(e.target.value)} - /> - } + control={ setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_sms")} /> setChannel(e.target.value)} - /> - } + control={ setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_call")} sx={{ marginRight: 0 }} /> @@ -619,9 +518,7 @@ const AddPhoneNumberDialog = (props) => { margin="dense" label={t("account_basics_phone_numbers_dialog_code_label")} aria-label={t("account_basics_phone_numbers_dialog_code_label")} - placeholder={t( - "account_basics_phone_numbers_dialog_code_placeholder" - )} + placeholder={t("account_basics_phone_numbers_dialog_code_placeholder")} type="text" value={code} onChange={(ev) => setCode(ev.target.value)} @@ -632,21 +529,11 @@ const AddPhoneNumberDialog = (props) => { )} - - +
@@ -687,14 +574,7 @@ const Stats = () => { 0 - ? normalize( - account.stats.reservations, - account.limits.reservations - ) - : 100 - } + value={account.role === Role.USER && account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100} />
)} @@ -722,14 +602,7 @@ const Stats = () => { : t("account_usage_unlimited")} - +
{config.enable_emails && ( { : t("account_usage_unlimited")} + + + )} + {config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && ( + + {t("account_usage_calls_title")} + + + + + + + } + > +
+ + {account.stats.calls.toLocaleString()} + + + {account.role === Role.USER + ? t("account_usage_of_limit", { + limit: account.limits.calls.toLocaleString(), + }) + : t("account_usage_unlimited")} + +
0 ? normalize(account.stats.calls, account.limits.calls) : 100} />
)} - {config.enable_calls && - (account.role === Role.ADMIN || account.limits.calls > 0) && ( - - {t("account_usage_calls_title")} - - - - - - - } - > -
- - {account.stats.calls.toLocaleString()} - - - {account.role === Role.USER - ? t("account_usage_of_limit", { - limit: account.limits.calls.toLocaleString(), - }) - : t("account_usage_unlimited")} - -
- 0 - ? normalize(account.stats.calls, account.limits.calls) - : 100 - } - /> -
- )}
@@ -830,49 +688,36 @@ const Stats = () => {
- {config.enable_reservations && - account.role === Role.USER && - account.limits.reservations === 0 && ( - - {t("account_usage_reservations_title")} - {config.enable_payments && } - - } - > - {t("account_usage_reservations_none")} - - )} - {config.enable_calls && - account.role === Role.USER && - account.limits.calls === 0 && ( - - {t("account_usage_calls_title")} - {config.enable_payments && } - - } - > - {t("account_usage_calls_none")} - - )} + {config.enable_reservations && account.role === Role.USER && account.limits.reservations === 0 && ( + + {t("account_usage_reservations_title")} + {config.enable_payments && } + + } + > + {t("account_usage_reservations_none")} + + )} + {config.enable_calls && account.role === Role.USER && account.limits.calls === 0 && ( + + {t("account_usage_calls_title")} + {config.enable_payments && } + + } + > + {t("account_usage_calls_none")} + + )} {account.role === Role.USER && account.limits.basis === LimitBasis.IP && ( - - {t("account_usage_basis_ip_description")} - + {t("account_usage_basis_ip_description")} )} ); @@ -928,15 +773,9 @@ const Tokens = () => { {tokens?.length > 0 && } - + - + ); }; @@ -984,9 +823,7 @@ const TokensTable = (props) => { - - {t("account_tokens_table_token_header")} - + {t("account_tokens_table_token_header")} {t("account_tokens_table_label_header")} {t("account_tokens_table_expires_header")} {t("account_tokens_table_last_access_header")} @@ -995,25 +832,12 @@ const TokensTable = (props) => { {tokens.map((token) => ( - - + + - - {token.token.slice(0, 12)} - + {token.token.slice(0, 12)} ... - + handleCopy(token.token)}> @@ -1021,25 +845,13 @@ const TokensTable = (props) => { - {token.token === session.token() && ( - {t("account_tokens_table_current_session")} - )} + {token.token === session.token() && {t("account_tokens_table_current_session")}} {token.token !== session.token() && (token.label || "-")} - - {token.expires ? ( - formatShortDateTime(token.expires) - ) : ( - {t("account_tokens_table_never_expires")} - )} + + {token.expires ? formatShortDateTime(token.expires) : {t("account_tokens_table_never_expires")}} - +
{formatShortDateTime(token.last_access)} { ip: token.last_origin, })} > - - openUrl( - `https://whatismyipaddress.com/ip/${token.last_origin}` - ) - } - > + openUrl(`https://whatismyipaddress.com/ip/${token.last_origin}`)}> @@ -1062,24 +868,16 @@ const TokensTable = (props) => { {token.token !== session.token() && ( <> - handleEditClick(token)} - aria-label={t("account_tokens_dialog_title_edit")} - > + handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}> - handleDeleteClick(token)} - aria-label={t("account_tokens_dialog_title_delete")} - > + handleDeleteClick(token)} aria-label={t("account_tokens_dialog_title_delete")}> )} {token.token === session.token() && ( - + @@ -1095,24 +893,10 @@ const TokensTable = (props) => { ))} - setSnackOpen(false)} - message={t("account_tokens_table_copied_to_clipboard")} - /> + setSnackOpen(false)} message={t("account_tokens_table_copied_to_clipboard")} /> - - + +
); }; @@ -1144,18 +928,8 @@ const TokenDialog = (props) => { }; return ( - - - {editMode - ? t("account_tokens_dialog_title_edit") - : t("account_tokens_dialog_title_create")} - + + {editMode ? t("account_tokens_dialog_title_edit") : t("account_tokens_dialog_title_create")} { variant="standard" /> - setExpires(ev.target.value)} aria-label={t("account_tokens_dialog_expires_label")}> + {editMode && {t("account_tokens_dialog_expires_unchanged")}} + {t("account_tokens_dialog_expires_never")} + {t("account_tokens_dialog_expires_x_hours", { hours: 6 })} + {t("account_tokens_dialog_expires_x_hours", { hours: 12 })} + {t("account_tokens_dialog_expires_x_days", { days: 3 })} + {t("account_tokens_dialog_expires_x_days", { days: 7 })} + {t("account_tokens_dialog_expires_x_days", { days: 30 })} + {t("account_tokens_dialog_expires_x_days", { days: 90 })} + {t("account_tokens_dialog_expires_x_days", { days: 180 })} - - + + ); @@ -1285,26 +1029,13 @@ const DeleteAccount = () => { }; return ( - +
-
- +
); }; @@ -1325,9 +1056,7 @@ const DeleteAccountDialog = (props) => { } catch (e) { console.log(`[Account] Error deleting account`, e); if (e instanceof IncorrectPasswordError) { - setError( - t("account_basics_password_dialog_current_password_incorrect") - ); + setError(t("account_basics_password_dialog_current_password_incorrect")); } else if (e instanceof UnauthorizedError) { session.resetAndRedirect(routes.login); } else { @@ -1340,9 +1069,7 @@ const DeleteAccountDialog = (props) => { {t("account_delete_title")} - - {t("account_delete_dialog_description")} - + {t("account_delete_dialog_description")} { )} - - + diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index b6c8416..2d44014 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -51,8 +51,7 @@ const ActionBar = (props) => { { {title} - {props.selected && ( - - )} + {props.selected && } @@ -101,34 +95,13 @@ const SettingsIcons = (props) => { return ( <> - - {subscription.mutedUntil ? ( - - ) : ( - - )} + + {subscription.mutedUntil ? : } - setAnchorEl(ev.currentTarget)} - aria-label={t("action_bar_toggle_action_menu")} - > + setAnchorEl(ev.currentTarget)} aria-label={t("action_bar_toggle_action_menu")}> - setAnchorEl(null)} - /> + setAnchorEl(null)} /> ); }; @@ -159,43 +132,21 @@ const ProfileIcon = () => { return ( <> {session.exists() && ( - + )} {!session.exists() && config.enable_login && ( - )} {!session.exists() && config.enable_signup && ( - )} - + navigate(routes.account)}> diff --git a/web/src/components/App.js b/web/src/components/App.js index b2c204a..50f2ad6 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -1,11 +1,5 @@ import * as React from "react"; -import { - createContext, - Suspense, - useContext, - useEffect, - useState, -} from "react"; +import { createContext, Suspense, useContext, useEffect, useState } from "react"; import Box from "@mui/material/Box"; import { ThemeProvider } from "@mui/material/styles"; import CssBaseline from "@mui/material/CssBaseline"; @@ -19,21 +13,11 @@ import Preferences from "./Preferences"; import { useLiveQuery } from "dexie-react-hooks"; import subscriptionManager from "../app/SubscriptionManager"; import userManager from "../app/UserManager"; -import { - BrowserRouter, - Outlet, - Route, - Routes, - useParams, -} from "react-router-dom"; +import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom"; import { expandUrl } from "../app/utils"; import ErrorBoundary from "./ErrorBoundary"; import routes from "./routes"; -import { - useAccountListener, - useBackgroundProcesses, - useConnectionListeners, -} from "./hooks"; +import { useAccountListener, useBackgroundProcesses, useConnectionListeners } from "./hooks"; import PublishDialog from "./PublishDialog"; import Messaging from "./Messaging"; import "./i18n"; // Translations! @@ -60,14 +44,8 @@ const App = () => { } /> } /> } /> - } - /> - } - /> + } /> + } /> @@ -82,22 +60,15 @@ const Layout = () => { const params = useParams(); const { account, setAccount } = useContext(AccountContext); const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); - const [notificationsGranted, setNotificationsGranted] = useState( - notifier.granted() - ); + const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); const users = useLiveQuery(() => userManager.all()); const subscriptions = useLiveQuery(() => subscriptionManager.all()); - const subscriptionsWithoutInternal = subscriptions?.filter( - (s) => !s.internal - ); - const newNotificationsCount = - subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; + const subscriptionsWithoutInternal = subscriptions?.filter((s) => !s.internal); + const newNotificationsCount = subscriptionsWithoutInternal?.reduce((prev, cur) => prev + cur.new, 0) || 0; const [selected] = (subscriptionsWithoutInternal || []).filter((s) => { return ( - (params.baseUrl && - expandUrl(params.baseUrl).includes(s.baseUrl) && - params.topic === s.topic) || + (params.baseUrl && expandUrl(params.baseUrl).includes(s.baseUrl) && params.topic === s.topic) || (config.base_url === s.baseUrl && params.topic === s.topic) ); }); @@ -109,10 +80,7 @@ const Layout = () => { return ( - setMobileDrawerOpen(!mobileDrawerOpen)} - /> + setMobileDrawerOpen(!mobileDrawerOpen)} /> { mobileDrawerOpen={mobileDrawerOpen} onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)} onNotificationGranted={setNotificationsGranted} - onPublishMessageClick={() => - setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT) - } + onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)} />
@@ -133,11 +99,7 @@ const Layout = () => { }} />
- +
); }; @@ -155,10 +117,7 @@ const Main = (props) => { width: { sm: `calc(100% - ${Navigation.width}px)` }, height: "100vh", overflow: "auto", - backgroundColor: (theme) => - theme.palette.mode === "light" - ? theme.palette.grey[100] - : theme.palette.grey[900], + backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), }} > {props.children} @@ -171,10 +130,7 @@ const Loader = () => ( open={true} sx={{ zIndex: 100000, - backgroundColor: (theme) => - theme.palette.mode === "light" - ? theme.palette.grey[100] - : theme.palette.grey[900], + backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), }} > @@ -182,8 +138,7 @@ const Loader = () => ( ); const updateTitle = (newNotificationsCount) => { - document.title = - newNotificationsCount > 0 ? `(${newNotificationsCount}) ntfy` : "ntfy"; + document.title = newNotificationsCount > 0 ? `(${newNotificationsCount}) ntfy` : "ntfy"; }; export default App; diff --git a/web/src/components/AvatarBox.js b/web/src/components/AvatarBox.js index 5a612f1..506ae63 100644 --- a/web/src/components/AvatarBox.js +++ b/web/src/components/AvatarBox.js @@ -16,11 +16,7 @@ const AvatarBox = (props) => { height: "100vh", }} > - + {props.children} ); diff --git a/web/src/components/EmojiPicker.js b/web/src/components/EmojiPicker.js index 03badb7..3f9f4df 100644 --- a/web/src/components/EmojiPicker.js +++ b/web/src/components/EmojiPicker.js @@ -17,8 +17,7 @@ import { useTranslation } from "react-i18next"; // This is a hack, but on Ubuntu 18.04, with Chrome 99, only Emoji <= 11 are supported. const emojisByCategory = {}; -const isDesktopChrome = - /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent); +const isDesktopChrome = /Chrome/.test(navigator.userAgent) && !/Mobile/.test(navigator.userAgent); const maxSupportedVersionForDesktopChrome = 11; rawEmojis.forEach((emoji) => { if (!emojisByCategory[emoji.category]) { @@ -26,12 +25,9 @@ rawEmojis.forEach((emoji) => { } try { const unicodeVersion = parseFloat(emoji.unicode_version); - const supportedEmoji = - unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; + const supportedEmoji = unicodeVersion <= maxSupportedVersionForDesktopChrome || !isDesktopChrome; if (supportedEmoji) { - const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join( - " " - )} ${emoji.tags.join(" ")}`; + const searchBase = `${emoji.description.toLowerCase()} ${emoji.aliases.join(" ")} ${emoji.tags.join(" ")}`; const emojiWithSearchBase = { ...emoji, searchBase: searchBase }; emojisByCategory[emoji.category].push(emojiWithSearchBase); } @@ -53,13 +49,7 @@ const EmojiPicker = (props) => { }; return ( - + {({ TransitionProps }) => ( @@ -92,16 +82,8 @@ const EmojiPicker = (props) => { }} InputProps={{ endAdornment: ( - - + + @@ -117,13 +99,7 @@ const EmojiPicker = (props) => { }} > {Object.keys(emojisByCategory).map((category) => ( - + ))} @@ -144,12 +120,7 @@ const Category = (props) => { )} {props.emojis.map((emoji) => ( - props.onPick(emoji.aliases[0])} - /> + props.onPick(emoji.aliases[0])} /> ))} ); @@ -160,12 +131,7 @@ const Emoji = (props) => { const matches = emojiMatches(emoji, props.search); const title = `${emoji.description} (${emoji.aliases[0]})`; return ( - + {props.emoji.emoji} ); diff --git a/web/src/components/ErrorBoundary.js b/web/src/components/ErrorBoundary.js index f1ce7c2..2928218 100644 --- a/web/src/components/ErrorBoundary.js +++ b/web/src/components/ErrorBoundary.js @@ -22,9 +22,7 @@ class ErrorBoundaryImpl extends React.Component { // - https://github.com/dexie/Dexie.js/issues/312 // - https://bugzilla.mozilla.org/show_bug.cgi?id=781982 const isUnsupportedIndexedDB = - error?.name === "InvalidStateError" || - (error?.name === "DatabaseClosedError" && - error?.message?.indexOf("InvalidStateError") !== -1); + error?.name === "InvalidStateError" || (error?.name === "DatabaseClosedError" && error?.message?.indexOf("InvalidStateError") !== -1); if (isUnsupportedIndexedDB) { this.handleUnsupportedIndexedDB(); @@ -48,14 +46,7 @@ class ErrorBoundaryImpl extends React.Component { // Fetch additional info and a better stack trace StackTrace.fromError(error).then((stack) => { console.error("[ErrorBoundary] Stacktrace fetched", stack); - const niceStack = - `${error.toString()}\n` + - stack - .map( - (el) => - ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})` - ) - .join("\n"); + const niceStack = `${error.toString()}\n` + stack.map((el) => ` at ${el.functionName} (${el.fileName}:${el.columnNumber}:${el.lineNumber})`).join("\n"); this.setState({ niceStack }); }); } @@ -96,9 +87,7 @@ class ErrorBoundaryImpl extends React.Component { - ), + githubLink: , discordLink: , matrixLink: , }} @@ -117,9 +106,7 @@ class ErrorBoundaryImpl extends React.Component { - ), + githubLink: , discordLink: , matrixLink: , }} @@ -135,11 +122,7 @@ class ErrorBoundaryImpl extends React.Component {
{this.state.niceStack}
) : ( <> - {" "} - {t("error_boundary_gathering_info")} + {t("error_boundary_gathering_info")} )}
{this.state.originalStack}
diff --git a/web/src/components/Login.js b/web/src/components/Login.js index a109ae6..ce4f3b5 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -28,9 +28,7 @@ const Login = () => { const user = { username, password }; try { const token = await accountApi.login(user); - console.log( - `[Login] User auth for user ${user.username} successful, token is ${token}` - ); + console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); session.store(user.username, token); window.location.href = routes.app; } catch (e) { @@ -52,12 +50,7 @@ const Login = () => { return ( {t("login_title")} - + { ), }} /> - {error && ( diff --git a/web/src/components/Messaging.js b/web/src/components/Messaging.js index d69bc57..2fa7ed5 100644 --- a/web/src/components/Messaging.js +++ b/web/src/components/Messaging.js @@ -29,14 +29,7 @@ const Messaging = (props) => { return ( <> - {subscription && ( - - )} + {subscription && } { topic={subscription?.topic ?? ""} message={message} onClose={handleDialogClose} - onDragEnter={() => - props.onDialogOpenModeChange((prev) => - prev ? prev : PublishDialog.OPEN_MODE_DRAG - ) - } // Only update if not already open - onResetOpenMode={() => - props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT) - } + onDragEnter={() => props.onDialogOpenModeChange((prev) => (prev ? prev : PublishDialog.OPEN_MODE_DRAG))} // Only update if not already open + onResetOpenMode={() => props.onDialogOpenModeChange(PublishDialog.OPEN_MODE_DEFAULT)} /> ); @@ -63,11 +50,7 @@ const MessageBar = (props) => { const [snackOpen, setSnackOpen] = useState(false); const handleSendClick = async () => { try { - await api.publish( - subscription.baseUrl, - subscription.topic, - props.message - ); + await api.publish(subscription.baseUrl, subscription.topic, props.message); } catch (e) { console.log(`[MessageBar] Error publishing message`, e); setSnackOpen(true); @@ -84,19 +67,10 @@ const MessageBar = (props) => { right: 0, padding: 2, width: { xs: "100%", sm: `calc(100% - ${Navigation.width}px)` }, - backgroundColor: (theme) => - theme.palette.mode === "light" - ? theme.palette.grey[100] - : theme.palette.grey[900], + backgroundColor: (theme) => (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), }} > - + { } }} /> - + - setSnackOpen(false)} - message={t("message_bar_error_publishing")} - /> + setSnackOpen(false)} message={t("message_bar_error_publishing")} /> ); diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js index 654e29b..922d6fe 100644 --- a/web/src/components/Navigation.js +++ b/web/src/components/Navigation.js @@ -12,16 +12,7 @@ import List from "@mui/material/List"; import SettingsIcon from "@mui/icons-material/Settings"; import AddIcon from "@mui/icons-material/Add"; import SubscribeDialog from "./SubscribeDialog"; -import { - Alert, - AlertTitle, - Badge, - CircularProgress, - Link, - ListSubheader, - Portal, - Tooltip, -} from "@mui/material"; +import { Alert, AlertTitle, Badge, CircularProgress, Link, ListSubheader, Portal, Tooltip } from "@mui/material"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; import { openUrl, topicDisplayName, topicUrl } from "../app/utils"; @@ -29,12 +20,7 @@ import routes from "./routes"; import { ConnectionState } from "../app/Connection"; import { useLocation, useNavigate } from "react-router-dom"; import subscriptionManager from "../app/SubscriptionManager"; -import { - ChatBubble, - MoreVert, - NotificationsOffOutlined, - Send, -} from "@mui/icons-material"; +import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material"; import Box from "@mui/material/Box"; import notifier from "../app/Notifier"; import config from "../app/config"; @@ -45,12 +31,7 @@ import accountApi, { Permission, Role } from "../app/AccountApi"; import CelebrationIcon from "@mui/icons-material/Celebration"; import UpgradeDialog from "./UpgradeDialog"; import { AccountContext } from "./App"; -import { - PermissionDenyAll, - PermissionRead, - PermissionReadWrite, - PermissionWrite, -} from "./ReserveIcons"; +import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; import IconButton from "@mui/material/IconButton"; import { SubscriptionPopup } from "./SubscriptionPopup"; @@ -59,11 +40,7 @@ const navWidth = 280; const Navigation = (props) => { const navigationList = ; return ( - + {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */} { }; const handleSubscribeSubmit = (subscription) => { - console.log( - `[Navigation] New subscription: ${subscription.id}`, - subscription - ); + console.log(`[Navigation] New subscription: ${subscription.id}`, subscription); handleSubscribeReset(); navigate(routes.forSubscription(subscription)); handleRequestNotificationPermission(); }; const handleRequestNotificationPermission = () => { - notifier.maybeRequestPermission((granted) => - props.onNotificationGranted(granted) - ); + notifier.maybeRequestPermission((granted) => props.onNotificationGranted(granted)); }; const handleAccountClick = () => { @@ -134,39 +106,19 @@ const NavList = (props) => { const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid; const showSubscriptionsList = props.subscriptions?.length > 0; const showNotificationBrowserNotSupportedBox = !notifier.browserSupported(); - const showNotificationContextNotSupportedBox = - notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser - const showNotificationGrantBox = - notifier.supported() && - props.subscriptions?.length > 0 && - !props.notificationsGranted; - const navListPadding = - showNotificationGrantBox || - showNotificationBrowserNotSupportedBox || - showNotificationContextNotSupportedBox - ? "0" - : ""; + const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser + const showNotificationGrantBox = notifier.supported() && props.subscriptions?.length > 0 && !props.notificationsGranted; + const navListPadding = showNotificationGrantBox || showNotificationBrowserNotSupportedBox || showNotificationContextNotSupportedBox ? "0" : ""; return ( <> - {showNotificationBrowserNotSupportedBox && ( - - )} - {showNotificationContextNotSupportedBox && ( - - )} - {showNotificationGrantBox && ( - - )} + {showNotificationBrowserNotSupportedBox && } + {showNotificationContextNotSupportedBox && } + {showNotificationGrantBox && } {!showSubscriptionsList && ( - navigate(routes.app)} - selected={location.pathname === config.app_root} - > + navigate(routes.app)} selected={location.pathname === config.app_root}> @@ -176,37 +128,25 @@ const NavList = (props) => { {showSubscriptionsList && ( <> {t("nav_topics_title")} - navigate(routes.app)} - selected={location.pathname === config.app_root} - > + navigate(routes.app)} selected={location.pathname === config.app_root}> - + )} {session.exists() && ( - + )} - navigate(routes.settings)} - selected={location.pathname === routes.settings} - > + navigate(routes.settings)} selected={location.pathname === routes.settings}> @@ -260,8 +200,7 @@ const UpgradeBanner = () => { width: `${Navigation.width - 1}px`, bottom: 0, mt: "auto", - background: - "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)", + background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)", }} > @@ -277,8 +216,7 @@ const UpgradeBanner = () => { style: { fontWeight: 500, fontSize: "1.1rem", - background: - "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)", + background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", }, @@ -290,11 +228,7 @@ const UpgradeBanner = () => { }} /> - setDialogOpen(false)} - /> + setDialogOpen(false)} /> ); }; @@ -303,9 +237,7 @@ const SubscriptionList = (props) => { const sortedSubscriptions = props.subscriptions .filter((s) => !s.internal) .sort((a, b) => { - return topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) - ? -1 - : 1; + return topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic) ? -1 : 1; }); return ( <> @@ -313,10 +245,7 @@ const SubscriptionList = (props) => { ))} @@ -331,19 +260,12 @@ const SubscriptionItem = (props) => { const subscription = props.subscription; const iconBadge = subscription.new <= 99 ? subscription.new : "99+"; const displayName = topicDisplayName(subscription); - const ariaLabel = - subscription.state === ConnectionState.Connecting - ? `${displayName} (${t("nav_button_connecting")})` - : displayName; + const ariaLabel = subscription.state === ConnectionState.Connecting ? `${displayName} (${t("nav_button_connecting")})` : displayName; const icon = subscription.state === ConnectionState.Connecting ? ( ) : ( - + ); @@ -355,12 +277,7 @@ const SubscriptionItem = (props) => { return ( <> - + {icon} { {subscription.reservation?.everyone && ( {subscription.reservation?.everyone === Permission.READ_WRITE && ( - + )} @@ -383,9 +298,7 @@ const SubscriptionItem = (props) => { )} {subscription.reservation?.everyone === Permission.WRITE_ONLY && ( - + )} @@ -397,11 +310,7 @@ const SubscriptionItem = (props) => { )} {subscription.mutedUntil > 0 && ( - + @@ -421,11 +330,7 @@ const SubscriptionItem = (props) => { - setMenuAnchorEl(null)} - /> + setMenuAnchorEl(null)} /> ); @@ -438,12 +343,7 @@ const NotificationGrantAlert = (props) => { {t("alert_grant_title")} {t("alert_grant_description")} - @@ -458,9 +358,7 @@ const NotificationBrowserNotSupportedAlert = () => { <> {t("alert_not_supported_title")} - - {t("alert_not_supported_description")} - + {t("alert_not_supported_description")} @@ -477,13 +375,7 @@ const NotificationContextNotSupportedAlert = () => { - ), + mdnLink: , }} /> diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js index e55674b..ad44873 100644 --- a/web/src/components/Notifications.js +++ b/web/src/components/Notifications.js @@ -1,16 +1,5 @@ import Container from "@mui/material/Container"; -import { - ButtonBase, - CardActions, - CardContent, - CircularProgress, - Fade, - Link, - Modal, - Snackbar, - Stack, - Tooltip, -} from "@mui/material"; +import { ButtonBase, CardActions, CardContent, CircularProgress, Fade, Link, Modal, Snackbar, Stack, Tooltip } from "@mui/material"; import Card from "@mui/material/Card"; import Typography from "@mui/material/Typography"; import * as React from "react"; @@ -29,11 +18,7 @@ import { import IconButton from "@mui/material/IconButton"; import CheckIcon from "@mui/icons-material/Check"; import CloseIcon from "@mui/icons-material/Close"; -import { - LightboxBackdrop, - Paragraph, - VerticallyCenteredContainer, -} from "./styles"; +import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles"; import { useLiveQuery } from "dexie-react-hooks"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; @@ -68,10 +53,7 @@ export const SingleSubscription = () => { const AllSubscriptionsList = (props) => { const subscriptions = props.subscriptions; - const notifications = useLiveQuery( - () => subscriptionManager.getAllNotifications(), - [] - ); + const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []); if (notifications === null || notifications === undefined) { return ; } else if (subscriptions.length === 0) { @@ -79,33 +61,18 @@ const AllSubscriptionsList = (props) => { } else if (notifications.length === 0) { return ; } - return ( - - ); + return ; }; const SingleSubscriptionList = (props) => { const subscription = props.subscription; - const notifications = useLiveQuery( - () => subscriptionManager.getNotifications(subscription.id), - [subscription] - ); + const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]); if (notifications === null || notifications === undefined) { return ; } else if (notifications.length === 0) { return ; } - return ( - - ); + return ; }; const NotificationList = (props) => { @@ -146,18 +113,9 @@ const NotificationList = (props) => { > {notifications.slice(0, count).map((notification) => ( - setSnackOpen(true)} - /> + setSnackOpen(true)} /> ))} - setSnackOpen(false)} - message={t("notifications_copied_to_clipboard")} - /> + setSnackOpen(false)} message={t("notifications_copied_to_clipboard")} /> @@ -176,45 +134,29 @@ const NotificationItem = (props) => { await subscriptionManager.deleteNotification(notification.id); }; const handleMarkRead = async () => { - console.log( - `[Notifications] Marking notification ${notification.id} as read` - ); + console.log(`[Notifications] Marking notification ${notification.id} as read`); await subscriptionManager.markNotificationRead(notification.id); }; const handleCopy = (s) => { navigator.clipboard.writeText(s); props.onShowSnack(); }; - const expired = - attachment && attachment.expires && attachment.expires < Date.now() / 1000; + const expired = attachment && attachment.expires && attachment.expires < Date.now() / 1000; const hasAttachmentActions = attachment && !expired; const hasClickAction = notification.click; - const hasUserActions = - notification.actions && notification.actions.length > 0; + const hasUserActions = notification.actions && notification.actions.length > 0; const showActions = hasAttachmentActions || hasClickAction || hasUserActions; return ( - + - + {notification.new === 1 && ( - + @@ -247,9 +189,7 @@ const NotificationItem = (props) => { )} - {autolink( - maybeAppendActionErrors(formatMessage(notification), notification) - )} + {autolink(maybeAppendActionErrors(formatMessage(notification), notification))} {attachment && } {tags && ( @@ -263,36 +203,28 @@ const NotificationItem = (props) => { {hasAttachmentActions && ( <> - + - + )} {hasClickAction && ( <> - + - + )} @@ -311,18 +243,10 @@ const NotificationItem = (props) => { * [2] https://github.com/bryanwoods/autolink-js/blob/master/autolink.js#L9 */ const autolink = (s) => { - const parts = s.split( - /(\bhttps?:\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|]\b)/gi - ); + const parts = s.split(/(\bhttps?:\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|]\b)/gi); for (let i = 1; i < parts.length; i += 2) { parts[i] = ( - + {shortUrl(parts[i])} ); @@ -342,8 +266,7 @@ const Attachment = (props) => { const attachment = props.attachment; const expired = attachment.expires && attachment.expires < Date.now() / 1000; const expires = attachment.expires && attachment.expires > Date.now() / 1000; - const displayableImage = - !expired && attachment.type && attachment.type.startsWith("image/"); + const displayableImage = !expired && attachment.type && attachment.type.startsWith("image/"); // Unexpired image if (displayableImage) { @@ -386,10 +309,7 @@ const Attachment = (props) => { }} > - + {attachment.name} {maybeInfoText} @@ -420,10 +340,7 @@ const Attachment = (props) => { }} > - + {attachment.name} {maybeInfoText} @@ -453,11 +370,7 @@ const Image = (props) => { cursor: "pointer", }} /> - setOpen(false)} - BackdropComponent={LightboxBackdrop} - > + setOpen(false)} BackdropComponent={LightboxBackdrop}> { return ( <> {props.notification.actions.map((action) => ( - + ))} ); @@ -502,10 +411,7 @@ const UserAction = (props) => { return ( - @@ -513,9 +419,7 @@ const UserAction = (props) => { ); } else if (action.action === "view") { return ( - + - + ); @@ -350,9 +277,7 @@ const UserTable = (props) => { setDialogOpen(false); try { await userManager.save(user); - console.debug( - `[Preferences] User ${user.username} for ${user.baseUrl} updated` - ); + console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`); } catch (e) { console.log(`[Preferences] Error updating user.`, e); } @@ -361,9 +286,7 @@ const UserTable = (props) => { const handleDeleteClick = async (user) => { try { await userManager.delete(user.baseUrl); - console.debug( - `[Preferences] User ${user.username} for ${user.baseUrl} deleted` - ); + console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`); } catch (e) { console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e); } @@ -373,43 +296,25 @@ const UserTable = (props) => { - - {t("prefs_users_table_user_header")} - + {t("prefs_users_table_user_header")} {t("prefs_users_table_base_url_header")} {props.users?.map((user) => ( - - + + {user.username} - - {user.baseUrl} - + {user.baseUrl} {(!session.exists() || user.baseUrl !== config.base_url) && ( <> - handleEditClick(user)} - aria-label={t("prefs_users_edit_button")} - > + handleEditClick(user)} aria-label={t("prefs_users_edit_button")}> - handleDeleteClick(user)} - aria-label={t("prefs_users_delete_button")} - > + handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}> @@ -454,15 +359,8 @@ const UserDialog = (props) => { return username.length > 0 && password.length > 0; } const baseUrlValid = validUrl(baseUrl); - const baseUrlExists = props.users - ?.map((user) => user.baseUrl) - .includes(baseUrl); - return ( - baseUrlValid && - !baseUrlExists && - username.length > 0 && - password.length > 0 - ); + const baseUrlExists = props.users?.map((user) => user.baseUrl).includes(baseUrl); + return baseUrlValid && !baseUrlExists && username.length > 0 && password.length > 0; })(); const handleSubmit = async () => { props.onSubmit({ @@ -480,11 +378,7 @@ const UserDialog = (props) => { }, [editMode, props.user]); return ( - - {editMode - ? t("prefs_users_dialog_title_edit") - : t("prefs_users_dialog_title_add")} - + {editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")} {!editMode && ( { // Country flags are displayed using emoji. Emoji rendering is handled by platform fonts. // Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows. - const randomFlags = shuffle([ - "🇬🇧", - "🇺🇸", - "🇪🇸", - "🇫🇷", - "🇧🇬", - "🇨🇿", - "🇩🇪", - "🇵🇱", - "🇺🇦", - "🇨🇳", - "🇮🇹", - "🇭🇺", - "🇧🇷", - "🇳🇱", - "🇮🇩", - "🇯🇵", - "🇷🇺", - "🇹🇷", - ]).slice(0, 3); + const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3); const showFlags = !navigator.userAgent.includes("Windows"); let title = t("prefs_appearance_language_title"); if (showFlags) { @@ -635,8 +510,7 @@ const Reservations = () => { return <>; } const reservations = account.reservations || []; - const limitReached = - account.role === Role.USER && account.stats.reservations_remaining === 0; + const limitReached = account.role === Role.USER && account.stats.reservations_remaining === 0; const handleAddClick = () => { setDialogKey((prev) => prev + 1); @@ -650,23 +524,14 @@ const Reservations = () => { {t("prefs_reservations_title")} {t("prefs_reservations_description")} - {reservations.length > 0 && ( - - )} - {limitReached && ( - {t("prefs_reservations_limit_reached")} - )} + {reservations.length > 0 && } + {limitReached && {t("prefs_reservations_limit_reached")}} - setDialogOpen(false)} - /> + setDialogOpen(false)} /> ); @@ -680,14 +545,7 @@ const ReservationsTable = (props) => { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const { subscriptions } = useOutletContext(); const localSubscriptions = - subscriptions?.length > 0 - ? Object.assign( - {}, - ...subscriptions - .filter((s) => s.baseUrl === config.base_url) - .map((s) => ({ [s.topic]: s })) - ) - : {}; + subscriptions?.length > 0 ? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s }))) : {}; const handleEditClick = (reservation) => { setDialogKey((prev) => prev + 1); @@ -709,70 +567,46 @@ const ReservationsTable = (props) => {
- - {t("prefs_reservations_table_topic_header")} - + {t("prefs_reservations_table_topic_header")} {t("prefs_reservations_table_access_header")} {props.reservations.map((reservation) => ( - - + + {reservation.topic} {reservation.everyone === Permission.READ_WRITE && ( <> - + {t("prefs_reservations_table_everyone_read_write")} )} {reservation.everyone === Permission.READ_ONLY && ( <> - + {t("prefs_reservations_table_everyone_read_only")} )} {reservation.everyone === Permission.WRITE_ONLY && ( <> - + {t("prefs_reservations_table_everyone_write_only")} )} {reservation.everyone === Permission.DENY_ALL && ( <> - + {t("prefs_reservations_table_everyone_deny_all")} )} {!localSubscriptions[reservation.topic] && ( - + } onClick={() => handleSubscribeClick(reservation)} @@ -782,16 +616,10 @@ const ReservationsTable = (props) => { /> )} - handleEditClick(reservation)} - aria-label={t("prefs_reservations_edit_button")} - > + handleEditClick(reservation)} aria-label={t("prefs_reservations_edit_button")}> - handleDeleteClick(reservation)} - aria-label={t("prefs_reservations_delete_button")} - > + handleDeleteClick(reservation)} aria-label={t("prefs_reservations_delete_button")}> diff --git a/web/src/components/PublishDialog.js b/web/src/components/PublishDialog.js index e8825de..7d20103 100644 --- a/web/src/components/PublishDialog.js +++ b/web/src/components/PublishDialog.js @@ -1,17 +1,7 @@ import * as React from "react"; import { useContext, useEffect, useRef, useState } from "react"; import theme from "./theme"; -import { - Checkbox, - Chip, - FormControl, - FormControlLabel, - InputLabel, - Link, - Select, - Tooltip, - useMediaQuery, -} from "@mui/material"; +import { Checkbox, Chip, FormControl, FormControlLabel, InputLabel, Link, Select, Tooltip, useMediaQuery } from "@mui/material"; import TextField from "@mui/material/TextField"; import priority1 from "../img/priority-1.svg"; import priority2 from "../img/priority-2.svg"; @@ -27,14 +17,7 @@ import IconButton from "@mui/material/IconButton"; import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon"; import { Close } from "@mui/icons-material"; import MenuItem from "@mui/material/MenuItem"; -import { - formatBytes, - maybeWithAuth, - topicShortUrl, - topicUrl, - validTopic, - validUrl, -} from "../app/utils"; +import { formatBytes, maybeWithAuth, topicShortUrl, topicUrl, validTopic, validUrl } from "../app/utils"; import Box from "@mui/material/Box"; import AttachmentIcon from "./AttachmentIcon"; import DialogFooter from "./DialogFooter"; @@ -152,10 +135,7 @@ const PublishDialog = (props) => { url.searchParams.append("delay", delay.trim()); } if (attachFile && message.trim()) { - url.searchParams.append( - "message", - message.replaceAll("\n", "\\n").trim() - ); + url.searchParams.append("message", message.replaceAll("\n", "\\n").trim()); } const body = attachFile ? attachFile : message; try { @@ -184,11 +164,7 @@ const PublishDialog = (props) => { setActiveRequest(null); } } catch (e) { - setStatus( - - {e} - - ); + setStatus({e}); setActiveRequest(null); } }; @@ -198,8 +174,7 @@ const PublishDialog = (props) => { const account = await accountApi.get(); const fileSizeLimit = account.limits.attachment_file_size ?? 0; const remainingBytes = account.stats.attachment_total_size_remaining; - const fileSizeLimitReached = - fileSizeLimit > 0 && file.size > fileSizeLimit; + const fileSizeLimitReached = fileSizeLimit > 0 && file.size > fileSizeLimit; const quotaReached = remainingBytes > 0 && file.size > remainingBytes; if (fileSizeLimitReached && quotaReached) { return setAttachFileError( @@ -282,18 +257,8 @@ const PublishDialog = (props) => { return ( <> - {dropZone && ( - - )} - + {dropZone && } + {baseUrl && topic ? t("publish_dialog_title_topic", { @@ -377,16 +342,8 @@ const PublishDialog = (props) => { }} />
- - + + { "aria-label": t("publish_dialog_tags_label"), }} /> - + + {showAttachFile && ( { /> )} {account && !account?.phone_numbers && ( - + { - ), + docsLink: , }} /> - {activeRequest && ( - - )} + {activeRequest && } {!activeRequest && ( <> { checked={publishAnother} onChange={(ev) => setPublishAnother(ev.target.checked)} inputProps={{ - "aria-label": t( - "publish_dialog_checkbox_publish_another" - ), + "aria-label": t("publish_dialog_checkbox_publish_another"), }} /> } /> - + @@ -796,12 +721,7 @@ const ClosableRow = (props) => { {props.children} {closable && ( - + )} @@ -856,23 +776,14 @@ const AttachmentBox = (props) => { {formatBytes(file.size)} {props.error && ( - + {" "} ({props.error}) )} - + @@ -888,22 +799,14 @@ const ExpandingTextField = (props) => { if (!boundingRect) { return props.minWidth; } - return boundingRect.width >= props.minWidth - ? Math.round(boundingRect.width) - : props.minWidth; + return boundingRect.width >= props.minWidth ? Math.round(boundingRect.width) : props.minWidth; }; useEffect(() => { setTextWidth(determineTextWidth() + 5); }, [props.value]); return ( <> - + {props.value} { alignItems: "center", }} > - - {t("publish_dialog_drop_file_here")} - + {t("publish_dialog_drop_file_here")} ); diff --git a/web/src/components/ReserveDialogs.js b/web/src/components/ReserveDialogs.js index f36ea6c..e606c0d 100644 --- a/web/src/components/ReserveDialogs.js +++ b/web/src/components/ReserveDialogs.js @@ -28,16 +28,13 @@ export const ReserveAddDialog = (props) => { const [everyone, setEveryone] = useState(Permission.DENY_ALL); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const allowTopicEdit = !props.topic; - const alreadyReserved = - props.reservations.filter((r) => r.topic === topic).length > 0; + const alreadyReserved = props.reservations.filter((r) => r.topic === topic).length > 0; const submitButtonEnabled = validTopic(topic) && !alreadyReserved; const handleSubmit = async () => { try { await accountApi.upsertReservation(topic, everyone); - console.debug( - `[ReserveAddDialog] Added reservation for topic ${topic}: ${everyone}` - ); + console.debug(`[ReserveAddDialog] Added reservation for topic ${topic}: ${everyone}`); } catch (e) { console.log(`[ReserveAddDialog] Error adding topic reservation.`, e); if (e instanceof UnauthorizedError) { @@ -54,18 +51,10 @@ export const ReserveAddDialog = (props) => { }; return ( - + {t("prefs_reservations_dialog_title_add")} - - {t("prefs_reservations_dialog_description")} - + {t("prefs_reservations_dialog_description")} {allowTopicEdit && ( { variant="standard" /> )} - + @@ -99,17 +84,13 @@ export const ReserveAddDialog = (props) => { export const ReserveEditDialog = (props) => { const { t } = useTranslation(); const [error, setError] = useState(""); - const [everyone, setEveryone] = useState( - props.reservation?.everyone || Permission.DENY_ALL - ); + const [everyone, setEveryone] = useState(props.reservation?.everyone || Permission.DENY_ALL); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const handleSubmit = async () => { try { await accountApi.upsertReservation(props.reservation.topic, everyone); - console.debug( - `[ReserveEditDialog] Updated reservation for topic ${t}: ${everyone}` - ); + console.debug(`[ReserveEditDialog] Updated reservation for topic ${t}: ${everyone}`); } catch (e) { console.log(`[ReserveEditDialog] Error updating topic reservation.`, e); if (e instanceof UnauthorizedError) { @@ -123,23 +104,11 @@ export const ReserveEditDialog = (props) => { }; return ( - + {t("prefs_reservations_dialog_title_edit")} - - {t("prefs_reservations_dialog_description")} - - + {t("prefs_reservations_dialog_description")} + @@ -158,9 +127,7 @@ export const ReserveDeleteDialog = (props) => { const handleSubmit = async () => { try { await accountApi.deleteReservation(props.topic, deleteMessages); - console.debug( - `[ReserveDeleteDialog] Deleted reservation for topic ${props.topic}` - ); + console.debug(`[ReserveDeleteDialog] Deleted reservation for topic ${props.topic}`); } catch (e) { console.log(`[ReserveDeleteDialog] Error deleting topic reservation.`, e); if (e instanceof UnauthorizedError) { @@ -174,18 +141,10 @@ export const ReserveDeleteDialog = (props) => { }; return ( - + {t("prefs_reservations_dialog_title_delete")} - - {t("reservation_delete_dialog_description")} - + {t("reservation_delete_dialog_description")} diff --git a/web/src/components/ReserveTopicSelect.js b/web/src/components/ReserveTopicSelect.js index 76113ba..83ac069 100644 --- a/web/src/components/ReserveTopicSelect.js +++ b/web/src/components/ReserveTopicSelect.js @@ -4,12 +4,7 @@ import { useTranslation } from "react-i18next"; import MenuItem from "@mui/material/MenuItem"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; -import { - PermissionDenyAll, - PermissionRead, - PermissionReadWrite, - PermissionWrite, -} from "./ReserveIcons"; +import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons"; import { Permission } from "../app/AccountApi"; const ReserveTopicSelect = (props) => { @@ -34,33 +29,25 @@ const ReserveTopicSelect = (props) => { - + - + - + - + diff --git a/web/src/components/Signup.js b/web/src/components/Signup.js index 39409a5..37a3928 100644 --- a/web/src/components/Signup.js +++ b/web/src/components/Signup.js @@ -31,9 +31,7 @@ const Signup = () => { try { await accountApi.create(user.username, user.password); const token = await accountApi.login(user); - console.log( - `[Signup] User signup for user ${user.username} successful, token is ${token}` - ); + console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); session.store(user.username, token); window.location.href = routes.app; } catch (e) { @@ -51,9 +49,7 @@ const Signup = () => { if (!config.enable_signup) { return ( - - {t("signup_disabled")} - + {t("signup_disabled")} ); } @@ -61,12 +57,7 @@ const Signup = () => { return ( {t("signup_title")} - + { ), }} /> - {error && ( diff --git a/web/src/components/SubscribeDialog.js b/web/src/components/SubscribeDialog.js index 940eafe..7be69a6 100644 --- a/web/src/components/SubscribeDialog.js +++ b/web/src/components/SubscribeDialog.js @@ -6,21 +6,10 @@ import Dialog from "@mui/material/Dialog"; import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; -import { - Autocomplete, - Checkbox, - FormControlLabel, - FormGroup, - useMediaQuery, -} from "@mui/material"; +import { Autocomplete, Checkbox, FormControlLabel, FormGroup, useMediaQuery } from "@mui/material"; import theme from "./theme"; import api from "../app/Api"; -import { - randomAlphanumericString, - topicUrl, - validTopic, - validUrl, -} from "../app/utils"; +import { randomAlphanumericString, topicUrl, validTopic, validUrl } from "../app/utils"; import userManager from "../app/UserManager"; import subscriptionManager from "../app/SubscriptionManager"; import poller from "../app/Poller"; @@ -64,14 +53,7 @@ const SubscribeDialog = (props) => { onSuccess={handleSuccess} /> )} - {showLoginPage && ( - setShowLoginPage(false)} - onSuccess={handleSuccess} - /> - )} + {showLoginPage && setShowLoginPage(false)} onSuccess={handleSuccess} />}
); }; @@ -85,37 +67,20 @@ const SubscribePage = (props) => { const [everyone, setEveryone] = useState(Permission.DENY_ALL); const baseUrl = anotherServerVisible ? props.baseUrl : config.base_url; const topic = props.topic; - const existingTopicUrls = props.subscriptions.map((s) => - topicUrl(s.baseUrl, s.topic) - ); - const existingBaseUrls = Array.from( - new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)]) - ).filter((s) => s !== config.base_url); - const showReserveTopicCheckbox = - config.enable_reservations && - !anotherServerVisible && - (config.enable_payments || account); + const existingTopicUrls = props.subscriptions.map((s) => topicUrl(s.baseUrl, s.topic)); + const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map((s) => s.baseUrl)])).filter((s) => s !== config.base_url); + const showReserveTopicCheckbox = config.enable_reservations && !anotherServerVisible && (config.enable_payments || account); const reserveTopicEnabled = - session.exists() && - (account?.role === Role.ADMIN || - (account?.role === Role.USER && - (account?.stats.reservations_remaining || 0) > 0)); + session.exists() && (account?.role === Role.ADMIN || (account?.role === Role.USER && (account?.stats.reservations_remaining || 0) > 0)); const handleSubscribe = async () => { const user = await userManager.get(baseUrl); // May be undefined - const username = user - ? user.username - : t("subscribe_dialog_error_user_anonymous"); + const username = user ? user.username : t("subscribe_dialog_error_user_anonymous"); // Check read access to topic const success = await api.topicAuth(baseUrl, topic, user); if (!success) { - console.log( - `[SubscribeDialog] Login to ${topicUrl( - baseUrl, - topic - )} failed for user ${username}` - ); + console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); if (user) { setError( t("subscribe_dialog_error_user_not_authorized", { @@ -130,14 +95,8 @@ const SubscribePage = (props) => { } // Reserve topic (if requested) - if ( - session.exists() && - baseUrl === config.base_url && - reserveTopicVisible - ) { - console.log( - `[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}` - ); + if (session.exists() && baseUrl === config.base_url && reserveTopicVisible) { + console.log(`[SubscribeDialog] Reserving topic ${topic} with everyone access ${everyone}`); try { await accountApi.upsertReservation(topic, everyone); } catch (e) { @@ -151,12 +110,7 @@ const SubscribePage = (props) => { } } - console.log( - `[SubscribeDialog] Successful login to ${topicUrl( - baseUrl, - topic - )} for user ${username}` - ); + console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); props.onSuccess(); }; @@ -167,14 +121,10 @@ const SubscribePage = (props) => { const subscribeButtonEnabled = (() => { if (anotherServerVisible) { - const isExistingTopicUrl = existingTopicUrls.includes( - topicUrl(baseUrl, topic) - ); + const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic)); return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl; } else { - const isExistingTopicUrl = existingTopicUrls.includes( - topicUrl(config.base_url, topic) - ); + const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.base_url, topic)); return validTopic(topic) && !isExistingTopicUrl; } })(); @@ -191,9 +141,7 @@ const SubscribePage = (props) => { <> {t("subscribe_dialog_subscribe_title")} - - {t("subscribe_dialog_subscribe_description")} - + {t("subscribe_dialog_subscribe_description")}
{ } /> - {reserveTopicVisible && ( - - )} + {reserveTopicVisible && } )} {!reserveTopicVisible && ( @@ -253,9 +199,7 @@ const SubscribePage = (props) => { } @@ -268,12 +212,7 @@ const SubscribePage = (props) => { inputValue={props.baseUrl} onInputChange={updateBaseUrl} renderInput={(params) => ( - + )} /> )} @@ -281,9 +220,7 @@ const SubscribePage = (props) => { )} - + @@ -304,23 +241,11 @@ const LoginPage = (props) => { const user = { baseUrl, username, password }; const success = await api.topicAuth(baseUrl, topic, user); if (!success) { - console.log( - `[SubscribeDialog] Login to ${topicUrl( - baseUrl, - topic - )} failed for user ${username}` - ); - setError( - t("subscribe_dialog_error_user_not_authorized", { username: username }) - ); + console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`); + setError(t("subscribe_dialog_error_user_not_authorized", { username: username })); return; } - console.log( - `[SubscribeDialog] Successful login to ${topicUrl( - baseUrl, - topic - )} for user ${username}` - ); + console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`); await userManager.save(user); props.onSuccess(); }; @@ -329,9 +254,7 @@ const LoginPage = (props) => { <> {t("subscribe_dialog_login_title")} - - {t("subscribe_dialog_login_description")} - + {t("subscribe_dialog_login_description")} { - + ); diff --git a/web/src/components/SubscriptionPopup.js b/web/src/components/SubscriptionPopup.js index eb575dc..7452b8e 100644 --- a/web/src/components/SubscriptionPopup.js +++ b/web/src/components/SubscriptionPopup.js @@ -6,13 +6,7 @@ import Dialog from "@mui/material/Dialog"; import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; -import { - Chip, - InputAdornment, - Portal, - Snackbar, - useMediaQuery, -} from "@mui/material"; +import { Chip, InputAdornment, Portal, Snackbar, useMediaQuery } from "@mui/material"; import theme from "./theme"; import subscriptionManager from "../app/SubscriptionManager"; import DialogFooter from "./DialogFooter"; @@ -28,11 +22,7 @@ import { useNavigate } from "react-router-dom"; import IconButton from "@mui/material/IconButton"; import { Clear } from "@mui/icons-material"; import { AccountContext } from "./App"; -import { - ReserveAddDialog, - ReserveDeleteDialog, - ReserveEditDialog, -} from "./ReserveDialogs"; +import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs"; import { UnauthorizedError } from "../app/errors"; export const SubscriptionPopup = (props) => { @@ -48,19 +38,11 @@ export const SubscriptionPopup = (props) => { const placement = props.placement ?? "left"; const reservations = account?.reservations || []; - const showReservationAdd = - config.enable_reservations && - !subscription?.reservation && - account?.stats.reservations_remaining > 0; + const showReservationAdd = config.enable_reservations && !subscription?.reservation && account?.stats.reservations_remaining > 0; const showReservationAddDisabled = - !showReservationAdd && - config.enable_reservations && - !subscription?.reservation && - (config.enable_payments || account?.stats.reservations_remaining === 0); - const showReservationEdit = - config.enable_reservations && !!subscription?.reservation; - const showReservationDelete = - config.enable_reservations && !!subscription?.reservation; + !showReservationAdd && config.enable_reservations && !subscription?.reservation && (config.enable_payments || account?.stats.reservations_remaining === 0); + const showReservationEdit = config.enable_reservations && !!subscription?.reservation; + const showReservationDelete = config.enable_reservations && !!subscription?.reservation; const handleChangeDisplayName = async () => { setDisplayNameDialogOpen(true); @@ -115,14 +97,10 @@ export const SubscriptionPopup = (props) => { ])[0]; const nowSeconds = Math.round(Date.now() / 1000); const message = shuffle([ - `Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime( - nowSeconds - )} right now. Is that early or late?`, + `Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime(nowSeconds)} right now. Is that early or late?`, `So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`, `It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`, - `Alright then, it's ${formatShortDateTime( - nowSeconds - )} already. Boy oh boy, where did the time go? I hope you're alright, friend.`, + `Alright then, it's ${formatShortDateTime(nowSeconds)} already. Boy oh boy, where did the time go? I hope you're alright, friend.`, `There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`, `I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/announcements.`, `It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?`, @@ -140,24 +118,16 @@ export const SubscriptionPopup = (props) => { }; const handleClearAll = async () => { - console.log( - `[SubscriptionPopup] Deleting all notifications from ${props.subscription.id}` - ); + console.log(`[SubscriptionPopup] Deleting all notifications from ${props.subscription.id}`); await subscriptionManager.deleteNotifications(props.subscription.id); }; const handleUnsubscribe = async () => { - console.log( - `[SubscriptionPopup] Unsubscribing from ${props.subscription.id}`, - props.subscription - ); + console.log(`[SubscriptionPopup] Unsubscribing from ${props.subscription.id}`, props.subscription); await subscriptionManager.remove(props.subscription.id); if (session.exists() && !subscription.internal) { try { - await accountApi.deleteSubscription( - props.subscription.baseUrl, - props.subscription.topic - ); + await accountApi.deleteSubscription(props.subscription.baseUrl, props.subscription.topic); } catch (e) { console.log(`[SubscriptionPopup] Error unsubscribing`, e); if (e instanceof UnauthorizedError) { @@ -175,67 +145,26 @@ export const SubscriptionPopup = (props) => { return ( <> - - - {t("action_bar_change_display_name")} - - {showReservationAdd && ( - - {t("action_bar_reservation_add")} - - )} + + {t("action_bar_change_display_name")} + {showReservationAdd && {t("action_bar_reservation_add")}} {showReservationAddDisabled && ( - - {t("action_bar_reservation_add")} - + {t("action_bar_reservation_add")} )} - {showReservationEdit && ( - - {t("action_bar_reservation_edit")} - - )} - {showReservationDelete && ( - - {t("action_bar_reservation_delete")} - - )} - - {t("action_bar_send_test_notification")} - - - {t("action_bar_clear_notifications")} - - - {t("action_bar_unsubscribe")} - + {showReservationEdit && {t("action_bar_reservation_edit")}} + {showReservationDelete && {t("action_bar_reservation_delete")}} + {t("action_bar_send_test_notification")} + {t("action_bar_clear_notifications")} + {t("action_bar_unsubscribe")} - setShowPublishError(false)} - message={t("message_bar_error_publishing")} - /> - setDisplayNameDialogOpen(false)} - /> + setShowPublishError(false)} message={t("message_bar_error_publishing")} /> + setDisplayNameDialogOpen(false)} /> {showReservationAdd && ( - setReserveAddDialogOpen(false)} - /> + setReserveAddDialogOpen(false)} /> )} {showReservationEdit && ( { /> )} {showReservationDelete && ( - setReserveDeleteDialogOpen(false)} - /> + setReserveDeleteDialogOpen(false)} /> )} @@ -261,28 +186,17 @@ const DisplayNameDialog = (props) => { const { t } = useTranslation(); const subscription = props.subscription; const [error, setError] = useState(""); - const [displayName, setDisplayName] = useState( - subscription.displayName ?? "" - ); + const [displayName, setDisplayName] = useState(subscription.displayName ?? ""); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const handleSave = async () => { await subscriptionManager.setDisplayName(subscription.id, displayName); if (session.exists() && !subscription.internal) { try { - console.log( - `[SubscriptionSettingsDialog] Updating subscription display name to ${displayName}` - ); - await accountApi.updateSubscription( - subscription.baseUrl, - subscription.topic, - { display_name: displayName } - ); + console.log(`[SubscriptionSettingsDialog] Updating subscription display name to ${displayName}`); + await accountApi.updateSubscription(subscription.baseUrl, subscription.topic, { display_name: displayName }); } catch (e) { - console.log( - `[SubscriptionSettingsDialog] Error updating subscription`, - e - ); + console.log(`[SubscriptionSettingsDialog] Error updating subscription`, e); if (e instanceof UnauthorizedError) { session.resetAndRedirect(routes.login); } else { @@ -295,18 +209,10 @@ const DisplayNameDialog = (props) => { }; return ( - + {t("display_name_dialog_title")} - - {t("display_name_dialog_description")} - + {t("display_name_dialog_description")} { export const ReserveLimitChip = () => { const { account } = useContext(AccountContext); - if ( - account?.role === Role.ADMIN || - account?.stats.reservations_remaining > 0 - ) { + if (account?.role === Role.ADMIN || account?.stats.reservations_remaining > 0) { return <>; } else if (config.enable_payments) { - return account?.limits.reservations > 0 ? ( - - ) : ( - - ); + return account?.limits.reservations > 0 ? : ; } else if (account) { return ; } diff --git a/web/src/components/UpgradeDialog.js b/web/src/components/UpgradeDialog.js index 94b878c..5ebbd7b 100644 --- a/web/src/components/UpgradeDialog.js +++ b/web/src/components/UpgradeDialog.js @@ -3,16 +3,7 @@ import { useContext, useEffect, useState } from "react"; import Dialog from "@mui/material/Dialog"; import DialogContent from "@mui/material/DialogContent"; import DialogTitle from "@mui/material/DialogTitle"; -import { - Alert, - CardActionArea, - CardContent, - Chip, - Link, - ListItem, - Switch, - useMediaQuery, -} from "@mui/material"; +import { Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery } from "@mui/material"; import theme from "./theme"; import Button from "@mui/material/Button"; import accountApi, { SubscriptionInterval } from "../app/AccountApi"; @@ -21,12 +12,7 @@ import routes from "./routes"; import Card from "@mui/material/Card"; import Typography from "@mui/material/Typography"; import { AccountContext } from "./App"; -import { - formatBytes, - formatNumber, - formatPrice, - formatShortDate, -} from "../app/utils"; +import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils"; import { Trans, useTranslation } from "react-i18next"; import List from "@mui/material/List"; import { Check, Close } from "@mui/icons-material"; @@ -43,9 +29,7 @@ const UpgradeDialog = (props) => { const { account } = useContext(AccountContext); // May be undefined! const [error, setError] = useState(""); const [tiers, setTiers] = useState(null); - const [interval, setInterval] = useState( - account?.billing?.interval || SubscriptionInterval.YEAR - ); + const [interval, setInterval] = useState(account?.billing?.interval || SubscriptionInterval.YEAR); const [newTierCode, setNewTierCode] = useState(account?.tier?.code); // May be undefined const [loading, setLoading] = useState(false); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); @@ -61,9 +45,7 @@ const UpgradeDialog = (props) => { return <>; } - const tiersMap = Object.assign( - ...tiers.map((tier) => ({ [tier.code]: tier })) - ); + const tiersMap = Object.assign(...tiers.map((tier) => ({ [tier.code]: tier }))); const newTier = tiersMap[newTierCode]; // May be undefined const currentTier = account?.tier; // May be undefined const currentInterval = account?.billing?.interval; // May be undefined @@ -75,10 +57,7 @@ const UpgradeDialog = (props) => { submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup"); submitAction = Action.REDIRECT_SIGNUP; banner = null; - } else if ( - currentTierCode === newTierCode && - (currentInterval === undefined || currentInterval === interval) - ) { + } else if (currentTierCode === newTierCode && (currentInterval === undefined || currentInterval === interval)) { submitButtonLabel = t("account_upgrade_dialog_button_update_subscription"); submitAction = null; banner = currentTierCode ? Banner.PRORATION_INFO : null; @@ -99,10 +78,7 @@ const UpgradeDialog = (props) => { // Exceptional conditions if (loading) { submitAction = null; - } else if ( - newTier?.code && - account?.reservations?.length > newTier?.limits?.reservations - ) { + } else if (newTier?.code && account?.reservations?.length > newTier?.limits?.reservations) { submitAction = null; banner = Banner.RESERVATIONS_WARNING; } @@ -115,10 +91,7 @@ const UpgradeDialog = (props) => { try { setLoading(true); if (submitAction === Action.CREATE_SUBSCRIPTION) { - const response = await accountApi.createBillingSubscription( - newTierCode, - interval - ); + const response = await accountApi.createBillingSubscription(newTierCode, interval); window.location.href = response.redirect_url; } else if (submitAction === Action.UPDATE_SUBSCRIPTION) { await accountApi.updateBillingSubscription(newTierCode, interval); @@ -142,16 +115,12 @@ const UpgradeDialog = (props) => { let discount = 0, upto = false; if (newTier?.prices) { - discount = Math.round( - ((newTier.prices.month * 12) / newTier.prices.year - 1) * 100 - ); + discount = Math.round(((newTier.prices.month * 12) / newTier.prices.year - 1) * 100); } else { let n = 0; for (const t of tiers) { if (t.prices) { - const tierDiscount = Math.round( - ((t.prices.month * 12) / t.prices.year - 1) * 100 - ); + const tierDiscount = Math.round(((t.prices.month * 12) / t.prices.year - 1) * 100); if (tierDiscount > discount) { discount = tierDiscount; n++; @@ -162,12 +131,7 @@ const UpgradeDialog = (props) => { } return ( - +
{t("account_upgrade_dialog_title")}
@@ -184,13 +148,7 @@ const UpgradeDialog = (props) => { - setInterval( - ev.target.checked - ? SubscriptionInterval.YEAR - : SubscriptionInterval.MONTH - ) - } + onChange={(ev) => setInterval(ev.target.checked ? SubscriptionInterval.YEAR : SubscriptionInterval.MONTH)} /> {t("account_upgrade_dialog_interval_yearly")} @@ -199,20 +157,12 @@ const UpgradeDialog = (props) => { )} @@ -258,9 +208,7 @@ const UpgradeDialog = (props) => { , }} @@ -309,9 +257,7 @@ const UpgradeDialog = (props) => { {error} - + @@ -382,16 +328,10 @@ const TierCard = (props) => { {tier.name || t("account_basics_tier_free")}
- + {formatPrice(monthlyPrice)} - {monthlyPrice > 0 && ( - <>/ {t("account_upgrade_dialog_tier_price_per_month")} - )} + {monthlyPrice > 0 && <>/ {t("account_upgrade_dialog_tier_price_per_month")}}
{tier.limits.reservations > 0 && ( @@ -423,21 +363,10 @@ const TierCard = (props) => { )} - {t( - "account_upgrade_dialog_tier_features_attachment_file_size", - { filesize: formatBytes(tier.limits.attachment_file_size, 0) } - )} + {t("account_upgrade_dialog_tier_features_attachment_file_size", { filesize: formatBytes(tier.limits.attachment_file_size, 0) })} - {tier.limits.reservations === 0 && ( - - {t("account_upgrade_dialog_tier_features_no_reservations")} - - )} - {tier.limits.calls === 0 && ( - - {t("account_upgrade_dialog_tier_features_no_calls")} - - )} + {tier.limits.reservations === 0 && {t("account_upgrade_dialog_tier_features_no_reservations")}} + {tier.limits.calls === 0 && {t("account_upgrade_dialog_tier_features_no_calls")}} {tier.prices && props.interval === SubscriptionInterval.MONTH && ( @@ -476,10 +405,7 @@ const FeatureItem = (props) => { {props.feature && } {!props.feature && } - {props.children}} - /> + {props.children}} /> ); }; diff --git a/web/src/components/hooks.js b/web/src/components/hooks.js index 0fc0204..1835a4b 100644 --- a/web/src/components/hooks.js +++ b/web/src/components/hooks.js @@ -32,41 +32,25 @@ export const useConnectionListeners = (account, subscriptions, users) => { }; const handleInternalMessage = async (message) => { - console.log( - `[ConnectionListener] Received message on sync topic`, - message.message - ); + console.log(`[ConnectionListener] Received message on sync topic`, message.message); try { const data = JSON.parse(message.message); if (data.event === "sync") { console.log(`[ConnectionListener] Triggering account sync`); await accountApi.sync(); } else { - console.log( - `[ConnectionListener] Unknown message type. Doing nothing.` - ); + console.log(`[ConnectionListener] Unknown message type. Doing nothing.`); } } catch (e) { - console.log( - `[ConnectionListener] Error parsing sync topic message`, - e - ); + console.log(`[ConnectionListener] Error parsing sync topic message`, e); } }; const handleNotification = async (subscriptionId, notification) => { - const added = await subscriptionManager.addNotification( - subscriptionId, - notification - ); + const added = await subscriptionManager.addNotification(subscriptionId, notification); if (added) { - const defaultClickAction = (subscription) => - navigate(routes.forSubscription(subscription)); - await notifier.notify( - subscriptionId, - notification, - defaultClickAction - ); + const defaultClickAction = (subscription) => navigate(routes.forSubscription(subscription)); + await notifier.notify(subscriptionId, notification, defaultClickAction); } }; connectionManager.registerStateListener(subscriptionManager.updateState); @@ -109,20 +93,12 @@ export const useAutoSubscribe = (subscriptions, selected) => { return; } setHasRun(true); - const eligible = - params.topic && !selected && !disallowedTopic(params.topic); + const eligible = params.topic && !selected && !disallowedTopic(params.topic); if (eligible) { - const baseUrl = params.baseUrl - ? expandSecureUrl(params.baseUrl) - : config.base_url; - console.log( - `[Hooks] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}` - ); + const baseUrl = params.baseUrl ? expandSecureUrl(params.baseUrl) : config.base_url; + console.log(`[Hooks] Auto-subscribing to ${topicUrl(baseUrl, params.topic)}`); (async () => { - const subscription = await subscriptionManager.add( - baseUrl, - params.topic - ); + const subscription = await subscriptionManager.add(baseUrl, params.topic); if (session.exists()) { try { await accountApi.addSubscription(baseUrl, params.topic);