diff --git a/server/1684249347630-datasource.ts b/server/1684249347630-datasource.ts new file mode 100644 index 000000000..4b7356f4a --- /dev/null +++ b/server/1684249347630-datasource.ts @@ -0,0 +1,51 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class datasource1684249347630 implements MigrationInterface { + name = 'datasource1684249347630'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temporary_user_push_subscription" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "endpoint" varchar NOT NULL, "p256dh" varchar NOT NULL, "auth" varchar NOT NULL, "userId" integer, "userAgent" varchar, "createdAt" datetime DEFAULT (datetime('now')), CONSTRAINT "UQ_95f313437ec4a8f8148d74a0ed8" UNIQUE ("auth"), CONSTRAINT "FK_03f7958328e311761b0de675fbe" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_user_push_subscription"("id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt") SELECT "id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt" FROM "user_push_subscription"` + ); + await queryRunner.query(`DROP TABLE "user_push_subscription"`); + await queryRunner.query( + `ALTER TABLE "temporary_user_push_subscription" RENAME TO "user_push_subscription"` + ); + await queryRunner.query( + `CREATE TABLE "temporary_user_push_subscription" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "endpoint" varchar NOT NULL, "p256dh" varchar NOT NULL, "auth" varchar NOT NULL, "userId" integer, "userAgent" varchar, "createdAt" datetime DEFAULT (datetime('now')), CONSTRAINT "UQ_95f313437ec4a8f8148d74a0ed8" UNIQUE ("auth"), CONSTRAINT "FK_03f7958328e311761b0de675fbe" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_user_push_subscription"("id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt") SELECT "id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt" FROM "user_push_subscription"` + ); + await queryRunner.query(`DROP TABLE "user_push_subscription"`); + await queryRunner.query( + `ALTER TABLE "temporary_user_push_subscription" RENAME TO "user_push_subscription"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user_push_subscription" RENAME TO "temporary_user_push_subscription"` + ); + await queryRunner.query( + `CREATE TABLE "user_push_subscription" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "endpoint" varchar NOT NULL, "p256dh" varchar NOT NULL, "auth" varchar NOT NULL, "userId" integer, "userAgent" varchar, "createdAt" datetime DEFAULT (datetime('now')), CONSTRAINT "UQ_95f313437ec4a8f8148d74a0ed8" UNIQUE ("auth"), CONSTRAINT "FK_03f7958328e311761b0de675fbe" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "user_push_subscription"("id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt") SELECT "id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt" FROM "temporary_user_push_subscription"` + ); + await queryRunner.query(`DROP TABLE "temporary_user_push_subscription"`); + await queryRunner.query( + `ALTER TABLE "user_push_subscription" RENAME TO "temporary_user_push_subscription"` + ); + await queryRunner.query( + `CREATE TABLE "user_push_subscription" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "endpoint" varchar NOT NULL, "p256dh" varchar NOT NULL, "auth" varchar NOT NULL, "userId" integer, "userAgent" varchar, "createdAt" datetime DEFAULT (datetime('now')), CONSTRAINT "UQ_95f313437ec4a8f8148d74a0ed8" UNIQUE ("auth"), CONSTRAINT "FK_03f7958328e311761b0de675fbe" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "user_push_subscription"("id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt") SELECT "id", "endpoint", "p256dh", "auth", "userId", "userAgent", "createdAt" FROM "temporary_user_push_subscription"` + ); + await queryRunner.query(`DROP TABLE "temporary_user_push_subscription"`); + } +} diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/DeviceItem.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/DeviceItem.tsx new file mode 100644 index 000000000..64ad47b4e --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/DeviceItem.tsx @@ -0,0 +1,107 @@ +import ConfirmButton from '@app/components/Common/ConfirmButton'; +import globalMessages from '@app/i18n/globalMessages'; +import { + ComputerDesktopIcon, + DevicePhoneMobileIcon, + TrashIcon, +} from '@heroicons/react/24/solid'; +import { defineMessages, useIntl } from 'react-intl'; +import { UAParser } from 'ua-parser-js'; + +interface DeviceItemProps { + disablePushNotifications: (p256dh: string) => void; + device: { + endpoint: string; + p256dh: string; + auth: string; + userAgent: string; + createdAt: Date; + }; +} + +const messages = defineMessages({ + operatingsystem: 'Operating System', + browser: 'Browser', + engine: 'Engine', + deletesubscription: 'Delete Subscription', +}); + +const DeviceItem = ({ disablePushNotifications, device }: DeviceItemProps) => { + const intl = useIntl(); + + return ( +
+
+
+
+ {UAParser(device.userAgent).device.type === 'mobile' ? ( + + ) : ( + + )} +
+
+
+ {device.createdAt + ? intl.formatDate(device.createdAt, { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + : 'Unknown'} +
+
+ {device.userAgent + ? UAParser(device.userAgent).device.model + : 'Unknown'} +
+
+
+
+
+ + {intl.formatMessage(messages.operatingsystem)} + + + {device.userAgent + ? UAParser(device.userAgent).os.name + : 'Unknown'} + +
+
+ + {intl.formatMessage(messages.browser)} + + + {device.userAgent + ? UAParser(device.userAgent).browser.name + : 'Unknown'} + +
+
+ + {intl.formatMessage(messages.engine)} + + + {device.userAgent + ? UAParser(device.userAgent).engine.name + : 'Unknown'} + +
+
+
+
+ disablePushNotifications(device.p256dh)} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + {intl.formatMessage(messages.deletesubscription)} + +
+
+ ); +}; + +export default DeviceItem; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx similarity index 82% rename from src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx rename to src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx index c2d373545..1615c228a 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx @@ -1,10 +1,10 @@ import Alert from '@app/components/Common/Alert'; import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; -import Table from '@app/components/Common/Table'; import NotificationTypeSelector, { ALL_NOTIFICATIONS, } from '@app/components/NotificationTypeSelector'; +import DeviceItem from '@app/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/DeviceItem'; import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; @@ -12,9 +12,6 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { CloudArrowDownIcon, CloudArrowUpIcon, - ComputerDesktopIcon, - DevicePhoneMobileIcon, - TrashIcon, } from '@heroicons/react/24/solid'; import type { UserPushSubscription } from '@server/entity/UserPushSubscription'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -25,14 +22,13 @@ import { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; -import { UAParser } from 'ua-parser-js'; const messages = defineMessages({ webpushsettingssaved: 'Web push notification settings saved successfully!', webpushsettingsfailed: 'Web push notification settings failed to save.', enablewebpush: 'Enable web push', disablewebpush: 'Disable web push', - managedevices: 'Manage devices', + managedevices: 'Manage Devices', type: 'type', created: 'Created', device: 'Device', @@ -314,68 +310,26 @@ const UserWebPushSettings = () => {

{intl.formatMessage(messages.managedevices)}

- {dataDevices?.length ? ( - - - - {intl.formatMessage(messages.type)} - {intl.formatMessage(messages.device)} - {intl.formatMessage(messages.created)} - - - - - {dataDevices?.map((device) => ( - - -
-
- {UAParser(device.userAgent).device.type === 'mobile' ? ( - - ) : ( - - )} -
-
-
- - {device.userAgent - ? UAParser(device.userAgent).device.model - : 'Unknown'} - - - {device.createdAt - ? intl.formatDate(device.createdAt, { - year: 'numeric', - month: 'long', - day: 'numeric', - }) - : 'Unknown'} - - - - - - ))} - -
- ) : ( - <> -
- - - )} +
+ {dataDevices?.length ? ( + dataDevices?.map((device, index) => ( +
+ +
+ )) + ) : ( + <> + + + )} +
); diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 07d3f22bd..09b8b155c 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -1101,7 +1101,25 @@ "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Settings saved successfully!", "components.UserProfile.UserSettings.UserGeneralSettings.user": "User", "components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID", - "components.UserProfile.UserSettings.UserNotificationSettings.disablewebpush": "Disable web push", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.browser": "Browser", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.created": "Created", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.deletesubscription": "Delete Subscription", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.device": "Device", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.disablewebpush": "Disable web push", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.disablingwebpusherror": "Something went wrong while disabling web push.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.enablewebpush": "Enable web push", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.enablingwebpusherror": "Something went wrong while enable web push.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.engine": "Engine", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.managedevices": "Manage Devices", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.nodevicestoshow": "You have no web push subscriptions to show.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.operatingsystem": "Operating System", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.subscriptiondeleted": "Subscription deleted.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.subscriptiondeleteerror": "Something went wrong while deleting the user subscription.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.type": "type", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.webpushhasbeendisabled": "Web push has been disabled.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.webpushhasbeenenabled": "Web push has been enabled.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.webpushsettingsfailed": "Web push notification settings failed to save.", + "components.UserProfile.UserSettings.UserNotificationSettings.UserNotificationsWebPush.webpushsettingssaved": "Web push notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "User ID", "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "The multi-digit ID number associated with your user account", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord notification settings failed to save.", @@ -1109,7 +1127,6 @@ "components.UserProfile.UserSettings.UserNotificationSettings.email": "Email", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Email notification settings failed to save.", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Email notification settings saved successfully!", - "components.UserProfile.UserSettings.UserNotificationSettings.enablewebpush": "Enable web push", "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Notifications", "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notification Settings", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP Public Key", @@ -1137,8 +1154,6 @@ "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "You must provide a valid user or group key", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID", "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push notification settings failed to save.", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Web push notification settings saved successfully!", "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password", "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current Password", "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "New Password",